diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a781837..118cdc9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,12 +8,12 @@ on: jobs: main-windows: - uses: asottile/workflows/.github/workflows/tox.yml@v1.5.0 + uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 with: - env: '["py38"]' + env: '["py39"]' os: windows-latest main-linux: - uses: asottile/workflows/.github/workflows/tox.yml@v1.5.0 + uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 with: - env: '["py38", "py39", "py310", "py311", "py312"]' + env: '["py39", "py310", "py311", "py312"]' os: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c36193b..ad7a843 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer @@ -10,32 +10,32 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.4.0 + rev: v2.8.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports - rev: v3.10.0 + rev: v3.14.0 hooks: - id: reorder-python-imports - args: [--py38-plus, --add-import, 'from __future__ import annotations'] + args: [--py39-plus, --add-import, 'from __future__ import annotations'] - repo: https://github.com/asottile/add-trailing-comma - rev: v3.1.0 + rev: v3.2.0 hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.10.1 + rev: v3.19.1 hooks: - id: pyupgrade - args: [--py38-plus] -- repo: https://github.com/pre-commit/mirrors-autopep8 - rev: v2.0.4 + args: [--py39-plus] +- repo: https://github.com/hhatto/autopep8 + rev: v2.3.2 hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 + rev: 7.2.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.1 + rev: v1.15.0 hooks: - id: mypy diff --git a/README.md b/README.md index 965938c..888bb9a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Sample `.pre-commit-config.yaml`: ```yaml - repo: https://github.com/asottile/add-trailing-comma - rev: v3.1.0 + rev: v3.2.0 hooks: - id: add-trailing-comma ``` diff --git a/add_trailing_comma/_ast_helpers.py b/add_trailing_comma/_ast_helpers.py index 4fbcb27..942803e 100644 --- a/add_trailing_comma/_ast_helpers.py +++ b/add_trailing_comma/_ast_helpers.py @@ -2,6 +2,7 @@ import ast import warnings +from typing import Protocol from tokenize_rt import Offset @@ -12,14 +13,12 @@ def ast_parse(contents_text: str) -> ast.Module: return ast.parse(contents_text.encode()) -def ast_to_offset(node: ast.AST) -> Offset: - candidates = [node] - while candidates: - candidate = candidates.pop() - if hasattr(candidate, 'lineno'): - return Offset(candidate.lineno, candidate.col_offset) - elif hasattr(candidate, '_fields'): # pragma: <3.9 cover - for field in reversed(candidate._fields): - candidates.append(getattr(candidate, field)) - else: - raise AssertionError(node) +class _HasOffsetInfo(Protocol): + @property + def lineno(self) -> int: ... + @property + def col_offset(self) -> int: ... + + +def ast_to_offset(node: _HasOffsetInfo) -> Offset: + return Offset(node.lineno, node.col_offset) diff --git a/add_trailing_comma/_data.py b/add_trailing_comma/_data.py index ea70efe..740fbd3 100644 --- a/add_trailing_comma/_data.py +++ b/add_trailing_comma/_data.py @@ -3,12 +3,10 @@ import ast import collections import pkgutil +from collections.abc import Iterable from typing import Callable -from typing import Iterable -from typing import List from typing import NamedTuple from typing import Protocol -from typing import Tuple from typing import TypeVar from tokenize_rt import Offset @@ -22,10 +20,11 @@ class State(NamedTuple): AST_T = TypeVar('AST_T', bound=ast.AST) -TokenFunc = Callable[[int, List[Token]], None] -ASTFunc = Callable[[State, AST_T], Iterable[Tuple[Offset, TokenFunc]]] +TokenFunc = Callable[[int, list[Token]], None] +ASTFunc = Callable[[State, AST_T], Iterable[tuple[Offset, TokenFunc]]] -FUNCS = collections.defaultdict(list) +FUNCS: ASTCallbackMapping # python/mypy#17566 +FUNCS = collections.defaultdict(list) # type: ignore[assignment] def register(tp: type[AST_T]) -> Callable[[ASTFunc[AST_T]], ASTFunc[AST_T]]: diff --git a/add_trailing_comma/_main.py b/add_trailing_comma/_main.py index b09cd1b..f50ed8f 100644 --- a/add_trailing_comma/_main.py +++ b/add_trailing_comma/_main.py @@ -2,8 +2,8 @@ import argparse import sys -from typing import Iterable -from typing import Sequence +from collections.abc import Iterable +from collections.abc import Sequence from tokenize_rt import src_to_tokens from tokenize_rt import Token diff --git a/add_trailing_comma/_plugins/_with.py b/add_trailing_comma/_plugins/_with.py index ffbaf81..f673d73 100644 --- a/add_trailing_comma/_plugins/_with.py +++ b/add_trailing_comma/_plugins/_with.py @@ -1,8 +1,7 @@ from __future__ import annotations import ast -import sys -from typing import Iterable +from collections.abc import Iterable from tokenize_rt import Offset from tokenize_rt import Token @@ -15,20 +14,20 @@ from add_trailing_comma._token_helpers import fix_brace -if sys.version_info >= (3, 9): # pragma: >=3.9 cover - def _fix_with(i: int, tokens: list[Token]) -> None: +def _fix_with(i: int, tokens: list[Token]) -> None: + i += 1 + if tokens[i].name == 'UNIMPORTANT_WS': i += 1 - if tokens[i].name == 'UNIMPORTANT_WS': - i += 1 - if tokens[i].src == '(': - fix = find_simple(i, tokens) - # only fix if outer parens are for the with items (next is ':') - if fix is not None and tokens[fix.braces[-1] + 1].src == ':': - fix_brace(tokens, fix, add_comma=True, remove_comma=True) + if tokens[i].src == '(': + fix = find_simple(i, tokens) + # only fix if outer parens are for the with items (next is ':') + if fix is not None and tokens[fix.braces[-1] + 1].src == ':': + fix_brace(tokens, fix, add_comma=True, remove_comma=True) - @register(ast.With) - def visit_With( - state: State, - node: ast.With, - ) -> Iterable[tuple[Offset, TokenFunc]]: - yield ast_to_offset(node), _fix_with + +@register(ast.With) +def visit_With( + state: State, + node: ast.With, +) -> Iterable[tuple[Offset, TokenFunc]]: + yield ast_to_offset(node), _fix_with diff --git a/add_trailing_comma/_plugins/calls.py b/add_trailing_comma/_plugins/calls.py index 933daf9..db6380c 100644 --- a/add_trailing_comma/_plugins/calls.py +++ b/add_trailing_comma/_plugins/calls.py @@ -2,7 +2,7 @@ import ast import functools -from typing import Iterable +from collections.abc import Iterable from tokenize_rt import Offset from tokenize_rt import Token @@ -34,7 +34,7 @@ def visit_Call( state: State, node: ast.Call, ) -> Iterable[tuple[Offset, TokenFunc]]: - argnodes = [*node.args, *node.keywords] + argnodes: list[ast.expr | ast.keyword] = [*node.args, *node.keywords] arg_offsets = set() for argnode in argnodes: offset = ast_to_offset(argnode) diff --git a/add_trailing_comma/_plugins/classes.py b/add_trailing_comma/_plugins/classes.py index ac54804..2e4af74 100644 --- a/add_trailing_comma/_plugins/classes.py +++ b/add_trailing_comma/_plugins/classes.py @@ -2,7 +2,7 @@ import ast import functools -from typing import Iterable +from collections.abc import Iterable from tokenize_rt import Offset from tokenize_rt import Token @@ -37,7 +37,7 @@ def visit_ClassDef( # starargs are allowed in py3 class definitions, py35+ allows trailing # commas. py34 does not, but adding an option for this very obscure # case seems not worth it. - args = [*node.bases, *node.keywords] + args: list[ast.expr | ast.keyword] = [*node.bases, *node.keywords] arg_offsets = {ast_to_offset(arg) for arg in args} if arg_offsets: diff --git a/add_trailing_comma/_plugins/functions.py b/add_trailing_comma/_plugins/functions.py index 827a2ce..b6be398 100644 --- a/add_trailing_comma/_plugins/functions.py +++ b/add_trailing_comma/_plugins/functions.py @@ -2,7 +2,7 @@ import ast import functools -from typing import Iterable +from collections.abc import Iterable from tokenize_rt import Offset from tokenize_rt import Token diff --git a/add_trailing_comma/_plugins/imports.py b/add_trailing_comma/_plugins/imports.py index f7ff9a9..ecfa69a 100644 --- a/add_trailing_comma/_plugins/imports.py +++ b/add_trailing_comma/_plugins/imports.py @@ -1,7 +1,7 @@ from __future__ import annotations import ast -from typing import Iterable +from collections.abc import Iterable from tokenize_rt import Offset from tokenize_rt import Token diff --git a/add_trailing_comma/_plugins/literals.py b/add_trailing_comma/_plugins/literals.py index 1106461..5f07885 100644 --- a/add_trailing_comma/_plugins/literals.py +++ b/add_trailing_comma/_plugins/literals.py @@ -2,7 +2,7 @@ import ast import functools -from typing import Iterable +from collections.abc import Iterable from tokenize_rt import NON_CODING_TOKENS from tokenize_rt import Offset @@ -91,7 +91,7 @@ def _fix_tuple_py38( tokens: list[Token], *, one_el_tuple: bool, -) -> None: # pragma: >=3.8 cover +) -> None: fix = find_simple(i, tokens) # for tuples we *must* find a comma, otherwise it is not a tuple diff --git a/add_trailing_comma/_plugins/match.py b/add_trailing_comma/_plugins/match.py index e35ed95..a439016 100644 --- a/add_trailing_comma/_plugins/match.py +++ b/add_trailing_comma/_plugins/match.py @@ -3,7 +3,7 @@ import ast import functools import sys -from typing import Iterable +from collections.abc import Iterable from tokenize_rt import Offset from tokenize_rt import Token diff --git a/add_trailing_comma/_plugins/pep695.py b/add_trailing_comma/_plugins/pep695.py index 46fc639..0f4f170 100644 --- a/add_trailing_comma/_plugins/pep695.py +++ b/add_trailing_comma/_plugins/pep695.py @@ -2,7 +2,7 @@ import ast import sys -from typing import Iterable +from collections.abc import Iterable from tokenize_rt import Offset from tokenize_rt import Token diff --git a/add_trailing_comma/_token_helpers.py b/add_trailing_comma/_token_helpers.py index f07b4c8..b402fea 100644 --- a/add_trailing_comma/_token_helpers.py +++ b/add_trailing_comma/_token_helpers.py @@ -136,6 +136,10 @@ def fix_brace( tokens[first_brace + 1].name == 'FSTRING_START' and tokens[last_brace - 1].name == 'FSTRING_END' ) or + ( + tokens[first_brace + 1].name == 'TSTRING_START' and + tokens[last_brace - 1].name == 'TSTRING_END' + ) or # don't unhug if it is a single line fix_data.remove_comma ): diff --git a/setup.cfg b/setup.cfg index ecad5b0..786bbad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = add_trailing_comma -version = 3.1.0 +version = 3.2.0 description = Automatically add trailing commas to calls and literals long_description = file: README.md long_description_content_type = text/markdown @@ -10,7 +10,6 @@ author_email = asottile@umich.edu license = MIT license_files = LICENSE classifiers = - License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython @@ -20,7 +19,7 @@ classifiers = packages = find: install_requires = tokenize-rt>=3.0.1 -python_requires = >=3.8 +python_requires = >=3.9 [options.packages.find] exclude = diff --git a/tests/features/unhug_test.py b/tests/features/unhug_test.py index 7c8e09d..71b490f 100644 --- a/tests/features/unhug_test.py +++ b/tests/features/unhug_test.py @@ -27,6 +27,10 @@ 'textwrap.dedent(f"""\n' ' hi\n' '""")', + # single triple-quoted tstring argument, don't unhug + 'textwrap.dedent(t"""\n' + ' hi\n' + '""")', ), ) def test_noop_unhugs(src):