test_fstring.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import pytest
  2. from textwrap import dedent
  3. from parso import load_grammar, ParserSyntaxError
  4. from parso.python.tokenize import tokenize
  5. @pytest.fixture
  6. def grammar():
  7. return load_grammar(version='3.8')
  8. @pytest.mark.parametrize(
  9. 'code', [
  10. # simple cases
  11. 'f"{1}"',
  12. 'f"""{1}"""',
  13. 'f"{foo} {bar}"',
  14. # empty string
  15. 'f""',
  16. 'f""""""',
  17. # empty format specifier is okay
  18. 'f"{1:}"',
  19. # use of conversion options
  20. 'f"{1!a}"',
  21. 'f"{1!a:1}"',
  22. # format specifiers
  23. 'f"{1:1}"',
  24. 'f"{1:1.{32}}"',
  25. 'f"{1::>4}"',
  26. 'f"{x:{y}}"',
  27. 'f"{x:{y:}}"',
  28. 'f"{x:{y:1}}"',
  29. # Escapes
  30. 'f"{{}}"',
  31. 'f"{{{1}}}"',
  32. 'f"{{{1}"',
  33. 'f"1{{2{{3"',
  34. 'f"}}"',
  35. # New Python 3.8 syntax f'{a=}'
  36. 'f"{a=}"',
  37. 'f"{a()=}"',
  38. # multiline f-string
  39. 'f"""abc\ndef"""',
  40. 'f"""abc{\n123}def"""',
  41. # a line continuation inside of an fstring_string
  42. 'f"abc\\\ndef"',
  43. 'f"\\\n{123}\\\n"',
  44. # a line continuation inside of an fstring_expr
  45. 'f"{\\\n123}"',
  46. # a line continuation inside of an format spec
  47. 'f"{123:.2\\\nf}"',
  48. # some unparenthesized syntactic structures
  49. 'f"{*x,}"',
  50. 'f"{*x, *y}"',
  51. 'f"{x, *y}"',
  52. 'f"{*x, y}"',
  53. 'f"{x for x in [1]}"',
  54. # named unicode characters
  55. 'f"\\N{BULLET}"',
  56. 'f"\\N{FLEUR-DE-LIS}"',
  57. 'f"\\N{NO ENTRY}"',
  58. 'f"Combo {expr} and \\N{NO ENTRY}"',
  59. 'f"\\N{NO ENTRY} and {expr}"',
  60. 'f"\\N{no entry}"',
  61. 'f"\\N{SOYOMBO LETTER -A}"',
  62. 'f"\\N{DOMINO TILE HORIZONTAL-00-00}"',
  63. 'f"""\\N{NO ENTRY}"""',
  64. ]
  65. )
  66. def test_valid(code, grammar):
  67. module = grammar.parse(code, error_recovery=False)
  68. fstring = module.children[0]
  69. assert fstring.type == 'fstring'
  70. assert fstring.get_code() == code
  71. @pytest.mark.parametrize(
  72. 'code', [
  73. # an f-string can't contain unmatched curly braces
  74. 'f"}"',
  75. 'f"{"',
  76. 'f"""}"""',
  77. 'f"""{"""',
  78. # invalid conversion characters
  79. 'f"{1!{a}}"',
  80. 'f"{1=!{a}}"',
  81. 'f"{!{a}}"',
  82. # The curly braces must contain an expression
  83. 'f"{}"',
  84. 'f"{:}"',
  85. 'f"{:}}}"',
  86. 'f"{:1}"',
  87. 'f"{!:}"',
  88. 'f"{!}"',
  89. 'f"{!a}"',
  90. # invalid (empty) format specifiers
  91. 'f"{1:{}}"',
  92. 'f"{1:{:}}"',
  93. # a newline without a line continuation inside a single-line string
  94. 'f"abc\ndef"',
  95. # various named unicode escapes that aren't name-shaped
  96. 'f"\\N{ BULLET }"',
  97. 'f"\\N{NO ENTRY}"',
  98. 'f"""\\N{NO\nENTRY}"""',
  99. ]
  100. )
  101. def test_invalid(code, grammar):
  102. with pytest.raises(ParserSyntaxError):
  103. grammar.parse(code, error_recovery=False)
  104. # It should work with error recovery.
  105. grammar.parse(code, error_recovery=True)
  106. @pytest.mark.parametrize(
  107. ('code', 'positions'), [
  108. # 2 times 2, 5 because python expr and endmarker.
  109. ('f"}{"', [(1, 0), (1, 2), (1, 3), (1, 4), (1, 5)]),
  110. ('f" :{ 1 : } "', [(1, 0), (1, 2), (1, 4), (1, 6), (1, 8), (1, 9),
  111. (1, 10), (1, 11), (1, 12), (1, 13)]),
  112. ('f"""\n {\nfoo\n }"""', [(1, 0), (1, 4), (2, 1), (3, 0), (4, 1),
  113. (4, 2), (4, 5)]),
  114. ('f"\\N{NO ENTRY} and {expr}"', [(1, 0), (1, 2), (1, 19), (1, 20),
  115. (1, 24), (1, 25), (1, 26)]),
  116. ]
  117. )
  118. def test_tokenize_start_pos(code, positions):
  119. tokens = list(tokenize(code, version_info=(3, 6)))
  120. assert positions == [p.start_pos for p in tokens]
  121. @pytest.mark.parametrize(
  122. 'code', [
  123. dedent("""\
  124. f'''s{
  125. str.uppe
  126. '''
  127. """),
  128. 'f"foo',
  129. 'f"""foo',
  130. 'f"abc\ndef"',
  131. ]
  132. )
  133. def test_roundtrip(grammar, code):
  134. tree = grammar.parse(code)
  135. assert tree.get_code() == code