diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 088a066..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"]' + env: '["py39", "py310", "py311", "py312"]' os: ubuntu-latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10808c3..33a25b8 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,33 +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.7.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.0.1 + rev: v3.1.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.2 + 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.1.2 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.1 + rev: v1.15.0 hooks: - id: mypy - additional_dependencies: [types-all] diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index c0d811c..b71169b 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -3,7 +3,8 @@ description: prevents giant files from being committed. entry: check-added-large-files language: python - stages: [commit, push, manual] + stages: [pre-commit, pre-push, manual] + minimum_pre_commit_version: 3.2.0 - id: check-ast name: check python ast description: simply checks whether the files parse as valid python. @@ -39,7 +40,13 @@ entry: check-executables-have-shebangs language: python types: [text, executable] - stages: [commit, push, manual] + stages: [pre-commit, pre-push, manual] + minimum_pre_commit_version: 3.2.0 +- id: check-illegal-windows-names + name: check illegal windows names + entry: Illegal Windows filenames detected + language: fail + files: '(?i)((^|/)(CON|PRN|AUX|NUL|COM[\d¹²³]|LPT[\d¹²³])(\.|/|$)|[<>:\"\\|?*\x00-\x1F]|/[^/]*[\.\s]/|[^/]*[\.\s]$)' - id: check-json name: check json description: checks json files for parseable syntax. @@ -52,7 +59,8 @@ entry: check-shebang-scripts-are-executable language: python types: [text] - stages: [commit, push, manual] + stages: [pre-commit, pre-push, manual] + minimum_pre_commit_version: 3.2.0 - id: pretty-format-json name: pretty format json description: sets a standard for formatting json files. @@ -107,6 +115,7 @@ entry: destroyed-symlinks language: python types: [file] + stages: [pre-commit, pre-push, manual] - id: detect-aws-credentials name: detect aws credentials description: detects *your* aws credentials from the aws cli credentials file. @@ -131,7 +140,8 @@ entry: end-of-file-fixer language: python types: [text] - stages: [commit, push, manual] + stages: [pre-commit, pre-push, manual] + minimum_pre_commit_version: 3.2.0 - id: file-contents-sorter name: file contents sorter description: sorts the lines in specified files (defaults to alphabetical). you must provide list of target files as input in your .pre-commit-config.yaml file. @@ -145,7 +155,7 @@ language: python types: [text] - id: fix-encoding-pragma - name: fix python encoding pragma + name: fix python encoding pragma (deprecated) description: 'adds # -*- coding: utf-8 -*- to the top of python files.' language: python entry: fix-encoding-pragma @@ -198,4 +208,5 @@ entry: trailing-whitespace-fixer language: python types: [text] - stages: [commit, push, manual] + stages: [pre-commit, pre-push, manual] + minimum_pre_commit_version: 3.2.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d5aeb..e165574 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,65 @@ +5.0.0 - 2024-10-05 +================== + +### Features +- `requirements-txt-fixer`: also remove `pkg_resources==...`. + - #850 PR by @ericfrederich. + - #1030 issue by @ericfrederich. +- `check-illegal-windows-names`: new hook! + - #1044 PR by @ericfrederich. + - #589 issue by @ericfrederich. + - #1049 PR by @Jeffrey-Lim. +- `pretty-format-json`: continue processing even if a file has a json error. + - #1039 PR by @amarvin. + - #1038 issue by @amarvin. + +### Fixes +- `destroyed-symlinks`: set `stages` to `[pre-commit, pre-push, manual]` + - PR #1085 by @AdrianDC. + +### Migrating +- pre-commit-hooks now requires `pre-commit>=3.2.0`. +- use non-deprecated names for `stages`. + - #1093 PR by @asottile. + +4.6.0 - 2024-04-06 +================== + +### Features +- `requirements-txt-fixer`: remove duplicate packages. + - #1014 PR by @vhoulbreque-withings. + - #960 issue @csibe17. + +### Migrating +- `fix-encoding-pragma`: deprecated -- will be removed in 5.0.0. use + [pyupgrade](https://github.com/asottile/pyupgrade) or some other tool. + - #1033 PR by @mxr. + - #1032 issue by @mxr. + +4.5.0 - 2023-10-07 +================== + +### Features +- `requirements-txt-fixer`: also sort `constraints.txt` by default. + - #857 PR by @lev-blit. + - #830 issue by @PLPeeters. +- `debug-statements`: add `bpdb` debugger. + - #942 PR by @mwip. + - #941 issue by @mwip. + +### Fixes +- `file-contents-sorter`: fix sorting an empty file. + - #944 PR by @RoelAdriaans. + - #935 issue by @paduszyk. +- `double-quote-string-fixer`: don't rewrite inside f-strings in 3.12+. + - #973 PR by @asottile. + - #971 issue by @XuehaiPan. + +## Migrating +- now requires python >= 3.8. + - #926 PR by @asottile. + - #927 PR by @asottile. + 4.4.0 - 2022-11-23 ================== diff --git a/README.md b/README.md index 1ebba89..c0f678f 100644 --- a/README.md +++ b/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: v4.4.0 # Use the ref you want to point at + rev: v5.0.0 # Use the ref you want to point at hooks: - id: trailing-whitespace # - id: ... @@ -51,6 +51,9 @@ Checks for a common error of placing code before the docstring. #### `check-executables-have-shebangs` Checks that non-binary executables have a proper shebang. +#### `check-illegal-windows-names` +Check for files that cannot be created on Windows. + #### `check-json` Attempts to load all json files to verify syntax. @@ -117,6 +120,7 @@ Makes sure files end in a newline and only a newline. Sort the lines in specified files (defaults to alphabetical). You must provide the target [`files`](https://pre-commit.com/#config-files) as input. Note that this hook WILL remove blank lines and does NOT respect any comments. +All newlines will be converted to line feeds (`\n`). The following arguments are available: - `--ignore-case` - fold lower case to upper case characters. @@ -126,6 +130,9 @@ The following arguments are available: 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) diff --git a/pre_commit_hooks/check_added_large_files.py b/pre_commit_hooks/check_added_large_files.py index 79c8d4e..e674162 100644 --- a/pre_commit_hooks/check_added_large_files.py +++ b/pre_commit_hooks/check_added_large_files.py @@ -4,7 +4,7 @@ import argparse import math import os import subprocess -from typing import Sequence +from collections.abc import Sequence from pre_commit_hooks.util import added_files from pre_commit_hooks.util import zsplit @@ -46,7 +46,7 @@ def find_large_added_files( filenames_filtered &= added_files() for filename in filenames_filtered: - kb = int(math.ceil(os.stat(filename).st_size / 1024)) + kb = math.ceil(os.stat(filename).st_size / 1024) if kb > maxkb: print(f'{filename} ({kb} KB) exceeds {maxkb} KB.') retv = 1 diff --git a/pre_commit_hooks/check_ast.py b/pre_commit_hooks/check_ast.py index fdac361..c1f165b 100644 --- a/pre_commit_hooks/check_ast.py +++ b/pre_commit_hooks/check_ast.py @@ -5,7 +5,7 @@ import ast import platform import sys import traceback -from typing import Sequence +from collections.abc import Sequence def main(argv: Sequence[str] | None = None) -> int: diff --git a/pre_commit_hooks/check_builtin_literals.py b/pre_commit_hooks/check_builtin_literals.py index d3054aa..16d59b5 100644 --- a/pre_commit_hooks/check_builtin_literals.py +++ b/pre_commit_hooks/check_builtin_literals.py @@ -2,8 +2,8 @@ from __future__ import annotations import argparse import ast +from collections.abc import Sequence from typing import NamedTuple -from typing import Sequence BUILTIN_TYPES = { diff --git a/pre_commit_hooks/check_byte_order_marker.py b/pre_commit_hooks/check_byte_order_marker.py index 59cc561..4fba4b3 100644 --- a/pre_commit_hooks/check_byte_order_marker.py +++ b/pre_commit_hooks/check_byte_order_marker.py @@ -1,7 +1,7 @@ from __future__ import annotations import argparse -from typing import Sequence +from collections.abc import Sequence def main(argv: Sequence[str] | None = None) -> int: diff --git a/pre_commit_hooks/check_case_conflict.py b/pre_commit_hooks/check_case_conflict.py index 33a13f1..475c91c 100644 --- a/pre_commit_hooks/check_case_conflict.py +++ b/pre_commit_hooks/check_case_conflict.py @@ -1,9 +1,9 @@ from __future__ import annotations import argparse -from typing import Iterable -from typing import Iterator -from typing import Sequence +from collections.abc import Iterable +from collections.abc import Iterator +from collections.abc import Sequence from pre_commit_hooks.util import added_files from pre_commit_hooks.util import cmd_output diff --git a/pre_commit_hooks/check_docstring_first.py b/pre_commit_hooks/check_docstring_first.py index d55f08a..42fbd15 100644 --- a/pre_commit_hooks/check_docstring_first.py +++ b/pre_commit_hooks/check_docstring_first.py @@ -3,8 +3,8 @@ from __future__ import annotations import argparse import io import tokenize +from collections.abc import Sequence from tokenize import tokenize as tokenize_tokenize -from typing import Sequence NON_CODE_TOKENS = frozenset(( tokenize.COMMENT, tokenize.ENDMARKER, tokenize.NEWLINE, tokenize.NL, diff --git a/pre_commit_hooks/check_executables_have_shebangs.py b/pre_commit_hooks/check_executables_have_shebangs.py index d8e4f49..707863b 100644 --- a/pre_commit_hooks/check_executables_have_shebangs.py +++ b/pre_commit_hooks/check_executables_have_shebangs.py @@ -4,9 +4,9 @@ from __future__ import annotations import argparse import shlex import sys -from typing import Generator +from collections.abc import Generator +from collections.abc import Sequence from typing import NamedTuple -from typing import Sequence from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import zsplit @@ -35,7 +35,7 @@ class GitLsFile(NamedTuple): filename: str -def git_ls_files(paths: Sequence[str]) -> Generator[GitLsFile, None, None]: +def git_ls_files(paths: Sequence[str]) -> Generator[GitLsFile]: outs = cmd_output('git', 'ls-files', '-z', '--stage', '--', *paths) for out in zsplit(outs): metadata, filename = out.split('\t') diff --git a/pre_commit_hooks/check_json.py b/pre_commit_hooks/check_json.py index 6a679fe..612111c 100644 --- a/pre_commit_hooks/check_json.py +++ b/pre_commit_hooks/check_json.py @@ -2,8 +2,8 @@ from __future__ import annotations import argparse import json +from collections.abc import Sequence from typing import Any -from typing import Sequence def raise_duplicate_keys( diff --git a/pre_commit_hooks/check_merge_conflict.py b/pre_commit_hooks/check_merge_conflict.py index 15ec284..54a083e 100644 --- a/pre_commit_hooks/check_merge_conflict.py +++ b/pre_commit_hooks/check_merge_conflict.py @@ -2,7 +2,7 @@ from __future__ import annotations import argparse import os.path -from typing import Sequence +from collections.abc import Sequence from pre_commit_hooks.util import cmd_output diff --git a/pre_commit_hooks/check_shebang_scripts_are_executable.py b/pre_commit_hooks/check_shebang_scripts_are_executable.py index 621696c..937425b 100644 --- a/pre_commit_hooks/check_shebang_scripts_are_executable.py +++ b/pre_commit_hooks/check_shebang_scripts_are_executable.py @@ -4,7 +4,7 @@ from __future__ import annotations import argparse import shlex import sys -from typing import Sequence +from collections.abc import Sequence from pre_commit_hooks.check_executables_have_shebangs import EXECUTABLE_VALUES from pre_commit_hooks.check_executables_have_shebangs import git_ls_files @@ -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, ) diff --git a/pre_commit_hooks/check_symlinks.py b/pre_commit_hooks/check_symlinks.py index a85c82a..be8a800 100644 --- a/pre_commit_hooks/check_symlinks.py +++ b/pre_commit_hooks/check_symlinks.py @@ -2,7 +2,7 @@ from __future__ import annotations import argparse import os.path -from typing import Sequence +from collections.abc import Sequence def main(argv: Sequence[str] | None = None) -> int: diff --git a/pre_commit_hooks/check_toml.py b/pre_commit_hooks/check_toml.py index 0407371..2105b07 100644 --- a/pre_commit_hooks/check_toml.py +++ b/pre_commit_hooks/check_toml.py @@ -2,7 +2,7 @@ from __future__ import annotations import argparse import sys -from typing import Sequence +from collections.abc import Sequence if sys.version_info >= (3, 11): # pragma: >=3.11 cover import tomllib diff --git a/pre_commit_hooks/check_vcs_permalinks.py b/pre_commit_hooks/check_vcs_permalinks.py index 68639bd..108656a 100644 --- a/pre_commit_hooks/check_vcs_permalinks.py +++ b/pre_commit_hooks/check_vcs_permalinks.py @@ -3,8 +3,8 @@ from __future__ import annotations import argparse import re import sys -from typing import Pattern -from typing import Sequence +from collections.abc import Sequence +from re import Pattern def _get_pattern(domain: str) -> Pattern[bytes]: diff --git a/pre_commit_hooks/check_xml.py b/pre_commit_hooks/check_xml.py index c256af9..ff5536b 100644 --- a/pre_commit_hooks/check_xml.py +++ b/pre_commit_hooks/check_xml.py @@ -2,7 +2,7 @@ from __future__ import annotations import argparse import xml.sax.handler -from typing import Sequence +from collections.abc import Sequence def main(argv: Sequence[str] | None = None) -> int: diff --git a/pre_commit_hooks/check_yaml.py b/pre_commit_hooks/check_yaml.py index 250794e..c94ea71 100644 --- a/pre_commit_hooks/check_yaml.py +++ b/pre_commit_hooks/check_yaml.py @@ -1,17 +1,17 @@ from __future__ import annotations import argparse +from collections.abc import Generator +from collections.abc import Sequence from typing import Any -from typing import Generator from typing import NamedTuple -from typing import Sequence import ruamel.yaml yaml = ruamel.yaml.YAML(typ='safe') -def _exhaust(gen: Generator[str, None, None]) -> None: +def _exhaust(gen: Generator[str]) -> None: for _ in gen: pass @@ -46,7 +46,7 @@ def main(argv: Sequence[str] | None = None) -> int: '--unsafe', action='store_true', help=( 'Instead of loading the files, simply parse them for syntax. ' - 'A syntax-only check enables extensions and unsafe contstructs ' + 'A syntax-only check enables extensions and unsafe constructs ' 'which would otherwise be forbidden. Using this option removes ' 'all guarantees of portability to other yaml implementations. ' 'Implies --allow-multiple-documents' diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py index cf544c7..7e6be95 100644 --- a/pre_commit_hooks/debug_statement_hook.py +++ b/pre_commit_hooks/debug_statement_hook.py @@ -3,8 +3,8 @@ from __future__ import annotations import argparse import ast import traceback +from collections.abc import Sequence from typing import NamedTuple -from typing import Sequence DEBUG_STATEMENTS = { diff --git a/pre_commit_hooks/destroyed_symlinks.py b/pre_commit_hooks/destroyed_symlinks.py index f256908..9bc2589 100644 --- a/pre_commit_hooks/destroyed_symlinks.py +++ b/pre_commit_hooks/destroyed_symlinks.py @@ -3,7 +3,7 @@ from __future__ import annotations import argparse import shlex import subprocess -from typing import Sequence +from collections.abc import Sequence from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import zsplit diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py index 4f59d9c..8582288 100644 --- a/pre_commit_hooks/detect_aws_credentials.py +++ b/pre_commit_hooks/detect_aws_credentials.py @@ -3,8 +3,8 @@ from __future__ import annotations import argparse import configparser import os +from collections.abc import Sequence from typing import NamedTuple -from typing import Sequence class BadFile(NamedTuple): diff --git a/pre_commit_hooks/detect_private_key.py b/pre_commit_hooks/detect_private_key.py index cd51f90..9ad703a 100644 --- a/pre_commit_hooks/detect_private_key.py +++ b/pre_commit_hooks/detect_private_key.py @@ -1,7 +1,7 @@ from __future__ import annotations import argparse -from typing import Sequence +from collections.abc import Sequence BLACKLIST = [ b'BEGIN RSA PRIVATE KEY', diff --git a/pre_commit_hooks/end_of_file_fixer.py b/pre_commit_hooks/end_of_file_fixer.py index a30dce9..a88425c 100644 --- a/pre_commit_hooks/end_of_file_fixer.py +++ b/pre_commit_hooks/end_of_file_fixer.py @@ -2,8 +2,8 @@ from __future__ import annotations import argparse import os +from collections.abc import Sequence from typing import IO -from typing import Sequence def fix_file(file_obj: IO[bytes]) -> int: diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py index 02bdbcc..4435d6a 100644 --- a/pre_commit_hooks/file_contents_sorter.py +++ b/pre_commit_hooks/file_contents_sorter.py @@ -12,11 +12,11 @@ conflicts and keep the file nicely ordered. from __future__ import annotations import argparse +from collections.abc import Iterable +from collections.abc import Sequence from typing import Any from typing import Callable from typing import IO -from typing import Iterable -from typing import Sequence PASS = 0 FAIL = 1 @@ -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 diff --git a/pre_commit_hooks/fix_byte_order_marker.py b/pre_commit_hooks/fix_byte_order_marker.py index 22a4990..100ffea 100644 --- a/pre_commit_hooks/fix_byte_order_marker.py +++ b/pre_commit_hooks/fix_byte_order_marker.py @@ -1,7 +1,7 @@ from __future__ import annotations import argparse -from typing import Sequence +from collections.abc import Sequence def main(argv: Sequence[str] | None = None) -> int: diff --git a/pre_commit_hooks/fix_encoding_pragma.py b/pre_commit_hooks/fix_encoding_pragma.py index 60c71ee..5354104 100644 --- a/pre_commit_hooks/fix_encoding_pragma.py +++ b/pre_commit_hooks/fix_encoding_pragma.py @@ -1,9 +1,10 @@ from __future__ import annotations import argparse +import sys +from collections.abc import Sequence from typing import IO from typing import NamedTuple -from typing import Sequence DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-' @@ -107,6 +108,13 @@ def _normalize_pragma(pragma: str) -> bytes: 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', ) diff --git a/pre_commit_hooks/forbid_new_submodules.py b/pre_commit_hooks/forbid_new_submodules.py index b806cad..b7a63cd 100644 --- a/pre_commit_hooks/forbid_new_submodules.py +++ b/pre_commit_hooks/forbid_new_submodules.py @@ -2,7 +2,7 @@ from __future__ import annotations import argparse import os -from typing import Sequence +from collections.abc import Sequence from pre_commit_hooks.util import cmd_output diff --git a/pre_commit_hooks/mixed_line_ending.py b/pre_commit_hooks/mixed_line_ending.py index 0328e86..2fbf067 100644 --- a/pre_commit_hooks/mixed_line_ending.py +++ b/pre_commit_hooks/mixed_line_ending.py @@ -2,7 +2,7 @@ from __future__ import annotations import argparse import collections -from typing import Sequence +from collections.abc import Sequence CRLF = b'\r\n' diff --git a/pre_commit_hooks/no_commit_to_branch.py b/pre_commit_hooks/no_commit_to_branch.py index 741f726..b0b8b23 100644 --- a/pre_commit_hooks/no_commit_to_branch.py +++ b/pre_commit_hooks/no_commit_to_branch.py @@ -2,8 +2,8 @@ from __future__ import annotations import argparse import re +from collections.abc import Sequence from typing import AbstractSet -from typing import Sequence from pre_commit_hooks.util import CalledProcessError from pre_commit_hooks.util import cmd_output diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py index c15fbda..01bfd7f 100644 --- a/pre_commit_hooks/pretty_format_json.py +++ b/pre_commit_hooks/pretty_format_json.py @@ -4,9 +4,9 @@ import argparse import json import re import sys +from collections.abc import Mapping +from collections.abc import Sequence from difflib import unified_diff -from typing import Mapping -from typing import Sequence def _insert_linebreaks(json_str: str) -> str: return re.sub( @@ -135,17 +135,22 @@ def main(argv: Sequence[str] | None = None) -> int: f'Input File {json_file} is not a valid JSON, consider using ' f'check-json', ) - return 1 - - if contents != pretty_contents: status = 1 - if args.autofix: - _autofix(json_file, pretty_contents) - if args.empty_object_with_newline: - status = 0 - else: - diff_output = get_diff(contents, pretty_contents, json_file) - sys.stdout.buffer.write(diff_output.encode()) + else: + if contents != pretty_contents: + if args.autofix: + _autofix(json_file, pretty_contents) + if args.empty_object_with_newline: + status = 0 + else: + diff_output = get_diff( + contents, + pretty_contents, + json_file + ) + sys.stdout.buffer.write(diff_output.encode()) + + status = 1 return status diff --git a/pre_commit_hooks/removed.py b/pre_commit_hooks/removed.py index 6f6c7b7..fb2b6d9 100644 --- a/pre_commit_hooks/removed.py +++ b/pre_commit_hooks/removed.py @@ -1,7 +1,7 @@ from __future__ import annotations import sys -from typing import Sequence +from collections.abc import Sequence def main(argv: Sequence[str] | None = None) -> int: diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py index 5884394..8ce8ec6 100644 --- a/pre_commit_hooks/requirements_txt_fixer.py +++ b/pre_commit_hooks/requirements_txt_fixer.py @@ -2,8 +2,8 @@ from __future__ import annotations import argparse import re +from collections.abc import Sequence from typing import IO -from typing import Sequence PASS = 0 @@ -45,6 +45,11 @@ class Requirement: elif requirement.value == b'\n': return False else: + # if 2 requirements have the same name, the one with comments + # needs to go first (so that when removing duplicates, the one + # with comments is kept) + if self.name == requirement.name: + return bool(self.comments) > bool(requirement.comments) return self.name < requirement.name def is_complete(self) -> bool: @@ -110,13 +115,20 @@ def fix_requirements(f: IO[bytes]) -> int: # which is automatically added by broken pip package under Debian requirements = [ req for req in requirements - if req.value != b'pkg-resources==0.0.0\n' + if req.value not in [ + b'pkg-resources==0.0.0\n', + b'pkg_resources==0.0.0\n', + ] ] + # sort the requirements and remove duplicates + prev = None for requirement in sorted(requirements): after.extend(requirement.comments) assert requirement.value, requirement.value - after.append(requirement.value) + if prev is None or requirement.value != prev.value: + after.append(requirement.value) + prev = requirement after.extend(rest) after_string = b''.join(after) diff --git a/pre_commit_hooks/sort_simple_yaml.py b/pre_commit_hooks/sort_simple_yaml.py index 116b5c1..65e6b7a 100644 --- a/pre_commit_hooks/sort_simple_yaml.py +++ b/pre_commit_hooks/sort_simple_yaml.py @@ -20,7 +20,7 @@ complicated YAML files. from __future__ import annotations import argparse -from typing import Sequence +from collections.abc import Sequence QUOTES = ["'", '"'] diff --git a/pre_commit_hooks/string_fixer.py b/pre_commit_hooks/string_fixer.py index 0ef9bc7..76eb352 100644 --- a/pre_commit_hooks/string_fixer.py +++ b/pre_commit_hooks/string_fixer.py @@ -3,8 +3,15 @@ from __future__ import annotations import argparse import io import re +import sys import tokenize -from typing import Sequence +from collections.abc import Sequence + +if sys.version_info >= (3, 12): # pragma: >=3.12 cover + FSTRING_START = tokenize.FSTRING_START + FSTRING_END = tokenize.FSTRING_END +else: # pragma: <3.12 cover + FSTRING_START = FSTRING_END = -1 START_QUOTE_RE = re.compile('^[a-zA-Z]*"') @@ -40,11 +47,17 @@ def fix_strings(filename: str) -> int: # Basically a mutable string splitcontents = list(contents) + fstring_depth = 0 + # Iterate in reverse so the offsets are always correct tokens_l = list(tokenize.generate_tokens(io.StringIO(contents).readline)) tokens = reversed(tokens_l) for token_type, token_text, (srow, scol), (erow, ecol), _ in tokens: - if token_type == tokenize.STRING: + if token_type == FSTRING_START: # pragma: >=3.12 cover + fstring_depth += 1 + elif token_type == FSTRING_END: # pragma: >=3.12 cover + fstring_depth -= 1 + elif fstring_depth == 0 and token_type == tokenize.STRING: new_text = handle_match(token_text) splitcontents[ line_offsets[srow] + scol: diff --git a/pre_commit_hooks/tests_should_end_in_test.py b/pre_commit_hooks/tests_should_end_in_test.py index e7842af..07af277 100644 --- a/pre_commit_hooks/tests_should_end_in_test.py +++ b/pre_commit_hooks/tests_should_end_in_test.py @@ -3,7 +3,7 @@ from __future__ import annotations import argparse import os.path import re -from typing import Sequence +from collections.abc import Sequence def main(argv: Sequence[str] | None = None) -> int: diff --git a/pre_commit_hooks/trailing_whitespace_fixer.py b/pre_commit_hooks/trailing_whitespace_fixer.py index 84f5067..dab8b14 100644 --- a/pre_commit_hooks/trailing_whitespace_fixer.py +++ b/pre_commit_hooks/trailing_whitespace_fixer.py @@ -2,7 +2,7 @@ from __future__ import annotations import argparse import os -from typing import Sequence +from collections.abc import Sequence def _fix_file( diff --git a/setup.cfg b/setup.cfg index b72aa64..3b7e5ee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit_hooks -version = 4.4.0 +version = 5.0.0 description = Some out-of-the-box hooks for pre-commit. long_description = file: README.md long_description_content_type = text/markdown @@ -21,7 +21,7 @@ packages = find: install_requires = ruamel.yaml>=0.15 tomli>=1.1.0;python_version<"3.11" -python_requires = >=3.8 +python_requires = >=3.9 [options.packages.find] exclude = diff --git a/tests/check_illegal_windows_names_test.py b/tests/check_illegal_windows_names_test.py new file mode 100644 index 0000000..82d7532 --- /dev/null +++ b/tests/check_illegal_windows_names_test.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import os.path +import re + +import pytest + +from pre_commit_hooks.check_yaml import yaml + + +@pytest.fixture(scope='module') +def hook_re(): + here = os.path.dirname(__file__) + with open(os.path.join(here, '..', '.pre-commit-hooks.yaml')) as f: + hook_defs = yaml.load(f) + hook, = ( + hook + for hook in hook_defs + if hook['id'] == 'check-illegal-windows-names' + ) + yield re.compile(hook['files']) + + +@pytest.mark.parametrize( + 's', + ( + pytest.param('aux.txt', id='with ext'), + pytest.param('aux', id='without ext'), + pytest.param('AuX.tXt', id='capitals'), + pytest.param('com7.dat', id='com with digit'), + pytest.param(':', id='bare colon'), + pytest.param('file:Zone.Identifier', id='mid colon'), + pytest.param('path/COM¹.json', id='com with superscript'), + pytest.param('dir/LPT³.toml', id='lpt with superscript'), + pytest.param('with < less than', id='with less than'), + pytest.param('Fast or Slow?.md', id='with question mark'), + pytest.param('with "double" quotes', id='with double quotes'), + pytest.param('with_null\x00byte', id='with null byte'), + pytest.param('ends_with.', id='ends with period'), + pytest.param('ends_with ', id='ends with space'), + pytest.param('ends_with\t', id='ends with tab'), + pytest.param('dir/ends./with.txt', id='directory ends with period'), + pytest.param('dir/ends /with.txt', id='directory ends with space'), + ), +) +def test_check_illegal_windows_names_matches(hook_re, s): + assert hook_re.search(s) + + +@pytest.mark.parametrize( + 's', + ( + pytest.param('README.md', id='standard file'), + pytest.param('foo.aux', id='as ext'), + pytest.param('com.dat', id='com without digit'), + pytest.param('.python-version', id='starts with period'), + pytest.param(' pseudo nan', id='with spaces'), + pytest.param('!@#$%^&;=≤\'~`¡¿€🤗', id='with allowed characters'), + pytest.param('path.to/file.py', id='standard path'), + ), +) +def test_check_illegal_windows_names_does_not_match(hook_re, s): + assert hook_re.search(s) is None diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py index 49b3b79..f178ae6 100644 --- a/tests/file_contents_sorter_test.py +++ b/tests/file_contents_sorter_test.py @@ -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) diff --git a/tests/pretty_format_json_test.py b/tests/pretty_format_json_test.py index bd7dab9..44884ad 100644 --- a/tests/pretty_format_json_test.py +++ b/tests/pretty_format_json_test.py @@ -83,6 +83,24 @@ def test_autofix_main(tmpdir): assert ret == 0 +def test_invalid_main(tmpdir): + srcfile1 = tmpdir.join('not_valid_json.json') + srcfile1.write( + '{\n' + ' // not json\n' + ' "a": "b"\n' + '}', + ) + srcfile2 = tmpdir.join('to_be_json_formatted.json') + srcfile2.write('{ "a": "b" }') + + # it should have skipped the first file and formatted the second one + assert main(['--autofix', str(srcfile1), str(srcfile2)]) == 1 + + # confirm second file was formatted (shouldn't trigger linter again) + assert main([str(srcfile2)]) == 0 + + def test_orderfile_get_pretty_format(): ret = main(( '--top-keys=alist', get_resource_path('pretty_formatted_json.json'), diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py index b725afa..c0d2c65 100644 --- a/tests/requirements_txt_fixer_test.py +++ b/tests/requirements_txt_fixer_test.py @@ -68,6 +68,12 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement b'f<=2\n' b'g<2\n', ), + (b'a==1\nb==1\na==1\n', FAIL, b'a==1\nb==1\n'), + ( + b'a==1\nb==1\n#comment about a\na==1\n', + FAIL, + b'#comment about a\na==1\nb==1\n', + ), (b'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'), ( b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\nPyMySQL\n', @@ -76,6 +82,8 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement ), (b'bar\npkg-resources==0.0.0\nfoo\n', FAIL, b'bar\nfoo\n'), (b'foo\npkg-resources==0.0.0\nbar\n', FAIL, b'bar\nfoo\n'), + (b'bar\npkg_resources==0.0.0\nfoo\n', FAIL, b'bar\nfoo\n'), + (b'foo\npkg_resources==0.0.0\nbar\n', FAIL, b'bar\nfoo\n'), ( b'git+ssh://git_url@tag#egg=ocflib\nDjango\nijk\n', FAIL, diff --git a/tests/string_fixer_test.py b/tests/string_fixer_test.py index 9dd7315..8eb164c 100644 --- a/tests/string_fixer_test.py +++ b/tests/string_fixer_test.py @@ -37,6 +37,12 @@ TESTS = ( 1, ), ('"foo""bar"', "'foo''bar'", 1), + pytest.param( + "f'hello{\"world\"}'", + "f'hello{\"world\"}'", + 0, + id='ignore nested fstrings', + ), )