Compare commits

..

No commits in common. "main" and "v6.0.0" have entirely different histories.
main ... v6.0.0

9 changed files with 39 additions and 37 deletions

View file

@ -10,10 +10,10 @@ jobs:
main-windows: main-windows:
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
with: with:
env: '["py310"]' env: '["py39"]'
os: windows-latest os: windows-latest
main-linux: main-linux:
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1 uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
with: with:
env: '["py310", "py311", "py312", "py313"]' env: '["py39", "py310", "py311", "py312"]'
os: ubuntu-latest os: ubuntu-latest

View file

@ -10,23 +10,23 @@ repos:
- id: name-tests-test - id: name-tests-test
- id: requirements-txt-fixer - id: requirements-txt-fixer
- repo: https://github.com/asottile/setup-cfg-fmt - repo: https://github.com/asottile/setup-cfg-fmt
rev: v3.2.0 rev: v2.8.0
hooks: hooks:
- id: setup-cfg-fmt - id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder-python-imports - repo: https://github.com/asottile/reorder-python-imports
rev: v3.16.0 rev: v3.15.0
hooks: hooks:
- id: reorder-python-imports - id: reorder-python-imports
args: [--py310-plus, --add-import, 'from __future__ import annotations'] args: [--py39-plus, --add-import, 'from __future__ import annotations']
- repo: https://github.com/asottile/add-trailing-comma - repo: https://github.com/asottile/add-trailing-comma
rev: v4.0.0 rev: v3.2.0
hooks: hooks:
- id: add-trailing-comma - id: add-trailing-comma
- repo: https://github.com/asottile/pyupgrade - repo: https://github.com/asottile/pyupgrade
rev: v3.21.2 rev: v3.20.0
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py310-plus] args: [--py39-plus]
- repo: https://github.com/hhatto/autopep8 - repo: https://github.com/hhatto/autopep8
rev: v2.3.2 rev: v2.3.2
hooks: hooks:
@ -36,6 +36,6 @@ repos:
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.19.1 rev: v1.17.1
hooks: hooks:
- id: mypy - id: mypy

View file

@ -29,7 +29,7 @@
entry: check-case-conflict entry: check-case-conflict
language: python language: python
- id: check-docstring-first - id: check-docstring-first
name: check docstring is first (deprecated) name: check docstring is first
description: checks a common error of defining a docstring after code. description: checks a common error of defining a docstring after code.
entry: check-docstring-first entry: check-docstring-first
language: python language: python

View file

@ -1,4 +1,4 @@
6.0.0 - 2025-08-09 6.0.0 - 2024-08-09
================== ==================
## Fixes ## Fixes

View file

@ -45,6 +45,9 @@ Require literal syntax when initializing empty or zero Python builtin types.
#### `check-case-conflict` #### `check-case-conflict`
Check for files with names that would conflict on a case-insensitive filesystem like MacOS HFS+ or Windows FAT. Check for files with names that would conflict on a case-insensitive filesystem like MacOS HFS+ or Windows FAT.
#### `check-docstring-first`
Checks for a common error of placing code before the docstring.
#### `check-executables-have-shebangs` #### `check-executables-have-shebangs`
Checks that non-binary executables have a proper shebang. Checks that non-binary executables have a proper shebang.
@ -204,8 +207,6 @@ Trims trailing whitespace.
- `check-byte-order-marker`: instead use fix-byte-order-marker - `check-byte-order-marker`: instead use fix-byte-order-marker
- `fix-encoding-pragma`: instead use [`pyupgrade`](https://github.com/asottile/pyupgrade) - `fix-encoding-pragma`: instead use [`pyupgrade`](https://github.com/asottile/pyupgrade)
- `check-docstring-first`: fundamentally flawed, deprecated without replacement.
### As a standalone package ### As a standalone package

View file

@ -26,37 +26,36 @@ class Call(NamedTuple):
class Visitor(ast.NodeVisitor): class Visitor(ast.NodeVisitor):
def __init__( def __init__(
self, self,
ignore: set[str], ignore: Sequence[str] | None = None,
allow_dict_kwargs: bool = True, allow_dict_kwargs: bool = True,
) -> None: ) -> None:
self.builtin_type_calls: list[Call] = [] self.builtin_type_calls: list[Call] = []
self.ignore = set(ignore) if ignore else set()
self.allow_dict_kwargs = allow_dict_kwargs self.allow_dict_kwargs = allow_dict_kwargs
self._disallowed = BUILTIN_TYPES.keys() - ignore
def _check_dict_call(self, node: ast.Call) -> bool: def _check_dict_call(self, node: ast.Call) -> bool:
return self.allow_dict_kwargs and bool(node.keywords) return self.allow_dict_kwargs and bool(node.keywords)
def visit_Call(self, node: ast.Call) -> None: def visit_Call(self, node: ast.Call) -> None:
if ( if not isinstance(node.func, ast.Name):
# Ignore functions that are object attributes (`foo.bar()`). # Ignore functions that are object attributes (`foo.bar()`).
# Assume that if the user calls `builtins.list()`, they know what # Assume that if the user calls `builtins.list()`, they know what
# they're doing. # they're doing.
isinstance(node.func, ast.Name) and return
node.func.id in self._disallowed and if node.func.id not in set(BUILTIN_TYPES).difference(self.ignore):
(node.func.id != 'dict' or not self._check_dict_call(node)) and return
not node.args if node.func.id == 'dict' and self._check_dict_call(node):
): return
self.builtin_type_calls.append( elif node.args:
Call(node.func.id, node.lineno, node.col_offset), return
) self.builtin_type_calls.append(
Call(node.func.id, node.lineno, node.col_offset),
self.generic_visit(node) )
def check_file( def check_file(
filename: str, filename: str,
*, ignore: Sequence[str] | None = None,
ignore: set[str],
allow_dict_kwargs: bool = True, allow_dict_kwargs: bool = True,
) -> list[Call]: ) -> list[Call]:
with open(filename, 'rb') as f: with open(filename, 'rb') as f:

View file

@ -12,10 +12,10 @@ conflicts and keep the file nicely ordered.
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from collections.abc import Callable
from collections.abc import Iterable from collections.abc import Iterable
from collections.abc import Sequence from collections.abc import Sequence
from typing import Any from typing import Any
from typing import Callable
from typing import IO from typing import IO
PASS = 0 PASS = 0

View file

@ -20,7 +20,7 @@ packages = find:
install_requires = install_requires =
ruamel.yaml>=0.15 ruamel.yaml>=0.15
tomli>=1.1.0;python_version<"3.11" tomli>=1.1.0;python_version<"3.11"
python_requires = >=3.10 python_requires = >=3.9
[options.packages.find] [options.packages.find]
exclude = exclude =

View file

@ -38,6 +38,11 @@ t1 = ()
''' '''
@pytest.fixture
def visitor():
return Visitor()
@pytest.mark.parametrize( @pytest.mark.parametrize(
('expression', 'calls'), ('expression', 'calls'),
[ [
@ -80,8 +85,7 @@ t1 = ()
('builtins.tuple()', []), ('builtins.tuple()', []),
], ],
) )
def test_non_dict_exprs(expression, calls): def test_non_dict_exprs(visitor, expression, calls):
visitor = Visitor(ignore=set())
visitor.visit(ast.parse(expression)) visitor.visit(ast.parse(expression))
assert visitor.builtin_type_calls == calls assert visitor.builtin_type_calls == calls
@ -98,8 +102,7 @@ def test_non_dict_exprs(expression, calls):
('builtins.dict()', []), ('builtins.dict()', []),
], ],
) )
def test_dict_allow_kwargs_exprs(expression, calls): def test_dict_allow_kwargs_exprs(visitor, expression, calls):
visitor = Visitor(ignore=set())
visitor.visit(ast.parse(expression)) visitor.visit(ast.parse(expression))
assert visitor.builtin_type_calls == calls assert visitor.builtin_type_calls == calls
@ -111,18 +114,17 @@ def test_dict_allow_kwargs_exprs(expression, calls):
('dict(a=1, b=2, c=3)', [Call('dict', 1, 0)]), ('dict(a=1, b=2, c=3)', [Call('dict', 1, 0)]),
("dict(**{'a': 1, 'b': 2, 'c': 3})", [Call('dict', 1, 0)]), ("dict(**{'a': 1, 'b': 2, 'c': 3})", [Call('dict', 1, 0)]),
('builtins.dict()', []), ('builtins.dict()', []),
pytest.param('f(dict())', [Call('dict', 1, 2)], id='nested'),
], ],
) )
def test_dict_no_allow_kwargs_exprs(expression, calls): def test_dict_no_allow_kwargs_exprs(expression, calls):
visitor = Visitor(ignore=set(), allow_dict_kwargs=False) visitor = Visitor(allow_dict_kwargs=False)
visitor.visit(ast.parse(expression)) visitor.visit(ast.parse(expression))
assert visitor.builtin_type_calls == calls assert visitor.builtin_type_calls == calls
def test_ignore_constructors(): def test_ignore_constructors():
visitor = Visitor( visitor = Visitor(
ignore={'complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'}, ignore=('complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'),
) )
visitor.visit(ast.parse(BUILTIN_CONSTRUCTORS)) visitor.visit(ast.parse(BUILTIN_CONSTRUCTORS))
assert visitor.builtin_type_calls == [] assert visitor.builtin_type_calls == []