From e65f9351db2d9a0f2d4ac9de3ede8da765cc17ea Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 23 Jan 2022 16:36:27 +0530 Subject: [PATCH 01/16] Use parenthesis on method access on number integerals (float and int) --- src/black/linegen.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/black/linegen.py b/src/black/linegen.py index 9ee42aaaf72..dad232b9c3b 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -197,6 +197,14 @@ def visit_decorators(self, node: Node) -> Iterator[Line]: yield from self.line() yield from self.visit(child) + def visit_power(self, node: Node) -> Iterator[Line]: + for idx, leaf in enumerate(node.children[:-1]): + next_leaf = node.children[idx + 1] + if leaf.type == token.NUMBER and next_leaf.type == syms.trailer: + wrap_in_parentheses(node, leaf) + + yield from self.visit_default(node) + def visit_SEMI(self, leaf: Leaf) -> Iterator[Line]: """Remove a semicolon and put the other statement on a separate line.""" yield from self.line() From 5ee6627f4e97903768bf37f51f1071b00a736152 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 23 Jan 2022 16:37:12 +0530 Subject: [PATCH 02/16] Add new tests for format feature --- tests/data/use_braces_in_int_method_calls.py | 24 ++++++++++++++++++++ tests/test_format.py | 1 + 2 files changed, 25 insertions(+) create mode 100644 tests/data/use_braces_in_int_method_calls.py diff --git a/tests/data/use_braces_in_int_method_calls.py b/tests/data/use_braces_in_int_method_calls.py new file mode 100644 index 00000000000..a96336bbffe --- /dev/null +++ b/tests/data/use_braces_in_int_method_calls.py @@ -0,0 +1,24 @@ +count = 5 .bit_count() +abs = 10 .__abs__() +is_integer = 10.5 .is_integer() + +if 10 .real: ... + +for number in range(1, 10): + if number.imag: ... + + +# output +count = (5).bit_count() +abs = (10).__abs__() +is_integer = (10.5).is_integer() + +if (10).real: + ... + +for number in range(1, 10): + if number.imag: + ... + + + diff --git a/tests/test_format.py b/tests/test_format.py index 3895a095e86..3c66604fd3e 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -53,6 +53,7 @@ "string_prefixes", "tricky_unicode_symbols", "tupleassign", + "use_braces_in_int_method_calls", ] PY310_CASES: List[str] = [ From 51d6be8d9281a150a883f789eebe251919549366 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 23 Jan 2022 16:37:33 +0530 Subject: [PATCH 03/16] Edit exisiting tests to reflect new formatting change --- tests/data/expression.diff | 9 ++++++--- tests/data/expression.py | 4 ++-- tests/data/expression_skip_magic_trailing_comma.diff | 9 ++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/data/expression.diff b/tests/data/expression.diff index 721a07d2141..ca03b80e41f 100644 --- a/tests/data/expression.diff +++ b/tests/data/expression.diff @@ -11,7 +11,7 @@ True False 1 -@@ -29,63 +29,96 @@ +@@ -29,91 +29,127 @@ ~great +value -1 @@ -130,8 +130,11 @@ call(**self.screen_kwargs) call(b, **self.screen_kwargs) lukasz.langa.pl -@@ -94,26 +127,29 @@ - 1.0 .real + call.me(maybe) +-1 .real +-1.0 .real ++(1).real ++(1.0).real ....__class__ list[str] dict[str, int] diff --git a/tests/data/expression.py b/tests/data/expression.py index d13450cda68..deaebbb8c4d 100644 --- a/tests/data/expression.py +++ b/tests/data/expression.py @@ -382,8 +382,8 @@ async def f(): call(b, **self.screen_kwargs) lukasz.langa.pl call.me(maybe) -1 .real -1.0 .real +(1).real +(1.0).real ....__class__ list[str] dict[str, int] diff --git a/tests/data/expression_skip_magic_trailing_comma.diff b/tests/data/expression_skip_magic_trailing_comma.diff index 4a8a95c7237..d1189fbaff5 100644 --- a/tests/data/expression_skip_magic_trailing_comma.diff +++ b/tests/data/expression_skip_magic_trailing_comma.diff @@ -11,7 +11,7 @@ True False 1 -@@ -29,63 +29,84 @@ +@@ -29,91 +29,110 @@ ~great +value -1 @@ -118,8 +118,11 @@ call(**self.screen_kwargs) call(b, **self.screen_kwargs) lukasz.langa.pl -@@ -94,26 +115,24 @@ - 1.0 .real + call.me(maybe) +-1 .real +-1.0 .real ++(1).real ++(1.0).real ....__class__ list[str] dict[str, int] From 74b7b3928434e6a82c27ed5639ead49135f2560b Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 24 Jan 2022 10:18:17 +0530 Subject: [PATCH 04/16] Add changelog entry --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 634db79bf73..bd6cfdff503 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,6 +38,7 @@ `--preview` (#2789) - Enable Python 3.10+ by default, without any extra need to specify `--target-version=py310`. (#2758) +- Use parenthesis on method access on number integerals (float and int) (#2799) ### Packaging From 0be035290e8d575fa5a869f6c5af1b98f60bdf14 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 24 Jan 2022 10:21:44 +0530 Subject: [PATCH 05/16] Update CHANGES.md Co-authored-by: Jelle Zijlstra --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index bd6cfdff503..758bbb3d354 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,7 +38,7 @@ `--preview` (#2789) - Enable Python 3.10+ by default, without any extra need to specify `--target-version=py310`. (#2758) -- Use parenthesis on method access on number integerals (float and int) (#2799) +- Use parentheses on method access on float and int literals (#2799) ### Packaging From 9c8ba3e9cfc55df74c173859932c7b986fcf0f3e Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 24 Jan 2022 12:32:30 +0530 Subject: [PATCH 06/16] Don"t format 0x prefixed integers literals and exclusive complex literals --- src/black/linegen.py | 12 +++++++++++- tests/data/use_braces_in_int_method_calls.py | 6 ++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index dad232b9c3b..77b4daf29bf 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -200,7 +200,17 @@ def visit_decorators(self, node: Node) -> Iterator[Line]: def visit_power(self, node: Node) -> Iterator[Line]: for idx, leaf in enumerate(node.children[:-1]): next_leaf = node.children[idx + 1] - if leaf.type == token.NUMBER and next_leaf.type == syms.trailer: + if ( + isinstance(leaf, Leaf) + and leaf.type == token.NUMBER + and next_leaf.type == syms.trailer + # Integers prefixed by 0x are represented under the hood in just + # the same way as integers that aren't. This means that to Python, + # 0xFF is the same as 255, and there's no way to tell them apart + and (not leaf.value.startswith("0x")) + # It shouldn't wrap complex numbers + and "j" not in leaf.value + ): wrap_in_parentheses(node, leaf) yield from self.visit_default(node) diff --git a/tests/data/use_braces_in_int_method_calls.py b/tests/data/use_braces_in_int_method_calls.py index a96336bbffe..30b8c925d77 100644 --- a/tests/data/use_braces_in_int_method_calls.py +++ b/tests/data/use_braces_in_int_method_calls.py @@ -7,6 +7,9 @@ for number in range(1, 10): if number.imag: ... +exclusive_complex = 10j.imag + +hex_decimal = 0x10.real # output count = (5).bit_count() @@ -20,5 +23,8 @@ if number.imag: ... +exclusive_complex = 10j .imag + +hex_decimal = 0x10 .real From c2ca825eb8230f1038e0dc9cf3321a713e5207bc Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 25 Jan 2022 15:24:58 +0530 Subject: [PATCH 07/16] Don"t add whitespaces after number literal in dot operator access --- src/black/lines.py | 20 +++++++++++++++----- tests/data/use_braces_in_int_method_calls.py | 4 ++-- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index c602aa69ce9..043d5dcc1d5 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -62,11 +62,21 @@ def append(self, leaf: Leaf, preformatted: bool = False) -> None: if token.COLON == leaf.type and self.is_class_paren_empty: del self.leaves[-2:] if self.leaves and not preformatted: - # Note: at this point leaf.prefix should be empty except for - # imports, for which we only preserve newlines. - leaf.prefix += whitespace( - leaf, complex_subscript=self.is_complex_subscript(leaf) - ) + grandparent_leaf = leaf.parent.parent if leaf.parent else None + + # It shouldn't add whitespaces after dot operators in expressions + # accessing method/attributes from integer and float literals + if not ( + grandparent_leaf + and grandparent_leaf.type == syms.power + and grandparent_leaf.children[0].type == token.NUMBER + and leaf.type == token.DOT + ): + # Note: at this point leaf.prefix should be empty except for + # imports, for which we only preserve newlines. + leaf.prefix += whitespace( + leaf, complex_subscript=self.is_complex_subscript(leaf) + ) if self.inside_brackets or not preformatted: self.bracket_tracker.mark(leaf) if self.mode.magic_trailing_comma: diff --git a/tests/data/use_braces_in_int_method_calls.py b/tests/data/use_braces_in_int_method_calls.py index 30b8c925d77..c6cb057e434 100644 --- a/tests/data/use_braces_in_int_method_calls.py +++ b/tests/data/use_braces_in_int_method_calls.py @@ -23,8 +23,8 @@ if number.imag: ... -exclusive_complex = 10j .imag +exclusive_complex = 10j.imag -hex_decimal = 0x10 .real +hex_decimal = 0x10.real From faf131c93867a419560d767c75d48598ebcfebb6 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 26 Jan 2022 08:17:17 +0530 Subject: [PATCH 08/16] Update changelog wording Co-authored-by: Jelle Zijlstra --- CHANGES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7e27539afe1..de300fa3e7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -40,9 +40,9 @@ - Enable Python 3.10+ by default, without any extra need to specify `--target-version=py310`. (#2758) - Make passing `SRC` or `--code` mandatory and mutually exclusive (#2804) -- Use parentheses on method access on float and int literals (#2799) -- Don't add whitespaces after hex-decimals and exclusive complex numbers (ending "j") on - method access (#2799) +- Use parentheses for attribute access on decimal float and int literals (#2799) +- Don't add whitespace for attribute access on hexadecimal, binary, octal, and complex + literals (#2799) ### Packaging From 3dbe39d53a860e26c4820e3a123faa23a9edbd4d Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 26 Jan 2022 08:39:46 +0530 Subject: [PATCH 09/16] Don't wrap binary and octal integers with parentheses This commit also adds a few more tests for the feature --- src/black/linegen.py | 22 ++++++---- .../attribute_access_on_number_literals.py | 41 +++++++++++++++++++ tests/data/use_braces_in_int_method_calls.py | 30 -------------- tests/test_format.py | 2 +- 4 files changed, 56 insertions(+), 39 deletions(-) create mode 100644 tests/data/attribute_access_on_number_literals.py delete mode 100644 tests/data/use_braces_in_int_method_calls.py diff --git a/src/black/linegen.py b/src/black/linegen.py index 14e107eb5ea..89d541cd606 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -200,16 +200,22 @@ def visit_decorators(self, node: Node) -> Iterator[Line]: def visit_power(self, node: Node) -> Iterator[Line]: for idx, leaf in enumerate(node.children[:-1]): next_leaf = node.children[idx + 1] + + if not isinstance(leaf, Leaf): + continue + + value = leaf.value.lower() if ( - isinstance(leaf, Leaf) - and leaf.type == token.NUMBER + leaf.type == token.NUMBER and next_leaf.type == syms.trailer - # Integers prefixed by 0x are represented under the hood in just - # the same way as integers that aren't. This means that to Python, - # 0xFF is the same as 255, and there's no way to tell them apart - and (not leaf.value.startswith("0x")) - # It shouldn't wrap complex numbers - and "j" not in leaf.value + # Shouldn't wrap octal literals + and (not value.startswith("0o")) + # Shouldn't wrap binary literals + and (not value.startswith("0b")) + # Shouldn't wrap hexadecimal literals + and (not value.startswith("0x")) + # It shouldn't wrap complex literals + and "j" not in value ): wrap_in_parentheses(node, leaf) diff --git a/tests/data/attribute_access_on_number_literals.py b/tests/data/attribute_access_on_number_literals.py new file mode 100644 index 00000000000..ff565a5b483 --- /dev/null +++ b/tests/data/attribute_access_on_number_literals.py @@ -0,0 +1,41 @@ +x = 123456789 .bit_count() +x = (123456).__abs__() +x = .1.is_integer() +x = 1. .imag +x = 1E+1.imag +x = 1E-1.real +x = 123456789.123456789.hex() +x = 123456789.123456789E123456789 .real +x = 123456789E123456789 .conjugate() +x = 123456789J.real +x = 123456789.123456789J.__add__(0b1011.bit_length()) +x = 0XB1ACC.conjugate() +x = 0B1011 .conjugate() +x = 0O777 .real +x = 0.000000006 .hex() +x = -100.0000J + +if 10 .real: + ... + +# output + +x = (123456789).bit_count() +x = (123456).__abs__() +x = (0.1).is_integer() +x = (1.0).imag +x = (1e1).imag +x = (1e-1).real +x = (123456789.123456789).hex() +x = (123456789.123456789e123456789).real +x = (123456789e123456789).conjugate() +x = 123456789j.real +x = 123456789.123456789j.__add__(0b1011.bit_length()) +x = 0xB1ACC.conjugate() +x = 0b1011.conjugate() +x = 0o777.real +x = (0.000000006).hex() +x = -100.0000j + +if (10).real: + ... diff --git a/tests/data/use_braces_in_int_method_calls.py b/tests/data/use_braces_in_int_method_calls.py deleted file mode 100644 index c6cb057e434..00000000000 --- a/tests/data/use_braces_in_int_method_calls.py +++ /dev/null @@ -1,30 +0,0 @@ -count = 5 .bit_count() -abs = 10 .__abs__() -is_integer = 10.5 .is_integer() - -if 10 .real: ... - -for number in range(1, 10): - if number.imag: ... - -exclusive_complex = 10j.imag - -hex_decimal = 0x10.real - -# output -count = (5).bit_count() -abs = (10).__abs__() -is_integer = (10.5).is_integer() - -if (10).real: - ... - -for number in range(1, 10): - if number.imag: - ... - -exclusive_complex = 10j.imag - -hex_decimal = 0x10.real - - diff --git a/tests/test_format.py b/tests/test_format.py index 2527eb44f98..2176c0265bf 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -15,6 +15,7 @@ ) SIMPLE_CASES: List[str] = [ + "attribute_access_on_number_literals", "beginning_backslash", "bracketmatch", "class_blank_parentheses", @@ -54,7 +55,6 @@ "string_prefixes", "tricky_unicode_symbols", "tupleassign", - "use_braces_in_int_method_calls", ] PY310_CASES: List[str] = [ From db0ef3244a88d8a24ec927fe6624aa73b97030de Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 26 Jan 2022 08:44:55 +0530 Subject: [PATCH 10/16] Remove unnecessary `maxDiff` change to `BlackTestCase` --- tests/test_black.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_black.py b/tests/test_black.py index 46d40b0d62d..a3945bb7332 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -138,7 +138,6 @@ def invokeBlack( class BlackTestCase(BlackBaseTestCase): - maxDiff = None invokeBlack = staticmethod(invokeBlack) def test_empty_ff(self) -> None: From 5368ba5b853dc4b0888e7ea5242a2b7d967b9028 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 26 Jan 2022 08:59:20 +0530 Subject: [PATCH 11/16] Update linegen.py --- src/black/linegen.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index 89d541cd606..791d83459df 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -208,12 +208,8 @@ def visit_power(self, node: Node) -> Iterator[Line]: if ( leaf.type == token.NUMBER and next_leaf.type == syms.trailer - # Shouldn't wrap octal literals - and (not value.startswith("0o")) - # Shouldn't wrap binary literals - and (not value.startswith("0b")) - # Shouldn't wrap hexadecimal literals - and (not value.startswith("0x")) + # It shouldn't wrap hexadecimal, binary and octal literals + and (not value.startswith(("0x", "0b", "0o"))) # It shouldn't wrap complex literals and "j" not in value ): From ff4e5c9b1e86f8be46ced9e7801ca84ce688588b Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 26 Jan 2022 09:00:31 +0530 Subject: [PATCH 12/16] Remove extra newline --- tests/test_black.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_black.py b/tests/test_black.py index a3945bb7332..2dd284f2cd6 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -137,7 +137,6 @@ def invokeBlack( class BlackTestCase(BlackBaseTestCase): - invokeBlack = staticmethod(invokeBlack) def test_empty_ff(self) -> None: From bf29eeaac7c3011dfa3f16f403c816fcd5e67ce3 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 26 Jan 2022 09:34:42 +0530 Subject: [PATCH 13/16] Move logic for handling whitespace before attributes to nodes.py It is added as an extra case in the nodes.py, which could just be removed to mention this formatting change --- src/black/lines.py | 20 +++++--------------- src/black/nodes.py | 7 +------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/black/lines.py b/src/black/lines.py index 043d5dcc1d5..c602aa69ce9 100644 --- a/src/black/lines.py +++ b/src/black/lines.py @@ -62,21 +62,11 @@ def append(self, leaf: Leaf, preformatted: bool = False) -> None: if token.COLON == leaf.type and self.is_class_paren_empty: del self.leaves[-2:] if self.leaves and not preformatted: - grandparent_leaf = leaf.parent.parent if leaf.parent else None - - # It shouldn't add whitespaces after dot operators in expressions - # accessing method/attributes from integer and float literals - if not ( - grandparent_leaf - and grandparent_leaf.type == syms.power - and grandparent_leaf.children[0].type == token.NUMBER - and leaf.type == token.DOT - ): - # Note: at this point leaf.prefix should be empty except for - # imports, for which we only preserve newlines. - leaf.prefix += whitespace( - leaf, complex_subscript=self.is_complex_subscript(leaf) - ) + # Note: at this point leaf.prefix should be empty except for + # imports, for which we only preserve newlines. + leaf.prefix += whitespace( + leaf, complex_subscript=self.is_complex_subscript(leaf) + ) if self.inside_brackets or not preformatted: self.bracket_tracker.mark(leaf) if self.mode.magic_trailing_comma: diff --git a/src/black/nodes.py b/src/black/nodes.py index 74dfa896295..51d4cb8618d 100644 --- a/src/black/nodes.py +++ b/src/black/nodes.py @@ -306,12 +306,7 @@ def whitespace(leaf: Leaf, *, complex_subscript: bool) -> str: # noqa: C901 return NO if not prev: - if t == token.DOT: - prevp = preceding_leaf(p) - if not prevp or prevp.type != token.NUMBER: - return NO - - elif t == token.LSQB: + if t == token.DOT or t == token.LSQB: return NO elif prev.type != token.COMMA: From 08e566b4384fc6081959db25df398a1635a11136 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 26 Jan 2022 14:34:04 +0530 Subject: [PATCH 14/16] Make check stricter to ensure that we are in an attribute trailer --- src/black/linegen.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/black/linegen.py b/src/black/linegen.py index 791d83459df..7626472c2f0 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -208,6 +208,8 @@ def visit_power(self, node: Node) -> Iterator[Line]: if ( leaf.type == token.NUMBER and next_leaf.type == syms.trailer + # Ensure that we are in an attribute trailer + and next_leaf.children[0].type == token.DOT # It shouldn't wrap hexadecimal, binary and octal literals and (not value.startswith(("0x", "0b", "0o"))) # It shouldn't wrap complex literals From 9d6a0d4574f146bd5fa9e8b69ebaaf7fae9b8aa7 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 26 Jan 2022 15:36:42 +0530 Subject: [PATCH 15/16] Add tests to verify previous commit behaviour --- tests/data/attribute_access_on_number_literals.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/data/attribute_access_on_number_literals.py b/tests/data/attribute_access_on_number_literals.py index ff565a5b483..7c16bdfb3a5 100644 --- a/tests/data/attribute_access_on_number_literals.py +++ b/tests/data/attribute_access_on_number_literals.py @@ -18,6 +18,9 @@ if 10 .real: ... +y = 100[no] +y = 100(no) + # output x = (123456789).bit_count() @@ -39,3 +42,6 @@ if (10).real: ... + +y = 100[no] +y = 100(no) From 385d67f12a560dd7b408d5f4d860ceb096246bed Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Fri, 28 Jan 2022 10:39:52 +0530 Subject: [PATCH 16/16] Remove parentheses from `and not` expression MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Felix Hildén --- src/black/linegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/black/linegen.py b/src/black/linegen.py index ff387983f42..b572ed0b52f 100644 --- a/src/black/linegen.py +++ b/src/black/linegen.py @@ -211,7 +211,7 @@ def visit_power(self, node: Node) -> Iterator[Line]: # Ensure that we are in an attribute trailer and next_leaf.children[0].type == token.DOT # It shouldn't wrap hexadecimal, binary and octal literals - and (not value.startswith(("0x", "0b", "0o"))) + and not value.startswith(("0x", "0b", "0o")) # It shouldn't wrap complex literals and "j" not in value ):