mirror of
https://github.com/Instagram/LibCST.git
synced 2025-12-23 10:35:53 +00:00
1212 lines
45 KiB
Python
1212 lines
45 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
|
|
|
|
from typing import Any
|
|
|
|
import libcst as cst
|
|
from libcst import parse_expression
|
|
from libcst._nodes.tests.base import CSTNodeTest, parse_expression_as
|
|
from libcst._parser.entrypoints import is_native
|
|
from libcst.metadata import CodeRange
|
|
from libcst.testing.utils import data_provider
|
|
|
|
|
|
def _parse_expression_force_38(code: str) -> cst.BaseExpression:
|
|
return cst.parse_expression(
|
|
code, config=cst.PartialParserConfig(python_version="3.8")
|
|
)
|
|
|
|
|
|
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,
|
|
},
|
|
{
|
|
"node": cst.FormattedString(
|
|
parts=(
|
|
cst.FormattedStringText("\\"),
|
|
cst.FormattedStringExpression(
|
|
cst.Name(value="a"),
|
|
),
|
|
),
|
|
start='fr"',
|
|
),
|
|
"code": 'fr"\\{a}"',
|
|
"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,
|
|
},
|
|
# Unpacked tuple
|
|
{
|
|
"node": cst.FormattedString(
|
|
parts=[
|
|
cst.FormattedStringExpression(
|
|
expression=cst.Tuple(
|
|
elements=[
|
|
cst.Element(
|
|
value=cst.Name(
|
|
value="a",
|
|
),
|
|
comma=cst.Comma(
|
|
whitespace_before=cst.SimpleWhitespace(
|
|
value="",
|
|
),
|
|
whitespace_after=cst.SimpleWhitespace(
|
|
value=" ",
|
|
),
|
|
),
|
|
),
|
|
cst.Element(
|
|
value=cst.Name(
|
|
value="b",
|
|
),
|
|
),
|
|
],
|
|
lpar=[],
|
|
rpar=[],
|
|
),
|
|
),
|
|
],
|
|
start="f'",
|
|
end="'",
|
|
),
|
|
"code": "f'{a, b}'",
|
|
"parser": parse_expression,
|
|
"expected_position": None,
|
|
},
|
|
# Conditional expression
|
|
{
|
|
"node": cst.FormattedString(
|
|
parts=[
|
|
cst.FormattedStringExpression(
|
|
expression=cst.IfExp(
|
|
test=cst.Name(
|
|
value="b",
|
|
),
|
|
body=cst.Name(
|
|
value="a",
|
|
),
|
|
orelse=cst.Name(
|
|
value="c",
|
|
),
|
|
),
|
|
),
|
|
],
|
|
start="f'",
|
|
end="'",
|
|
),
|
|
"code": "f'{a if b else c}'",
|
|
"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:
|
|
if is_native() and not kwargs.get("expect_success", True):
|
|
self.skipTest("parse errors are disabled for native parser")
|
|
self.assert_parses(**kwargs)
|
|
|
|
|
|
class StringHelperTest(CSTNodeTest):
|
|
def test_string_prefix_and_quotes(self) -> None:
|
|
"""
|
|
Test our helpers out for various strings.
|
|
"""
|
|
emptybytestring = cst.ensure_type(parse_expression('b""'), cst.SimpleString)
|
|
bytestring = cst.ensure_type(parse_expression('b"abc"'), cst.SimpleString)
|
|
multilinestring = cst.ensure_type(parse_expression('""""""'), cst.SimpleString)
|
|
formatstring = cst.ensure_type(parse_expression('f""""""'), cst.FormattedString)
|
|
|
|
self.assertEqual(emptybytestring.prefix, "b")
|
|
self.assertEqual(emptybytestring.quote, '"')
|
|
self.assertEqual(emptybytestring.raw_value, "")
|
|
self.assertEqual(bytestring.prefix, "b")
|
|
self.assertEqual(bytestring.quote, '"')
|
|
self.assertEqual(bytestring.raw_value, "abc")
|
|
self.assertEqual(multilinestring.prefix, "")
|
|
self.assertEqual(multilinestring.quote, '"""')
|
|
self.assertEqual(multilinestring.raw_value, "")
|
|
self.assertEqual(formatstring.prefix, "f")
|
|
self.assertEqual(formatstring.quote, '"""')
|