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:
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
with:
env: '["py310"]'
env: '["py39"]'
os: windows-latest
main-linux:
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
with:
env: '["py310", "py311", "py312", "py313"]'
env: '["py39", "py310", "py311", "py312"]'
os: ubuntu-latest

View file

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

View file

@ -29,7 +29,7 @@
entry: check-case-conflict
language: python
- 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.
entry: check-docstring-first
language: python

View file

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

View file

@ -45,6 +45,9 @@ Require literal syntax when initializing empty or zero Python builtin types.
#### `check-case-conflict`
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`
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
- `fix-encoding-pragma`: instead use [`pyupgrade`](https://github.com/asottile/pyupgrade)
- `check-docstring-first`: fundamentally flawed, deprecated without replacement.
### As a standalone package

View file

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

View file

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

View file

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

View file

@ -38,6 +38,11 @@ t1 = ()
'''
@pytest.fixture
def visitor():
return Visitor()
@pytest.mark.parametrize(
('expression', 'calls'),
[
@ -80,8 +85,7 @@ t1 = ()
('builtins.tuple()', []),
],
)
def test_non_dict_exprs(expression, calls):
visitor = Visitor(ignore=set())
def test_non_dict_exprs(visitor, expression, calls):
visitor.visit(ast.parse(expression))
assert visitor.builtin_type_calls == calls
@ -98,8 +102,7 @@ def test_non_dict_exprs(expression, calls):
('builtins.dict()', []),
],
)
def test_dict_allow_kwargs_exprs(expression, calls):
visitor = Visitor(ignore=set())
def test_dict_allow_kwargs_exprs(visitor, expression, calls):
visitor.visit(ast.parse(expression))
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)]),
('builtins.dict()', []),
pytest.param('f(dict())', [Call('dict', 1, 2)], id='nested'),
],
)
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))
assert visitor.builtin_type_calls == calls
def test_ignore_constructors():
visitor = Visitor(
ignore={'complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'},
ignore=('complex', 'dict', 'float', 'int', 'list', 'str', 'tuple'),
)
visitor.visit(ast.parse(BUILTIN_CONSTRUCTORS))
assert visitor.builtin_type_calls == []