Merge branch 'main' into empty-object-with-newline

This commit is contained in:
ascheucher-shopify-partner 2025-02-21 18:18:36 +01:00
commit a23cc57a37
44 changed files with 321 additions and 97 deletions

View file

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

View file

@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v5.0.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
- id: end-of-file-fixer - id: end-of-file-fixer
@ -10,33 +10,32 @@ 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: v2.4.0 rev: v2.7.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.10.0 rev: v3.14.0
hooks: hooks:
- id: reorder-python-imports - 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 - repo: https://github.com/asottile/add-trailing-comma
rev: v3.0.1 rev: v3.1.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.10.1 rev: v3.19.1
hooks: hooks:
- id: pyupgrade - id: pyupgrade
args: [--py38-plus] args: [--py39-plus]
- repo: https://github.com/pre-commit/mirrors-autopep8 - repo: https://github.com/hhatto/autopep8
rev: v2.0.2 rev: v2.3.2
hooks: hooks:
- id: autopep8 - id: autopep8
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 6.1.0 rev: 7.1.2
hooks: hooks:
- id: flake8 - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy - repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.4.1 rev: v1.15.0
hooks: hooks:
- id: mypy - id: mypy
additional_dependencies: [types-all]

View file

@ -3,7 +3,8 @@
description: prevents giant files from being committed. description: prevents giant files from being committed.
entry: check-added-large-files entry: check-added-large-files
language: python language: python
stages: [commit, push, manual] stages: [pre-commit, pre-push, manual]
minimum_pre_commit_version: 3.2.0
- id: check-ast - id: check-ast
name: check python ast name: check python ast
description: simply checks whether the files parse as valid python. description: simply checks whether the files parse as valid python.
@ -39,7 +40,13 @@
entry: check-executables-have-shebangs entry: check-executables-have-shebangs
language: python language: python
types: [text, executable] 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 - id: check-json
name: check json name: check json
description: checks json files for parseable syntax. description: checks json files for parseable syntax.
@ -52,7 +59,8 @@
entry: check-shebang-scripts-are-executable entry: check-shebang-scripts-are-executable
language: python language: python
types: [text] types: [text]
stages: [commit, push, manual] stages: [pre-commit, pre-push, manual]
minimum_pre_commit_version: 3.2.0
- id: pretty-format-json - id: pretty-format-json
name: pretty format json name: pretty format json
description: sets a standard for formatting json files. description: sets a standard for formatting json files.
@ -107,6 +115,7 @@
entry: destroyed-symlinks entry: destroyed-symlinks
language: python language: python
types: [file] types: [file]
stages: [pre-commit, pre-push, manual]
- id: detect-aws-credentials - id: detect-aws-credentials
name: detect aws credentials name: detect aws credentials
description: detects *your* aws credentials from the aws cli credentials file. description: detects *your* aws credentials from the aws cli credentials file.
@ -131,7 +140,8 @@
entry: end-of-file-fixer entry: end-of-file-fixer
language: python language: python
types: [text] types: [text]
stages: [commit, push, manual] stages: [pre-commit, pre-push, manual]
minimum_pre_commit_version: 3.2.0
- id: file-contents-sorter - id: file-contents-sorter
name: 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. 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 language: python
types: [text] types: [text]
- id: fix-encoding-pragma - 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.' description: 'adds # -*- coding: utf-8 -*- to the top of python files.'
language: python language: python
entry: fix-encoding-pragma entry: fix-encoding-pragma
@ -198,4 +208,5 @@
entry: trailing-whitespace-fixer entry: trailing-whitespace-fixer
language: python language: python
types: [text] types: [text]
stages: [commit, push, manual] stages: [pre-commit, pre-push, manual]
minimum_pre_commit_version: 3.2.0

View file

@ -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 4.4.0 - 2022-11-23
================== ==================

View file

@ -15,7 +15,7 @@ Add this to your `.pre-commit-config.yaml`
```yaml ```yaml
- repo: https://github.com/pre-commit/pre-commit-hooks - 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: hooks:
- id: trailing-whitespace - id: trailing-whitespace
# - id: ... # - id: ...
@ -51,6 +51,9 @@ 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.
#### `check-illegal-windows-names`
Check for files that cannot be created on Windows.
#### `check-json` #### `check-json`
Attempts to load all json files to verify syntax. 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). Sort the lines in specified files (defaults to alphabetical).
You must provide the target [`files`](https://pre-commit.com/#config-files) as input. 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. 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: The following arguments are available:
- `--ignore-case` - fold lower case to upper case characters. - `--ignore-case` - fold lower case to upper case characters.
@ -126,6 +130,9 @@ The following arguments are available:
removes UTF-8 byte order marker removes UTF-8 byte order marker
#### `fix-encoding-pragma` #### `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. Add `# -*- coding: utf-8 -*-` to the top of python files.
- To remove the coding pragma pass `--remove` (useful in a python3-only codebase) - To remove the coding pragma pass `--remove` (useful in a python3-only codebase)

View file

@ -4,7 +4,7 @@ import argparse
import math import math
import os import os
import subprocess 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 added_files
from pre_commit_hooks.util import zsplit from pre_commit_hooks.util import zsplit
@ -46,7 +46,7 @@ def find_large_added_files(
filenames_filtered &= added_files() filenames_filtered &= added_files()
for filename in filenames_filtered: 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: if kb > maxkb:
print(f'{filename} ({kb} KB) exceeds {maxkb} KB.') print(f'{filename} ({kb} KB) exceeds {maxkb} KB.')
retv = 1 retv = 1

View file

@ -5,7 +5,7 @@ import ast
import platform import platform
import sys import sys
import traceback import traceback
from typing import Sequence from collections.abc import Sequence
def main(argv: Sequence[str] | None = None) -> int: def main(argv: Sequence[str] | None = None) -> int:

View file

@ -2,8 +2,8 @@ from __future__ import annotations
import argparse import argparse
import ast import ast
from collections.abc import Sequence
from typing import NamedTuple from typing import NamedTuple
from typing import Sequence
BUILTIN_TYPES = { BUILTIN_TYPES = {

View file

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from typing import Sequence from collections.abc import Sequence
def main(argv: Sequence[str] | None = None) -> int: def main(argv: Sequence[str] | None = None) -> int:

View file

@ -1,9 +1,9 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from typing import Iterable from collections.abc import Iterable
from typing import Iterator from collections.abc import Iterator
from typing import Sequence from collections.abc import Sequence
from pre_commit_hooks.util import added_files from pre_commit_hooks.util import added_files
from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import cmd_output

View file

@ -3,8 +3,8 @@ from __future__ import annotations
import argparse import argparse
import io import io
import tokenize import tokenize
from collections.abc import Sequence
from tokenize import tokenize as tokenize_tokenize from tokenize import tokenize as tokenize_tokenize
from typing import Sequence
NON_CODE_TOKENS = frozenset(( NON_CODE_TOKENS = frozenset((
tokenize.COMMENT, tokenize.ENDMARKER, tokenize.NEWLINE, tokenize.NL, tokenize.COMMENT, tokenize.ENDMARKER, tokenize.NEWLINE, tokenize.NL,

View file

@ -4,9 +4,9 @@ from __future__ import annotations
import argparse import argparse
import shlex import shlex
import sys import sys
from typing import Generator from collections.abc import Generator
from collections.abc import Sequence
from typing import NamedTuple from typing import NamedTuple
from typing import Sequence
from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import cmd_output
from pre_commit_hooks.util import zsplit from pre_commit_hooks.util import zsplit
@ -35,7 +35,7 @@ class GitLsFile(NamedTuple):
filename: str 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) outs = cmd_output('git', 'ls-files', '-z', '--stage', '--', *paths)
for out in zsplit(outs): for out in zsplit(outs):
metadata, filename = out.split('\t') metadata, filename = out.split('\t')

View file

@ -2,8 +2,8 @@ from __future__ import annotations
import argparse import argparse
import json import json
from collections.abc import Sequence
from typing import Any from typing import Any
from typing import Sequence
def raise_duplicate_keys( def raise_duplicate_keys(

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import argparse import argparse
import os.path import os.path
from typing import Sequence from collections.abc import Sequence
from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import cmd_output

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import argparse import argparse
import shlex import shlex
import sys 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 EXECUTABLE_VALUES
from pre_commit_hooks.check_executables_have_shebangs import git_ls_files 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'`chmod +x {shlex.quote(path)}`\n'
f' If on Windows, you may also need to: ' f' If on Windows, you may also need to: '
f'`git add --chmod=+x {shlex.quote(path)}`\n' 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', f'is wanted.\n',
file=sys.stderr, file=sys.stderr,
) )

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import argparse import argparse
import os.path import os.path
from typing import Sequence from collections.abc import Sequence
def main(argv: Sequence[str] | None = None) -> int: def main(argv: Sequence[str] | None = None) -> int:

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import argparse import argparse
import sys import sys
from typing import Sequence from collections.abc import Sequence
if sys.version_info >= (3, 11): # pragma: >=3.11 cover if sys.version_info >= (3, 11): # pragma: >=3.11 cover
import tomllib import tomllib

View file

@ -3,8 +3,8 @@ from __future__ import annotations
import argparse import argparse
import re import re
import sys import sys
from typing import Pattern from collections.abc import Sequence
from typing import Sequence from re import Pattern
def _get_pattern(domain: str) -> Pattern[bytes]: def _get_pattern(domain: str) -> Pattern[bytes]:

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import argparse import argparse
import xml.sax.handler import xml.sax.handler
from typing import Sequence from collections.abc import Sequence
def main(argv: Sequence[str] | None = None) -> int: def main(argv: Sequence[str] | None = None) -> int:

View file

@ -1,17 +1,17 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from collections.abc import Generator
from collections.abc import Sequence
from typing import Any from typing import Any
from typing import Generator
from typing import NamedTuple from typing import NamedTuple
from typing import Sequence
import ruamel.yaml import ruamel.yaml
yaml = ruamel.yaml.YAML(typ='safe') yaml = ruamel.yaml.YAML(typ='safe')
def _exhaust(gen: Generator[str, None, None]) -> None: def _exhaust(gen: Generator[str]) -> None:
for _ in gen: for _ in gen:
pass pass
@ -46,7 +46,7 @@ def main(argv: Sequence[str] | None = None) -> int:
'--unsafe', action='store_true', '--unsafe', action='store_true',
help=( help=(
'Instead of loading the files, simply parse them for syntax. ' '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 ' 'which would otherwise be forbidden. Using this option removes '
'all guarantees of portability to other yaml implementations. ' 'all guarantees of portability to other yaml implementations. '
'Implies --allow-multiple-documents' 'Implies --allow-multiple-documents'

View file

@ -3,8 +3,8 @@ from __future__ import annotations
import argparse import argparse
import ast import ast
import traceback import traceback
from collections.abc import Sequence
from typing import NamedTuple from typing import NamedTuple
from typing import Sequence
DEBUG_STATEMENTS = { DEBUG_STATEMENTS = {

View file

@ -3,7 +3,7 @@ from __future__ import annotations
import argparse import argparse
import shlex import shlex
import subprocess 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 cmd_output
from pre_commit_hooks.util import zsplit from pre_commit_hooks.util import zsplit

View file

@ -3,8 +3,8 @@ from __future__ import annotations
import argparse import argparse
import configparser import configparser
import os import os
from collections.abc import Sequence
from typing import NamedTuple from typing import NamedTuple
from typing import Sequence
class BadFile(NamedTuple): class BadFile(NamedTuple):

View file

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from typing import Sequence from collections.abc import Sequence
BLACKLIST = [ BLACKLIST = [
b'BEGIN RSA PRIVATE KEY', b'BEGIN RSA PRIVATE KEY',

View file

@ -2,8 +2,8 @@ from __future__ import annotations
import argparse import argparse
import os import os
from collections.abc import Sequence
from typing import IO from typing import IO
from typing import Sequence
def fix_file(file_obj: IO[bytes]) -> int: def fix_file(file_obj: IO[bytes]) -> int:

View file

@ -12,11 +12,11 @@ conflicts and keep the file nicely ordered.
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from collections.abc import Iterable
from collections.abc import Sequence
from typing import Any from typing import Any
from typing import Callable from typing import Callable
from typing import IO from typing import IO
from typing import Iterable
from typing import Sequence
PASS = 0 PASS = 0
FAIL = 1 FAIL = 1
@ -54,18 +54,21 @@ def sort_file_contents(
def main(argv: Sequence[str] | None = None) -> int: def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='+', help='Files to sort') 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', '--ignore-case',
action='store_const', action='store_const',
const=bytes.lower, const=bytes.lower,
default=None, default=None,
help='fold lower case to upper case characters', help='fold lower case to upper case characters',
) )
parser.add_argument( mutex.add_argument(
'--unique', '--unique',
action='store_true', action='store_true',
help='ensure each line is unique', help='ensure each line is unique',
) )
args = parser.parse_args(argv) args = parser.parse_args(argv)
retv = PASS retv = PASS

View file

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from typing import Sequence from collections.abc import Sequence
def main(argv: Sequence[str] | None = None) -> int: def main(argv: Sequence[str] | None = None) -> int:

View file

@ -1,9 +1,10 @@
from __future__ import annotations from __future__ import annotations
import argparse import argparse
import sys
from collections.abc import Sequence
from typing import IO from typing import IO
from typing import NamedTuple from typing import NamedTuple
from typing import Sequence
DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-' DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-'
@ -107,6 +108,13 @@ def _normalize_pragma(pragma: str) -> bytes:
def main(argv: Sequence[str] | None = None) -> int: 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( parser = argparse.ArgumentParser(
'Fixes the encoding pragma of python files', 'Fixes the encoding pragma of python files',
) )

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import argparse import argparse
import os import os
from typing import Sequence from collections.abc import Sequence
from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import cmd_output

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import argparse import argparse
import collections import collections
from typing import Sequence from collections.abc import Sequence
CRLF = b'\r\n' CRLF = b'\r\n'

View file

@ -2,8 +2,8 @@ from __future__ import annotations
import argparse import argparse
import re import re
from collections.abc import Sequence
from typing import AbstractSet from typing import AbstractSet
from typing import Sequence
from pre_commit_hooks.util import CalledProcessError from pre_commit_hooks.util import CalledProcessError
from pre_commit_hooks.util import cmd_output from pre_commit_hooks.util import cmd_output

View file

@ -4,9 +4,9 @@ import argparse
import json import json
import re import re
import sys import sys
from collections.abc import Mapping
from collections.abc import Sequence
from difflib import unified_diff from difflib import unified_diff
from typing import Mapping
from typing import Sequence
def _insert_linebreaks(json_str: str) -> str: def _insert_linebreaks(json_str: str) -> str:
return re.sub( return re.sub(
@ -135,18 +135,23 @@ def main(argv: Sequence[str] | None = None) -> int:
f'Input File {json_file} is not a valid JSON, consider using ' f'Input File {json_file} is not a valid JSON, consider using '
f'check-json', f'check-json',
) )
return 1
if contents != pretty_contents:
status = 1 status = 1
else:
if contents != pretty_contents:
if args.autofix: if args.autofix:
_autofix(json_file, pretty_contents) _autofix(json_file, pretty_contents)
if args.empty_object_with_newline: if args.empty_object_with_newline:
status = 0 status = 0
else: else:
diff_output = get_diff(contents, pretty_contents, json_file) diff_output = get_diff(
contents,
pretty_contents,
json_file
)
sys.stdout.buffer.write(diff_output.encode()) sys.stdout.buffer.write(diff_output.encode())
status = 1
return status return status

View file

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from typing import Sequence from collections.abc import Sequence
def main(argv: Sequence[str] | None = None) -> int: def main(argv: Sequence[str] | None = None) -> int:

View file

@ -2,8 +2,8 @@ from __future__ import annotations
import argparse import argparse
import re import re
from collections.abc import Sequence
from typing import IO from typing import IO
from typing import Sequence
PASS = 0 PASS = 0
@ -45,6 +45,11 @@ class Requirement:
elif requirement.value == b'\n': elif requirement.value == b'\n':
return False return False
else: 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 return self.name < requirement.name
def is_complete(self) -> bool: 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 # which is automatically added by broken pip package under Debian
requirements = [ requirements = [
req for req in 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): for requirement in sorted(requirements):
after.extend(requirement.comments) after.extend(requirement.comments)
assert requirement.value, requirement.value assert requirement.value, requirement.value
if prev is None or requirement.value != prev.value:
after.append(requirement.value) after.append(requirement.value)
prev = requirement
after.extend(rest) after.extend(rest)
after_string = b''.join(after) after_string = b''.join(after)

View file

@ -20,7 +20,7 @@ complicated YAML files.
from __future__ import annotations from __future__ import annotations
import argparse import argparse
from typing import Sequence from collections.abc import Sequence
QUOTES = ["'", '"'] QUOTES = ["'", '"']

View file

@ -3,8 +3,15 @@ from __future__ import annotations
import argparse import argparse
import io import io
import re import re
import sys
import tokenize 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]*"') START_QUOTE_RE = re.compile('^[a-zA-Z]*"')
@ -40,11 +47,17 @@ def fix_strings(filename: str) -> int:
# Basically a mutable string # Basically a mutable string
splitcontents = list(contents) splitcontents = list(contents)
fstring_depth = 0
# Iterate in reverse so the offsets are always correct # Iterate in reverse so the offsets are always correct
tokens_l = list(tokenize.generate_tokens(io.StringIO(contents).readline)) tokens_l = list(tokenize.generate_tokens(io.StringIO(contents).readline))
tokens = reversed(tokens_l) tokens = reversed(tokens_l)
for token_type, token_text, (srow, scol), (erow, ecol), _ in tokens: 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) new_text = handle_match(token_text)
splitcontents[ splitcontents[
line_offsets[srow] + scol: line_offsets[srow] + scol:

View file

@ -3,7 +3,7 @@ from __future__ import annotations
import argparse import argparse
import os.path import os.path
import re import re
from typing import Sequence from collections.abc import Sequence
def main(argv: Sequence[str] | None = None) -> int: def main(argv: Sequence[str] | None = None) -> int:

View file

@ -2,7 +2,7 @@ from __future__ import annotations
import argparse import argparse
import os import os
from typing import Sequence from collections.abc import Sequence
def _fix_file( def _fix_file(

View file

@ -1,6 +1,6 @@
[metadata] [metadata]
name = pre_commit_hooks name = pre_commit_hooks
version = 4.4.0 version = 5.0.0
description = Some out-of-the-box hooks for pre-commit. description = Some out-of-the-box hooks for pre-commit.
long_description = file: README.md long_description = file: README.md
long_description_content_type = text/markdown long_description_content_type = text/markdown
@ -21,7 +21,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.8 python_requires = >=3.9
[options.packages.find] [options.packages.find]
exclude = exclude =

View file

@ -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

View file

@ -67,18 +67,6 @@ from pre_commit_hooks.file_contents_sorter import PASS
FAIL, FAIL,
b'Fie\nFoe\nfee\nfum\n', 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): 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 path.read_binary() == output
assert output_retval == expected_retval 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)

View file

@ -83,6 +83,24 @@ def test_autofix_main(tmpdir):
assert ret == 0 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(): def test_orderfile_get_pretty_format():
ret = main(( ret = main((
'--top-keys=alist', get_resource_path('pretty_formatted_json.json'), '--top-keys=alist', get_resource_path('pretty_formatted_json.json'),

View file

@ -68,6 +68,12 @@ from pre_commit_hooks.requirements_txt_fixer import Requirement
b'f<=2\n' b'f<=2\n'
b'g<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'ocflib\nDjango\nPyMySQL\n', FAIL, b'Django\nocflib\nPyMySQL\n'),
( (
b'-e git+ssh://git_url@tag#egg=ocflib\nDjango\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'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'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', b'git+ssh://git_url@tag#egg=ocflib\nDjango\nijk\n',
FAIL, FAIL,

View file

@ -37,6 +37,12 @@ TESTS = (
1, 1,
), ),
('"foo""bar"', "'foo''bar'", 1), ('"foo""bar"', "'foo''bar'", 1),
pytest.param(
"f'hello{\"world\"}'",
"f'hello{\"world\"}'",
0,
id='ignore nested fstrings',
),
) )