Skip to content

Commit

Permalink
Merge 8db6d84 into 811decd
Browse files Browse the repository at this point in the history
  • Loading branch information
QuentinSoubeyran committed Sep 19, 2020
2 parents 811decd + 8db6d84 commit cb04a10
Show file tree
Hide file tree
Showing 8 changed files with 530 additions and 117 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -17,6 +17,8 @@

- Prevent coloured diff output being interleaved with multiple files (#1673)

- Added support for PEP 614 relaxed decorator syntax on python 3.9 (#1711)

### 20.8b1

#### _Packaging_
Expand Down
2 changes: 1 addition & 1 deletion Pipfile
Expand Up @@ -29,7 +29,7 @@ mypy_extensions = ">=0.4.3"
pathspec = ">=0.6"
regex = ">=2020.1.8"
toml = ">=0.10.1"
typed-ast = "==1.4.0"
typed-ast = "==1.4.1"
typing_extensions = ">=3.7.4"
black = {editable = true,extras = ["d"],path = "."}
dataclasses = {"python_version <" = "3.7","version >" = "0.6"}
297 changes: 192 additions & 105 deletions Pipfile.lock

Large diffs are not rendered by default.

70 changes: 67 additions & 3 deletions src/black/__init__.py
Expand Up @@ -178,14 +178,12 @@ class TargetVersion(Enum):
PY36 = 6
PY37 = 7
PY38 = 8
PY39 = 9

def is_python2(self) -> bool:
return self is TargetVersion.PY27


PY36_VERSIONS = {TargetVersion.PY36, TargetVersion.PY37, TargetVersion.PY38}


class Feature(Enum):
# All string literals are unicode
UNICODE_LITERALS = 1
Expand All @@ -199,6 +197,7 @@ class Feature(Enum):
ASYNC_KEYWORDS = 7
ASSIGNMENT_EXPRESSIONS = 8
POS_ONLY_ARGUMENTS = 9
RELAXED_DECORATORS = 10
FORCE_OPTIONAL_PARENTHESES = 50


Expand Down Expand Up @@ -237,6 +236,17 @@ class Feature(Enum):
Feature.ASSIGNMENT_EXPRESSIONS,
Feature.POS_ONLY_ARGUMENTS,
},
TargetVersion.PY39: {
Feature.UNICODE_LITERALS,
Feature.F_STRINGS,
Feature.NUMERIC_UNDERSCORES,
Feature.TRAILING_COMMA_IN_CALL,
Feature.TRAILING_COMMA_IN_DEF,
Feature.ASYNC_KEYWORDS,
Feature.ASSIGNMENT_EXPRESSIONS,
Feature.RELAXED_DECORATORS,
Feature.POS_ONLY_ARGUMENTS,
},
}


Expand Down Expand Up @@ -2184,6 +2194,9 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901
):
# Python 2 print chevron
return NO
elif prevp.type == token.AT and p.parent and p.parent.type == syms.decorator:
# no space in decorators
return NO

elif prev.type in OPENING_BRACKETS:
return NO
Expand Down Expand Up @@ -5499,6 +5512,49 @@ def is_walrus_assignment(node: LN) -> bool:
return inner is not None and inner.type == syms.namedexpr_test


def is_simple_decorator_trailer(node: LN, last: bool = False) -> bool:
"""Return True iff `node` is a trailer valid in a simple decorator"""
return node.type == syms.trailer and (
(
len(node.children) == 2
and node.children[0].type == token.DOT
and node.children[1].type == token.NAME
)
# last trailer can be arguments
or (
last
and len(node.children) == 3
and node.children[0].type == token.LPAR
# and node.children[1].type == syms.argument
and node.children[2].type == token.RPAR
)
)


def is_simple_decorator_expression(node: LN) -> bool:
"""Return True iff `node` could be a 'dotted name' decorator
This function takes the node of the 'namedexpr_test' of the new decorator
grammar and test if it would be valid under the old decorator grammar.
The old grammar was: decorator: @ dotted_name [arguments] NEWLINE
The new grammar is : decorator: @ namedexpr_test NEWLINE
"""
if node.type == token.NAME:
return True
if node.type == syms.power:
if node.children:
return (
node.children[0].type == token.NAME
and all(map(is_simple_decorator_trailer, node.children[1:-1]))
and (
len(node.children) < 2
or is_simple_decorator_trailer(node.children[-1], last=True)
)
)
return False


def is_yield(node: LN) -> bool:
"""Return True if `node` holds a `yield` or `yield from` expression."""
if node.type == syms.yield_expr:
Expand Down Expand Up @@ -5684,6 +5740,8 @@ def get_features_used(node: Node) -> Set[Feature]:
- underscores in numeric literals;
- trailing commas after * or ** in function signatures and calls;
- positional only arguments in function signatures and lambdas;
- assignment expression;
- relaxed decorator syntax;
"""
features: Set[Feature] = set()
for n in node.pre_order():
Expand All @@ -5703,6 +5761,12 @@ def get_features_used(node: Node) -> Set[Feature]:
elif n.type == token.COLONEQUAL:
features.add(Feature.ASSIGNMENT_EXPRESSIONS)

elif n.type == syms.decorator:
if len(n.children) > 1 and not is_simple_decorator_expression(
n.children[1]
):
features.add(Feature.RELAXED_DECORATORS)

elif (
n.type in {syms.typedargslist, syms.arglist}
and n.children
Expand Down
2 changes: 1 addition & 1 deletion src/blib2to3/Grammar.txt
Expand Up @@ -12,7 +12,7 @@ file_input: (NEWLINE | stmt)* ENDMARKER
single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
eval_input: testlist NEWLINE* ENDMARKER

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorator: '@' namedexpr_test NEWLINE
decorators: decorator+
decorated: decorators (classdef | funcdef | async_funcdef)
async_funcdef: ASYNC funcdef
Expand Down
176 changes: 176 additions & 0 deletions tests/data/decorators.py
@@ -0,0 +1,176 @@
# This file doesn't use the standard decomposition.
# Decorator syntax test cases are separated by double # comments.
# Those before the 'output' comment are valid under the old syntax.
# Those after the 'ouput' comment require PEP614 relaxed syntax.
# Do not remove the double # separator before the first test case, it allows
# the comment before the test case to be ignored.

##

@decorator
def f():
...

##

@decorator(arg)
def f():
...

##

@decorator(kwarg=0)
def f():
...

##

@decorator(*args)
def f():
...

##

@decorator(**kwargs)
def f():
...

##

@decorator(*args, **kwargs)
def f():
...

##

@decorator(*args, **kwargs,)
def f():
...

##

@dotted.decorator
def f():
...

##

@dotted.decorator(arg)
def f():
...

##

@dotted.decorator(kwarg=0)
def f():
...

##

@dotted.decorator(*args)
def f():
...

##

@dotted.decorator(**kwargs)
def f():
...

##

@dotted.decorator(*args, **kwargs)
def f():
...

##

@dotted.decorator(*args, **kwargs,)
def f():
...

##

@double.dotted.decorator
def f():
...

##

@double.dotted.decorator(arg)
def f():
...

##

@double.dotted.decorator(kwarg=0)
def f():
...

##

@double.dotted.decorator(*args)
def f():
...

##

@double.dotted.decorator(**kwargs)
def f():
...

##

@double.dotted.decorator(*args, **kwargs)
def f():
...

##

@double.dotted.decorator(*args, **kwargs,)
def f():
...

##

@_(sequence["decorator"])
def f():
...

##

@eval("sequence['decorator']")
def f():
...

# output

##

@decorator()()
def f():
...

##

@(decorator)
def f():
...

##

@sequence["decorator"]
def f():
...

##

@decorator[List[str]]
def f():
...

##

@var := decorator
def f():
...
37 changes: 37 additions & 0 deletions tests/data/python39.py
@@ -0,0 +1,37 @@
#!/usr/bin/env python3.9

@relaxed_decorator[0]
def f():
...

@relaxed_decorator[extremely_long_name_that_definitely_will_not_fit_on_one_line_of_standard_length]
def f():
...

@extremely_long_variable_name_that_doesnt_fit := complex.expression(with_long="arguments_value_that_wont_fit_at_the_end_of_the_line")
def f():
...

# output


#!/usr/bin/env python3.9


@relaxed_decorator[0]
def f():
...


@relaxed_decorator[
extremely_long_name_that_definitely_will_not_fit_on_one_line_of_standard_length
]
def f():
...


@extremely_long_variable_name_that_doesnt_fit := complex.expression(
with_long="arguments_value_that_wont_fit_at_the_end_of_the_line"
)
def f():
...

0 comments on commit cb04a10

Please sign in to comment.