Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Python 2 only syntax detection #2592

Merged
merged 3 commits into from Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGES.md
@@ -1,5 +1,12 @@
# Change Log

## _Unreleased_

### _Black_

- Warn about Python 2 deprecation in more cases by improving Python 2 only syntax
detection (#2592)

## 21.10b0

### _Black_
Expand Down
31 changes: 28 additions & 3 deletions src/black/__init__.py
Expand Up @@ -1132,8 +1132,12 @@ def get_features_used(node: Node) -> Set[Feature]: # noqa: C901
features.add(Feature.F_STRINGS)

elif n.type == token.NUMBER:
if "_" in n.value: # type: ignore
assert isinstance(n, Leaf)
if "_" in n.value:
features.add(Feature.NUMERIC_UNDERSCORES)
elif n.value.endswith("L"):
ichard26 marked this conversation as resolved.
Show resolved Hide resolved
# Python 2: 10L
features.add(Feature.LONG_INT_LITERAL)

elif n.type == token.SLASH:
if n.parent and n.parent.type in {
Expand Down Expand Up @@ -1171,10 +1175,31 @@ def get_features_used(node: Node) -> Set[Feature]: # noqa: C901
if argch.type in STARS:
features.add(feature)

elif n.type == token.PRINT_STMT:
# Python 2 only features (for its deprecation) except for 10L, see above
elif n.type == syms.print_stmt:
features.add(Feature.PRINT_STMT)
elif n.type == token.EXEC_STMT:
elif n.type == syms.exec_stmt:
features.add(Feature.EXEC_STMT)
elif n.type == syms.tfpdef:
# def set_position((x, y), value):
# ...
features.add(Feature.AUTOMATIC_PARAMETER_UNPACKING)
elif n.type == syms.except_clause:
# try:
# ...
# except Exception, err:
# ...
if len(n.children) >= 4:
if n.children[-2].type == token.COMMA:
features.add(Feature.COMMA_STYLE_EXCEPT)
elif n.type == syms.raise_stmt:
# raise Exception, "msg"
if len(n.children) >= 4:
if n.children[-2].type == token.COMMA:
features.add(Feature.COMMA_STYLE_RAISE)
ichard26 marked this conversation as resolved.
Show resolved Hide resolved
elif n.type == token.BACKQUOTE:
# `i'm surprised this ever existed`
features.add(Feature.BACKQUOTE_REPR)

return features

Expand Down
10 changes: 10 additions & 0 deletions src/black/mode.py
Expand Up @@ -44,13 +44,23 @@ class Feature(Enum):
# temporary for Python 2 deprecation
PRINT_STMT = 200
EXEC_STMT = 201
AUTOMATIC_PARAMETER_UNPACKING = 202
COMMA_STYLE_EXCEPT = 203
COMMA_STYLE_RAISE = 204
LONG_INT_LITERAL = 205
BACKQUOTE_REPR = 206


VERSION_TO_FEATURES: Dict[TargetVersion, Set[Feature]] = {
TargetVersion.PY27: {
Feature.ASYNC_IDENTIFIERS,
Feature.PRINT_STMT,
Feature.EXEC_STMT,
Feature.AUTOMATIC_PARAMETER_UNPACKING,
Feature.COMMA_STYLE_EXCEPT,
Feature.COMMA_STYLE_RAISE,
Feature.LONG_INT_LITERAL,
Feature.BACKQUOTE_REPR,
},
TargetVersion.PY33: {Feature.UNICODE_LITERALS, Feature.ASYNC_IDENTIFIERS},
TargetVersion.PY34: {Feature.UNICODE_LITERALS, Feature.ASYNC_IDENTIFIERS},
Expand Down
3 changes: 0 additions & 3 deletions src/blib2to3/pgen2/token.py
Expand Up @@ -74,9 +74,6 @@
COLONEQUAL: Final = 59
N_TOKENS: Final = 60
NT_OFFSET: Final = 256
# temporary for Python 2 deprecation
PRINT_STMT: Final = 316
EXEC_STMT: Final = 288
# --end constants--

tok_name: Final[Dict[int, str]] = {}
Expand Down
66 changes: 66 additions & 0 deletions tests/data/python2_detection.py
@@ -0,0 +1,66 @@
# This uses a similar construction to the decorators.py test data file FYI.

print "hello, world!"

###

exec "print('hello, world!')"

###

def set_position((x, y), value):
pass

###

try:
pass
except Exception, err:
pass

###

raise RuntimeError, "I feel like crashing today :p"

###

`wow_these_really_did_exist`

###

10L

# output

print("hello python three!")

###

exec("I'm not sure if you can use exec like this but that's not important here!")

###

try:
pass
except make_exception(1, 2):
pass

###

try:
pass
except Exception as err:
pass

###

raise RuntimeError(make_msg(1, 2))

###

def set_position(x, y, value):
pass

###

10
13 changes: 13 additions & 0 deletions tests/test_black.py
Expand Up @@ -2017,6 +2017,7 @@ def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
)


@pytest.mark.python2
@pytest.mark.parametrize("explicit", [True, False], ids=["explicit", "autodetection"])
def test_python_2_deprecation_with_target_version(explicit: bool) -> None:
args = [
Expand All @@ -2032,6 +2033,18 @@ def test_python_2_deprecation_with_target_version(explicit: bool) -> None:
assert "DEPRECATION: Python 2 support will be removed" in result.stderr


@pytest.mark.python2
def test_python_2_deprecation_autodetection_extended() -> None:
# this test has a similar construction to test_get_features_used_decorator
python2, non_python2 = read_data("python2_detection")
for python2_case in python2.split("###"):
node = black.lib2to3_parse(python2_case)
assert black.detect_target_versions(node) == {TargetVersion.PY27}
for non_python2_case in non_python2.split("###"):
node = black.lib2to3_parse(non_python2_case)
assert black.detect_target_versions(node) != {TargetVersion.PY27}


with open(black.__file__, "r", encoding="utf-8") as _bf:
black_source_lines = _bf.readlines()

Expand Down