diff --git a/Python/Python.sublime-syntax b/Python/Python.sublime-syntax index bc0665b15d..0e0a1ad757 100644 --- a/Python/Python.sublime-syntax +++ b/Python/Python.sublime-syntax @@ -45,18 +45,8 @@ variables: exponent: (?:[eE][-+]?{{digits}}) path: '({{identifier}}[ ]*\.[ ]*)*{{identifier}}' illegal_names: (?:and|as|assert|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|not|or|pass|raise|return|try|while|with|yield) - format_spec: |- - (?x: - (?:.? [<>=^])? # fill align - [ +-]? # sign - \#? # alternate form - # technically, octal and hexadecimal integers are also supported as 'width', but rarely used - \d* # width - ,? # thousands separator - (?:\.\d+)? # precision - [bcdeEfFgGnosxX%]? # type - ) - strftime_spec: '(?:%(?:[aAwdbBGmyYHIpMSfzZjuUVWcxX%]|-[dmHIMSj]))' + + strftime_spec: (?:%(?:[aAwdbBGmyYHIpMSfzZjuUVWcxX%]|-[dmHIMSj])) augmented_assignment_operators: |- (?x: >>= | <<= | \*\*= | //= | \+= | -= | \*= | /= | %= | @= | &= | \|= | \^= ) @@ -2569,7 +2559,7 @@ contexts: triple-double-quoted-plain-raw-f-string-content: - include: string-prototype - - include: f-string-replacements + - include: triple-double-quoted-f-string-replacements triple-double-quoted-raw-f-strings: # Triple-quoted raw f-string, treated as regex @@ -2591,7 +2581,7 @@ contexts: triple-double-quoted-raw-f-string-content: - include: string-prototype - - include: f-string-replacements-regexp + - include: triple-double-quoted-f-string-replacements-regexp triple-double-quoted-plain-raw-u-strings: # Triple-quoted capital R raw string, unicode or not, no syntax embedding @@ -2659,7 +2649,7 @@ contexts: - include: string-prototype - include: escaped-unicode-chars - include: string-placeholders - - include: string-replacements + - include: triple-double-quoted-string-replacements triple-double-quoted-b-strings: # Triple-quoted string, bytes, no syntax embedding @@ -2680,7 +2670,7 @@ contexts: - include: string-continuations - include: escaped-chars - include: string-placeholders - - include: string-replacements + - include: triple-double-quoted-string-replacements triple-double-quoted-f-strings: # Triple-quoted f-string @@ -2699,10 +2689,9 @@ contexts: triple-double-quoted-f-string-content: - include: string-prototype - include: string-continuations - - include: escaped-f-string-braces + - include: triple-double-quoted-f-string-replacements - include: escaped-unicode-chars - include: escaped-chars - - include: f-string-replacements triple-double-quoted-u-strings: # Triple-quoted string, unicode or not, will detect SQL @@ -2747,7 +2736,88 @@ contexts: - include: escaped-unicode-chars - include: escaped-chars - include: string-placeholders - - include: string-replacements + - include: triple-double-quoted-string-replacements + + triple-double-quoted-string-replacements: + - include: escaped-string-braces + - match: (?=\{) + branch_point: triple-double-quoted-string-replacement + branch: + - triple-double-quoted-string-replacement + - string-replacement-fallback + + triple-double-quoted-string-replacement: + - match: \{ + scope: punctuation.definition.placeholder.begin.python + set: triple-double-quoted-string-replacement-body + + triple-double-quoted-string-replacement-body: + - meta_scope: constant.other.placeholder.python + - include: string-replacement-body + - match: \[ + scope: punctuation.section.brackets.begin.python + push: triple-double-string-replacement-brackets-body + - match: ':' + scope: punctuation.separator.format-spec.python + push: triple-double-quoted-string-replacement-formatspec-body + - match: (?!\w) # restrict field names to reduce risk of false positives + fail: triple-double-quoted-string-replacement + + triple-double-string-replacement-brackets-body: + - include: string-replacement-brackets-body + - include: triple-double-quoted-string-replacement-fail + + triple-double-quoted-string-replacement-formatspec-body: + - meta_content_scope: meta.format-spec.python constant.other.format-spec.python + - include: string-replacement-formatspec-body + - match: \{ + scope: punctuation.definition.placeholder.begin.python + push: triple-double-quoted-string-replacement-body + - include: triple-double-quoted-string-replacement-fail + + triple-double-quoted-string-replacement-fail: + - match: (?="""|$) + fail: triple-double-quoted-string-replacement + + triple-double-quoted-f-string-replacements: + - include: f-string-replacements + - match: \{ + scope: punctuation.section.interpolation.begin.python + push: + - f-string-replacement-meta + - triple-double-quoted-f-string-replacement-formatspec + - triple-double-quoted-f-string-replacement-expression + + triple-double-quoted-f-string-replacements-regexp: + # Same as f-string-replacements, but will reset the entire scope stack. + - include: f-string-replacements-regexp + - match: \{ + scope: punctuation.section.interpolation.begin.python + push: + - f-string-replacement-regexp-meta + - triple-double-quoted-f-string-replacement-formatspec + - triple-double-quoted-f-string-replacement-expression + + triple-double-quoted-f-string-replacement-expression: + - meta_content_scope: source.python.embedded + - include: f-string-replacement-expression + + triple-double-quoted-f-string-replacement-formatspec: + - meta_include_prototype: false + - match: ':' + scope: punctuation.separator.format-spec.python + set: triple-double-quoted-f-string-replacement-formatspec-body + - include: triple-double-quoted-f-string-replacement-end + + triple-double-quoted-f-string-replacement-formatspec-body: + - meta_content_scope: meta.format-spec.python constant.other.format-spec.python + - include: triple-double-quoted-f-string-replacement-end + - include: triple-double-quoted-f-string-replacements + + triple-double-quoted-f-string-replacement-end: + - include: f-string-replacement-end + - match: (?=""") + pop: 2 ###[ DOUBLE QUOTED STRINGS ]################################################## @@ -2830,7 +2900,7 @@ contexts: double-quoted-plain-raw-f-string-content: - include: string-prototype - - include: f-string-replacements + - include: double-quoted-f-string-replacements double-quoted-raw-f-strings: # Single-line raw f-string, treated as regex @@ -2852,7 +2922,7 @@ contexts: double-quoted-raw-f-string-content: - include: string-prototype - - include: f-string-replacements-regexp + - include: double-quoted-f-string-replacements-regexp double-quoted-plain-raw-u-strings: # Single-line capital R raw string, unicode or not, no syntax embedding @@ -2918,7 +2988,7 @@ contexts: double-quoted-sql-raw-u-string-content: - include: string-prototype - include: string-placeholders - - include: string-replacements + - include: double-quoted-string-replacements double-quoted-b-strings: # Single-line string, bytes @@ -2938,7 +3008,7 @@ contexts: - include: string-prototype - include: escaped-chars - include: string-placeholders - - include: string-replacements + - include: double-quoted-string-replacements double-quoted-f-strings: # Single-line f-string @@ -2956,10 +3026,9 @@ contexts: double-quoted-f-string-content: - include: string-prototype - - include: escaped-f-string-braces + - include: double-quoted-f-string-replacements - include: escaped-unicode-chars - include: escaped-chars - - include: f-string-replacements double-quoted-u-strings: - match: ([uU]?)(") @@ -3003,7 +3072,89 @@ contexts: - include: escaped-unicode-chars - include: escaped-chars - include: string-placeholders - - include: string-replacements + - include: double-quoted-string-replacements + + double-quoted-string-replacements: + - include: escaped-string-braces + - match: (?=\{) + branch_point: double-quoted-string-replacement + branch: + - double-quoted-string-replacement + - string-replacement-fallback + + double-quoted-string-replacement: + - match: \{ + scope: punctuation.definition.placeholder.begin.python + set: double-quoted-string-replacement-body + + double-quoted-string-replacement-body: + - meta_scope: constant.other.placeholder.python + - include: string-replacement-body + - match: \[ + scope: punctuation.section.brackets.begin.python + push: double-quoted-string-replacement-brackets-body + - match: ':' + scope: punctuation.separator.format-spec.python + push: double-quoted-string-replacement-formatspec-body + - match: (?!\w) # restrict field names to reduce risk of false positives + fail: double-quoted-string-replacement + + double-quoted-string-replacement-brackets-body: + - include: string-replacement-brackets-body + - include: double-quoted-string-replacement-fail + + double-quoted-string-replacement-formatspec-body: + - meta_content_scope: meta.format-spec.python constant.other.format-spec.python + - include: string-replacement-formatspec-body + - match: \{ + scope: punctuation.definition.placeholder.begin.python + push: double-quoted-string-replacement-body + - include: double-quoted-string-replacement-fail + + double-quoted-string-replacement-fail: + - match: (?="|$) + fail: double-quoted-string-replacement + + double-quoted-f-string-replacements: + - include: f-string-replacements + - match: \{ + scope: punctuation.section.interpolation.begin.python + push: + - f-string-replacement-meta + - double-quoted-f-string-replacement-formatspec + - double-quoted-f-string-replacement-expression + + double-quoted-f-string-replacements-regexp: + # Same as f-string-replacements, but will reset the entire scope stack. + - include: f-string-replacements-regexp + - match: \{ + scope: punctuation.section.interpolation.begin.python + push: + - f-string-replacement-regexp-meta + - double-quoted-f-string-replacement-formatspec + - double-quoted-f-string-replacement-expression + + double-quoted-f-string-replacement-expression: + - meta_content_scope: source.python.embedded + - include: f-string-replacement-expression + - include: eol-pop + + double-quoted-f-string-replacement-formatspec: + - meta_include_prototype: false + - match: ':' + scope: punctuation.separator.format-spec.python + set: double-quoted-f-string-replacement-formatspec-body + - include: double-quoted-f-string-replacement-end + + double-quoted-f-string-replacement-formatspec-body: + - meta_content_scope: meta.format-spec.python constant.other.format-spec.python + - include: double-quoted-f-string-replacement-end + - include: double-quoted-f-string-replacements + + double-quoted-f-string-replacement-end: + - include: f-string-replacement-end + - match: (?="|$) + pop: 2 ###[ TRIPLE SINGLE QUOTED STRINGS ]########################################### @@ -3081,7 +3232,7 @@ contexts: triple-single-quoted-plain-raw-f-string-content: - include: string-prototype - - include: f-string-replacements + - include: triple-single-quoted-f-string-replacements triple-single-quoted-raw-f-strings: # Triple-quoted raw f-string, treated as regex @@ -3103,7 +3254,7 @@ contexts: triple-single-quoted-raw-f-string-content: - include: string-prototype - - include: f-string-replacements-regexp + - include: triple-single-quoted-f-string-replacements-regexp triple-single-quoted-plain-raw-u-strings: # Triple-quoted capital R raw string, unicode or not, no syntax embedding @@ -3170,7 +3321,7 @@ contexts: - include: string-prototype - include: escaped-unicode-chars - include: string-placeholders - - include: string-replacements + - include: triple-single-quoted-string-replacements triple-single-quoted-b-strings: # Triple-quoted string, bytes, no syntax embedding @@ -3191,7 +3342,7 @@ contexts: - include: string-continuations - include: escaped-chars - include: string-placeholders - - include: string-replacements + - include: triple-single-quoted-string-replacements triple-single-quoted-f-strings: # Triple-quoted f-string @@ -3210,10 +3361,9 @@ contexts: triple-single-quoted-f-string-content: - include: string-prototype - include: string-continuations - - include: escaped-f-string-braces + - include: triple-single-quoted-f-string-replacements - include: escaped-unicode-chars - include: escaped-chars - - include: f-string-replacements triple-single-quoted-u-strings: # Triple-quoted string, unicode or not, will detect SQL @@ -3258,7 +3408,88 @@ contexts: - include: escaped-unicode-chars - include: escaped-chars - include: string-placeholders - - include: string-replacements + - include: triple-single-quoted-string-replacements + + triple-single-quoted-string-replacements: + - include: escaped-string-braces + - match: (?=\{) + branch_point: triple-single-quoted-string-replacement + branch: + - triple-single-quoted-string-replacement + - string-replacement-fallback + + triple-single-quoted-string-replacement: + - match: \{ + scope: punctuation.definition.placeholder.begin.python + set: triple-single-quoted-string-replacement-body + + triple-single-quoted-string-replacement-body: + - meta_scope: constant.other.placeholder.python + - include: string-replacement-body + - match: \[ + scope: punctuation.section.brackets.begin.python + push: triple-single-string-replacement-brackets-body + - match: ':' + scope: punctuation.separator.format-spec.python + push: triple-single-quoted-string-replacement-formatspec-body + - match: (?!\w) # restrict field names to reduce risk of false positives + fail: triple-single-quoted-string-replacement + + triple-single-string-replacement-brackets-body: + - include: string-replacement-brackets-body + - include: triple-single-quoted-string-replacement-fail + + triple-single-quoted-string-replacement-formatspec-body: + - meta_content_scope: meta.format-spec.python constant.other.format-spec.python + - include: string-replacement-formatspec-body + - match: \{ + scope: punctuation.definition.placeholder.begin.python + push: triple-single-quoted-string-replacement-body + - include: triple-single-quoted-string-replacement-fail + + triple-single-quoted-string-replacement-fail: + - match: (?='''|$) + fail: triple-single-quoted-string-replacement + + triple-single-quoted-f-string-replacements: + - include: f-string-replacements + - match: \{ + scope: punctuation.section.interpolation.begin.python + push: + - f-string-replacement-meta + - triple-single-quoted-f-string-replacement-formatspec + - triple-single-quoted-f-string-replacement-expression + + triple-single-quoted-f-string-replacements-regexp: + # Same as f-string-replacements, but will reset the entire scope stack. + - include: f-string-replacements-regexp + - match: \{ + scope: punctuation.section.interpolation.begin.python + push: + - f-string-replacement-regexp-meta + - triple-single-quoted-f-string-replacement-formatspec + - triple-single-quoted-f-string-replacement-expression + + triple-single-quoted-f-string-replacement-expression: + - meta_content_scope: source.python.embedded + - include: f-string-replacement-expression + + triple-single-quoted-f-string-replacement-formatspec: + - meta_include_prototype: false + - match: ':' + scope: punctuation.separator.format-spec.python + set: triple-single-quoted-f-string-replacement-formatspec-body + - include: triple-single-quoted-f-string-replacement-end + + triple-single-quoted-f-string-replacement-formatspec-body: + - meta_content_scope: meta.format-spec.python constant.other.format-spec.python + - include: triple-single-quoted-f-string-replacement-end + - include: triple-single-quoted-f-string-replacements + + triple-single-quoted-f-string-replacement-end: + - include: f-string-replacement-end + - match: (?=''') + pop: 2 ###[ SINGLE QUOTED STRINGS ]################################################## @@ -3389,7 +3620,7 @@ contexts: single-quoted-sql-raw-u-string-content: - include: string-prototype - include: string-placeholders - - include: string-replacements + - include: single-quoted-string-replacements single-quoted-plain-raw-f-strings: # Single-line raw f-string @@ -3407,7 +3638,7 @@ contexts: single-quoted-plain-raw-f-string-content: - include: string-prototype - - include: f-string-replacements + - include: single-quoted-f-string-replacements single-quoted-raw-f-strings: # Single-line raw f-string, treated as regex @@ -3429,7 +3660,7 @@ contexts: single-quoted-raw-f-string-content: - include: string-prototype - - include: f-string-replacements-regexp + - include: single-quoted-f-string-replacements-regexp single-quoted-b-strings: # Single-line string, bytes @@ -3449,7 +3680,7 @@ contexts: - include: string-prototype - include: escaped-chars - include: string-placeholders - - include: string-replacements + - include: single-quoted-string-replacements single-quoted-f-strings: # Single-line f-string @@ -3467,10 +3698,9 @@ contexts: single-quoted-f-string-content: - include: string-prototype - - include: escaped-f-string-braces + - include: single-quoted-f-string-replacements - include: escaped-unicode-chars - include: escaped-chars - - include: f-string-replacements single-quoted-u-strings: - match: ([uU]?)(') @@ -3514,7 +3744,89 @@ contexts: - include: escaped-unicode-chars - include: escaped-chars - include: string-placeholders - - include: string-replacements + - include: single-quoted-string-replacements + + single-quoted-string-replacements: + - include: escaped-string-braces + - match: (?=\{) + branch_point: single-quoted-string-replacement + branch: + - single-quoted-string-replacement + - string-replacement-fallback + + single-quoted-string-replacement: + - match: \{ + scope: punctuation.definition.placeholder.begin.python + set: single-quoted-string-replacement-body + + single-quoted-string-replacement-body: + - meta_scope: constant.other.placeholder.python + - include: string-replacement-body + - match: \[ + scope: punctuation.section.brackets.begin.python + push: single-string-replacement-brackets-body + - match: ':' + scope: punctuation.separator.format-spec.python + push: single-quoted-string-replacement-formatspec-body + - match: (?!\w) # restrict field names to reduce risk of false positives + fail: single-quoted-string-replacement + + single-string-replacement-brackets-body: + - include: string-replacement-brackets-body + - include: single-quoted-string-replacement-fail + + single-quoted-string-replacement-formatspec-body: + - meta_content_scope: meta.format-spec.python constant.other.format-spec.python + - include: string-replacement-formatspec-body + - match: \{ + scope: punctuation.definition.placeholder.begin.python + push: single-quoted-string-replacement-body + - include: single-quoted-string-replacement-fail + + single-quoted-string-replacement-fail: + - match: (?='|$) + fail: single-quoted-string-replacement + + single-quoted-f-string-replacements: + - include: f-string-replacements + - match: \{ + scope: punctuation.section.interpolation.begin.python + push: + - f-string-replacement-meta + - single-quoted-f-string-replacement-formatspec + - single-quoted-f-string-replacement-expression + + single-quoted-f-string-replacements-regexp: + # Same as f-string-replacements, but will reset the entire scope stack. + - include: f-string-replacements-regexp + - match: \{ + scope: punctuation.section.interpolation.begin.python + push: + - f-string-replacement-regexp-meta + - single-quoted-f-string-replacement-formatspec + - single-quoted-f-string-replacement-expression + + single-quoted-f-string-replacement-expression: + - meta_content_scope: source.python.embedded + - include: f-string-replacement-expression + - include: eol-pop + + single-quoted-f-string-replacement-formatspec: + - meta_include_prototype: false + - match: ':' + scope: punctuation.separator.format-spec.python + set: single-quoted-f-string-replacement-formatspec-body + - include: single-quoted-f-string-replacement-end + + single-quoted-f-string-replacement-formatspec-body: + - meta_content_scope: meta.format-spec.python constant.other.format-spec.python + - include: single-quoted-f-string-replacement-end + - include: single-quoted-f-string-replacements + + single-quoted-f-string-replacement-end: + - include: f-string-replacement-end + - match: (?='|$) + pop: 2 ###[ STRING CONTENTS ]######################################################## @@ -3544,13 +3856,9 @@ contexts: - match: \\N\{[-a-zA-Z ]+\} scope: constant.character.escape.unicode.name.python - escaped-f-string-braces: - # special-case the '\{{' sequence because it has higher priority than the deprecated '\{' - - match: (\\)(\{\{|\}\}) - scope: constant.character.escape.backslash.regexp - captures: - 1: invalid.deprecated.character.escape.python - 2: constant.character.escape.python + escaped-string-braces: + - match: \{\{|\}\} + scope: constant.character.escape.python string-placeholders: - match: |- # printf style @@ -3572,80 +3880,43 @@ contexts: - match: '{{strftime_spec}}' scope: constant.other.placeholder.python - string-replacements: - - match: \{\{|\}\} - scope: constant.character.escape.python - # https://docs.python.org/3.6/library/string.html#formatstrings - # Technically allows almost every character for the key, - # but those are rarely used if ever. - - match: |- # simple form - (?x) - (\{) - (?: [\w.\[\]]+)? # field_name - ( ! [ars])? # conversion - (?: (:) ({{format_spec}}| # format_spec OR - [^}%]*%.[^}]*) # any format-like string - )? - (\}) - scope: constant.other.placeholder.python - captures: - 1: punctuation.definition.placeholder.begin.python - 2: storage.modifier.conversion.python - 3: punctuation.separator.format-spec.python - 4: meta.format-spec.python constant.other.format-spec.python - 5: punctuation.definition.placeholder.end.python - - match: (?=\{[^{}"']+\{[^"']*\}) # complex (nested) form - branch_point: string-replacement - branch: - - string-replacement - - string-replacement-fallback - - string-replacement: - - match: \{ - scope: punctuation.definition.placeholder.begin.python - set: string-replacement-body - string-replacement-body: - - meta_scope: constant.other.placeholder.python - match: \} scope: punctuation.definition.placeholder.end.python pop: 1 - # TODO could match numeric indices or everything else as a key - # and also [] indexing - - match: '![ars]' + - match: \. + scope: punctuation.accessor.dot.python + - match: '![ars]\b' scope: storage.modifier.conversion.python - - match: ':' - scope: punctuation.separator.format-spec.python - push: string-replacement-formatspec-body - - match: '[{"''\n]' - fail: string-replacement + - include: escaped-chars - string-replacement-formatspec-body: - - meta_content_scope: meta.format-spec.python constant.other.format-spec.python - - match: (?=\}) + string-replacement-brackets-body: + - meta_scope: meta.brackets.python + - match: \] + scope: punctuation.section.brackets.end.python pop: 1 - - match: (?=\{) - push: string-replacement + - include: eol-pop + - include: escaped-chars + + string-replacement-formatspec-body: + - match: \} + scope: punctuation.definition.placeholder.end.python + pop: 2 + - include: escaped-chars string-replacement-fallback: - match: \{ - scope: meta.debug.string-replacement-fallback.python pop: 1 f-string-replacements: - # https://www.python.org/dev/peps/pep-0498/ + # https://peps.python.org/pep-0498 + # https://peps.python.org/pep-0701 # https://docs.python.org/3.6/reference/lexical_analysis.html#f-strings - - match: \{\{|\}\} + - match: (\\)?(?:\{\{|\}\}) scope: constant.character.escape.python - - match: \{\s*\} - scope: invalid.illegal.empty-expression.python - - match: \{ - scope: punctuation.section.interpolation.begin.python - push: - - f-string-replacement-meta - - f-string-replacement-modifier - - f-string-replacement-expression - - include: illegal-stray-braces + captures: + 1: invalid.deprecated.character.escape.python + - include: invalid-f-string-replacements f-string-replacement-meta: - clear_scopes: 1 @@ -3653,38 +3924,14 @@ contexts: - meta_scope: meta.interpolation.python - include: immediately-pop - f-string-replacement-expression: - - meta_content_scope: source.python.embedded - - match: (?=\s*(?:=\s*)?(![^=]|:|\})) - pop: 1 - - match: \\ - scope: invalid.illegal.backslash-in-fstring.python - - include: expression-in-a-sequence - - f-string-replacement-modifier: - - include: f-string-replacement-end - - include: f-string-replacement-common - - match: = - scope: storage.modifier.debug.python - f-string-replacements-regexp: # Same as f-string-replacements, but will reset the entire scope stack # and has an additional match. - - match: \\(\{\{|\}\}) - scope: constant.character.escape.backslash.regexp + - match: \\?(\{\{|\}\}) + scope: constant.character.escape.regexp captures: 1: constant.character.escape.python - - match: \{\{|\}\} - scope: constant.character.escape.python - - match: \{\s*\} - scope: invalid.illegal.empty-expression.python - - match: \{ - scope: punctuation.section.interpolation.begin.python - push: - - f-string-replacement-regexp-meta - - f-string-replacement-regexp-modifier - - f-string-replacement-regexp-expression - - include: illegal-stray-braces + - include: invalid-f-string-replacements f-string-replacement-regexp-meta: # Same as f-string-replacement, but with clear_scopes: true @@ -3693,38 +3940,26 @@ contexts: - meta_scope: source.python meta.string.python meta.interpolation.python - include: immediately-pop - f-string-replacement-regexp-expression: - - meta_content_scope: source.python.embedded - - match: (?=![^=]|:|\}) + f-string-replacement-expression: + - match: (?:\s*(=)\s*)?(![ars])?(?=[:}]) + captures: + 1: storage.modifier.debug.python + 2: storage.modifier.conversion.python pop: 1 - match: \\ scope: invalid.illegal.backslash-in-fstring.python - include: expression-in-a-sequence - f-string-replacement-regexp-modifier: - - include: f-string-replacement-end - - include: f-string-replacement-common - - f-string-replacement-common: - - match: '![ars]' - scope: storage.modifier.conversion.python - - match: ':' - scope: meta.format-spec.python constant.other.format-spec.python punctuation.separator.format-spec.python - set: f-string-format-spec - - f-string-format-spec: - - meta_content_scope: meta.format-spec.python constant.other.format-spec.python - # Because replacements can also be used *within* the format-spec, - # basically any character is valid and matching {{format_spec}} is useless. - # - match: '{{format_spec}}' - - include: f-string-replacement-end - - include: f-string-replacements - f-string-replacement-end: - match: \} scope: punctuation.section.interpolation.end.python pop: 2 + invalid-f-string-replacements: + - match: \{\s*\} + scope: invalid.illegal.empty-expression.python + - include: illegal-stray-braces + # for use by inheriting syntaxes to easily inject string interpolation # in any kind of quoted or unquoted string string-prototype: [] diff --git a/Python/tests/syntax_test_python_strings.py b/Python/tests/syntax_test_python_strings.py index 735256dff6..58576ac1ad 100644 --- a/Python/tests/syntax_test_python_strings.py +++ b/Python/tests/syntax_test_python_strings.py @@ -529,6 +529,7 @@ # ^^^^^^ constant.other.placeholder.python "Weight in tons {0.weight}" # 'weight' attribute of first positional arg # ^^^^^^^^^^ constant.other.placeholder.python +# ^ punctuation.accessor.dot.python "Units destroyed: {players[0]}" # First element of keyword argument 'players'. # ^^^^^^^^^^^^ constant.other.placeholder.python "Harold's a clever {0!s}" # Calls str() on the argument first @@ -586,6 +587,46 @@ # ^^^^^^^^^^^ constant.other.placeholder constant.other.placeholder # ^^ punctuation.definition.placeholder.end +# Nested quotes and escaped quotes + +"Testing {foo['bar']:'^9}".format(foo={"bar": 1000}) +# <- meta.string.python string.quoted.double.python punctuation.definition.string.begin.python +#^^^^^^^^^^^^^^^^^^^^^^^^^ meta.string.python string.quoted.double.python +# ^^^^^^^^^^^^^^^^ constant.other.placeholder +# ^ punctuation.separator.format-spec.python +# ^ punctuation.definition.string.end.python +# ^ punctuation.accessor.dot.python + +"Testing {foo[\"bar\"]:\"^9}".format(foo={"bar": 1000}) +# <- meta.string.python string.quoted.double.python punctuation.definition.string.begin.python +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.string.python string.quoted.double.python +# ^^^^^^^^^^^^^^^^^^^ constant.other.placeholder +# ^^ constant.character.escape.python +# ^^ constant.character.escape.python +# ^ punctuation.separator.format-spec.python +# ^^ constant.character.escape.python +# ^ punctuation.definition.string.end.python +# ^ punctuation.accessor.dot.python + +'Testing {foo["bar"]:"^9}'.format(foo={"bar": 1000}) +# <- meta.string.python string.quoted.single.python punctuation.definition.string.begin.python +#^^^^^^^^^^^^^^^^^^^^^^^^^ meta.string.python string.quoted.single.python +# ^^^^^^^^^^^^^^^^ constant.other.placeholder +# ^ punctuation.separator.format-spec.python +# ^ punctuation.definition.string.end.python +# ^ punctuation.accessor.dot.python + +'Testing {foo[\'bar\']:\'^9}'.format(foo={"bar": 1000}) +# <- meta.string.python string.quoted.single.python punctuation.definition.string.begin.python +#^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.string.python string.quoted.single.python +# ^^^^^^^^^^^^^^^^^^^ constant.other.placeholder +# ^^ constant.character.escape.python +# ^^ constant.character.escape.python +# ^ punctuation.separator.format-spec.python +# ^^ constant.character.escape.python +# ^ punctuation.definition.string.end.python +# ^ punctuation.accessor.dot.python + a=["aaaa{", "bbbb{"] # ^ - constant.other.placeholder # ^ punctuation.definition.string.end.python @@ -602,40 +643,91 @@ # so some of these matches are incorrect because of implementation details. # Not format specs -"{:{ }" # unclosed +"{:{ }" # unclosed # ^ - constant.other.placeholder -'{{foo!r:4.2}' # escaped opening +'{:{ }' # unclosed # ^ - constant.other.placeholder -'{{foo!r:4.2}}' # escaped opening and closing +"{{foo!r:4.2}" # escaped opening # ^ - constant.other.placeholder -'{foo!a:ran{dom}' # unclosed +'{{foo!r:4.2}' # escaped opening +# ^ - constant.other.placeholder +"{{foo!r:4.2}}" # escaped opening and closing +# ^ - constant.other.placeholder +'{{foo!r:4.2}}' # escaped opening and closing +# ^ - constant.other.placeholder +"{foo!a:ran{dom}" # unclosed # ^ - constant.other.placeholder '{foo!a:ran{dom}' # unclosed # ^ - constant.other.placeholder +# Incomplete field elements +"{foo[" # unclosed elemnt index +#^^^^^ - constant.other.placeholder +'{foo[' # unclosed elemnt index +#^^^^^ - constant.other.placeholder +"{foo[}" # unclosed elemnt index +#^^^^^^ - constant.other.placeholder +'{foo[}' # unclosed elemnt index +#^^^^^^ - constant.other.placeholder +"{foo[""]}" # unsupported nested quotes +#^^^^^^^^^ - constant.other.placeholder +'{foo['']}' # unsupported nested quotes +#^^^^^^^^^ - constant.other.placeholder + # Invalid field names +"{foo{d}}" +# ^ - constant.other.placeholder '{foo{d}}' # ^ - constant.other.placeholder "{:{ {}}" # issue 2232 # ^ - constant.other.placeholder +'{:{ {}}' # issue 2232 +# ^ - constant.other.placeholder +"{foo.!a:d}" # incomplete accessor (in simple form) +# ^ constant.other.placeholder +# ^ punctuation.accessor.dot '{foo.!a:d}' # incomplete accessor (in simple form) # ^ constant.other.placeholder +# ^ punctuation.accessor.dot -# Syntactically correct, but hardly come up in real code +# Issue 3649 +{"msg": "Type '{ _client: { _user?: { [x: string]: unknown; | undefined; }"} +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ string.quoted - constant.other.placeholder +# ^ punctuation.section.mapping.end.python + +# Syntactically correct, but hardly come up in real code and not covered by +# https://docs.python.org/3.11/library/string.html#format-string-syntax "{:{ ()}}".format(0, **{" ()": "d"}) == '0' -# ^ constant.other.placeholder +#^^^^^^^^^ meta.string.python string.quoted.double.python +#^^^^^^ - constant.other.placeholder '{foo/bar}'.format(**{"foo/bar": 1}) == '1' -# ^ - constant.other.placeholder +#^^^^^^^^^^ meta.string.python string.quoted.single.python +#^^^^^^^^^ - constant.other.placeholder +# ^ punctuation.definition.string.end.python # Legal but non-standard format spec -'{foo:{{w}}.{{p}}}' -# ^ - constant.other.placeholder -'{foo:w}}}' -# ^ - constant.other.placeholder -'{foo!a:random}' -# ^ - constant.other.placeholder +'{foo:^}}}' # valid format specifier +#^^^^^^^ constant.other.placeholder +#^ punctuation.definition.placeholder.begin +# ^ punctuation.separator.format-spec +# ^ punctuation.definition.placeholder.end +# ^^ constant.character.escape.python +'{foo:w}}}' # invalid format code +#^^^^^^^ constant.other.placeholder +#^ punctuation.definition.placeholder.begin +# ^ punctuation.separator.format-spec +# ^ punctuation.definition.placeholder.end +# ^^ constant.character.escape.python +'{foo:{{w}}.{{p}}}' # invalid format specifier +#^^^^^^^^^^^^^^^^^ - constant.other.placeholder +# ^^ constant.character.escape +# ^^ constant.character.escape +# ^^ constant.character.escape +# ^^ constant.character.escape +'{foo!a:random}' # invalid format specifier +#^^^^^^^^^^^^^^ constant.other.placeholder '{foo!a:ran{d}om}' # nested specification -# ^ constant.other.placeholder +#^^^^^^^^^^^^^^^^ constant.other.placeholder f"string" # <- storage.type.string @@ -653,15 +745,15 @@ f"{size:.2f}" # ^ meta.string.python - meta.interpolation -# ^^^^^ meta.string.python meta.interpolation.python - meta.format-spec -# ^^^^ meta.string.python meta.interpolation.python meta.format-spec.python - meta.format-spec meta.format-spec +# ^^^^^^ meta.string.python meta.interpolation.python - meta.format-spec +# ^^^ meta.string.python meta.interpolation.python meta.format-spec.python - meta.format-spec meta.format-spec # ^ meta.string.python meta.interpolation.python - meta.format-spec # ^ meta.string.python string.quoted.double.python - meta.interpolation # ^ punctuation.definition.string.begin.python # ^ punctuation.section.interpolation.begin.python # ^^^^ meta.path.python meta.generic-name.python -# ^^^^ constant.other.format-spec.python # ^ punctuation.separator.format-spec.python +# ^^^ constant.other.format-spec.python # ^ punctuation.section.interpolation.end.python # ^ punctuation.definition.string.end.python @@ -688,12 +780,17 @@ # ^ source.regexp.python meta.group.regexp punctuation.section.group.end.regexp line = re.sub(rf" ?\{{\\i.?\}}({x})\{{\\i.?\}}", r"\1", line) -# ^^^ constant.character.escape.backslash.regexp -# ^^ constant.character.escape.python +# ^ constant.character.escape.regexp - constant.character.escape.python +# ^^ constant.character.escape.regexp constant.character.escape.python # ^^ constant.character.escape.regexp -# ^^^ constant.character.escape.backslash.regexp -# ^^ constant.character.escape.python +# ^ constant.character.escape.regexp - constant.character.escape.python +# ^^ constant.character.escape.regexp constant.character.escape.python # ^ punctuation.section.interpolation.begin.python +# ^ constant.character.escape.regexp - constant.character.escape.python +# ^^ constant.character.escape.regexp constant.character.escape.python +# ^^ constant.character.escape.regexp +# ^ constant.character.escape.regexp - constant.character.escape.python +# ^^ constant.character.escape.regexp constant.character.escape.python match = re.match(r'(?Pa)?b(?(test)c|d)', line) # ^^^^^^^^^^ meta.group.regexp @@ -753,9 +850,14 @@ # ^^^^ meta.modifier # ^ storage.modifier.mode + +############################### +# f-strings +############################### + f"\{{{x}\}} test" # ^ invalid.deprecated.character.escape.python -# ^^ constant.character.escape.python +# ^^^ constant.character.escape.python # ^ punctuation.section.interpolation.begin.python f"{something}" @@ -774,7 +876,7 @@ # ^^^^ source source.python.embedded constant.language # ^^^^^^^ - source source.python.embedded # ^^ storage.modifier.conversion - constant.other.format-spec -# ^^^^ constant.other.format-spec +# ^^^ constant.other.format-spec # ^ punctuation.section.interpolation.end # ^ punctuation.definition.string.end # ^ source - meta, string, source source @@ -792,30 +894,30 @@ # ^^ punctuation.section.interpolation.end.python - source source # ^^ constant.character.escape # ^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.interpolation.python -# ^^^^^^^^^^^^^^^^^^^^ meta.format-spec.python +# ^^^^^^ - meta.format-spec +# ^^^^^^^^^^^^^^^^^^^ meta.format-spec.python +# ^^^^ - meta.format-spec # ^^^^^^ - meta.interpolation.python meta.interpolation.python # ^^^^^^^ meta.interpolation.python meta.interpolation.python # ^ - meta.interpolation.python meta.interpolation.python # ^^^^^^^^^^^ meta.interpolation.python meta.interpolation.python # ^^^ - meta.interpolation.python meta.interpolation.python + rf"{value:{width!s:d}}" -# <- storage.type.string.python - string -# ^^^^^^^^^^^^^^^^^^^^^ meta.string +# <- storage.type.string - meta.string - string +#^ storage.type.string - meta.string - string +# ^ meta.string string.quoted.double punctuation.definition.string.begin +# ^^^^^^^ meta.string.python meta.interpolation.python - meta.format-spec +# ^^^^^^^^^ meta.string meta.interpolation meta.format-spec meta.interpolation - meta.format-spec meta.format-spec +# ^ meta.string meta.interpolation meta.format-spec meta.interpolation meta.format-spec +# ^^ meta.string meta.interpolation punctuation.section.interpolation.end +# ^^^^^ source source.python.embedded +# ^ punctuation.separator.format-spec +# ^ punctuation.section.interpolation.begin # ^^^^^ source source.python.embedded # ^^ storage.modifier.conversion -# ^^ constant.other.format-spec - -F""" {} {\} } -#^^^^^^^^^^^ meta.string -#^^^ punctuation.definition.string.begin -# ^^ invalid.illegal.empty-expression -# ^ invalid.illegal.backslash-in-fstring -# ^ invalid.illegal.stray -""" - -fr''' -# ^ - invalid -''' +# ^ punctuation.separator.format-spec +# ^ constant.other.format-spec # Most of these were inspired by # https://github.com/python/cpython/commit/9a4135e939bc223f592045a38e0f927ba170da32 @@ -828,9 +930,11 @@ f'{x=!a}' # ^ storage.modifier.debug.python f'{x=!s:*^20}' +# ^^^^^^^^^^^ meta.string.python meta.interpolation.python # ^ storage.modifier.debug.python # ^^ storage.modifier.conversion.python -# ^^^^^ meta.format-spec.python +# ^ punctuation.separator.format-spec.python - constant.other.format-spec +# ^^^^ constant.other.format-spec.python f'{"Σ"=}' # ^ storage.modifier.debug.python f'{"Σ"= }' @@ -865,22 +969,320 @@ f'{f(a="3=")}' # ^^^^ -storage.modifier.debug.python -f" { -% ^ invalid.illegal.unclosed-string - # TODO make this test pass - }" - -f' \ - {1 + 2!a:02f}' -#^^^^^^^^^^^^^^ meta.string -# ^^^^^ source source.python.embedded - f"{d for d in range(10)}" # yes, this doesn't make sense # ^^^ keyword.control.loop.for.generator.python +# Nested f-strings + +f"These are the things: {", ".join(things)}" +#^^^^^^^^^^^^^^^^^^^^^^^ meta.string.python string.quoted.double.python - string string +# ^ meta.string.python meta.interpolation.python - string +# ^^^^ meta.string.python meta.interpolation.python meta.string.python string.quoted.double.python +# ^^^^^^^^^^^^^^ meta.string.python meta.interpolation.python - string +# ^ meta.string.python string.quoted.double.python - string string + +f"{source.removesuffix(".py")}.c: $(srcdir)/{source}" +#^ meta.string.python string.quoted.double.python +# ^^^^^^^^^^^^^^^^^^^^^ meta.string.python meta.interpolation.python - string +# ^^^^^ meta.string.python meta.interpolation.python meta.string.python string.quoted.double.python +# ^^ meta.string.python meta.interpolation.python - string +# ^^^^^^^^^^^^^^ meta.string.python string.quoted.double.python - meta.interpolation +# ^^^^^^^^ meta.string.python meta.interpolation.python - string +# ^ meta.string.python string.quoted.double.python + +f"{f"{f"infinite"}":{f"{foo}"}.{"bar"}}" +# <- storage.type.string.python +#^ meta.string.python string.quoted.double.python punctuation.definition.string.begin.python +# ^ meta.string.python meta.interpolation.python punctuation.section.interpolation.begin.python +# ^ meta.string.python meta.interpolation.python storage.type.string.python +# ^ meta.string.python meta.interpolation.python meta.string.python string.quoted.double.python punctuation.definition.string.begin.python +# ^ meta.string.python meta.interpolation.python meta.string.python meta.interpolation.python punctuation.section.interpolation.begin.python +# ^ meta.string.python meta.interpolation.python meta.string.python meta.interpolation.python storage.type.string.python +# ^^^^^^^^^^ meta.string.python meta.interpolation.python meta.string.python meta.interpolation.python meta.string.python string.quoted.double.python +# ^ meta.string.python meta.interpolation.python meta.string.python meta.interpolation.python punctuation.section.interpolation.end.python +# ^ meta.string.python meta.interpolation.python meta.string.python string.quoted.double.python punctuation.definition.string.end.python +# ^ meta.string.python meta.interpolation.python punctuation.separator.format-spec.python +# ^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python punctuation.section.interpolation.begin.python +# ^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python source.python.embedded storage.type.string.python +# ^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python source.python.embedded meta.string.python string.quoted.double.python punctuation.definition.string.begin.python +# ^^^^^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python source.python.embedded meta.string.python meta.interpolation.python +# ^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python source.python.embedded meta.string.python string.quoted.double.python punctuation.definition.string.end.python +# ^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python punctuation.section.interpolation.end.python +# ^ meta.string.python meta.interpolation.python meta.format-spec.python constant.other.format-spec.python +# ^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python punctuation.section.interpolation.begin.python +# ^^^^^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python source.python.embedded meta.string.python string.quoted.double.python +# ^ meta.string.python meta.interpolation.python meta.format-spec.python meta.interpolation.python punctuation.section.interpolation.end.python +# ^ meta.string.python meta.interpolation.python punctuation.section.interpolation.end.python +# ^ meta.string.python string.quoted.double.python punctuation.definition.string.end.python +# ^ - meta.string + +# Incomplete strings or premature bailouts + +f" +#^^ meta.string.python string.quoted.double.python +# ^ invalid.illegal.unclosed-string + +# <- - meta.string - string +# this test is to ensure we're not matching anything here anymore + f' +#^^ meta.string.python string.quoted.single.python # ^ invalid.illegal.unclosed-string +# <- - meta.string - string +# this test is to ensure we're not matching anything here anymore + +f" \ + +# <- meta.string.python string.quoted.double.python invalid.illegal.unclosed-string.python + +# <- - meta.string - string +# this test is to ensure we're not matching anything here anymore + +f' \ -# <- - meta +# <- meta.string.python string.quoted.single.python invalid.illegal.unclosed-string.python + +# <- - meta.string - string # this test is to ensure we're not matching anything here anymore + +f" \ + {1 + 2!a:02f}" +# <- meta.string.python string.quoted.double.python +#^^^^^^^^^^^^^ meta.string.python meta.interpolation.python - string.quoted +# ^^^^^ source source.python.embedded +# ^^ storage.modifier.conversion.python +# ^ punctuation.separator.format-spec.python +# ^^^ meta.format-spec.python constant.other.format-spec.python +# ^ punctuation.section.interpolation.end.python +# ^ meta.string.python string.quoted.double.python punctuation.definition.string.end.python + +f' \ + {1 + 2!a:02f}' +# <- meta.string.python string.quoted.single.python +#^^^^^^^^^^^^^ meta.string.python meta.interpolation.python - string.quoted +# ^^^^^ source source.python.embedded +# ^^ storage.modifier.conversion.python +# ^ punctuation.separator.format-spec.python +# ^^^ meta.format-spec.python constant.other.format-spec.python +# ^ punctuation.section.interpolation.end.python +# ^ meta.string.python string.quoted.single.python punctuation.definition.string.end.python + +f" { +#^^^^ meta.string.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.unclosed-string.python + }" +# ^^^ - meta.string +# ^ invalid.illegal.stray.python +# ^ punctuation.definition.string.begin.python +# ^ invalid.illegal.unclosed-string.python + +f' { +#^^^^ meta.string.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.unclosed-string.python + }' +# ^^^ - meta.string +# ^ invalid.illegal.stray.python +# ^ punctuation.definition.string.begin.python +# ^ invalid.illegal.unclosed-string.python + +f" { \ +#^^^^^^ meta.string.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.backslash-in-fstring.python +# ^ invalid.illegal.unclosed-string.python + }" +# ^^^ - meta.string +# ^ invalid.illegal.stray.python +# ^ punctuation.definition.string.begin.python +# ^ invalid.illegal.unclosed-string.python + +f' { \ +#^^^^ meta.string.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.backslash-in-fstring.python +# ^ invalid.illegal.unclosed-string.python + }' +# ^^^ - meta.string +# ^ invalid.illegal.stray.python +# ^ punctuation.definition.string.begin.python +# ^ invalid.illegal.unclosed-string.python + +f" { +:2d +# <- - meta.string - meta.interpolation +#^^ - meta.string - meta.interpolation + +f' { +:2d +# <- - meta.string - meta.interpolation +#^^ - meta.string - meta.interpolation + +f" { +!a:2d +# <- - meta.string - meta.interpolation +#^^^^ - meta.string - meta.interpolation + +f' { +!a:2d +# <- - meta.string - meta.interpolation +#^^^^ - meta.string - meta.interpolation + +f" {} {\} }" +#^^^^^^^^^^^ meta.string.python +# ^^ invalid.illegal.empty-expression.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.backslash-in-fstring.python +# ^ punctuation.section.interpolation.end.python +# ^ invalid.illegal.stray.python + +f' {} {\} }' +#^^^^^^^^^^^ meta.string.python +# ^^ invalid.illegal.empty-expression.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.backslash-in-fstring.python +# ^ punctuation.section.interpolation.end.python +# ^ invalid.illegal.stray.python + +f"{foo:"} # nested quotes terminate formatspec +#^^^^^^^ meta.string.python +# ^ invalid.illegal.stray.python - meta.string +f'{foo:'} # nested quotes terminate formatspec +#^^^^^^^ meta.string.python +# ^ invalid.illegal.stray.python - meta.string + +f"{"foo":"} # nested quotes supported by python 3.12+ +#^^^^^^^^^ meta.string.python +# ^ invalid.illegal.stray.python - meta.string +f'{'foo':'} # nested quotes supported by python 3.12+ +#^^^^^^^^^ meta.string.python +# ^ invalid.illegal.stray.python - meta.string + + +############################### +# f-block-strings +############################### + +f"""""" +# <- storage.type.string.python +#^^^^^^ meta.string.python string.quoted.double.block.python +#^^^ punctuation.definition.string.begin.python +# ^^^ punctuation.definition.string.end.python + +f'''''' +# <- storage.type.string.python +#^^^^^^ meta.string.python string.quoted.single.block.python +#^^^ punctuation.definition.string.begin.python +# ^^^ punctuation.definition.string.end.python + +f""" +# <- storage.type.string.python +#^^^^ meta.string.python string.quoted.double.block.python +#^^^ punctuation.definition.string.begin.python +# ^ - punctuation - invalid +""" +# <- meta.string.python string.quoted.double.block.python punctuation.definition.string.end.python +#^^ meta.string.python string.quoted.double.block.python punctuation.definition.string.end.python +# ^ - meta.string - string - punctuation + +f''' +# <- storage.type.string.python +#^^^^ meta.string.python string.quoted.single.block.python +#^^^ punctuation.definition.string.begin.python +# ^ - punctuation - invalid +''' +# <- meta.string.python string.quoted.single.block.python punctuation.definition.string.end.python +#^^ meta.string.python string.quoted.single.block.python punctuation.definition.string.end.python +# ^ - meta.string - string - punctuation + +f""" {} {\} } +#^^^^^^^^^^^^^ meta.string.python +# ^^ invalid.illegal.empty-expression.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.backslash-in-fstring.python +# ^ punctuation.section.interpolation.end.python +# ^ invalid.illegal.stray.python +""" + +f''' {} {\} } +#^^^^^^^^^^^^^ meta.string.python +# ^^ invalid.illegal.empty-expression.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.backslash-in-fstring.python +# ^ punctuation.section.interpolation.end.python +# ^ invalid.illegal.stray.python +''' + +fr""" +# <- storage.type.string.python +#^ storage.type.string.python +# ^^^^ meta.string.python string.quoted.double.block.python +# ^^^ punctuation.definition.string.begin.python +# ^ - punctuation - invalid +""" +# <- meta.string.python string.quoted.double.block.python punctuation.definition.string.end.python +#^^ meta.string.python string.quoted.double.block.python punctuation.definition.string.end.python +# ^ - meta.string - string - punctuation + +fr''' +# <- storage.type.string.python +#^ storage.type.string.python +# ^^^^ meta.string.python string.quoted.single.block.python +# ^^^ punctuation.definition.string.begin.python +# ^ - punctuation - invalid +''' +# <- meta.string.python string.quoted.single.block.python punctuation.definition.string.end.python +#^^ meta.string.python string.quoted.single.block.python punctuation.definition.string.end.python +# ^ - meta.string - string - punctuation + +fr""" {} {\} } +# <- storage.type.string.python +#^ storage.type.string.python +# ^^^^^^^^^^^^^ meta.string.python +# ^^ invalid.illegal.empty-expression.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.backslash-in-fstring.python +# ^ punctuation.section.interpolation.end.python +# ^ invalid.illegal.stray.python +""" + +fr''' {} {\} } +# <- storage.type.string.python +#^ storage.type.string.python +# ^^^^^^^^^^^^^ meta.string.python +# ^^ invalid.illegal.empty-expression.python +# ^ punctuation.section.interpolation.begin.python +# ^ invalid.illegal.backslash-in-fstring.python +# ^ punctuation.section.interpolation.end.python +# ^ invalid.illegal.stray.python +''' + +f"""{ +# ^^ meta.string.python meta.interpolation.python - invalid +# ^ source.python.embedded + foo +# ^^^ meta.string.python meta.interpolation.python source.python.embedded meta.path.python meta.generic-name.python + !a:2d +# ^^^^^^ meta.string.python meta.interpolation.python - source.python.embedded +# ^^ storage.modifier.conversion.python +# ^ punctuation.separator.format-spec.python +# ^^^ meta.format-spec.python constant.other.format-spec.python +}""" +# <- meta.string.python meta.interpolation.python punctuation.section.interpolation.end.python +#^^^ meta.string.python string.quoted.double.block.python punctuation.definition.string.end.python + +f'''{ +# ^^ meta.string.python meta.interpolation.python - invalid +# ^ source.python.embedded + foo +# ^^^ meta.string.python meta.interpolation.python source.python.embedded meta.path.python meta.generic-name.python + !a:2d +# ^^^^^^ meta.string.python meta.interpolation.python - source.python.embedded +# ^^ storage.modifier.conversion.python +# ^ punctuation.separator.format-spec.python +# ^^^ meta.format-spec.python constant.other.format-spec.python +}''' +# <- meta.string.python meta.interpolation.python punctuation.section.interpolation.end.python +#^^^ meta.string.python string.quoted.single.block.python punctuation.definition.string.end.python