From 91f63e89fbc95f550289c6382dd3949846c66fd5 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 16 May 2025 20:12:45 +0200 Subject: [PATCH] Adding upstream version 1.10.0. Signed-off-by: Daniel Baumann --- .gitignore | 1 + .pre-commit-config.yaml | 55 ++++++++ .pre-commit-hooks.yaml | 79 ++++++++++++ LICENSE | 19 +++ README.md | 36 ++++++ generate-readme | 28 ++++ tests/hooks_test.py | 274 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 492 insertions(+) create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .pre-commit-hooks.yaml create mode 100644 LICENSE create mode 100644 README.md create mode 100755 generate-readme create mode 100644 tests/hooks_test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1215eb6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,55 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: debug-statements + - id: double-quote-string-fixer + - id: name-tests-test + - id: requirements-txt-fixer +- repo: https://github.com/asottile/reorder_python_imports + rev: v3.9.0 + hooks: + - id: reorder-python-imports + args: [--py37-plus, --add-import, 'from __future__ import annotations'] +- repo: https://github.com/asottile/add-trailing-comma + rev: v2.4.0 + hooks: + - id: add-trailing-comma + args: [--py36-plus] +- repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: [--py37-plus] +- repo: https://github.com/pre-commit/mirrors-autopep8 + rev: v2.0.1 + hooks: + - id: autopep8 +- repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.991 + hooks: + - id: mypy + additional_dependencies: [types-all] +- repo: local + hooks: + - id: generate-readme + name: generate readme + entry: ./generate-readme + language: python + additional_dependencies: [pyyaml] + files: ^(\.pre-commit-hooks.yaml|generate-readme)$ + pass_filenames: false + - id: run-tests + name: run tests + entry: pytest tests + language: python + additional_dependencies: [pre-commit, pytest] + always_run: true + pass_filenames: false diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml new file mode 100644 index 0000000..dc9dc20 --- /dev/null +++ b/.pre-commit-hooks.yaml @@ -0,0 +1,79 @@ +- id: python-check-blanket-noqa + name: check blanket noqa + description: 'Enforce that `noqa` annotations always occur with specific codes. Sample annotations: `# noqa: F401`, `# noqa: F401,W203`' + entry: '(?i)# noqa(?!: )' + language: pygrep + types: [python] +- id: python-check-blanket-type-ignore + name: check blanket type ignore + description: 'Enforce that `# type: ignore` annotations always occur with specific codes. Sample annotations: `# type: ignore[attr-defined]`, `# type: ignore[attr-defined, name-defined]`' + entry: '# type:? *ignore(?!\[|\w)' + language: pygrep + types: [python] +- id: python-check-mock-methods + name: check for not-real mock methods + description: >- + Prevent common mistakes of `assert mck.not_called()`, `assert mck.called_once_with(...)` + and `mck.assert_called`. + language: pygrep + entry: > + (?x)( + assert .*\.( + not_called| + called_ + )| + # ''.join(rf'(? int: + with open('.pre-commit-hooks.yaml') as f: + hooks = yaml.load(f, Loader=Loader) + + with open('README.md') as f: + contents = f.read() + before, delim, _ = contents.partition('[generated]: # (generated)\n') + + rest = '\n'.join( + f'- **`{hook["id"]}`**: {hook["description"]}' for hook in hooks + ) + + with open('README.md', 'w') as f: + f.write(before + delim + rest + '\n') + + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/tests/hooks_test.py b/tests/hooks_test.py new file mode 100644 index 0000000..6cee816 --- /dev/null +++ b/tests/hooks_test.py @@ -0,0 +1,274 @@ +from __future__ import annotations + +import re + +import pytest +from pre_commit.clientlib import load_manifest +from pre_commit.constants import MANIFEST_FILE + +HOOKS = {h['id']: re.compile(h['entry']) for h in load_manifest(MANIFEST_FILE)} + + +@pytest.mark.parametrize( + 's', + ( + 'x = 1 # type: ignoreme', + 'x = 1 # type: int', + 'x = 1 # type int', + 'x = 1 # type: int # noqa', + ), +) +def test_python_use_type_annotations_positive(s): + assert HOOKS['python-use-type-annotations'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + 'x = 1', + 'x = 1 # type:ignore', + 'x = 1 # type: ignore', + 'x = 1 # type: ignore', + 'x = 1 # type: ignore # noqa', + 'x = 1 # type: ignore # noqa', + 'x = 1 # type: ignore[type-mismatch]', + 'x = 1 # type: ignore=E123', + ), +) +def test_python_use_type_annotations_negative(s): + assert not HOOKS['python-use-type-annotations'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + '# noqa', + '# NOQA', + '# noqa:F401', + '# noqa:F401,W203', + ), +) +def test_python_check_blanket_noqa_positive(s): + assert HOOKS['python-check-blanket-noqa'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + 'x = 1', + '# noqa: F401', + '# noqa: F401, W203', + ), +) +def test_python_check_blanket_noqa_negative(s): + assert not HOOKS['python-check-blanket-noqa'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + 'x = 1 # type: ignore', + 'x = 1 # type ignore', + 'x = 1 # type:ignore', + 'x = 1 # type ignore # noqa', + ), +) +def test_python_check_blanket_type_ignore_positive(s): + assert HOOKS['python-check-blanket-type-ignore'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + 'x = 1', + 'x = 1 # type: ignore[attr-defined]', + 'x = 1 # type: ignore[attr-defined, name-defined]', + 'x = 1 # type: ignore[type-mismatch] # noqa', + 'x = 1 # type: Union[int, str]', + 'x = 1 # type: ignoreme', + ), +) +def test_python_check_blanket_type_ignore_negative(s): + assert not HOOKS['python-check-blanket-type-ignore'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + 'assert my_mock.not_called()', + 'assert my_mock.called_once_with()', + 'my_mock.assert_not_called', + 'my_mock.assert_called', + 'my_mock.assert_called_once_with', + 'my_mock.assert_called_once_with# noqa', + 'MyMock.assert_called_once_with', + ), +) +def test_python_check_mock_methods_positive(s): + assert HOOKS['python-check-mock-methods'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + 'assert my_mock.call_count == 1', + 'assert my_mock.called', + 'my_mock.assert_not_called()', + 'my_mock.assert_called()', + 'my_mock.assert_called_once_with()', + '"""like :meth:`Mock.assert_called_once_with`"""', + '"""like :meth:`MagicMock.assert_called_once_with`"""', + ), +) +def test_python_check_mock_methods_negative(s): + assert not HOOKS['python-check-mock-methods'].search(s) + + +def test_python_noeval_positive(): + assert HOOKS['python-no-eval'].search('eval("3 + 4")') + + +def test_python_noeval_negative(): + assert not HOOKS['python-no-eval'].search('literal_eval("{1: 2}")') + + +@pytest.mark.parametrize( + 's', + ( + 'log.warn("this is deprecated")', + ), +) +def test_python_no_log_warn_positive(s): + assert HOOKS['python-no-log-warn'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + "warnings.warn('this is ok')", + 'log.warning("this is ok")', + 'from warnings import warn', + 'warn("by itself is also ok")', + ), +) +def test_python_no_log_warn_negative(s): + assert not HOOKS['python-no-log-warn'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + '`[code]`', + 'i like `_kitty`', + 'i like `_`', + '`a`', + '`cd`', + ' `indented` literal block', + '> quoted `literal` block', + ), +) +def test_python_rst_backticks_positive(s): + assert HOOKS['rst-backticks'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + ' ``[code]``', + 'i like _`kitty`', + 'i like `kitty`_', + '``b``', + '``ef``', + ' indented `literal` block', + ), +) +def test_python_rst_backticks_negative(s): + assert not HOOKS['rst-backticks'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + '``PyMem_Realloc()`` indirectly call``PyObject_Malloc()`` and', + 'This PEP proposes that ``bytes`` and ``bytearray``gain an optimised', + 'Reading this we first see the``break``, which obviously applies to', + 'for using``long_description`` and a corresponding', + '``inline`` normal``inline', + '``inline``normal ``inline', + '``inline``normal', + '``inline``normal``inline', + 'normal ``inline``normal', + 'normal``inline`` normal', + 'normal``inline``', + 'normal``inline``normal', + ), +) +def test_python_rst_inline_touching_normal_positive(s): + assert HOOKS['rst-inline-touching-normal'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + '``PyMem_Realloc()`` indirectly call ``PyObject_Malloc()`` and', + 'This PEP proposes that ``bytes`` and ``bytearray`` gain an optimised', + 'Reading this we first see the ``break``, which obviously applies to', + 'for using ``long_description`` and a corresponding', + '``inline`` normal ``inline', + '``inline`` normal', + 'normal ``inline`` normal', + 'normal ``inline``', + ), +) +def test_python_rst_inline_touching_normal_negative(s): + assert not HOOKS['rst-inline-touching-normal'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + str(b'\x80abc', errors='replace'), + ), +) +def test_text_unicode_replacement_char_positive(s): + assert HOOKS['text-unicode-replacement-char'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + 'foo', + ), +) +def test_text_unicode_replacement_char_negative(s): + assert not HOOKS['text-unicode-replacement-char'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + ' .. warning:', + '.. warning:', + ' .. warning ::', + '.. warning ::', + ' .. warning :', + '.. warning :', + ), +) +def test_rst_directive_colons_positive(s): + assert HOOKS['rst-directive-colons'].search(s) + + +@pytest.mark.parametrize( + 's', + ( + '.. warning::', + '.. code:: python', + ), +) +def test_rst_directive_colons_negative(s): + assert not HOOKS['rst-directive-colons'].search(s) + + +def test_that_hooks_are_sorted(): + assert list(HOOKS) == sorted(HOOKS)