mirror of
https://github.com/PyCQA/flake8.git
synced 2026-04-15 16:49:52 +00:00
add support for t-strings
This commit is contained in:
parent
6b6f3d5fef
commit
019424b80d
4 changed files with 57 additions and 3 deletions
|
|
@ -9,3 +9,10 @@ if sys.version_info >= (3, 12): # pragma: >=3.12 cover
|
||||||
FSTRING_END = tokenize.FSTRING_END
|
FSTRING_END = tokenize.FSTRING_END
|
||||||
else: # pragma: <3.12 cover
|
else: # pragma: <3.12 cover
|
||||||
FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1
|
FSTRING_START = FSTRING_MIDDLE = FSTRING_END = -1
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 14): # pragma: >=3.14 cover
|
||||||
|
TSTRING_START = tokenize.TSTRING_START
|
||||||
|
TSTRING_MIDDLE = tokenize.TSTRING_MIDDLE
|
||||||
|
TSTRING_END = tokenize.TSTRING_END
|
||||||
|
else: # pragma: <3.14 cover
|
||||||
|
TSTRING_START = TSTRING_MIDDLE = TSTRING_END = -1
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ from flake8 import exceptions
|
||||||
from flake8 import processor
|
from flake8 import processor
|
||||||
from flake8 import utils
|
from flake8 import utils
|
||||||
from flake8._compat import FSTRING_START
|
from flake8._compat import FSTRING_START
|
||||||
|
from flake8._compat import TSTRING_START
|
||||||
from flake8.discover_files import expand_paths
|
from flake8.discover_files import expand_paths
|
||||||
from flake8.options.parse_args import parse_args
|
from flake8.options.parse_args import parse_args
|
||||||
from flake8.plugins.finder import Checkers
|
from flake8.plugins.finder import Checkers
|
||||||
|
|
@ -554,6 +555,8 @@ class FileChecker:
|
||||||
assert self.processor is not None
|
assert self.processor is not None
|
||||||
if token.type == FSTRING_START: # pragma: >=3.12 cover
|
if token.type == FSTRING_START: # pragma: >=3.12 cover
|
||||||
self.processor.fstring_start(token.start[0])
|
self.processor.fstring_start(token.start[0])
|
||||||
|
elif token.type == TSTRING_START: # pragma: >=3.14 cover
|
||||||
|
self.processor.tstring_start(token.start[0])
|
||||||
# a newline token ends a single physical line.
|
# a newline token ends a single physical line.
|
||||||
elif processor.is_eol_token(token):
|
elif processor.is_eol_token(token):
|
||||||
# if the file does not end with a newline, the NEWLINE
|
# if the file does not end with a newline, the NEWLINE
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ from flake8 import defaults
|
||||||
from flake8 import utils
|
from flake8 import utils
|
||||||
from flake8._compat import FSTRING_END
|
from flake8._compat import FSTRING_END
|
||||||
from flake8._compat import FSTRING_MIDDLE
|
from flake8._compat import FSTRING_MIDDLE
|
||||||
|
from flake8._compat import TSTRING_END
|
||||||
|
from flake8._compat import TSTRING_MIDDLE
|
||||||
from flake8.plugins.finder import LoadedPlugin
|
from flake8.plugins.finder import LoadedPlugin
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
@ -113,7 +115,7 @@ class FileProcessor:
|
||||||
self.verbose = options.verbose
|
self.verbose = options.verbose
|
||||||
#: Statistics dictionary
|
#: Statistics dictionary
|
||||||
self.statistics = {"logical lines": 0}
|
self.statistics = {"logical lines": 0}
|
||||||
self._fstring_start = -1
|
self._fstring_start = self._tstring_start = -1
|
||||||
|
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
def file_tokens(self) -> list[tokenize.TokenInfo]:
|
def file_tokens(self) -> list[tokenize.TokenInfo]:
|
||||||
|
|
@ -125,10 +127,16 @@ class FileProcessor:
|
||||||
"""Signal the beginning of an fstring."""
|
"""Signal the beginning of an fstring."""
|
||||||
self._fstring_start = lineno
|
self._fstring_start = lineno
|
||||||
|
|
||||||
|
def tstring_start(self, lineno: int) -> None: # pragma: >=3.14 cover
|
||||||
|
"""Signal the beginning of an tstring."""
|
||||||
|
self._tstring_start = lineno
|
||||||
|
|
||||||
def multiline_string(self, token: tokenize.TokenInfo) -> Generator[str]:
|
def multiline_string(self, token: tokenize.TokenInfo) -> Generator[str]:
|
||||||
"""Iterate through the lines of a multiline string."""
|
"""Iterate through the lines of a multiline string."""
|
||||||
if token.type == FSTRING_END: # pragma: >=3.12 cover
|
if token.type == FSTRING_END: # pragma: >=3.12 cover
|
||||||
start = self._fstring_start
|
start = self._fstring_start
|
||||||
|
elif token.type == TSTRING_END: # pragma: >=3.14 cover
|
||||||
|
start = self._tstring_start
|
||||||
else:
|
else:
|
||||||
start = token.start[0]
|
start = token.start[0]
|
||||||
|
|
||||||
|
|
@ -198,7 +206,10 @@ class FileProcessor:
|
||||||
continue
|
continue
|
||||||
if token_type == tokenize.STRING:
|
if token_type == tokenize.STRING:
|
||||||
text = mutate_string(text)
|
text = mutate_string(text)
|
||||||
elif token_type == FSTRING_MIDDLE: # pragma: >=3.12 cover
|
elif token_type in {
|
||||||
|
FSTRING_MIDDLE,
|
||||||
|
TSTRING_MIDDLE,
|
||||||
|
}: # pragma: >=3.12 cover # noqa: E501
|
||||||
# A curly brace in an FSTRING_MIDDLE token must be an escaped
|
# A curly brace in an FSTRING_MIDDLE token must be an escaped
|
||||||
# curly brace. Both 'text' and 'end' will account for the
|
# curly brace. Both 'text' and 'end' will account for the
|
||||||
# escaped version of the token (i.e. a single brace) rather
|
# escaped version of the token (i.e. a single brace) rather
|
||||||
|
|
@ -382,7 +393,7 @@ def is_eol_token(token: tokenize.TokenInfo) -> bool:
|
||||||
|
|
||||||
def is_multiline_string(token: tokenize.TokenInfo) -> bool:
|
def is_multiline_string(token: tokenize.TokenInfo) -> bool:
|
||||||
"""Check if this is a multiline string."""
|
"""Check if this is a multiline string."""
|
||||||
return token.type == FSTRING_END or (
|
return token.type in {FSTRING_END, TSTRING_END} or (
|
||||||
token.type == tokenize.STRING and "\n" in token.string
|
token.type == tokenize.STRING and "\n" in token.string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -296,3 +296,36 @@ t.py:1:1: T001 "f'xxxxxxxxxxxxxxxxxxxxxxxx'"
|
||||||
"""
|
"""
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert out == expected
|
assert out == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.xfail(sys.version_info < (3, 14), reason="3.14+")
|
||||||
|
def test_tstring_logical_line(tmpdir, capsys): # pragma: >=3.14 cover
|
||||||
|
cfg_s = f"""\
|
||||||
|
[flake8]
|
||||||
|
extend-ignore = F
|
||||||
|
[flake8:local-plugins]
|
||||||
|
extension =
|
||||||
|
T = {yields_logical_line.__module__}:{yields_logical_line.__name__}
|
||||||
|
"""
|
||||||
|
|
||||||
|
cfg = tmpdir.join("tox.ini")
|
||||||
|
cfg.write(cfg_s)
|
||||||
|
|
||||||
|
src = """\
|
||||||
|
t'''
|
||||||
|
hello {world}
|
||||||
|
'''
|
||||||
|
t'{{"{hello}": "{world}"}}'
|
||||||
|
"""
|
||||||
|
t_py = tmpdir.join("t.py")
|
||||||
|
t_py.write_binary(src.encode())
|
||||||
|
|
||||||
|
with tmpdir.as_cwd():
|
||||||
|
assert main(("t.py", "--config", str(cfg))) == 1
|
||||||
|
|
||||||
|
expected = """\
|
||||||
|
t.py:1:1: T001 "t'''xxxxxxx{world}x'''"
|
||||||
|
t.py:4:1: T001 "t'xxx{hello}xxxx{world}xxx'"
|
||||||
|
"""
|
||||||
|
out, err = capsys.readouterr()
|
||||||
|
assert out == expected
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue