def visit_ConcatenatedString(self, node: cst.ConcatenatedString) -> None: # Skip if our immediate parent is also a ConcatenatedString, since our parent # should've already reported this violation. if isinstance(self.context.node_stack[-2], cst.ConcatenatedString): return # collect nested ConcatenatedString nodes into a flat list from outer to # innermost children children: List[cst.ConcatenatedString] = [] el = node while isinstance(el, cst.ConcatenatedString): children.append(el) # left cannot be a ConcatenatedString, only right can. el = el.right # Build up a replacement by starting with the innermost child replacement = children[-1].right for el in reversed(children): replacement = cst.BinaryOperation( left=el.left, # left is never a ConcatenatedString operator=cst.Add( whitespace_before=el.whitespace_between, whitespace_after=cst.SimpleWhitespace(" "), ), right=replacement, lpar=el.lpar, rpar=el.rpar, ) # A binary operation has a lower priority in the order-of-operations than an # implicitly concatenated string, so we need to make sure the replacement is # parenthesized to make our change safe. if not replacement.lpar: # There's a good chance that the formatting might be messed up by this, but # black should be able to sort it out when it gets run next time. # # Because of the changes needed (e.g. increased indentation of children), # it's not really sane/possible for us to format this any better. replacement = replacement.with_changes(lpar=[cst.LeftParen()], rpar=[cst.RightParen()]) self.report(node, replacement=replacement)
class BinaryOperationTest(CSTNodeTest): @data_provider( ( # Simple binary operations { "node": cst.BinaryOperation(cst.Name("foo"), cst.Add(), cst.Float("5.5")), "code": "foo + 5.5", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.Subtract(), cst.Float("5.5")), "code": "foo - 5.5", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.LeftShift(), cst.Integer("5")), "code": "foo << 5", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.RightShift(), cst.Integer("5")), "code": "foo >> 5", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.BitAnd(), cst.Name("bar")), "code": "foo & bar", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.BitXor(), cst.Name("bar")), "code": "foo ^ bar", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.BitOr(), cst.Name("bar")), "code": "foo | bar", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.Multiply(), cst.Float("5.5")), "code": "foo * 5.5", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.MatrixMultiply(), cst.Float("5.5")), "code": "foo @ 5.5", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.Divide(), cst.Float("5.5")), "code": "foo / 5.5", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.Modulo(), cst.Float("5.5")), "code": "foo % 5.5", "parser": parse_expression, "expected_position": None, }, { "node": cst.BinaryOperation(cst.Name("foo"), cst.FloorDivide(), cst.Float("5.5")), "code": "foo // 5.5", "parser": parse_expression, "expected_position": None, }, # Parenthesized binary operation { "node": cst.BinaryOperation( lpar=(cst.LeftParen(), ), left=cst.Name("foo"), operator=cst.LeftShift(), right=cst.Integer("5"), rpar=(cst.RightParen(), ), ), "code": "(foo << 5)", "parser": parse_expression, "expected_position": None, }, # Make sure that spacing works { "node": cst.BinaryOperation( lpar=(cst.LeftParen( whitespace_after=cst.SimpleWhitespace(" ")), ), left=cst.Name("foo"), operator=cst.Multiply( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), ), right=cst.Name("bar"), rpar=(cst.RightParen( whitespace_before=cst.SimpleWhitespace(" ")), ), ), "code": "( foo * bar )", "parser": parse_expression, "expected_position": CodeRange((1, 2), (1, 13)), }, )) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs) @data_provider(( { "get_node": (lambda: cst.BinaryOperation( cst.Name("foo"), cst.Add(), cst.Name("bar"), lpar=(cst.LeftParen(), ), )), "expected_re": "left paren without right paren", }, { "get_node": (lambda: cst.BinaryOperation( cst.Name("foo"), cst.Add(), cst.Name("bar"), rpar=(cst.RightParen(), ), )), "expected_re": "right paren without left paren", }, )) def test_invalid(self, **kwargs: Any) -> None: self.assert_invalid(**kwargs)
class SmallStatementTest(CSTNodeTest): @data_provider(( # pyre-fixme[6]: Incompatible parameter type { "node": cst.Pass(), "code": "pass" }, { "node": cst.Pass(semicolon=cst.Semicolon()), "code": "pass;" }, { "node": cst.Pass(semicolon=cst.Semicolon( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), )), "code": "pass ; ", "expected_position": CodeRange((1, 0), (1, 4)), }, { "node": cst.Continue(), "code": "continue" }, { "node": cst.Continue(semicolon=cst.Semicolon()), "code": "continue;" }, { "node": cst.Continue(semicolon=cst.Semicolon( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), )), "code": "continue ; ", "expected_position": CodeRange((1, 0), (1, 8)), }, { "node": cst.Break(), "code": "break" }, { "node": cst.Break(semicolon=cst.Semicolon()), "code": "break;" }, { "node": cst.Break(semicolon=cst.Semicolon( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), )), "code": "break ; ", "expected_position": CodeRange((1, 0), (1, 5)), }, { "node": cst.Expr( cst.BinaryOperation(cst.Name("x"), cst.Add(), cst.Name("y"))), "code": "x + y", }, { "node": cst.Expr( cst.BinaryOperation(cst.Name("x"), cst.Add(), cst.Name("y")), semicolon=cst.Semicolon(), ), "code": "x + y;", }, { "node": cst.Expr( cst.BinaryOperation(cst.Name("x"), cst.Add(), cst.Name("y")), semicolon=cst.Semicolon( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), ), ), "code": "x + y ; ", "expected_position": CodeRange((1, 0), (1, 5)), }, )) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs)
class AugAssignTest(CSTNodeTest): @data_provider(( # Simple assignment constructor case. { "node": cst.AugAssign(cst.Name("foo"), cst.AddAssign(), cst.Integer("5")), "code": "foo += 5", "parser": None, "expected_position": CodeRange((1, 0), (1, 8)), }, { "node": cst.AugAssign(cst.Name("bar"), cst.MultiplyAssign(), cst.Name("foo")), "code": "bar *= foo", "parser": None, "expected_position": None, }, # Whitespace constructor test { "node": cst.AugAssign( target=cst.Name("foo"), operator=cst.LeftShiftAssign( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), ), value=cst.Integer("5"), ), "code": "foo <<= 5", "parser": None, "expected_position": CodeRange((1, 0), (1, 11)), }, # Simple assignment parser case. { "node": cst.SimpleStatementLine((cst.AugAssign(cst.Name("foo"), cst.AddAssign(), cst.Integer("5")), )), "code": "foo += 5\n", "parser": parse_statement, "expected_position": None, }, { "node": cst.SimpleStatementLine((cst.AugAssign(cst.Name("bar"), cst.MultiplyAssign(), cst.Name("foo")), )), "code": "bar *= foo\n", "parser": parse_statement, "expected_position": None, }, # Whitespace parser test { "node": cst.SimpleStatementLine((cst.AugAssign( target=cst.Name("foo"), operator=cst.LeftShiftAssign( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), ), value=cst.Integer("5"), ), )), "code": "foo <<= 5\n", "parser": parse_statement, "expected_position": None, }, )) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs) @data_provider(( { "get_node": ( lambda: cst.AugAssign( # pyre-ignore: Incompatible parameter type [6] target=cst.BinaryOperation( left=cst.Name("x"), operator=cst.Add(), right=cst.Integer("1"), ), operator=cst.Add(), value=cst.Name("y"), )), "expected_re": ("Expected an instance of .*BaseAssignTargetExpression.*"), }, )) def test_invalid_types(self, **kwargs: Any) -> None: self.assert_invalid_types(**kwargs)
class AnnAssignTest(CSTNodeTest): @data_provider(( # Simple assignment creation case. { "node": cst.AnnAssign(cst.Name("foo"), cst.Annotation(cst.Name("str")), cst.Integer("5")), "code": "foo: str = 5", "parser": None, "expected_position": CodeRange((1, 0), (1, 12)), }, # Annotation creation without assignment { "node": cst.AnnAssign(cst.Name("foo"), cst.Annotation(cst.Name("str"))), "code": "foo: str", "parser": None, "expected_position": CodeRange((1, 0), (1, 8)), }, # Complex annotation creation { "node": cst.AnnAssign( cst.Name("foo"), cst.Annotation( cst.Subscript( cst.Name("Optional"), (cst.SubscriptElement(cst.Index(cst.Name("str"))), ), )), cst.Integer("5"), ), "code": "foo: Optional[str] = 5", "parser": None, "expected_position": CodeRange((1, 0), (1, 22)), }, # Simple assignment parser case. { "node": cst.SimpleStatementLine((cst.AnnAssign( target=cst.Name("foo"), annotation=cst.Annotation( annotation=cst.Name("str"), whitespace_before_indicator=cst.SimpleWhitespace(""), ), equal=cst.AssignEqual(), value=cst.Integer("5"), ), )), "code": "foo: str = 5\n", "parser": parse_statement, "expected_position": None, }, # Annotation without assignment { "node": cst.SimpleStatementLine((cst.AnnAssign( target=cst.Name("foo"), annotation=cst.Annotation( annotation=cst.Name("str"), whitespace_before_indicator=cst.SimpleWhitespace(""), ), value=None, ), )), "code": "foo: str\n", "parser": parse_statement, "expected_position": None, }, # Complex annotation { "node": cst.SimpleStatementLine((cst.AnnAssign( target=cst.Name("foo"), annotation=cst.Annotation( annotation=cst.Subscript( cst.Name("Optional"), (cst.SubscriptElement(cst.Index(cst.Name("str"))), ), ), whitespace_before_indicator=cst.SimpleWhitespace(""), ), equal=cst.AssignEqual(), value=cst.Integer("5"), ), )), "code": "foo: Optional[str] = 5\n", "parser": parse_statement, "expected_position": None, }, # Whitespace test { "node": cst.AnnAssign( target=cst.Name("foo"), annotation=cst.Annotation( annotation=cst.Subscript( cst.Name("Optional"), (cst.SubscriptElement(cst.Index(cst.Name("str"))), ), ), whitespace_before_indicator=cst.SimpleWhitespace(" "), whitespace_after_indicator=cst.SimpleWhitespace(" "), ), equal=cst.AssignEqual( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), ), value=cst.Integer("5"), ), "code": "foo : Optional[str] = 5", "parser": None, "expected_position": CodeRange((1, 0), (1, 26)), }, { "node": cst.SimpleStatementLine((cst.AnnAssign( target=cst.Name("foo"), annotation=cst.Annotation( annotation=cst.Subscript( cst.Name("Optional"), (cst.SubscriptElement(cst.Index(cst.Name("str"))), ), ), whitespace_before_indicator=cst.SimpleWhitespace(" "), whitespace_after_indicator=cst.SimpleWhitespace(" "), ), equal=cst.AssignEqual( whitespace_before=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), ), value=cst.Integer("5"), ), )), "code": "foo : Optional[str] = 5\n", "parser": parse_statement, "expected_position": None, }, )) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs) @data_provider(({ "get_node": (lambda: cst.AnnAssign( target=cst.Name("foo"), annotation=cst.Annotation(cst.Name("str")), equal=cst.AssignEqual(), value=None, )), "expected_re": "Must have a value when specifying an AssignEqual.", }, )) def test_invalid(self, **kwargs: Any) -> None: self.assert_invalid(**kwargs) @data_provider(( { "get_node": ( lambda: cst.AnnAssign( # pyre-ignore: Incompatible parameter type [6] target=cst.BinaryOperation( left=cst.Name("x"), operator=cst.Add(), right=cst.Integer("1"), ), annotation=cst.Annotation(cst.Name("int")), equal=cst.AssignEqual(), value=cst.Name("y"), )), "expected_re": ("Expected an instance of .*BaseAssignTargetExpression.*"), }, )) def test_invalid_types(self, **kwargs: Any) -> None: self.assert_invalid_types(**kwargs)
class AssignTest(CSTNodeTest): @data_provider(( # Simple assignment creation case. { "node": cst.Assign((cst.AssignTarget(cst.Name("foo")), ), cst.Integer("5")), "code": "foo = 5", "parser": None, "expected_position": CodeRange((1, 0), (1, 7)), }, # Multiple targets creation { "node": cst.Assign( ( cst.AssignTarget(cst.Name("foo")), cst.AssignTarget(cst.Name("bar")), ), cst.Integer("5"), ), "code": "foo = bar = 5", "parser": None, "expected_position": CodeRange((1, 0), (1, 13)), }, # Whitespace test for creating nodes { "node": cst.Assign( (cst.AssignTarget( cst.Name("foo"), whitespace_before_equal=cst.SimpleWhitespace(""), whitespace_after_equal=cst.SimpleWhitespace(""), ), ), cst.Integer("5"), ), "code": "foo=5", "parser": None, "expected_position": CodeRange((1, 0), (1, 5)), }, # Simple assignment parser case. { "node": cst.SimpleStatementLine((cst.Assign( (cst.AssignTarget(cst.Name("foo")), ), cst.Integer("5")), )), "code": "foo = 5\n", "parser": parse_statement, "expected_position": None, }, # Multiple targets parser { "node": cst.SimpleStatementLine((cst.Assign( ( cst.AssignTarget(cst.Name("foo")), cst.AssignTarget(cst.Name("bar")), ), cst.Integer("5"), ), )), "code": "foo = bar = 5\n", "parser": parse_statement, "expected_position": None, }, # Whitespace test parser { "node": cst.SimpleStatementLine((cst.Assign( (cst.AssignTarget( cst.Name("foo"), whitespace_before_equal=cst.SimpleWhitespace(""), whitespace_after_equal=cst.SimpleWhitespace(""), ), ), cst.Integer("5"), ), )), "code": "foo=5\n", "parser": parse_statement, "expected_position": None, }, )) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs) @data_provider(({ "get_node": (lambda: cst.Assign(targets=(), value=cst.Integer("5"))), "expected_re": "at least one AssignTarget", }, )) def test_invalid(self, **kwargs: Any) -> None: self.assert_invalid(**kwargs) @data_provider(( { "get_node": ( lambda: cst.Assign( # pyre-ignore: Incompatible parameter type [6] targets=[ cst.BinaryOperation( left=cst.Name("x"), operator=cst.Add(), right=cst.Integer("1"), ), ], value=cst.Name("y"), )), "expected_re": "Expected an instance of .*statement.AssignTarget.*", }, )) def test_invalid_types(self, **kwargs: Any) -> None: self.assert_invalid_types(**kwargs)
def add(left, right): return cst.BinaryOperation(left, cst.Add(), right)