aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/parso/py3/tests/test_fstring.py
blob: c81d027a16308bf78f97edabe946e1548046c252 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import pytest
from textwrap import dedent

from parso import load_grammar, ParserSyntaxError
from parso.python.tokenize import tokenize


@pytest.fixture
def grammar():
    return load_grammar(version='3.8')


@pytest.mark.parametrize(
    'code', [
        # simple cases
        'f"{1}"',
        'f"""{1}"""',
        'f"{foo} {bar}"',

        # empty string
        'f""',
        'f""""""',

        # empty format specifier is okay
        'f"{1:}"',

        # use of conversion options
        'f"{1!a}"',
        'f"{1!a:1}"',

        # format specifiers
        'f"{1:1}"',
        'f"{1:1.{32}}"',
        'f"{1::>4}"',
        'f"{x:{y}}"',
        'f"{x:{y:}}"',
        'f"{x:{y:1}}"',

        # Escapes
        'f"{{}}"',
        'f"{{{1}}}"',
        'f"{{{1}"',
        'f"1{{2{{3"',
        'f"}}"',

        # New Python 3.8 syntax f'{a=}'
        'f"{a=}"',
        'f"{a()=}"',

        # multiline f-string
        'f"""abc\ndef"""',
        'f"""abc{\n123}def"""',

        # a line continuation inside of an fstring_string
        'f"abc\\\ndef"',
        'f"\\\n{123}\\\n"',

        # a line continuation inside of an fstring_expr
        'f"{\\\n123}"',

        # a line continuation inside of an format spec
        'f"{123:.2\\\nf}"',

        # some unparenthesized syntactic structures
        'f"{*x,}"',
        'f"{*x, *y}"',
        'f"{x, *y}"',
        'f"{*x, y}"',
        'f"{x for x in [1]}"',

        # named unicode characters
        'f"\\N{BULLET}"',
        'f"\\N{FLEUR-DE-LIS}"',
        'f"\\N{NO ENTRY}"',
        'f"Combo {expr} and \\N{NO ENTRY}"',
        'f"\\N{NO ENTRY} and {expr}"',
        'f"\\N{no entry}"',
        'f"\\N{SOYOMBO LETTER -A}"',
        'f"\\N{DOMINO TILE HORIZONTAL-00-00}"',
        'f"""\\N{NO ENTRY}"""',
    ]
)
def test_valid(code, grammar):
    module = grammar.parse(code, error_recovery=False)
    fstring = module.children[0]
    assert fstring.type == 'fstring'
    assert fstring.get_code() == code


@pytest.mark.parametrize(
    'code', [
        # an f-string can't contain unmatched curly braces
        'f"}"',
        'f"{"',
        'f"""}"""',
        'f"""{"""',

        # invalid conversion characters
        'f"{1!{a}}"',
        'f"{1=!{a}}"',
        'f"{!{a}}"',

        # The curly braces must contain an expression
        'f"{}"',
        'f"{:}"',
        'f"{:}}}"',
        'f"{:1}"',
        'f"{!:}"',
        'f"{!}"',
        'f"{!a}"',

        # invalid (empty) format specifiers
        'f"{1:{}}"',
        'f"{1:{:}}"',

        # a newline without a line continuation inside a single-line string
        'f"abc\ndef"',

        # various named unicode escapes that aren't name-shaped
        'f"\\N{ BULLET }"',
        'f"\\N{NO   ENTRY}"',
        'f"""\\N{NO\nENTRY}"""',
    ]
)
def test_invalid(code, grammar):
    with pytest.raises(ParserSyntaxError):
        grammar.parse(code, error_recovery=False)

    # It should work with error recovery.
    grammar.parse(code, error_recovery=True)


@pytest.mark.parametrize(
    ('code', 'positions'), [
        # 2 times 2, 5 because python expr and endmarker.
        ('f"}{"', [(1, 0), (1, 2), (1, 3), (1, 4), (1, 5)]),
        ('f" :{ 1 : } "', [(1, 0), (1, 2), (1, 4), (1, 6), (1, 8), (1, 9),
                           (1, 10), (1, 11), (1, 12), (1, 13)]),
        ('f"""\n {\nfoo\n }"""', [(1, 0), (1, 4), (2, 1), (3, 0), (4, 1),
                                  (4, 2), (4, 5)]),
        ('f"\\N{NO ENTRY} and {expr}"', [(1, 0), (1, 2), (1, 19), (1, 20),
                                         (1, 24), (1, 25), (1, 26)]),
    ]
)
def test_tokenize_start_pos(code, positions):
    tokens = list(tokenize(code, version_info=(3, 6)))
    assert positions == [p.start_pos for p in tokens]


@pytest.mark.parametrize(
    'code', [
        dedent("""\
            f'''s{
               str.uppe
            '''
            """),
        'f"foo',
        'f"""foo',
        'f"abc\ndef"',
    ]
)
def test_roundtrip(grammar, code):
    tree = grammar.parse(code)
    assert tree.get_code() == code