Skip to content

Commit

Permalink
Use debug f-strings for feature detection (psf#3215)
Browse files Browse the repository at this point in the history
Fixes psfGH-2907.
  • Loading branch information
hauntsaninja authored and hugovk committed Sep 3, 2022
1 parent cf14a2e commit 29c3787
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

<!-- Changes to how Black can be configured -->

- Black now uses the presence of debug f-strings to detect target version. (#3215)

### Documentation

<!-- Major changes to documentation and policies. Small docs changes
Expand Down
7 changes: 7 additions & 0 deletions src/black/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
from black.parsing import InvalidInput # noqa F401
from black.parsing import lib2to3_parse, parse_ast, stringify_ast
from black.report import Changed, NothingChanged, Report
from black.trans import iter_fexpr_spans
from blib2to3.pgen2 import token
from blib2to3.pytree import Leaf, Node

Expand Down Expand Up @@ -1240,6 +1241,7 @@ def get_features_used( # noqa: C901
Currently looking for:
- f-strings;
- self-documenting expressions in f-strings (f"{x=}");
- underscores in numeric literals;
- trailing commas after * or ** in function signatures and calls;
- positional only arguments in function signatures and lambdas;
Expand All @@ -1261,6 +1263,11 @@ def get_features_used( # noqa: C901
value_head = n.value[:2]
if value_head in {'f"', 'F"', "f'", "F'", "rf", "fr", "RF", "FR"}:
features.add(Feature.F_STRINGS)
if Feature.DEBUG_F_STRINGS not in features:
for span_beg, span_end in iter_fexpr_spans(n.value):
if n.value[span_beg : span_end - 1].rstrip().endswith("="):
features.add(Feature.DEBUG_F_STRINGS)
break

elif is_number_token(n):
if "_" in n.value:
Expand Down
5 changes: 5 additions & 0 deletions src/black/mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class Feature(Enum):
ANN_ASSIGN_EXTENDED_RHS = 13
EXCEPT_STAR = 14
VARIADIC_GENERICS = 15
DEBUG_F_STRINGS = 16
FORCE_OPTIONAL_PARENTHESES = 50

# __future__ flags
Expand Down Expand Up @@ -81,6 +82,7 @@ class Feature(Enum):
},
TargetVersion.PY38: {
Feature.F_STRINGS,
Feature.DEBUG_F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Feature.TRAILING_COMMA_IN_DEF,
Expand All @@ -93,6 +95,7 @@ class Feature(Enum):
},
TargetVersion.PY39: {
Feature.F_STRINGS,
Feature.DEBUG_F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Feature.TRAILING_COMMA_IN_DEF,
Expand All @@ -106,6 +109,7 @@ class Feature(Enum):
},
TargetVersion.PY310: {
Feature.F_STRINGS,
Feature.DEBUG_F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Feature.TRAILING_COMMA_IN_DEF,
Expand All @@ -120,6 +124,7 @@ class Feature(Enum):
},
TargetVersion.PY311: {
Feature.F_STRINGS,
Feature.DEBUG_F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Feature.TRAILING_COMMA_IN_DEF,
Expand Down
20 changes: 20 additions & 0 deletions tests/test_black.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,26 @@ def test_detect_pos_only_arguments(self) -> None:
versions = black.detect_target_versions(root)
self.assertIn(black.TargetVersion.PY38, versions)

def test_detect_debug_f_strings(self) -> None:
root = black.lib2to3_parse("""f"{x=}" """)
features = black.get_features_used(root)
self.assertIn(black.Feature.DEBUG_F_STRINGS, features)
versions = black.detect_target_versions(root)
self.assertIn(black.TargetVersion.PY38, versions)

root = black.lib2to3_parse(
"""f"{x}"\nf'{"="}'\nf'{(x:=5)}'\nf'{f(a="3=")}'\nf'{x:=10}'\n"""
)
features = black.get_features_used(root)
self.assertNotIn(black.Feature.DEBUG_F_STRINGS, features)

# We don't yet support feature version detection in nested f-strings
root = black.lib2to3_parse(
"""f"heard a rumour that { f'{1+1=}' } ... seems like it could be true" """
)
features = black.get_features_used(root)
self.assertNotIn(black.Feature.DEBUG_F_STRINGS, features)

@patch("black.dump_to_file", dump_to_stderr)
def test_string_quotes(self) -> None:
source, expected = read_data("miscellaneous", "string_quotes")
Expand Down

0 comments on commit 29c3787

Please # to comment.