def replace_operators(self, node: cst.Comparison) -> cst.Comparison: prev_comparator = node.left comparisons = [] for target in node.comparisons: op, comparator = target.operator, target.comparator if isinstance(op, (cst.Is, cst.IsNot)): if isinstance(prev_comparator, self.PRIMITIVES) or isinstance( comparator, self.PRIMITIVES): target = target.with_changes(operator=cst.Equal( whitespace_before=op.whitespace_before, whitespace_after=op.whitespace_after, ) if isinstance(op, cst.Is) else cst.NotEqual( whitespace_before=op.whitespace_before, whitespace_after=op.whitespace_after, )) comparisons.append(target) prev_comparator = comparator return node.with_changes(comparisons=comparisons)
class ComparisonTest(CSTNodeTest): @data_provider(( # Simple comparison statements ( cst.Comparison( cst.Name("foo"), (cst.ComparisonTarget(cst.LessThan(), cst.Integer("5")), ), ), "foo < 5", ), ( cst.Comparison( cst.Name("foo"), (cst.ComparisonTarget(cst.NotEqual(), cst.Integer("5")), ), ), "foo != 5", ), ( cst.Comparison( cst.Name("foo"), (cst.ComparisonTarget(cst.Is(), cst.Name("True")), )), "foo is True", ), ( cst.Comparison( cst.Name("foo"), (cst.ComparisonTarget(cst.IsNot(), cst.Name("False")), ), ), "foo is not False", ), ( cst.Comparison( cst.Name("foo"), (cst.ComparisonTarget(cst.In(), cst.Name("bar")), )), "foo in bar", ), ( cst.Comparison( cst.Name("foo"), (cst.ComparisonTarget(cst.NotIn(), cst.Name("bar")), ), ), "foo not in bar", ), # Comparison with parens ( cst.Comparison( lpar=(cst.LeftParen(), ), left=cst.Name("foo"), comparisons=(cst.ComparisonTarget( operator=cst.NotIn(), comparator=cst.Name("bar")), ), rpar=(cst.RightParen(), ), ), "(foo not in bar)", ), ( cst.Comparison( left=cst.Name("a", lpar=(cst.LeftParen(), ), rpar=(cst.RightParen(), )), comparisons=( cst.ComparisonTarget( operator=cst.Is( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), comparator=cst.Name("b", lpar=(cst.LeftParen(), ), rpar=(cst.RightParen(), )), ), cst.ComparisonTarget( operator=cst.Is( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), comparator=cst.Name("c", lpar=(cst.LeftParen(), ), rpar=(cst.RightParen(), )), ), ), ), "(a)is(b)is(c)", ), # Valid expressions that look like they shouldn't parse ( cst.Comparison( left=cst.Integer("5"), comparisons=(cst.ComparisonTarget( operator=cst.NotIn( whitespace_before=cst.SimpleWhitespace("")), comparator=cst.Name("bar"), ), ), ), "5not in bar", ), # Validate that spacing works properly ( cst.Comparison( lpar=(cst.LeftParen( whitespace_after=cst.SimpleWhitespace(" ")), ), left=cst.Name("foo"), comparisons=(cst.ComparisonTarget( operator=cst.NotIn( whitespace_before=cst.SimpleWhitespace(" "), whitespace_between=cst.SimpleWhitespace(" "), whitespace_after=cst.SimpleWhitespace(" "), ), comparator=cst.Name("bar"), ), ), rpar=(cst.RightParen( whitespace_before=cst.SimpleWhitespace(" ")), ), ), "( foo not in bar )", ), # Do some complex nodes ( cst.Comparison( left=cst.Name("baz"), comparisons=(cst.ComparisonTarget( operator=cst.Equal(), comparator=cst.Comparison( lpar=(cst.LeftParen(), ), left=cst.Name("foo"), comparisons=(cst.ComparisonTarget( operator=cst.NotIn(), comparator=cst.Name("bar")), ), rpar=(cst.RightParen(), ), ), ), ), ), "baz == (foo not in bar)", CodeRange((1, 0), (1, 23)), ), ( cst.Comparison( left=cst.Name("a"), comparisons=( cst.ComparisonTarget(operator=cst.GreaterThan(), comparator=cst.Name("b")), cst.ComparisonTarget(operator=cst.GreaterThan(), comparator=cst.Name("c")), ), ), "a > b > c", CodeRange((1, 0), (1, 9)), ), # Is safe to use with word operators if it's leading/trailing children are ( cst.IfExp( body=cst.Comparison( left=cst.Name("a"), comparisons=(cst.ComparisonTarget( operator=cst.GreaterThan(), comparator=cst.Name( "b", lpar=(cst.LeftParen(), ), rpar=(cst.RightParen(), ), ), ), ), ), test=cst.Comparison( left=cst.Name("c", lpar=(cst.LeftParen(), ), rpar=(cst.RightParen(), )), comparisons=(cst.ComparisonTarget( operator=cst.GreaterThan(), comparator=cst.Name("d")), ), ), orelse=cst.Name("e"), whitespace_before_if=cst.SimpleWhitespace(""), whitespace_after_if=cst.SimpleWhitespace(""), ), "a > (b)if(c) > d else e", ), # is safe to use with word operators if entirely surrounded in parenthesis ( cst.IfExp( body=cst.Name("a"), test=cst.Comparison( left=cst.Name("b"), comparisons=(cst.ComparisonTarget( operator=cst.GreaterThan(), comparator=cst.Name("c")), ), lpar=(cst.LeftParen(), ), rpar=(cst.RightParen(), ), ), orelse=cst.Name("d"), whitespace_after_if=cst.SimpleWhitespace(""), whitespace_before_else=cst.SimpleWhitespace(""), ), "a if(b > c)else d", ), )) def test_valid(self, node: cst.CSTNode, code: str, position: Optional[CodeRange] = None) -> None: self.validate_node(node, code, parse_expression, expected_position=position) @data_provider(( ( lambda: cst.Comparison( cst.Name("foo"), (cst.ComparisonTarget(cst.LessThan(), cst.Integer("5")), ), lpar=(cst.LeftParen(), ), ), "left paren without right paren", ), ( lambda: cst.Comparison( cst.Name("foo"), (cst.ComparisonTarget(cst.LessThan(), cst.Integer("5")), ), rpar=(cst.RightParen(), ), ), "right paren without left paren", ), ( lambda: cst.Comparison(cst.Name("foo"), ()), "at least one ComparisonTarget", ), ( lambda: cst.Comparison( left=cst.Name("foo"), comparisons=(cst.ComparisonTarget( operator=cst.NotIn(whitespace_before=cst.SimpleWhitespace( "")), comparator=cst.Name("bar"), ), ), ), "at least one space around comparison operator", ), ( lambda: cst.Comparison( left=cst.Name("foo"), comparisons=(cst.ComparisonTarget( operator=cst.NotIn(whitespace_after=cst.SimpleWhitespace( "")), comparator=cst.Name("bar"), ), ), ), "at least one space around comparison operator", ), # multi-target comparisons ( lambda: cst.Comparison( left=cst.Name("a"), comparisons=( cst.ComparisonTarget(operator=cst.Is(), comparator=cst.Name("b")), cst.ComparisonTarget( operator=cst.Is(whitespace_before=cst.SimpleWhitespace( "")), comparator=cst.Name("c"), ), ), ), "at least one space around comparison operator", ), ( lambda: cst.Comparison( left=cst.Name("a"), comparisons=( cst.ComparisonTarget(operator=cst.Is(), comparator=cst.Name("b")), cst.ComparisonTarget( operator=cst.Is(whitespace_after=cst.SimpleWhitespace( "")), comparator=cst.Name("c"), ), ), ), "at least one space around comparison operator", ), # whitespace around the comparision itself # a ifb > c else d ( lambda: cst.IfExp( body=cst.Name("a"), test=cst.Comparison( left=cst.Name("b"), comparisons=(cst. ComparisonTarget(operator=cst.GreaterThan(), comparator=cst.Name("c")), ), ), orelse=cst.Name("d"), whitespace_after_if=cst.SimpleWhitespace(""), ), "Must have at least one space after 'if' keyword.", ), # a if b > celse d ( lambda: cst.IfExp( body=cst.Name("a"), test=cst.Comparison( left=cst.Name("b"), comparisons=(cst. ComparisonTarget(operator=cst.GreaterThan(), comparator=cst.Name("c")), ), ), orelse=cst.Name("d"), whitespace_before_else=cst.SimpleWhitespace(""), ), "Must have at least one space before 'else' keyword.", ), )) def test_invalid(self, get_node: Callable[[], cst.CSTNode], expected_re: str) -> None: self.assert_invalid(get_node, expected_re)
class AtomTest(CSTNodeTest): @data_provider( ( # Simple identifier { "node": cst.Name("test"), "code": "test", "parser": parse_expression, "expected_position": None, }, # Parenthesized identifier { "node": cst.Name( "test", lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),) ), "code": "(test)", "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 5)), }, # Decimal integers { "node": cst.Integer("12345"), "code": "12345", "parser": parse_expression, "expected_position": None, }, { "node": cst.Integer("0000"), "code": "0000", "parser": parse_expression, "expected_position": None, }, { "node": cst.Integer("1_234_567"), "code": "1_234_567", "parser": parse_expression, "expected_position": None, }, { "node": cst.Integer("0_000"), "code": "0_000", "parser": parse_expression, "expected_position": None, }, # Binary integers { "node": cst.Integer("0b0000"), "code": "0b0000", "parser": parse_expression, "expected_position": None, }, { "node": cst.Integer("0B1011_0100"), "code": "0B1011_0100", "parser": parse_expression, "expected_position": None, }, # Octal integers { "node": cst.Integer("0o12345"), "code": "0o12345", "parser": parse_expression, "expected_position": None, }, { "node": cst.Integer("0O12_345"), "code": "0O12_345", "parser": parse_expression, "expected_position": None, }, # Hex numbers { "node": cst.Integer("0x123abc"), "code": "0x123abc", "parser": parse_expression, "expected_position": None, }, { "node": cst.Integer("0X12_3ABC"), "code": "0X12_3ABC", "parser": parse_expression, "expected_position": None, }, # Parenthesized integers { "node": cst.Integer( "123", lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),) ), "code": "(123)", "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 4)), }, # Non-exponent floats { "node": cst.Float("12345."), "code": "12345.", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("00.00"), "code": "00.00", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("12.21"), "code": "12.21", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float(".321"), "code": ".321", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("1_234_567."), "code": "1_234_567.", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("0.000_000"), "code": "0.000_000", "parser": parse_expression, "expected_position": None, }, # Exponent floats { "node": cst.Float("12345.e10"), "code": "12345.e10", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("00.00e10"), "code": "00.00e10", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("12.21e10"), "code": "12.21e10", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float(".321e10"), "code": ".321e10", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("1_234_567.e10"), "code": "1_234_567.e10", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("0.000_000e10"), "code": "0.000_000e10", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("1e+10"), "code": "1e+10", "parser": parse_expression, "expected_position": None, }, { "node": cst.Float("1e-10"), "code": "1e-10", "parser": parse_expression, "expected_position": None, }, # Parenthesized floats { "node": cst.Float( "123.4", lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),) ), "code": "(123.4)", "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 6)), }, # Imaginary numbers { "node": cst.Imaginary("12345j"), "code": "12345j", "parser": parse_expression, "expected_position": None, }, { "node": cst.Imaginary("1_234_567J"), "code": "1_234_567J", "parser": parse_expression, "expected_position": None, }, { "node": cst.Imaginary("12345.e10j"), "code": "12345.e10j", "parser": parse_expression, "expected_position": None, }, { "node": cst.Imaginary(".321J"), "code": ".321J", "parser": parse_expression, "expected_position": None, }, # Parenthesized imaginary { "node": cst.Imaginary( "123.4j", lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),) ), "code": "(123.4j)", "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 7)), }, # Simple elipses { "node": cst.Ellipsis(), "code": "...", "parser": parse_expression, "expected_position": None, }, # Parenthesized elipses { "node": cst.Ellipsis(lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),)), "code": "(...)", "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 4)), }, # Simple strings { "node": cst.SimpleString('""'), "code": '""', "parser": parse_expression, "expected_position": None, }, { "node": cst.SimpleString("''"), "code": "''", "parser": parse_expression, "expected_position": None, }, { "node": cst.SimpleString('"test"'), "code": '"test"', "parser": parse_expression, "expected_position": None, }, { "node": cst.SimpleString('b"test"'), "code": 'b"test"', "parser": parse_expression, "expected_position": None, }, { "node": cst.SimpleString('r"test"'), "code": 'r"test"', "parser": parse_expression, "expected_position": None, }, { "node": cst.SimpleString('"""test"""'), "code": '"""test"""', "parser": parse_expression, "expected_position": None, }, # Validate parens { "node": cst.SimpleString( '"test"', lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),) ), "code": '("test")', "parser": parse_expression, "expected_position": None, }, { "node": cst.SimpleString( 'rb"test"', lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),) ), "code": '(rb"test")', "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 9)), }, # Test that _safe_to_use_with_word_operator allows no space around quotes { "node": cst.Comparison( cst.SimpleString('"a"'), [ cst.ComparisonTarget( cst.In( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), cst.SimpleString('"abc"'), ) ], ), "code": '"a"in"abc"', "parser": parse_expression, }, { "node": cst.Comparison( cst.SimpleString('"a"'), [ cst.ComparisonTarget( cst.In( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), cst.ConcatenatedString( cst.SimpleString('"a"'), cst.SimpleString('"bc"') ), ) ], ), "code": '"a"in"a""bc"', "parser": parse_expression, }, # Parenthesis make no spaces around a prefix okay { "node": cst.Comparison( cst.SimpleString('b"a"'), [ cst.ComparisonTarget( cst.In( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), cst.SimpleString( 'b"abc"', lpar=[cst.LeftParen()], rpar=[cst.RightParen()], ), ) ], ), "code": 'b"a"in(b"abc")', "parser": parse_expression, }, { "node": cst.Comparison( cst.SimpleString('b"a"'), [ cst.ComparisonTarget( cst.In( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), cst.ConcatenatedString( cst.SimpleString('b"a"'), cst.SimpleString('b"bc"'), lpar=[cst.LeftParen()], rpar=[cst.RightParen()], ), ) ], ), "code": 'b"a"in(b"a"b"bc")', "parser": parse_expression, }, # Empty formatted strings { "node": cst.FormattedString(start='f"', parts=(), end='"'), "code": 'f""', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString(start="f'", parts=(), end="'"), "code": "f''", "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString(start='f"""', parts=(), end='"""'), "code": 'f""""""', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString(start="f'''", parts=(), end="'''"), "code": "f''''''", "parser": parse_expression, "expected_position": None, }, # Non-empty formatted strings { "node": cst.FormattedString(parts=(cst.FormattedStringText("foo"),)), "code": 'f"foo"', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString( parts=(cst.FormattedStringExpression(cst.Name("foo")),) ), "code": 'f"{foo}"', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringText("foo "), cst.FormattedStringExpression(cst.Name("bar")), cst.FormattedStringText(" baz"), ) ), "code": 'f"foo {bar} baz"', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringText("foo "), cst.FormattedStringExpression(cst.Call(cst.Name("bar"))), cst.FormattedStringText(" baz"), ) ), "code": 'f"foo {bar()} baz"', "parser": parse_expression, "expected_position": None, }, # Formatted strings with conversions and format specifiers { "node": cst.FormattedString( parts=( cst.FormattedStringExpression(cst.Name("foo"), conversion="s"), ) ), "code": 'f"{foo!s}"', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression(cst.Name("foo"), format_spec=()), ) ), "code": 'f"{foo:}"', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Name("today"), format_spec=(cst.FormattedStringText("%B %d, %Y"),), ), ) ), "code": 'f"{today:%B %d, %Y}"', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Name("foo"), format_spec=( cst.FormattedStringExpression(cst.Name("bar")), ), ), ) ), "code": 'f"{foo:{bar}}"', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Name("foo"), format_spec=( cst.FormattedStringExpression(cst.Name("bar")), cst.FormattedStringText("."), cst.FormattedStringExpression(cst.Name("baz")), ), ), ) ), "code": 'f"{foo:{bar}.{baz}}"', "parser": parse_expression, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Name("foo"), conversion="s", format_spec=( cst.FormattedStringExpression(cst.Name("bar")), ), ), ) ), "code": 'f"{foo!s:{bar}}"', "parser": parse_expression, "expected_position": None, }, # Test equality expression added in 3.8. { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Name("foo"), equal=cst.AssignEqual( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), ), ), ), "code": 'f"{foo=}"', "parser": _parse_expression_force_38, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Name("foo"), equal=cst.AssignEqual( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), conversion="s", ), ), ), "code": 'f"{foo=!s}"', "parser": _parse_expression_force_38, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Name("foo"), equal=cst.AssignEqual( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), conversion="s", format_spec=( cst.FormattedStringExpression(cst.Name("bar")), ), ), ), ), "code": 'f"{foo=!s:{bar}}"', "parser": _parse_expression_force_38, "expected_position": None, }, # Test that equality support doesn't break existing support { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Comparison( left=cst.Name( value="a", ), comparisons=[ cst.ComparisonTarget( operator=cst.Equal(), comparator=cst.Name( value="b", ), ), ], ), ), ), ), "code": 'f"{a == b}"', "parser": _parse_expression_force_38, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Comparison( left=cst.Name( value="a", ), comparisons=[ cst.ComparisonTarget( operator=cst.NotEqual(), comparator=cst.Name( value="b", ), ), ], ), ), ), ), "code": 'f"{a != b}"', "parser": _parse_expression_force_38, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.NamedExpr( target=cst.Name( value="a", ), value=cst.Integer( value="5", ), lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),), ), ), ), ), "code": 'f"{(a := 5)}"', "parser": _parse_expression_force_38, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringExpression( cst.Yield( value=cst.Integer("1"), whitespace_after_yield=cst.SimpleWhitespace(" "), ), ), ), ), "code": 'f"{yield 1}"', "parser": _parse_expression_force_38, "expected_position": None, }, { "node": cst.FormattedString( parts=( cst.FormattedStringText("\\N{X Y}"), cst.FormattedStringExpression( cst.Name(value="Z"), ), ), ), "code": 'f"\\N{X Y}{Z}"', "parser": parse_expression, "expected_position": None, }, # Validate parens { "node": cst.FormattedString( start='f"', parts=(), end='"', lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),), ), "code": '(f"")', "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 4)), }, # Generator expression (doesn't make sense, but legal syntax) { "node": cst.FormattedString( start='f"', parts=[ cst.FormattedStringExpression( expression=cst.GeneratorExp( elt=cst.Name( value="x", ), for_in=cst.CompFor( target=cst.Name( value="x", ), iter=cst.Name( value="y", ), ), lpar=[], rpar=[], ), ), ], end='"', ), "code": 'f"{x for x in y}"', "parser": parse_expression, "expected_position": None, }, # Concatenated strings { "node": cst.ConcatenatedString( cst.SimpleString('"ab"'), cst.SimpleString('"c"') ), "code": '"ab""c"', "parser": parse_expression, "expected_position": None, }, { "node": cst.ConcatenatedString( cst.SimpleString('"ab"'), cst.ConcatenatedString( cst.SimpleString('"c"'), cst.SimpleString('"d"') ), ), "code": '"ab""c""d"', "parser": parse_expression, "expected_position": None, }, # mixed SimpleString and FormattedString { "node": cst.ConcatenatedString( cst.FormattedString([cst.FormattedStringText("ab")]), cst.SimpleString('"c"'), ), "code": 'f"ab""c"', "parser": parse_expression, "expected_position": None, }, { "node": cst.ConcatenatedString( cst.SimpleString('"ab"'), cst.FormattedString([cst.FormattedStringText("c")]), ), "code": '"ab"f"c"', "parser": parse_expression, "expected_position": None, }, # Concatenated parenthesized strings { "node": cst.ConcatenatedString( lpar=(cst.LeftParen(),), left=cst.SimpleString('"ab"'), right=cst.SimpleString('"c"'), rpar=(cst.RightParen(),), ), "code": '("ab""c")', "parser": parse_expression, "expected_position": None, }, # Validate spacing { "node": cst.ConcatenatedString( lpar=(cst.LeftParen(whitespace_after=cst.SimpleWhitespace(" ")),), left=cst.SimpleString('"ab"'), whitespace_between=cst.SimpleWhitespace(" "), right=cst.SimpleString('"c"'), rpar=(cst.RightParen(whitespace_before=cst.SimpleWhitespace(" ")),), ), "code": '( "ab" "c" )', "parser": parse_expression, "expected_position": CodeRange((1, 2), (1, 10)), }, ) ) def test_valid(self, **kwargs: Any) -> None: # We don't have sentinel nodes for atoms, so we know that 100% of atoms # can be parsed identically to their creation. self.validate_node(**kwargs) @data_provider( ( { "node": cst.FormattedStringExpression( cst.Name("today"), format_spec=(cst.FormattedStringText("%B %d, %Y"),), ), "code": "{today:%B %d, %Y}", "parser": None, "expected_position": CodeRange((1, 0), (1, 17)), }, ) ) def test_valid_no_parse(self, **kwargs: Any) -> None: # Test some nodes that aren't valid source code by themselves self.validate_node(**kwargs) @data_provider( ( # Expression wrapping parenthesis rules { "get_node": (lambda: cst.Name("foo", lpar=(cst.LeftParen(),))), "expected_re": "left paren without right paren", }, { "get_node": lambda: cst.Name("foo", rpar=(cst.RightParen(),)), "expected_re": "right paren without left paren", }, { "get_node": lambda: cst.Ellipsis(lpar=(cst.LeftParen(),)), "expected_re": "left paren without right paren", }, { "get_node": lambda: cst.Ellipsis(rpar=(cst.RightParen(),)), "expected_re": "right paren without left paren", }, { "get_node": lambda: cst.Integer("5", lpar=(cst.LeftParen(),)), "expected_re": "left paren without right paren", }, { "get_node": lambda: cst.Integer("5", rpar=(cst.RightParen(),)), "expected_re": "right paren without left paren", }, { "get_node": lambda: cst.Float("5.5", lpar=(cst.LeftParen(),)), "expected_re": "left paren without right paren", }, { "get_node": lambda: cst.Float("5.5", rpar=(cst.RightParen(),)), "expected_re": "right paren without left paren", }, { "get_node": (lambda: cst.Imaginary("5j", lpar=(cst.LeftParen(),))), "expected_re": "left paren without right paren", }, { "get_node": (lambda: cst.Imaginary("5j", rpar=(cst.RightParen(),))), "expected_re": "right paren without left paren", }, { "get_node": (lambda: cst.Integer("5", lpar=(cst.LeftParen(),))), "expected_re": "left paren without right paren", }, { "get_node": (lambda: cst.Integer("5", rpar=(cst.RightParen(),))), "expected_re": "right paren without left paren", }, { "get_node": ( lambda: cst.SimpleString("'foo'", lpar=(cst.LeftParen(),)) ), "expected_re": "left paren without right paren", }, { "get_node": ( lambda: cst.SimpleString("'foo'", rpar=(cst.RightParen(),)) ), "expected_re": "right paren without left paren", }, { "get_node": ( lambda: cst.FormattedString(parts=(), lpar=(cst.LeftParen(),)) ), "expected_re": "left paren without right paren", }, { "get_node": ( lambda: cst.FormattedString(parts=(), rpar=(cst.RightParen(),)) ), "expected_re": "right paren without left paren", }, { "get_node": ( lambda: cst.ConcatenatedString( cst.SimpleString("'foo'"), cst.SimpleString("'foo'"), lpar=(cst.LeftParen(),), ) ), "expected_re": "left paren without right paren", }, { "get_node": ( lambda: cst.ConcatenatedString( cst.SimpleString("'foo'"), cst.SimpleString("'foo'"), rpar=(cst.RightParen(),), ) ), "expected_re": "right paren without left paren", }, # Node-specific rules { "get_node": (lambda: cst.Name("")), "expected_re": "empty name identifier", }, { "get_node": (lambda: cst.Name(r"\/")), "expected_re": "not a valid identifier", }, { "get_node": (lambda: cst.Integer("")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Integer("012345")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Integer("012345")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Integer("_12345")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Integer("0b2")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Integer("0o8")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Integer("0xg")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Integer("123.45")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Integer("12345j")), "expected_re": "not a valid integer", }, { "get_node": (lambda: cst.Float("12.3.45")), "expected_re": "not a valid float", }, {"get_node": (lambda: cst.Float("12")), "expected_re": "not a valid float"}, { "get_node": (lambda: cst.Float("12.3j")), "expected_re": "not a valid float", }, { "get_node": (lambda: cst.Imaginary("_12345j")), "expected_re": "not a valid imaginary", }, { "get_node": (lambda: cst.Imaginary("0b0j")), "expected_re": "not a valid imaginary", }, { "get_node": (lambda: cst.Imaginary("0o0j")), "expected_re": "not a valid imaginary", }, { "get_node": (lambda: cst.Imaginary("0x0j")), "expected_re": "not a valid imaginary", }, { "get_node": (lambda: cst.SimpleString('wee""')), "expected_re": "Invalid string prefix", }, { "get_node": (lambda: cst.SimpleString("'")), "expected_re": "must have enclosing quotes", }, { "get_node": (lambda: cst.SimpleString('"')), "expected_re": "must have enclosing quotes", }, { "get_node": (lambda: cst.SimpleString("\"'")), "expected_re": "must have matching enclosing quotes", }, { "get_node": (lambda: cst.SimpleString("")), "expected_re": "must have enclosing quotes", }, { "get_node": (lambda: cst.SimpleString("'bla")), "expected_re": "must have matching enclosing quotes", }, { "get_node": (lambda: cst.SimpleString("f''")), "expected_re": "Invalid string prefix", }, { "get_node": (lambda: cst.SimpleString("'''bla''")), "expected_re": "must have matching enclosing quotes", }, { "get_node": (lambda: cst.SimpleString("'''bla\"\"\"")), "expected_re": "must have matching enclosing quotes", }, { "get_node": (lambda: cst.FormattedString(start="'", parts=(), end="'")), "expected_re": "Invalid f-string prefix", }, { "get_node": ( lambda: cst.FormattedString(start="f'", parts=(), end='"') ), "expected_re": "must have matching enclosing quotes", }, { "get_node": ( lambda: cst.ConcatenatedString( cst.SimpleString( '"ab"', lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),) ), cst.SimpleString('"c"'), ) ), "expected_re": "Cannot concatenate parenthesized", }, { "get_node": ( lambda: cst.ConcatenatedString( cst.SimpleString('"ab"'), cst.SimpleString( '"c"', lpar=(cst.LeftParen(),), rpar=(cst.RightParen(),) ), ) ), "expected_re": "Cannot concatenate parenthesized", }, { "get_node": ( lambda: cst.ConcatenatedString( cst.SimpleString('"ab"'), cst.SimpleString('b"c"') ) ), "expected_re": "Cannot concatenate string and bytes", }, # This isn't valid code: `"a" inb"abc"` { "get_node": ( lambda: cst.Comparison( cst.SimpleString('"a"'), [ cst.ComparisonTarget( cst.In(whitespace_after=cst.SimpleWhitespace("")), cst.SimpleString('b"abc"'), ) ], ) ), "expected_re": "Must have at least one space around comparison operator.", }, # Also not valid: `"a" in b"a"b"bc"` { "get_node": ( lambda: cst.Comparison( cst.SimpleString('"a"'), [ cst.ComparisonTarget( cst.In(whitespace_after=cst.SimpleWhitespace("")), cst.ConcatenatedString( cst.SimpleString('b"a"'), cst.SimpleString('b"bc"') ), ) ], ) ), "expected_re": "Must have at least one space around comparison operator.", }, ) ) def test_invalid(self, **kwargs: Any) -> None: self.assert_invalid(**kwargs) @data_provider( ( { "code": "u'x'", "parser": parse_expression_as(python_version="3.3"), "expect_success": True, }, { "code": "u'x'", "parser": parse_expression_as(python_version="3.1"), "expect_success": False, }, ) ) def test_versions(self, **kwargs: Any) -> None: self.assert_parses(**kwargs)