mirror of
https://github.com/pre-commit/pre-commit-hooks.git
synced 2026-03-29 10:16:52 +00:00
Merge remote-tracking branch 'origin/uk/no_ticket/forbid_artcles' into uk/no_ticket/forbid_artcles
This commit is contained in:
commit
2dff30a2db
21 changed files with 142 additions and 472 deletions
8
.github/workflows/main.yml
vendored
8
.github/workflows/main.yml
vendored
|
|
@ -8,12 +8,12 @@ on:
|
|||
|
||||
jobs:
|
||||
main-windows:
|
||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.7.0
|
||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
|
||||
with:
|
||||
env: '["py39"]'
|
||||
env: '["py310"]'
|
||||
os: windows-latest
|
||||
main-linux:
|
||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.7.0
|
||||
uses: asottile/workflows/.github/workflows/tox.yml@v1.8.1
|
||||
with:
|
||||
env: '["py39", "py310", "py311", "py312"]'
|
||||
env: '["py310", "py311", "py312", "py313"]'
|
||||
os: ubuntu-latest
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
rev: v6.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.5.0
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.13.0
|
||||
rev: v3.16.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: [--py39-plus, --add-import, 'from __future__ import annotations']
|
||||
args: [--py310-plus, --add-import, 'from __future__ import annotations']
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v3.1.0
|
||||
rev: v4.0.0
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.17.0
|
||||
rev: v3.21.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
args: [--py310-plus]
|
||||
- repo: https://github.com/hhatto/autopep8
|
||||
rev: v2.3.1
|
||||
rev: v2.3.2
|
||||
hooks:
|
||||
- id: autopep8
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 7.1.1
|
||||
rev: 7.3.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.11.2
|
||||
rev: v1.19.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@
|
|||
language: python
|
||||
types: [python]
|
||||
- id: check-byte-order-marker
|
||||
name: 'check BOM - deprecated: use fix-byte-order-marker'
|
||||
description: forbids files which have a utf-8 byte-order marker.
|
||||
entry: check-byte-order-marker
|
||||
name: check-byte-order-marker (removed)
|
||||
description: (removed) use fix-byte-order-marker instead.
|
||||
entry: pre-commit-hooks-removed check-byte-order-marker fix-byte-order-marker https://github.com/pre-commit/pre-commit-hooks
|
||||
language: python
|
||||
types: [text]
|
||||
- id: check-builtin-literals
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
entry: check-case-conflict
|
||||
language: python
|
||||
- id: check-docstring-first
|
||||
name: check docstring is first
|
||||
name: check docstring is first (deprecated)
|
||||
description: checks a common error of defining a docstring after code.
|
||||
entry: check-docstring-first
|
||||
language: python
|
||||
|
|
@ -155,10 +155,10 @@
|
|||
language: python
|
||||
types: [text]
|
||||
- id: fix-encoding-pragma
|
||||
name: fix python encoding pragma (deprecated)
|
||||
description: 'adds # -*- coding: utf-8 -*- to the top of python files.'
|
||||
name: fix python encoding pragma (removed)
|
||||
description: (removed) use pyupgrade instead.
|
||||
entry: pre-commit-hooks-removed fix-encoding-pragma pyupgrade https://github.com/asottile/pyupgrade
|
||||
language: python
|
||||
entry: fix-encoding-pragma
|
||||
types: [python]
|
||||
- id: forbid-new-submodules
|
||||
name: forbid new submodules
|
||||
|
|
|
|||
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -1,3 +1,24 @@
|
|||
6.0.0 - 2025-08-09
|
||||
==================
|
||||
|
||||
## Fixes
|
||||
- `check-shebang-scripts-are-executable`: improve error message.
|
||||
- #1115 PR by @homebysix.
|
||||
|
||||
## Migrating
|
||||
- now requires python >= 3.9.
|
||||
- #1098 PR by @asottile.
|
||||
- `file-contents-sorter`: disallow `--unique` and `--ignore-case` at the same
|
||||
time.
|
||||
- #1095 PR by @nemacysts.
|
||||
- #794 issue by @teksturi.
|
||||
- Removed `check-byte-order-marker` and `fix-encoding-pragma`.
|
||||
- `check-byte-order-marker`: migrate to `fix-byte-order-marker`.
|
||||
- `fix-encoding-pragma`: migrate to `pyupgrade`.
|
||||
- #1034 PR by @mxr.
|
||||
- #1032 issue by @mxr.
|
||||
- #522 PR by @jgowdy.
|
||||
|
||||
5.0.0 - 2024-10-05
|
||||
==================
|
||||
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -15,7 +15,7 @@ Add this to your `.pre-commit-config.yaml`
|
|||
|
||||
```yaml
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0 # Use the ref you want to point at
|
||||
rev: v6.0.0 # Use the ref you want to point at
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
# - id: ...
|
||||
|
|
@ -45,9 +45,6 @@ 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.
|
||||
|
||||
|
|
@ -129,13 +126,6 @@ The following arguments are available:
|
|||
#### `fix-byte-order-marker`
|
||||
removes UTF-8 byte order marker
|
||||
|
||||
#### `fix-encoding-pragma`
|
||||
|
||||
_Deprecated since py2 is EOL - use [pyupgrade](https://github.com/asottile/pyupgrade) instead._
|
||||
|
||||
Add `# -*- coding: utf-8 -*-` to the top of python files.
|
||||
- To remove the coding pragma pass `--remove` (useful in a python3-only codebase)
|
||||
|
||||
#### `forbid-new-submodules`
|
||||
Prevent addition of new git submodules.
|
||||
|
||||
|
|
@ -213,6 +203,9 @@ Trims trailing whitespace.
|
|||
### Deprecated / replaced hooks
|
||||
|
||||
- `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
|
||||
|
||||
|
|
|
|||
|
|
@ -26,36 +26,37 @@ class Call(NamedTuple):
|
|||
class Visitor(ast.NodeVisitor):
|
||||
def __init__(
|
||||
self,
|
||||
ignore: Sequence[str] | None = None,
|
||||
ignore: set[str],
|
||||
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 not isinstance(node.func, ast.Name):
|
||||
if (
|
||||
# Ignore functions that are object attributes (`foo.bar()`).
|
||||
# Assume that if the user calls `builtins.list()`, they know what
|
||||
# they're doing.
|
||||
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),
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
def check_file(
|
||||
filename: str,
|
||||
ignore: Sequence[str] | None = None,
|
||||
*,
|
||||
ignore: set[str],
|
||||
allow_dict_kwargs: bool = True,
|
||||
) -> list[Call]:
|
||||
with open(filename, 'rb') as f:
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to check')
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
retv = 0
|
||||
|
||||
for filename in args.filenames:
|
||||
with open(filename, 'rb') as f:
|
||||
if f.read(3) == b'\xef\xbb\xbf':
|
||||
retv = 1
|
||||
print(f'{filename}: Has a byte-order marker')
|
||||
|
||||
return retv
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -36,7 +36,7 @@ def _message(path: str) -> None:
|
|||
f'`chmod +x {shlex.quote(path)}`\n'
|
||||
f' If on Windows, you may also need to: '
|
||||
f'`git add --chmod=+x {shlex.quote(path)}`\n'
|
||||
f' If it not supposed to be executable, double-check its shebang '
|
||||
f' If it is not supposed to be executable, double-check its shebang '
|
||||
f'is wanted.\n',
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -54,18 +54,21 @@ def sort_file_contents(
|
|||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='+', help='Files to sort')
|
||||
parser.add_argument(
|
||||
|
||||
mutex = parser.add_mutually_exclusive_group(required=False)
|
||||
mutex.add_argument(
|
||||
'--ignore-case',
|
||||
action='store_const',
|
||||
const=bytes.lower,
|
||||
default=None,
|
||||
help='fold lower case to upper case characters',
|
||||
)
|
||||
parser.add_argument(
|
||||
mutex.add_argument(
|
||||
'--unique',
|
||||
action='store_true',
|
||||
help='ensure each line is unique',
|
||||
)
|
||||
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
retv = PASS
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from collections.abc import Sequence
|
||||
from typing import IO
|
||||
from typing import NamedTuple
|
||||
|
||||
DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-'
|
||||
|
||||
|
||||
def has_coding(line: bytes) -> bool:
|
||||
if not line.strip():
|
||||
return False
|
||||
return (
|
||||
line.lstrip()[:1] == b'#' and (
|
||||
b'unicode' in line or
|
||||
b'encoding' in line or
|
||||
b'coding:' in line or
|
||||
b'coding=' in line
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class ExpectedContents(NamedTuple):
|
||||
shebang: bytes
|
||||
rest: bytes
|
||||
# True: has exactly the coding pragma expected
|
||||
# False: missing coding pragma entirely
|
||||
# None: has a coding pragma, but it does not match
|
||||
pragma_status: bool | None
|
||||
ending: bytes
|
||||
|
||||
@property
|
||||
def has_any_pragma(self) -> bool:
|
||||
return self.pragma_status is not False
|
||||
|
||||
def is_expected_pragma(self, remove: bool) -> bool:
|
||||
expected_pragma_status = not remove
|
||||
return self.pragma_status is expected_pragma_status
|
||||
|
||||
|
||||
def _get_expected_contents(
|
||||
first_line: bytes,
|
||||
second_line: bytes,
|
||||
rest: bytes,
|
||||
expected_pragma: bytes,
|
||||
) -> ExpectedContents:
|
||||
ending = b'\r\n' if first_line.endswith(b'\r\n') else b'\n'
|
||||
|
||||
if first_line.startswith(b'#!'):
|
||||
shebang = first_line
|
||||
potential_coding = second_line
|
||||
else:
|
||||
shebang = b''
|
||||
potential_coding = first_line
|
||||
rest = second_line + rest
|
||||
|
||||
if potential_coding.rstrip(b'\r\n') == expected_pragma:
|
||||
pragma_status: bool | None = True
|
||||
elif has_coding(potential_coding):
|
||||
pragma_status = None
|
||||
else:
|
||||
pragma_status = False
|
||||
rest = potential_coding + rest
|
||||
|
||||
return ExpectedContents(
|
||||
shebang=shebang, rest=rest, pragma_status=pragma_status, ending=ending,
|
||||
)
|
||||
|
||||
|
||||
def fix_encoding_pragma(
|
||||
f: IO[bytes],
|
||||
remove: bool = False,
|
||||
expected_pragma: bytes = DEFAULT_PRAGMA,
|
||||
) -> int:
|
||||
expected = _get_expected_contents(
|
||||
f.readline(), f.readline(), f.read(), expected_pragma,
|
||||
)
|
||||
|
||||
# Special cases for empty files
|
||||
if not expected.rest.strip():
|
||||
# If a file only has a shebang or a coding pragma, remove it
|
||||
if expected.has_any_pragma or expected.shebang:
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(b'')
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
if expected.is_expected_pragma(remove):
|
||||
return 0
|
||||
|
||||
# Otherwise, write out the new file
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
f.write(expected.shebang)
|
||||
if not remove:
|
||||
f.write(expected_pragma + expected.ending)
|
||||
f.write(expected.rest)
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
def _normalize_pragma(pragma: str) -> bytes:
|
||||
return pragma.encode().rstrip()
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
print(
|
||||
'warning: this hook is deprecated and will be removed in a future '
|
||||
'release because py2 is EOL. instead, use '
|
||||
'https://github.com/asottile/pyupgrade',
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
'Fixes the encoding pragma of python files',
|
||||
)
|
||||
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
|
||||
parser.add_argument(
|
||||
'--pragma', default=DEFAULT_PRAGMA, type=_normalize_pragma,
|
||||
help=(
|
||||
f'The encoding pragma to use. '
|
||||
f'Default: {DEFAULT_PRAGMA.decode()}'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--remove', action='store_true',
|
||||
help='Remove the encoding pragma (Useful in a python3-only codebase)',
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
retv = 0
|
||||
|
||||
if args.remove:
|
||||
fmt = 'Removed encoding pragma from {filename}'
|
||||
else:
|
||||
fmt = 'Added `{pragma}` to {filename}'
|
||||
|
||||
for filename in args.filenames:
|
||||
with open(filename, 'r+b') as f:
|
||||
file_ret = fix_encoding_pragma(
|
||||
f, remove=args.remove, expected_pragma=args.pragma,
|
||||
)
|
||||
retv |= file_ret
|
||||
if file_ret:
|
||||
print(
|
||||
fmt.format(pragma=args.pragma.decode(), filename=filename),
|
||||
)
|
||||
|
||||
return retv
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
raise SystemExit(main())
|
||||
|
|
@ -1,15 +1,16 @@
|
|||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
FORBIDDEN = {"a", "an", "the"}
|
||||
FORBIDDEN = {'a', 'an', 'the'}
|
||||
|
||||
|
||||
def git_ls_python_files():
|
||||
result = subprocess.run(
|
||||
["git", "ls-files", "*.py"],
|
||||
['git', 'ls-files', '*.py'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
|
|
@ -20,14 +21,14 @@ def git_ls_python_files():
|
|||
def is_test_file(path: Path) -> bool:
|
||||
name = path.name
|
||||
return (
|
||||
name.startswith("test_")
|
||||
or name.startswith("tests_")
|
||||
or name.endswith("_test.py")
|
||||
name.startswith('test_') or
|
||||
name.startswith('tests_') or
|
||||
name.endswith('_test.py')
|
||||
)
|
||||
|
||||
|
||||
def has_forbidden_article(path: Path) -> bool:
|
||||
parts = path.stem.split("_")
|
||||
parts = path.stem.split('_')
|
||||
return any(part in FORBIDDEN for part in parts)
|
||||
|
||||
|
||||
|
|
@ -37,12 +38,12 @@ def main() -> int:
|
|||
continue
|
||||
|
||||
if has_forbidden_article(path):
|
||||
print("ERROR: Forbidden article in test filename:")
|
||||
print('ERROR: Forbidden article in test filename:')
|
||||
print(path)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os.path
|
||||
import re
|
||||
from collections.abc import Sequence
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('filenames', nargs='*')
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[metadata]
|
||||
name = pre_commit_hooks
|
||||
version = 5.0.0
|
||||
version = 6.0.0
|
||||
description = Some out-of-the-box hooks for pre-commit.
|
||||
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
|
||||
|
|
@ -21,7 +20,7 @@ packages = find:
|
|||
install_requires =
|
||||
ruamel.yaml>=0.15
|
||||
tomli>=1.1.0;python_version<"3.11"
|
||||
python_requires = >=3.9
|
||||
python_requires = >=3.10
|
||||
|
||||
[options.packages.find]
|
||||
exclude =
|
||||
|
|
@ -33,7 +32,6 @@ console_scripts =
|
|||
check-added-large-files = pre_commit_hooks.check_added_large_files:main
|
||||
check-ast = pre_commit_hooks.check_ast:main
|
||||
check-builtin-literals = pre_commit_hooks.check_builtin_literals:main
|
||||
check-byte-order-marker = pre_commit_hooks.check_byte_order_marker:main
|
||||
check-case-conflict = pre_commit_hooks.check_case_conflict:main
|
||||
check-docstring-first = pre_commit_hooks.check_docstring_first:main
|
||||
check-executables-have-shebangs = pre_commit_hooks.check_executables_have_shebangs:main
|
||||
|
|
@ -53,7 +51,6 @@ console_scripts =
|
|||
end-of-file-fixer = pre_commit_hooks.end_of_file_fixer:main
|
||||
file-contents-sorter = pre_commit_hooks.file_contents_sorter:main
|
||||
fix-byte-order-marker = pre_commit_hooks.fix_byte_order_marker:main
|
||||
fix-encoding-pragma = pre_commit_hooks.fix_encoding_pragma:main
|
||||
forbid-new-submodules = pre_commit_hooks.forbid_new_submodules:main
|
||||
mixed-line-ending = pre_commit_hooks.mixed_line_ending:main
|
||||
name-tests-test = pre_commit_hooks.tests_should_end_in_test:main
|
||||
|
|
|
|||
|
|
@ -38,11 +38,6 @@ t1 = ()
|
|||
'''
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def visitor():
|
||||
return Visitor()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('expression', 'calls'),
|
||||
[
|
||||
|
|
@ -85,7 +80,8 @@ def visitor():
|
|||
('builtins.tuple()', []),
|
||||
],
|
||||
)
|
||||
def test_non_dict_exprs(visitor, expression, calls):
|
||||
def test_non_dict_exprs(expression, calls):
|
||||
visitor = Visitor(ignore=set())
|
||||
visitor.visit(ast.parse(expression))
|
||||
assert visitor.builtin_type_calls == calls
|
||||
|
||||
|
|
@ -102,7 +98,8 @@ def test_non_dict_exprs(visitor, expression, calls):
|
|||
('builtins.dict()', []),
|
||||
],
|
||||
)
|
||||
def test_dict_allow_kwargs_exprs(visitor, expression, calls):
|
||||
def test_dict_allow_kwargs_exprs(expression, calls):
|
||||
visitor = Visitor(ignore=set())
|
||||
visitor.visit(ast.parse(expression))
|
||||
assert visitor.builtin_type_calls == calls
|
||||
|
||||
|
|
@ -114,17 +111,18 @@ def test_dict_allow_kwargs_exprs(visitor, 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(allow_dict_kwargs=False)
|
||||
visitor = Visitor(ignore=set(), 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 == []
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pre_commit_hooks import check_byte_order_marker
|
||||
|
||||
|
||||
def test_failure(tmpdir):
|
||||
f = tmpdir.join('f.txt')
|
||||
f.write_text('ohai', encoding='utf-8-sig')
|
||||
assert check_byte_order_marker.main((str(f),)) == 1
|
||||
|
||||
|
||||
def test_success(tmpdir):
|
||||
f = tmpdir.join('f.txt')
|
||||
f.write_text('ohai', encoding='utf-8')
|
||||
assert check_byte_order_marker.main((str(f),)) == 0
|
||||
|
|
@ -27,10 +27,10 @@ def f1_is_a_conflict_file(tmpdir):
|
|||
|
||||
cmd_output('git', 'clone', str(repo1), str(repo2))
|
||||
|
||||
# Commit in master
|
||||
# Commit in mainline
|
||||
with repo1.as_cwd():
|
||||
repo1_f1.write('parent\n')
|
||||
git_commit('-am', 'master commit2')
|
||||
git_commit('-am', 'mainline commit2')
|
||||
|
||||
# Commit in clone and pull
|
||||
with repo2.as_cwd():
|
||||
|
|
@ -82,10 +82,10 @@ def repository_pending_merge(tmpdir):
|
|||
|
||||
cmd_output('git', 'clone', str(repo1), str(repo2))
|
||||
|
||||
# Commit in master
|
||||
# Commit in mainline
|
||||
with repo1.as_cwd():
|
||||
repo1_f1.write('parent\n')
|
||||
git_commit('-am', 'master commit2')
|
||||
git_commit('-am', 'mainline commit2')
|
||||
|
||||
# Commit in clone and pull without committing
|
||||
with repo2.as_cwd():
|
||||
|
|
@ -112,7 +112,7 @@ def test_merge_conflicts_git(capsys):
|
|||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'contents', (b'<<<<<<< HEAD\n', b'=======\n', b'>>>>>>> master\n'),
|
||||
'contents', (b'<<<<<<< HEAD\n', b'=======\n', b'>>>>>>> main\n'),
|
||||
)
|
||||
def test_merge_conflicts_failing(contents, repository_pending_merge):
|
||||
repository_pending_merge.join('f2').write_binary(contents)
|
||||
|
|
@ -150,7 +150,7 @@ def test_worktree_merge_conflicts(f1_is_a_conflict_file, tmpdir, capsys):
|
|||
cmd_output('git', 'worktree', 'add', str(worktree))
|
||||
with worktree.as_cwd():
|
||||
cmd_output(
|
||||
'git', 'pull', '--no-rebase', 'origin', 'master', retcode=None,
|
||||
'git', 'pull', '--no-rebase', 'origin', 'HEAD', retcode=None,
|
||||
)
|
||||
msg = f1_is_a_conflict_file.join('.git/worktrees/worktree/MERGE_MSG')
|
||||
assert msg.exists()
|
||||
|
|
|
|||
|
|
@ -16,9 +16,9 @@ def test_passing(tmpdir):
|
|||
# tags are ok
|
||||
b'https://github.com/asottile/test/blob/1.0.0/foo%20bar#L1\n'
|
||||
# links to files but not line numbers are ok
|
||||
b'https://github.com/asottile/test/blob/master/foo%20bar\n'
|
||||
b'https://github.com/asottile/test/blob/main/foo%20bar\n'
|
||||
# regression test for overly-greedy regex
|
||||
b'https://github.com/ yes / no ? /blob/master/foo#L1\n',
|
||||
b'https://github.com/ yes / no ? /blob/main/foo#L1\n',
|
||||
)
|
||||
assert not main((str(f),))
|
||||
|
||||
|
|
@ -26,17 +26,15 @@ def test_passing(tmpdir):
|
|||
def test_failing(tmpdir, capsys):
|
||||
with tmpdir.as_cwd():
|
||||
tmpdir.join('f.txt').write_binary(
|
||||
b'https://github.com/asottile/test/blob/master/foo#L1\n'
|
||||
b'https://example.com/asottile/test/blob/master/foo#L1\n'
|
||||
b'https://github.com/asottile/test/blob/main/foo#L1\n'
|
||||
b'https://example.com/asottile/test/blob/main/foo#L1\n',
|
||||
)
|
||||
|
||||
assert main(('f.txt', '--additional-github-domain', 'example.com'))
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == (
|
||||
'f.txt:1:https://github.com/asottile/test/blob/master/foo#L1\n'
|
||||
'f.txt:2:https://example.com/asottile/test/blob/master/foo#L1\n'
|
||||
'f.txt:3:https://example.com/asottile/test/blob/main/foo#L1\n'
|
||||
'f.txt:1:https://github.com/asottile/test/blob/main/foo#L1\n'
|
||||
'f.txt:2:https://example.com/asottile/test/blob/main/foo#L1\n'
|
||||
'\n'
|
||||
'Non-permanent github link detected.\n'
|
||||
'On any page on github press [y] to load a permalink.\n'
|
||||
|
|
|
|||
|
|
@ -67,18 +67,6 @@ from pre_commit_hooks.file_contents_sorter import PASS
|
|||
FAIL,
|
||||
b'Fie\nFoe\nfee\nfum\n',
|
||||
),
|
||||
(
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
['--unique', '--ignore-case'],
|
||||
PASS,
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
(
|
||||
b'fee\nfee\nFie\nFoe\nfum\n',
|
||||
['--unique', '--ignore-case'],
|
||||
FAIL,
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_integration(input_s, argv, expected_retval, output, tmpdir):
|
||||
|
|
@ -89,3 +77,24 @@ def test_integration(input_s, argv, expected_retval, output, tmpdir):
|
|||
|
||||
assert path.read_binary() == output
|
||||
assert output_retval == expected_retval
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'argv'),
|
||||
(
|
||||
(
|
||||
b'fee\nFie\nFoe\nfum\n',
|
||||
['--unique', '--ignore-case'],
|
||||
),
|
||||
(
|
||||
b'fee\nfee\nFie\nFoe\nfum\n',
|
||||
['--unique', '--ignore-case'],
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_integration_invalid_args(input_s, argv, tmpdir):
|
||||
path = tmpdir.join('file.txt')
|
||||
path.write_binary(input_s)
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
main([str(path)] + argv)
|
||||
|
|
|
|||
|
|
@ -1,161 +0,0 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
|
||||
import pytest
|
||||
|
||||
from pre_commit_hooks.fix_encoding_pragma import _normalize_pragma
|
||||
from pre_commit_hooks.fix_encoding_pragma import fix_encoding_pragma
|
||||
from pre_commit_hooks.fix_encoding_pragma import main
|
||||
|
||||
|
||||
def test_integration_inserting_pragma(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'import httplib\n')
|
||||
|
||||
assert main((str(path),)) == 1
|
||||
|
||||
assert path.read_binary() == (
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'import httplib\n'
|
||||
)
|
||||
|
||||
|
||||
def test_integration_ok(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'# -*- coding: utf-8 -*-\nx = 1\n')
|
||||
assert main((str(path),)) == 0
|
||||
|
||||
|
||||
def test_integration_remove(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'# -*- coding: utf-8 -*-\nx = 1\n')
|
||||
|
||||
assert main((str(path), '--remove')) == 1
|
||||
|
||||
assert path.read_binary() == b'x = 1\n'
|
||||
|
||||
|
||||
def test_integration_remove_ok(tmpdir):
|
||||
path = tmpdir.join('foo.py')
|
||||
path.write_binary(b'x = 1\n')
|
||||
assert main((str(path), '--remove')) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'input_str',
|
||||
(
|
||||
b'',
|
||||
(
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'x = 1\n'
|
||||
),
|
||||
(
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'foo = "bar"\n'
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_ok_inputs(input_str):
|
||||
bytesio = io.BytesIO(input_str)
|
||||
assert fix_encoding_pragma(bytesio) == 0
|
||||
bytesio.seek(0)
|
||||
assert bytesio.read() == input_str
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_str', 'output'),
|
||||
(
|
||||
(
|
||||
b'import httplib\n',
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'import httplib\n',
|
||||
),
|
||||
(
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'x = 1\n',
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'x = 1\n',
|
||||
),
|
||||
(
|
||||
b'#coding=utf-8\n'
|
||||
b'x = 1\n',
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'x = 1\n',
|
||||
),
|
||||
(
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'#coding=utf8\n'
|
||||
b'x = 1\n',
|
||||
b'#!/usr/bin/env python\n'
|
||||
b'# -*- coding: utf-8 -*-\n'
|
||||
b'x = 1\n',
|
||||
),
|
||||
# These should each get truncated
|
||||
(b'#coding: utf-8\n', b''),
|
||||
(b'# -*- coding: utf-8 -*-\n', b''),
|
||||
(b'#!/usr/bin/env python\n', b''),
|
||||
(b'#!/usr/bin/env python\n#coding: utf8\n', b''),
|
||||
(b'#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n', b''),
|
||||
),
|
||||
)
|
||||
def test_not_ok_inputs(input_str, output):
|
||||
bytesio = io.BytesIO(input_str)
|
||||
assert fix_encoding_pragma(bytesio) == 1
|
||||
bytesio.seek(0)
|
||||
assert bytesio.read() == output
|
||||
|
||||
|
||||
def test_ok_input_alternate_pragma():
|
||||
input_s = b'# coding: utf-8\nx = 1\n'
|
||||
bytesio = io.BytesIO(input_s)
|
||||
ret = fix_encoding_pragma(bytesio, expected_pragma=b'# coding: utf-8')
|
||||
assert ret == 0
|
||||
bytesio.seek(0)
|
||||
assert bytesio.read() == input_s
|
||||
|
||||
|
||||
def test_not_ok_input_alternate_pragma():
|
||||
bytesio = io.BytesIO(b'x = 1\n')
|
||||
ret = fix_encoding_pragma(bytesio, expected_pragma=b'# coding: utf-8')
|
||||
assert ret == 1
|
||||
bytesio.seek(0)
|
||||
assert bytesio.read() == b'# coding: utf-8\nx = 1\n'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
('input_s', 'expected'),
|
||||
(
|
||||
('# coding: utf-8', b'# coding: utf-8'),
|
||||
# trailing whitespace
|
||||
('# coding: utf-8\n', b'# coding: utf-8'),
|
||||
),
|
||||
)
|
||||
def test_normalize_pragma(input_s, expected):
|
||||
assert _normalize_pragma(input_s) == expected
|
||||
|
||||
|
||||
def test_integration_alternate_pragma(tmpdir, capsys):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write('x = 1\n')
|
||||
|
||||
pragma = '# coding: utf-8'
|
||||
assert main((str(f), '--pragma', pragma)) == 1
|
||||
assert f.read() == '# coding: utf-8\nx = 1\n'
|
||||
out, _ = capsys.readouterr()
|
||||
assert out == f'Added `# coding: utf-8` to {str(f)}\n'
|
||||
|
||||
|
||||
def test_crlf_ok(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write_binary(b'# -*- coding: utf-8 -*-\r\nx = 1\r\n')
|
||||
assert not main((str(f),))
|
||||
|
||||
|
||||
def test_crfl_adds(tmpdir):
|
||||
f = tmpdir.join('f.py')
|
||||
f.write_binary(b'x = 1\r\n')
|
||||
assert main((str(f),))
|
||||
assert f.read_binary() == b'# -*- coding: utf-8 -*-\r\nx = 1\r\n'
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
HOOK = Path(__file__).parents[1] / "pre_commit_hooks" / "forbid_articles_in_test_filenames.py"
|
||||
HOOK = Path(__file__).parents[1] / 'pre_commit_hooks' / 'forbid_articles_in_test_filenames.py'
|
||||
|
||||
|
||||
def run_hook(repo_path: Path):
|
||||
|
|
@ -18,39 +20,39 @@ def run_hook(repo_path: Path):
|
|||
|
||||
|
||||
def init_git_repo(tmp_path: Path):
|
||||
subprocess.run(["git", "init"], cwd=tmp_path, check=True)
|
||||
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
|
||||
subprocess.run(["git", "config", "user.name", "Test User"], cwd=tmp_path, check=True)
|
||||
subprocess.run(['git', 'init'], cwd=tmp_path, check=True)
|
||||
subprocess.run(['git', 'config', 'user.email', 'test@example.com'], cwd=tmp_path, check=True)
|
||||
subprocess.run(['git', 'config', 'user.name', 'Test User'], cwd=tmp_path, check=True)
|
||||
|
||||
|
||||
def git_add_all(tmp_path: Path):
|
||||
subprocess.run(["git", "add", "."], cwd=tmp_path, check=True)
|
||||
subprocess.run(['git', 'add', '.'], cwd=tmp_path, check=True)
|
||||
|
||||
|
||||
def test_fails_on_forbidden_article_in_test_filename(tmp_path: Path):
|
||||
init_git_repo(tmp_path)
|
||||
|
||||
bad_test = tmp_path / "tests_create_an_address.py"
|
||||
bad_test.write_text("def test_something(): pass\n")
|
||||
bad_test = tmp_path / 'tests_create_an_address.py'
|
||||
bad_test.write_text('def test_something(): pass\n')
|
||||
|
||||
git_add_all(tmp_path)
|
||||
|
||||
code, output = run_hook(tmp_path)
|
||||
|
||||
assert code == 1
|
||||
assert "ERROR: Forbidden article in test filename:" in output
|
||||
assert "tests_create_an_address.py" in output
|
||||
assert 'ERROR: Forbidden article in test filename:' in output
|
||||
assert 'tests_create_an_address.py' in output
|
||||
|
||||
|
||||
def test_passes_on_valid_test_filename(tmp_path: Path):
|
||||
init_git_repo(tmp_path)
|
||||
|
||||
good_test = tmp_path / "tests_create_address.py"
|
||||
good_test.write_text("def test_something(): pass\n")
|
||||
good_test = tmp_path / 'tests_create_address.py'
|
||||
good_test.write_text('def test_something(): pass\n')
|
||||
|
||||
git_add_all(tmp_path)
|
||||
|
||||
code, output = run_hook(tmp_path)
|
||||
|
||||
assert code == 0
|
||||
assert output == ""
|
||||
assert output == ''
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ from testing.util import git_commit
|
|||
def test_other_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'anotherbranch')
|
||||
assert is_on_branch({'master'}) is False
|
||||
assert is_on_branch({'placeholder'}) is False
|
||||
|
||||
|
||||
def test_multi_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', 'another/branch')
|
||||
assert is_on_branch({'master'}) is False
|
||||
assert is_on_branch({'placeholder'}) is False
|
||||
|
||||
|
||||
def test_multi_branch_fail(temp_git_dir):
|
||||
|
|
@ -26,9 +26,10 @@ def test_multi_branch_fail(temp_git_dir):
|
|||
assert is_on_branch({'another/branch'}) is True
|
||||
|
||||
|
||||
def test_master_branch(temp_git_dir):
|
||||
def test_exact_branch(temp_git_dir):
|
||||
with temp_git_dir.as_cwd():
|
||||
assert is_on_branch({'master'}) is True
|
||||
cmd_output('git', 'checkout', '-b', 'branchname')
|
||||
assert is_on_branch({'branchname'}) is True
|
||||
|
||||
|
||||
def test_main_branch_call(temp_git_dir):
|
||||
|
|
@ -50,11 +51,11 @@ def test_branch_pattern_fail(temp_git_dir):
|
|||
assert is_on_branch(set(), {'another/.*'}) is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize('branch_name', ('master', 'another/branch'))
|
||||
@pytest.mark.parametrize('branch_name', ('somebranch', 'another/branch'))
|
||||
def test_branch_pattern_multiple_branches_fail(temp_git_dir, branch_name):
|
||||
with temp_git_dir.as_cwd():
|
||||
cmd_output('git', 'checkout', '-b', branch_name)
|
||||
assert main(('--branch', 'master', '--pattern', 'another/.*'))
|
||||
assert main(('--branch', 'somebranch', '--pattern', 'another/.*'))
|
||||
|
||||
|
||||
def test_main_default_call(temp_git_dir):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue