class ListTest(CSTNodeTest): # A lot of Element/StarredElement tests are provided by the tests for Tuple, so we # we don't need to duplicate them here. @data_provider([ # one-element list, sentinel comma value { "node": cst.Set([cst.Element(cst.Name("single_element"))]), "code": "{single_element}", "parser": parse_expression, }, # custom whitespace between brackets { "node": cst.Set( [cst.Element(cst.Name("single_element"))], lbrace=cst.LeftCurlyBrace( whitespace_after=cst.SimpleWhitespace("\t")), rbrace=cst.RightCurlyBrace( whitespace_before=cst.SimpleWhitespace(" ")), ), "code": "{\tsingle_element }", "parser": parse_expression, }, # two-element list, sentinel comma value { "node": cst.Set( [cst.Element(cst.Name("one")), cst.Element(cst.Name("two"))]), "code": "{one, two}", "parser": None, }, # with parenthesis { "node": cst.Set( [cst.Element(cst.Name("one"))], lpar=[cst.LeftParen()], rpar=[cst.RightParen()], ), "code": "({one})", "parser": None, }, # starred element { "node": cst.Set([ cst.StarredElement(cst.Name("one")), cst.StarredElement(cst.Name("two")), ]), "code": "{*one, *two}", "parser": None, }, # missing spaces around set, always okay { "node": cst.GeneratorExp( cst.Name("elt"), cst.CompFor( target=cst.Name("elt"), iter=cst.Set([ cst.Element( cst.Name("one"), cst.Comma( whitespace_after=cst.SimpleWhitespace(" ")), ), cst.Element(cst.Name("two")), ]), ifs=[ cst.CompIf( cst.Name("test"), whitespace_before=cst.SimpleWhitespace(""), ) ], whitespace_after_in=cst.SimpleWhitespace(""), ), ), "code": "(elt for elt in{one, two}if test)", "parser": parse_expression, }, ]) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs) @data_provider(( ( lambda: cst.Set( [cst.Element(cst.Name("mismatched"))], lpar=[cst.LeftParen(), cst.LeftParen()], rpar=[cst.RightParen()], ), "unbalanced parens", ), (lambda: cst.Set([]), "at least one element"), )) def test_invalid(self, get_node: Callable[[], cst.CSTNode], expected_re: str) -> None: self.assert_invalid(get_node, expected_re) @data_provider(( { "code": "{*x, 2}", "parser": parse_expression_as(python_version="3.5"), "expect_success": True, }, { "code": "{*x, 2}", "parser": parse_expression_as(python_version="3.3"), "expect_success": False, }, )) def test_versions(self, **kwargs: Any) -> None: if is_native() and not kwargs.get("expect_success", True): self.skipTest("parse errors are disabled for native parser") self.assert_parses(**kwargs)
class SimpleCompTest(CSTNodeTest): @data_provider( [ # simple GeneratorExp { "node": cst.GeneratorExp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")) ), "code": "(a for b in c)", "parser": parse_expression, "expected_position": CodeRange.create((1, 1), (1, 13)), }, # simple ListComp { "node": cst.ListComp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")) ), "code": "[a for b in c]", "parser": parse_expression, "expected_position": CodeRange.create((1, 0), (1, 14)), }, # simple SetComp { "node": cst.SetComp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")) ), "code": "{a for b in c}", "parser": parse_expression, }, # async GeneratorExp { "node": cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), asynchronous=cst.Asynchronous(), ), ), "code": "(a async for b in c)", "parser": parse_expression, }, # a generator doesn't have to own it's own parenthesis { "node": cst.Call( cst.Name("func"), [ cst.Arg( cst.GeneratorExp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), lpar=[], rpar=[], ) ) ], ), "code": "func(a for b in c)", "parser": parse_expression, }, # add a few 'if' clauses { "node": cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), ifs=[ cst.CompIf(cst.Name("d")), cst.CompIf(cst.Name("e")), cst.CompIf(cst.Name("f")), ], ), ), "code": "(a for b in c if d if e if f)", "parser": parse_expression, "expected_position": CodeRange.create((1, 1), (1, 28)), }, # nested/inner for-in clause { "node": cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), inner_for_in=cst.CompFor( target=cst.Name("d"), iter=cst.Name("e") ), ), ), "code": "(a for b in c for d in e)", "parser": parse_expression, }, # nested/inner for-in clause with an 'if' clause { "node": cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), ifs=[cst.CompIf(cst.Name("d"))], inner_for_in=cst.CompFor( target=cst.Name("e"), iter=cst.Name("f") ), ), ), "code": "(a for b in c if d for e in f)", "parser": parse_expression, }, # custom whitespace { "node": cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), ifs=[ cst.CompIf( cst.Name("d"), whitespace_before=cst.SimpleWhitespace("\t"), whitespace_before_test=cst.SimpleWhitespace("\t\t"), ) ], whitespace_before=cst.SimpleWhitespace(" "), whitespace_after_for=cst.SimpleWhitespace(" "), whitespace_before_in=cst.SimpleWhitespace(" "), whitespace_after_in=cst.SimpleWhitespace(" "), ), lpar=[cst.LeftParen(whitespace_after=cst.SimpleWhitespace("\f"))], rpar=[ cst.RightParen(whitespace_before=cst.SimpleWhitespace("\f\f")) ], ), "code": "(\fa for b in c\tif\t\td\f\f)", "parser": parse_expression, "expected_position": CodeRange.create((1, 2), (1, 30)), }, # custom whitespace around ListComp's brackets { "node": cst.ListComp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), lbracket=cst.LeftSquareBracket( whitespace_after=cst.SimpleWhitespace("\t") ), rbracket=cst.RightSquareBracket( whitespace_before=cst.SimpleWhitespace("\t\t") ), lpar=[cst.LeftParen(whitespace_after=cst.SimpleWhitespace("\f"))], rpar=[ cst.RightParen(whitespace_before=cst.SimpleWhitespace("\f\f")) ], ), "code": "(\f[\ta for b in c\t\t]\f\f)", "parser": parse_expression, "expected_position": CodeRange.create((1, 2), (1, 19)), }, # custom whitespace around SetComp's braces { "node": cst.SetComp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), lbrace=cst.LeftCurlyBrace( whitespace_after=cst.SimpleWhitespace("\t") ), rbrace=cst.RightCurlyBrace( whitespace_before=cst.SimpleWhitespace("\t\t") ), lpar=[cst.LeftParen(whitespace_after=cst.SimpleWhitespace("\f"))], rpar=[ cst.RightParen(whitespace_before=cst.SimpleWhitespace("\f\f")) ], ), "code": "(\f{\ta for b in c\t\t}\f\f)", "parser": parse_expression, }, # no whitespace between elements { "node": cst.GeneratorExp( cst.Name("a", lpar=[cst.LeftParen()], rpar=[cst.RightParen()]), cst.CompFor( target=cst.Name( "b", lpar=[cst.LeftParen()], rpar=[cst.RightParen()] ), iter=cst.Name( "c", lpar=[cst.LeftParen()], rpar=[cst.RightParen()] ), ifs=[ cst.CompIf( cst.Name( "d", lpar=[cst.LeftParen()], rpar=[cst.RightParen()] ), whitespace_before=cst.SimpleWhitespace(""), whitespace_before_test=cst.SimpleWhitespace(""), ) ], inner_for_in=cst.CompFor( target=cst.Name( "e", lpar=[cst.LeftParen()], rpar=[cst.RightParen()] ), iter=cst.Name( "f", lpar=[cst.LeftParen()], rpar=[cst.RightParen()] ), whitespace_before=cst.SimpleWhitespace(""), whitespace_after_for=cst.SimpleWhitespace(""), whitespace_before_in=cst.SimpleWhitespace(""), whitespace_after_in=cst.SimpleWhitespace(""), ), whitespace_before=cst.SimpleWhitespace(""), whitespace_after_for=cst.SimpleWhitespace(""), whitespace_before_in=cst.SimpleWhitespace(""), whitespace_after_in=cst.SimpleWhitespace(""), ), lpar=[cst.LeftParen()], rpar=[cst.RightParen()], ), "code": "((a)for(b)in(c)if(d)for(e)in(f))", "parser": parse_expression, "expected_position": CodeRange.create((1, 1), (1, 31)), }, # no whitespace before/after GeneratorExp is valid { "node": cst.Comparison( cst.GeneratorExp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), ), [ cst.ComparisonTarget( cst.Is( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), cst.GeneratorExp( cst.Name("d"), cst.CompFor(target=cst.Name("e"), iter=cst.Name("f")), ), ) ], ), "code": "(a for b in c)is(d for e in f)", "parser": parse_expression, }, # no whitespace before/after ListComp is valid { "node": cst.Comparison( cst.ListComp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), ), [ cst.ComparisonTarget( cst.Is( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), cst.ListComp( cst.Name("d"), cst.CompFor(target=cst.Name("e"), iter=cst.Name("f")), ), ) ], ), "code": "[a for b in c]is[d for e in f]", "parser": parse_expression, }, # no whitespace before/after SetComp is valid { "node": cst.Comparison( cst.SetComp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), ), [ cst.ComparisonTarget( cst.Is( whitespace_before=cst.SimpleWhitespace(""), whitespace_after=cst.SimpleWhitespace(""), ), cst.SetComp( cst.Name("d"), cst.CompFor(target=cst.Name("e"), iter=cst.Name("f")), ), ) ], ), "code": "{a for b in c}is{d for e in f}", "parser": parse_expression, }, ] ) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs) @data_provider( ( ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), lpar=[cst.LeftParen(), cst.LeftParen()], rpar=[cst.RightParen()], ), "unbalanced parens", ), ( lambda: cst.ListComp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), lpar=[cst.LeftParen(), cst.LeftParen()], rpar=[cst.RightParen()], ), "unbalanced parens", ), ( lambda: cst.SetComp( cst.Name("a"), cst.CompFor(target=cst.Name("b"), iter=cst.Name("c")), lpar=[cst.LeftParen(), cst.LeftParen()], rpar=[cst.RightParen()], ), "unbalanced parens", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), whitespace_before=cst.SimpleWhitespace(""), ), ), "Must have at least one space before 'for' keyword.", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), asynchronous=cst.Asynchronous(), whitespace_before=cst.SimpleWhitespace(""), ), ), "Must have at least one space before 'async' keyword.", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), whitespace_after_for=cst.SimpleWhitespace(""), ), ), "Must have at least one space after 'for' keyword.", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), whitespace_before_in=cst.SimpleWhitespace(""), ), ), "Must have at least one space before 'in' keyword.", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), whitespace_after_in=cst.SimpleWhitespace(""), ), ), "Must have at least one space after 'in' keyword.", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), ifs=[ cst.CompIf( cst.Name("d"), whitespace_before=cst.SimpleWhitespace(""), ) ], ), ), "Must have at least one space before 'if' keyword.", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), ifs=[ cst.CompIf( cst.Name("d"), whitespace_before_test=cst.SimpleWhitespace(""), ) ], ), ), "Must have at least one space after 'if' keyword.", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), inner_for_in=cst.CompFor( target=cst.Name("d"), iter=cst.Name("e"), whitespace_before=cst.SimpleWhitespace(""), ), ), ), "Must have at least one space before 'for' keyword.", ), ( lambda: cst.GeneratorExp( cst.Name("a"), cst.CompFor( target=cst.Name("b"), iter=cst.Name("c"), inner_for_in=cst.CompFor( target=cst.Name("d"), iter=cst.Name("e"), asynchronous=cst.Asynchronous(), whitespace_before=cst.SimpleWhitespace(""), ), ), ), "Must have at least one space before 'async' 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)
class DictCompTest(CSTNodeTest): @data_provider([ # simple DictComp { "node": cst.DictComp( cst.Name("k"), cst.Name("v"), cst.CompFor(target=cst.Name("a"), iter=cst.Name("b")), ), "code": "{k: v for a in b}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 17)), }, # custom whitespace around colon { "node": cst.DictComp( cst.Name("k"), cst.Name("v"), cst.CompFor(target=cst.Name("a"), iter=cst.Name("b")), whitespace_before_colon=cst.SimpleWhitespace("\t"), whitespace_after_colon=cst.SimpleWhitespace("\t\t"), ), "code": "{k\t:\t\tv for a in b}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 19)), }, # custom whitespace inside braces { "node": cst.DictComp( cst.Name("k"), cst.Name("v"), cst.CompFor(target=cst.Name("a"), iter=cst.Name("b")), lbrace=cst.LeftCurlyBrace( whitespace_after=cst.SimpleWhitespace("\t")), rbrace=cst.RightCurlyBrace( whitespace_before=cst.SimpleWhitespace("\t\t")), ), "code": "{\tk: v for a in b\t\t}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 20)), }, # parenthesis { "node": cst.DictComp( cst.Name("k"), cst.Name("v"), cst.CompFor(target=cst.Name("a"), iter=cst.Name("b")), lpar=[cst.LeftParen()], rpar=[cst.RightParen()], ), "code": "({k: v for a in b})", "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 18)), }, # missing spaces around DictComp is always okay { "node": cst.DictComp( cst.Name("a"), cst.Name("b"), cst.CompFor( target=cst.Name("c"), iter=cst.DictComp( cst.Name("d"), cst.Name("e"), cst.CompFor(target=cst.Name("f"), iter=cst.Name("g")), ), ifs=[ cst.CompIf( cst.Name("h"), whitespace_before=cst.SimpleWhitespace(""), ) ], whitespace_after_in=cst.SimpleWhitespace(""), ), ), "code": "{a: b for c in{d: e for f in g}if h}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 36)), }, # no whitespace before `for` clause { "node": cst.DictComp( cst.Name("k"), cst.Name("v", lpar=[cst.LeftParen()], rpar=[cst.RightParen()]), cst.CompFor( target=cst.Name("a"), iter=cst.Name("b"), whitespace_before=cst.SimpleWhitespace(""), ), ), "code": "{k: (v)for a in b}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 18)), }, ]) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs) @data_provider([ # unbalanced DictComp { "get_node": lambda: cst.DictComp( cst.Name("k"), cst.Name("v"), cst.CompFor(target=cst.Name("a"), iter=cst.Name("b")), lpar=[cst.LeftParen()], ), "expected_re": "left paren without right paren", }, # invalid whitespace before for/async { "get_node": lambda: cst.DictComp( cst.Name("k"), cst.Name("v"), cst.CompFor( target=cst.Name("a"), iter=cst.Name("b"), whitespace_before=cst.SimpleWhitespace(""), ), ), "expected_re": "Must have at least one space before 'for' keyword.", }, { "get_node": lambda: cst.DictComp( cst.Name("k"), cst.Name("v"), cst.CompFor( target=cst.Name("a"), iter=cst.Name("b"), asynchronous=cst.Asynchronous(), whitespace_before=cst.SimpleWhitespace(""), ), ), "expected_re": "Must have at least one space before 'async' keyword.", }, ]) def test_invalid(self, **kwargs: Any) -> None: self.assert_invalid(**kwargs)
class DictTest(CSTNodeTest): @data_provider([ # zero-element dict { "node": cst.Dict([]), "code": "{}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 2)), }, # one-element dict, sentinel comma value { "node": cst.Dict([cst.DictElement(cst.Name("k"), cst.Name("v"))]), "code": "{k: v}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 6)), }, { "node": cst.Dict([cst.StarredDictElement(cst.Name("expanded"))]), "code": "{**expanded}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 12)), }, # two-element dict, sentinel comma value { "node": cst.Dict([ cst.DictElement(cst.Name("k1"), cst.Name("v1")), cst.DictElement(cst.Name("k2"), cst.Name("v2")), ]), "code": "{k1: v1, k2: v2}", "parser": None, "expected_position": CodeRange((1, 0), (1, 16)), }, # custom whitespace between brackets { "node": cst.Dict( [cst.DictElement(cst.Name("k"), cst.Name("v"))], lbrace=cst.LeftCurlyBrace( whitespace_after=cst.SimpleWhitespace("\t")), rbrace=cst.RightCurlyBrace( whitespace_before=cst.SimpleWhitespace("\t\t")), ), "code": "{\tk: v\t\t}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 9)), }, # with parenthesis { "node": cst.Dict( [cst.DictElement(cst.Name("k"), cst.Name("v"))], lpar=[cst.LeftParen()], rpar=[cst.RightParen()], ), "code": "({k: v})", "parser": parse_expression, "expected_position": CodeRange((1, 1), (1, 7)), }, # starred element { "node": cst.Dict([ cst.StarredDictElement(cst.Name("one")), cst.StarredDictElement(cst.Name("two")), ]), "code": "{**one, **two}", "parser": None, "expected_position": CodeRange((1, 0), (1, 14)), }, # custom comma on DictElement { "node": cst.Dict([ cst.DictElement(cst.Name("k"), cst.Name("v"), comma=cst.Comma()) ]), "code": "{k: v,}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 7)), }, # custom comma on StarredDictElement { "node": cst.Dict([ cst.StarredDictElement(cst.Name("expanded"), comma=cst.Comma()) ]), "code": "{**expanded,}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 13)), }, # custom whitespace on DictElement { "node": cst.Dict([ cst.DictElement( cst.Name("k"), cst.Name("v"), whitespace_before_colon=cst.SimpleWhitespace("\t"), whitespace_after_colon=cst.SimpleWhitespace("\t\t"), ) ]), "code": "{k\t:\t\tv}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 8)), }, # custom whitespace on StarredDictElement { "node": cst.Dict([ cst.DictElement(cst.Name("k"), cst.Name("v"), comma=cst.Comma()), cst.StarredDictElement( cst.Name("expanded"), whitespace_before_value=cst.SimpleWhitespace(" "), ), ]), "code": "{k: v,** expanded}", "parser": parse_expression, "expected_position": CodeRange((1, 0), (1, 19)), }, # missing spaces around dict is always okay { "node": cst.GeneratorExp( cst.Name("a"), cst.CompFor( cst.Name("b"), cst.Dict([cst.DictElement(cst.Name("k"), cst.Name("v"))]), ifs=[ cst.CompIf( cst.Name("c"), whitespace_before=cst.SimpleWhitespace(""), ) ], whitespace_after_in=cst.SimpleWhitespace(""), ), ), "parser": parse_expression, "code": "(a for b in{k: v}if c)", }, ]) def test_valid(self, **kwargs: Any) -> None: self.validate_node(**kwargs) @data_provider([ # unbalanced Dict { "get_node": lambda: cst.Dict([], lpar=[cst.LeftParen()]), "expected_re": "left paren without right paren", } ]) def test_invalid(self, **kwargs: Any) -> None: self.assert_invalid(**kwargs) @data_provider(( { "code": "{**{}}", "parser": parse_expression_as(python_version="3.5"), "expect_success": True, }, { "code": "{**{}}", "parser": parse_expression_as(python_version="3.3"), "expect_success": False, }, )) def test_versions(self, **kwargs: Any) -> None: self.assert_parses(**kwargs)