summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--.pre-commit-config.yaml46
-rw-r--r--.pre-commit-hooks.yaml5
-rw-r--r--CHANGELOG.md44
-rw-r--r--README.md23
-rw-r--r--azure-pipelines.yml6
-rw-r--r--pre_commit_hooks/check_added_large_files.py8
-rw-r--r--pre_commit_hooks/check_ast.py5
-rw-r--r--pre_commit_hooks/check_builtin_literals.py17
-rw-r--r--pre_commit_hooks/check_byte_order_marker.py5
-rw-r--r--pre_commit_hooks/check_case_conflict.py10
-rw-r--r--pre_commit_hooks/check_docstring_first.py9
-rw-r--r--pre_commit_hooks/check_executables_have_shebangs.py16
-rw-r--r--pre_commit_hooks/check_json.py12
-rw-r--r--pre_commit_hooks/check_merge_conflict.py12
-rw-r--r--pre_commit_hooks/check_shebang_scripts_are_executable.py13
-rw-r--r--pre_commit_hooks/check_symlinks.py5
-rw-r--r--pre_commit_hooks/check_toml.py16
-rw-r--r--pre_commit_hooks/check_vcs_permalinks.py8
-rw-r--r--pre_commit_hooks/check_xml.py5
-rw-r--r--pre_commit_hooks/check_yaml.py5
-rw-r--r--pre_commit_hooks/debug_statement_hook.py10
-rw-r--r--pre_commit_hooks/destroyed_symlinks.py10
-rw-r--r--pre_commit_hooks/detect_aws_credentials.py19
-rw-r--r--pre_commit_hooks/detect_private_key.py5
-rw-r--r--pre_commit_hooks/end_of_file_fixer.py5
-rw-r--r--pre_commit_hooks/file_contents_sorter.py7
-rw-r--r--pre_commit_hooks/fix_byte_order_marker.py5
-rw-r--r--pre_commit_hooks/fix_encoding_pragma.py9
-rw-r--r--pre_commit_hooks/forbid_new_submodules.py5
-rw-r--r--pre_commit_hooks/mixed_line_ending.py8
-rw-r--r--pre_commit_hooks/no_commit_to_branch.py5
-rw-r--r--pre_commit_hooks/pretty_format_json.py14
-rw-r--r--pre_commit_hooks/removed.py5
-rw-r--r--pre_commit_hooks/requirements_txt_fixer.py16
-rw-r--r--pre_commit_hooks/sort_simple_yaml.py14
-rw-r--r--pre_commit_hooks/string_fixer.py8
-rw-r--r--pre_commit_hooks/tests_should_end_in_test.py36
-rw-r--r--pre_commit_hooks/trailing_whitespace_fixer.py9
-rw-r--r--pre_commit_hooks/util.py11
-rw-r--r--setup.cfg7
-rw-r--r--setup.py2
-rw-r--r--testing/util.py2
-rw-r--r--tests/check_added_large_files_test.py2
-rw-r--r--tests/check_ast_test.py2
-rw-r--r--tests/check_builtin_literals_test.py2
-rw-r--r--tests/check_byte_order_marker_test.py2
-rw-r--r--tests/check_case_conflict_test.py2
-rw-r--r--tests/check_docstring_first_test.py8
-rw-r--r--tests/check_executables_have_shebangs_test.py2
-rw-r--r--tests/check_json_test.py2
-rw-r--r--tests/check_merge_conflict_test.py14
-rw-r--r--tests/check_shebang_scripts_are_executable_test.py2
-rw-r--r--tests/check_symlinks_test.py2
-rw-r--r--tests/check_toml_test.py2
-rw-r--r--tests/check_vcs_permalinks_test.py2
-rw-r--r--tests/check_xml_test.py2
-rw-r--r--tests/check_yaml_test.py2
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/debug_statement_hook_test.py6
-rw-r--r--tests/destroyed_symlinks_test.py2
-rw-r--r--tests/detect_aws_credentials_test.py2
-rw-r--r--tests/detect_private_key_test.py2
-rw-r--r--tests/end_of_file_fixer_test.py2
-rw-r--r--tests/file_contents_sorter_test.py2
-rw-r--r--tests/fix_byte_order_marker_test.py2
-rw-r--r--tests/fix_encoding_pragma_test.py2
-rw-r--r--tests/forbid_new_submodules_test.py2
-rw-r--r--tests/mixed_line_ending_test.py2
-rw-r--r--tests/no_commit_to_branch_test.py2
-rw-r--r--tests/pretty_format_json_test.py2
-rw-r--r--tests/readme_test.py2
-rw-r--r--tests/removed_test.py2
-rw-r--r--tests/requirements_txt_fixer_test.py2
-rw-r--r--tests/sort_simple_yaml_test.py2
-rw-r--r--tests/string_fixer_test.py2
-rw-r--r--tests/tests_should_end_in_test_test.py7
-rw-r--r--tests/trailing_whitespace_fixer_test.py2
-rw-r--r--tests/util_test.py2
-rw-r--r--tox.ini4
80 files changed, 380 insertions, 210 deletions
diff --git a/.gitignore b/.gitignore
index 32c2fec..4f6c5b7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,9 +3,4 @@
.*.sw[a-z]
.coverage
.tox
-.venv.touch
-/.mypy_cache
-/.pytest_cache
-/venv*
-coverage-html
dist
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 4639bc6..0440edf 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,47 +1,43 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.1.0
+ rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- - id: check-docstring-first
- - id: check-json
- - id: check-added-large-files
- id: check-yaml
- id: debug-statements
- - id: name-tests-test
- id: double-quote-string-fixer
+ - id: name-tests-test
- id: requirements-txt-fixer
-- repo: https://github.com/PyCQA/flake8
- rev: 4.0.1
- hooks:
- - id: flake8
- additional_dependencies: [flake8-typing-imports==1.7.0]
-- repo: https://github.com/pre-commit/mirrors-autopep8
- rev: v1.5.7
+- repo: https://github.com/asottile/setup-cfg-fmt
+ rev: v1.20.1
hooks:
- - id: autopep8
+ - id: setup-cfg-fmt
- repo: https://github.com/asottile/reorder_python_imports
- rev: v2.6.0
+ rev: v3.1.0
hooks:
- id: reorder-python-imports
- args: [--py3-plus]
-- repo: https://github.com/asottile/pyupgrade
- rev: v2.29.1
- hooks:
- - id: pyupgrade
- args: [--py36-plus]
+ args: [--py37-plus, --add-import, 'from __future__ import annotations']
- repo: https://github.com/asottile/add-trailing-comma
- rev: v2.2.1
+ rev: v2.2.3
hooks:
- id: add-trailing-comma
args: [--py36-plus]
-- repo: https://github.com/asottile/setup-cfg-fmt
- rev: v1.20.0
+- repo: https://github.com/asottile/pyupgrade
+ rev: v2.32.1
hooks:
- - id: setup-cfg-fmt
+ - id: pyupgrade
+ args: [--py37-plus]
+- repo: https://github.com/pre-commit/mirrors-autopep8
+ rev: v1.6.0
+ hooks:
+ - id: autopep8
+- repo: https://github.com/PyCQA/flake8
+ rev: 4.0.1
+ hooks:
+ - id: flake8
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: v0.920
+ rev: v0.960
hooks:
- id: mypy
additional_dependencies: [types-all]
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
index 5690660..1a6056b 100644
--- a/.pre-commit-hooks.yaml
+++ b/.pre-commit-hooks.yaml
@@ -3,6 +3,7 @@
description: prevents giant files from being committed.
entry: check-added-large-files
language: python
+ stages: [commit, push, manual]
- id: check-ast
name: check python ast
description: simply checks whether the files parse as valid python.
@@ -162,8 +163,8 @@
language: python
types: [text]
- id: name-tests-test
- name: tests should end in _test.py
- description: this verifies that test files are named correctly.
+ name: python tests naming
+ description: verifies that test files are named correctly.
entry: name-tests-test
language: python
files: (^|/)tests/.+\.py$
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50e7202..d6e3171 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,47 @@
+4.3.0 - 2022-06-07
+==================
+
+### Features
+- `check-executables-have-shebangs`: use `git config core.fileMode` to
+ determine if it should query `git`.
+ - #730 PR by @Kurt-von-Laven.
+- `name-tests-test`: add `--pytest-test-first` test convention.
+ - #779 PR by @asottile.
+
+### Fixes
+- `check-shebang-scripts-are-executable`: update windows instructions.
+ - #774 PR by @mdeweerd.
+ - #770 issue by @mdeweerd.
+- `check-toml`: use stdlib `tomllib` when available.
+ - #771 PR by @DanielNoord.
+ - #755 issue by @sognetic.
+- `check-added-large-files`: don't run on non-file `stages`.
+ - #778 PR by @asottile.
+ - #777 issue by @skyj.
+
+4.2.0 - 2022-04-06
+==================
+
+### Features
+- `name-tests-test`: updated display text.
+ - #713 PR by @asottile.
+- `check-docstring-first`: make output more parsable.
+ - #748 PR by @asottile.
+- `check-merge-conflict`: make output more parsable.
+ - #748 PR by @asottile.
+- `debug-statements`: make output more parsable.
+ - #748 PR by @asottile.
+
+### Fixes
+- `check-merge-conflict`: fix detection of `======` conflict marker on windows.
+ - #748 PR by @asottile.
+
+### Updating
+- Drop python<3.7.
+ - #719 PR by @asottile.
+- Changed default branch from `master` to `main`.
+ - #744 PR by @asottile.
+
4.1.0 - 2021-12-22
==================
diff --git a/README.md b/README.md
index c5a199e..411529a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-[![Build Status](https://asottile.visualstudio.com/asottile/_apis/build/status/pre-commit.pre-commit-hooks?branchName=master)](https://asottile.visualstudio.com/asottile/_build/latest?definitionId=17&branchName=master)
-[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/17/master.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=17&branchName=master)
-[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pre-commit/pre-commit-hooks/master.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit-hooks/master)
+[![Build Status](https://asottile.visualstudio.com/asottile/_apis/build/status/pre-commit.pre-commit-hooks?branchName=main)](https://asottile.visualstudio.com/asottile/_build/latest?definitionId=17&branchName=main)
+[![Azure DevOps coverage](https://img.shields.io/azure-devops/coverage/asottile/asottile/17/main.svg)](https://dev.azure.com/asottile/asottile/_build/latest?definitionId=17&branchName=main)
+[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/pre-commit/pre-commit-hooks/main.svg)](https://results.pre-commit.ci/latest/github/pre-commit/pre-commit-hooks/main)
pre-commit-hooks
================
@@ -16,7 +16,7 @@ Add this to your `.pre-commit-config.yaml`
```yaml
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.1.0 # Use the ref you want to point at
+ rev: v4.3.0 # Use the ref you want to point at
hooks:
- id: trailing-whitespace
# - id: ...
@@ -57,6 +57,7 @@ Attempts to load all json files to verify syntax.
#### `check-merge-conflict`
Check for files that contain merge conflict strings.
+ - `--assume-in-merge` - Allows running the hook when there is no ongoing merge operation
#### `check-shebang-scripts-are-executable`
Checks that scripts with shebangs are executable.
@@ -125,6 +126,10 @@ Sort the lines in specified files (defaults to alphabetical).
You must provide list of target files as input to it.
Note that this hook WILL remove blank lines and does NOT respect any comments.
+The following arguments are available:
+- `--ignore-case` - fold lower case to upper case characters.
+- `--unique` - ensure each line is unique.
+
#### `forbid-new-submodules`
Prevent addition of new git submodules.
@@ -137,13 +142,15 @@ Replaces or checks mixed line ending.
- `no` - Checks if there is any mixed line ending without modifying any file.
#### `name-tests-test`
-Assert that files in tests/ end in `_test.py`.
- - Use `args: ['--django']` to match `test*.py` instead.
+verifies that test files are named correctly.
+- `--pytest` (the default): ensure tests match `.*_test\.py`
+- `--pytest-test-first`: ensure tests match `test_.*\.py`
+- `--django` / `--unittest`: ensure tests match `test.*\.py`
#### `no-commit-to-branch`
Protect specific branches from direct checkins.
- - Use `args: [--branch, staging, --branch, master]` to set the branch.
- Both `master` and `main` are protected by default if no branch argument is set.
+ - Use `args: [--branch, staging, --branch, main]` to set the branch.
+ Both `main` and `master` are protected by default if no branch argument is set.
- `-b` / `--branch` may be specified multiple times to protect multiple
branches.
- `-p` / `--pattern` can be used to protect branches that match a supplied regex
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 58dc61d..117b014 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -1,6 +1,6 @@
trigger:
branches:
- include: [master, test-me-*]
+ include: [main, test-me-*]
tags:
include: ['*']
@@ -10,7 +10,7 @@ resources:
type: github
endpoint: github
name: asottile/azure-pipeline-templates
- ref: refs/tags/v2.1.0
+ ref: refs/tags/v2.4.0
jobs:
- template: job--python-tox.yml@asottile
@@ -19,5 +19,5 @@ jobs:
os: windows
- template: job--python-tox.yml@asottile
parameters:
- toxenvs: [pypy3, py36, py37, py38]
+ toxenvs: [py37, py38, py39, py310]
os: linux
diff --git a/pre_commit_hooks/check_added_large_files.py b/pre_commit_hooks/check_added_large_files.py
index e1beb4e..79c8d4e 100644
--- a/pre_commit_hooks/check_added_large_files.py
+++ b/pre_commit_hooks/check_added_large_files.py
@@ -1,16 +1,16 @@
+from __future__ import annotations
+
import argparse
import math
import os
import subprocess
-from typing import Optional
from typing import Sequence
-from typing import Set
from pre_commit_hooks.util import added_files
from pre_commit_hooks.util import zsplit
-def filter_lfs_files(filenames: Set[str]) -> None: # pragma: no cover (lfs)
+def filter_lfs_files(filenames: set[str]) -> None: # pragma: no cover (lfs)
"""Remove files tracked by git-lfs from the set."""
if not filenames:
return
@@ -54,7 +54,7 @@ def find_large_added_files(
return retv
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'filenames', nargs='*',
diff --git a/pre_commit_hooks/check_ast.py b/pre_commit_hooks/check_ast.py
index ab5661d..fdac361 100644
--- a/pre_commit_hooks/check_ast.py
+++ b/pre_commit_hooks/check_ast.py
@@ -1,13 +1,14 @@
+from __future__ import annotations
+
import argparse
import ast
import platform
import sys
import traceback
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_builtin_literals.py b/pre_commit_hooks/check_builtin_literals.py
index 3fbae3e..d3054aa 100644
--- a/pre_commit_hooks/check_builtin_literals.py
+++ b/pre_commit_hooks/check_builtin_literals.py
@@ -1,10 +1,9 @@
+from __future__ import annotations
+
import argparse
import ast
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
-from typing import Set
BUILTIN_TYPES = {
@@ -27,10 +26,10 @@ class Call(NamedTuple):
class Visitor(ast.NodeVisitor):
def __init__(
self,
- ignore: Optional[Sequence[str]] = None,
+ ignore: Sequence[str] | None = None,
allow_dict_kwargs: bool = True,
) -> None:
- self.builtin_type_calls: List[Call] = []
+ self.builtin_type_calls: list[Call] = []
self.ignore = set(ignore) if ignore else set()
self.allow_dict_kwargs = allow_dict_kwargs
@@ -56,9 +55,9 @@ class Visitor(ast.NodeVisitor):
def check_file(
filename: str,
- ignore: Optional[Sequence[str]] = None,
+ ignore: Sequence[str] | None = None,
allow_dict_kwargs: bool = True,
-) -> List[Call]:
+) -> list[Call]:
with open(filename, 'rb') as f:
tree = ast.parse(f.read(), filename=filename)
visitor = Visitor(ignore=ignore, allow_dict_kwargs=allow_dict_kwargs)
@@ -66,11 +65,11 @@ def check_file(
return visitor.builtin_type_calls
-def parse_ignore(value: str) -> Set[str]:
+def parse_ignore(value: str) -> set[str]:
return set(value.split(','))
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
parser.add_argument('--ignore', type=parse_ignore, default=set())
diff --git a/pre_commit_hooks/check_byte_order_marker.py b/pre_commit_hooks/check_byte_order_marker.py
index fda05e8..59cc561 100644
--- a/pre_commit_hooks/check_byte_order_marker.py
+++ b/pre_commit_hooks/check_byte_order_marker.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+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)
diff --git a/pre_commit_hooks/check_case_conflict.py b/pre_commit_hooks/check_case_conflict.py
index c3f39db..33a13f1 100644
--- a/pre_commit_hooks/check_case_conflict.py
+++ b/pre_commit_hooks/check_case_conflict.py
@@ -1,15 +1,15 @@
+from __future__ import annotations
+
import argparse
from typing import Iterable
from typing import Iterator
-from typing import Optional
from typing import Sequence
-from typing import Set
from pre_commit_hooks.util import added_files
from pre_commit_hooks.util import cmd_output
-def lower_set(iterable: Iterable[str]) -> Set[str]:
+def lower_set(iterable: Iterable[str]) -> set[str]:
return {x.lower() for x in iterable}
@@ -21,7 +21,7 @@ def parents(file: str) -> Iterator[str]:
path_parts.pop()
-def directories_for(files: Set[str]) -> Set[str]:
+def directories_for(files: set[str]) -> set[str]:
return {parent for file in files for parent in parents(file)}
@@ -56,7 +56,7 @@ def find_conflicting_filenames(filenames: Sequence[str]) -> int:
return retv
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'filenames', nargs='*',
diff --git a/pre_commit_hooks/check_docstring_first.py b/pre_commit_hooks/check_docstring_first.py
index 875c0fb..d55f08a 100644
--- a/pre_commit_hooks/check_docstring_first.py
+++ b/pre_commit_hooks/check_docstring_first.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import argparse
import io
import tokenize
from tokenize import tokenize as tokenize_tokenize
-from typing import Optional
from typing import Sequence
NON_CODE_TOKENS = frozenset((
@@ -27,13 +28,13 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
if tok_type == tokenize.STRING and scol == 0:
if found_docstring_line is not None:
print(
- f'{filename}:{sline} Multiple module docstrings '
+ f'{filename}:{sline}: Multiple module docstrings '
f'(first docstring on line {found_docstring_line}).',
)
return 1
elif found_code_line is not None:
print(
- f'{filename}:{sline} Module docstring appears after code '
+ f'{filename}:{sline}: Module docstring appears after code '
f'(code seen on line {found_code_line}).',
)
return 1
@@ -45,7 +46,7 @@ def check_docstring_first(src: bytes, filename: str = '<unknown>') -> int:
return 0
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_executables_have_shebangs.py b/pre_commit_hooks/check_executables_have_shebangs.py
index 34af5ca..d8e4f49 100644
--- a/pre_commit_hooks/check_executables_have_shebangs.py
+++ b/pre_commit_hooks/check_executables_have_shebangs.py
@@ -1,13 +1,12 @@
"""Check that executable text files have a shebang."""
+from __future__ import annotations
+
import argparse
import shlex
import sys
from typing import Generator
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
-from typing import Set
from pre_commit_hooks.util import cmd_output
from pre_commit_hooks.util import zsplit
@@ -15,8 +14,11 @@ from pre_commit_hooks.util import zsplit
EXECUTABLE_VALUES = frozenset(('1', '3', '5', '7'))
-def check_executables(paths: List[str]) -> int:
- if sys.platform == 'win32': # pragma: win32 cover
+def check_executables(paths: list[str]) -> int:
+ fs_tracks_executable_bit = cmd_output(
+ 'git', 'config', 'core.fileMode', retcode=None,
+ ).strip()
+ if fs_tracks_executable_bit == 'false': # pragma: win32 cover
return _check_git_filemode(paths)
else: # pragma: win32 no cover
retv = 0
@@ -42,7 +44,7 @@ def git_ls_files(paths: Sequence[str]) -> Generator[GitLsFile, None, None]:
def _check_git_filemode(paths: Sequence[str]) -> int:
- seen: Set[str] = set()
+ seen: set[str] = set()
for ls_file in git_ls_files(paths):
is_executable = any(b in EXECUTABLE_VALUES for b in ls_file.mode[-3:])
if is_executable and not has_shebang(ls_file.filename):
@@ -71,7 +73,7 @@ def _message(path: str) -> None:
)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_json.py b/pre_commit_hooks/check_json.py
index 96ba024..6a679fe 100644
--- a/pre_commit_hooks/check_json.py
+++ b/pre_commit_hooks/check_json.py
@@ -1,16 +1,14 @@
+from __future__ import annotations
+
import argparse
import json
from typing import Any
-from typing import Dict
-from typing import List
-from typing import Optional
from typing import Sequence
-from typing import Tuple
def raise_duplicate_keys(
- ordered_pairs: List[Tuple[str, Any]],
-) -> Dict[str, Any]:
+ ordered_pairs: list[tuple[str, Any]],
+) -> dict[str, Any]:
d = {}
for key, val in ordered_pairs:
if key in d:
@@ -20,7 +18,7 @@ def raise_duplicate_keys(
return d
-def main(argv: Optional[Sequence[str]] = None) -> int:
+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)
diff --git a/pre_commit_hooks/check_merge_conflict.py b/pre_commit_hooks/check_merge_conflict.py
index aed1e9c..15ec284 100644
--- a/pre_commit_hooks/check_merge_conflict.py
+++ b/pre_commit_hooks/check_merge_conflict.py
@@ -1,6 +1,7 @@
+from __future__ import annotations
+
import argparse
import os.path
-from typing import Optional
from typing import Sequence
from pre_commit_hooks.util import cmd_output
@@ -9,6 +10,7 @@ from pre_commit_hooks.util import cmd_output
CONFLICT_PATTERNS = [
b'<<<<<<< ',
b'======= ',
+ b'=======\r\n',
b'=======\n',
b'>>>>>>> ',
]
@@ -26,7 +28,7 @@ def is_in_merge() -> bool:
)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
parser.add_argument('--assume-in-merge', action='store_true')
@@ -38,12 +40,12 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
retcode = 0
for filename in args.filenames:
with open(filename, 'rb') as inputfile:
- for i, line in enumerate(inputfile):
+ for i, line in enumerate(inputfile, start=1):
for pattern in CONFLICT_PATTERNS:
if line.startswith(pattern):
print(
- f'Merge conflict string "{pattern.decode()}" '
- f'found in {filename}:{i + 1}',
+ f'{filename}:{i}: Merge conflict string '
+ f'{pattern.strip().decode()!r} found',
)
retcode = 1
diff --git a/pre_commit_hooks/check_shebang_scripts_are_executable.py b/pre_commit_hooks/check_shebang_scripts_are_executable.py
index 50bc9c0..621696c 100644
--- a/pre_commit_hooks/check_shebang_scripts_are_executable.py
+++ b/pre_commit_hooks/check_shebang_scripts_are_executable.py
@@ -1,18 +1,17 @@
"""Check that text files with a shebang are executable."""
+from __future__ import annotations
+
import argparse
import shlex
import sys
-from typing import List
-from typing import Optional
from typing import Sequence
-from typing import Set
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 has_shebang
-def check_shebangs(paths: List[str]) -> int:
+def check_shebangs(paths: list[str]) -> int:
# Cannot optimize on non-executability here if we intend this check to
# work on win32 -- and that's where problems caused by non-executability
# (elsewhere) are most likely to arise from.
@@ -20,7 +19,7 @@ def check_shebangs(paths: List[str]) -> int:
def _check_git_filemode(paths: Sequence[str]) -> int:
- seen: Set[str] = set()
+ seen: set[str] = set()
for ls_file in git_ls_files(paths):
is_executable = any(b in EXECUTABLE_VALUES for b in ls_file.mode[-3:])
if not is_executable and has_shebang(ls_file.filename):
@@ -35,13 +34,15 @@ def _message(path: str) -> None:
f'{path}: has a shebang but is not marked executable!\n'
f' If it is supposed to be executable, try: '
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'is wanted.\n',
file=sys.stderr,
)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_symlinks.py b/pre_commit_hooks/check_symlinks.py
index db3b4fe..a85c82a 100644
--- a/pre_commit_hooks/check_symlinks.py
+++ b/pre_commit_hooks/check_symlinks.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import argparse
import os.path
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(description='Checks for broken symlinks.')
parser.add_argument('filenames', nargs='*', help='Filenames to check')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_toml.py b/pre_commit_hooks/check_toml.py
index f623e68..0407371 100644
--- a/pre_commit_hooks/check_toml.py
+++ b/pre_commit_hooks/check_toml.py
@@ -1,11 +1,16 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
+import sys
from typing import Sequence
-import toml
+if sys.version_info >= (3, 11): # pragma: >=3.11 cover
+ import tomllib
+else: # pragma: <3.11 cover
+ import tomli as tomllib
-def main(argv: Optional[Sequence[str]] = None) -> int:
+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)
@@ -13,8 +18,9 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
retval = 0
for filename in args.filenames:
try:
- toml.load(filename)
- except toml.TomlDecodeError as exc:
+ with open(filename, mode='rb') as fp:
+ tomllib.load(fp)
+ except tomllib.TOMLDecodeError as exc:
print(f'{filename}: {exc}')
retval = 1
return retval
diff --git a/pre_commit_hooks/check_vcs_permalinks.py b/pre_commit_hooks/check_vcs_permalinks.py
index 1c77b9a..68639bd 100644
--- a/pre_commit_hooks/check_vcs_permalinks.py
+++ b/pre_commit_hooks/check_vcs_permalinks.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
import argparse
import re
import sys
-from typing import List
-from typing import Optional
from typing import Pattern
from typing import Sequence
@@ -15,7 +15,7 @@ def _get_pattern(domain: str) -> Pattern[bytes]:
return re.compile(regex.encode())
-def _check_filename(filename: str, patterns: List[Pattern[bytes]]) -> int:
+def _check_filename(filename: str, patterns: list[Pattern[bytes]]) -> int:
retv = 0
with open(filename, 'rb') as f:
for i, line in enumerate(f, 1):
@@ -28,7 +28,7 @@ def _check_filename(filename: str, patterns: List[Pattern[bytes]]) -> int:
return retv
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
parser.add_argument(
diff --git a/pre_commit_hooks/check_xml.py b/pre_commit_hooks/check_xml.py
index 0fa6bb2..c256af9 100644
--- a/pre_commit_hooks/check_xml.py
+++ b/pre_commit_hooks/check_xml.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
import argparse
import xml.sax.handler
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='XML filenames to check.')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/check_yaml.py b/pre_commit_hooks/check_yaml.py
index 5e86b73..250794e 100644
--- a/pre_commit_hooks/check_yaml.py
+++ b/pre_commit_hooks/check_yaml.py
@@ -1,8 +1,9 @@
+from __future__ import annotations
+
import argparse
from typing import Any
from typing import Generator
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
import ruamel.yaml
@@ -36,7 +37,7 @@ LOAD_FNS = {
}
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'-m', '--multi', '--allow-multiple-documents', action='store_true',
diff --git a/pre_commit_hooks/debug_statement_hook.py b/pre_commit_hooks/debug_statement_hook.py
index f78d6d6..9ada657 100644
--- a/pre_commit_hooks/debug_statement_hook.py
+++ b/pre_commit_hooks/debug_statement_hook.py
@@ -1,9 +1,9 @@
+from __future__ import annotations
+
import argparse
import ast
import traceback
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
@@ -29,7 +29,7 @@ class Debug(NamedTuple):
class DebugStatementParser(ast.NodeVisitor):
def __init__(self) -> None:
- self.breakpoints: List[Debug] = []
+ self.breakpoints: list[Debug] = []
def visit_Import(self, node: ast.Import) -> None:
for name in node.names:
@@ -65,12 +65,12 @@ def check_file(filename: str) -> int:
visitor.visit(ast_obj)
for bp in visitor.breakpoints:
- print(f'{filename}:{bp.line}:{bp.col} - {bp.name} {bp.reason}')
+ print(f'{filename}:{bp.line}:{bp.col}: {bp.name} {bp.reason}')
return int(bool(visitor.breakpoints))
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to run')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/destroyed_symlinks.py b/pre_commit_hooks/destroyed_symlinks.py
index a3f122f..88253c0 100644
--- a/pre_commit_hooks/destroyed_symlinks.py
+++ b/pre_commit_hooks/destroyed_symlinks.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
import argparse
import shlex
import subprocess
-from typing import List
-from typing import Optional
from typing import Sequence
from pre_commit_hooks.util import cmd_output
@@ -13,8 +13,8 @@ PERMS_LINK = '120000'
PERMS_NONEXIST = '000000'
-def find_destroyed_symlinks(files: Sequence[str]) -> List[str]:
- destroyed_links: List[str] = []
+def find_destroyed_symlinks(files: Sequence[str]) -> list[str]:
+ destroyed_links: list[str] = []
if not files:
return destroyed_links
for line in zsplit(
@@ -66,7 +66,7 @@ def find_destroyed_symlinks(files: Sequence[str]) -> List[str]:
return destroyed_links
-def main(argv: Optional[Sequence[str]] = None) -> int:
+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)
diff --git a/pre_commit_hooks/detect_aws_credentials.py b/pre_commit_hooks/detect_aws_credentials.py
index ba1d789..4f59d9c 100644
--- a/pre_commit_hooks/detect_aws_credentials.py
+++ b/pre_commit_hooks/detect_aws_credentials.py
@@ -1,11 +1,10 @@
+from __future__ import annotations
+
import argparse
import configparser
import os
-from typing import List
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
-from typing import Set
class BadFile(NamedTuple):
@@ -13,7 +12,7 @@ class BadFile(NamedTuple):
key: str
-def get_aws_cred_files_from_env() -> Set[str]:
+def get_aws_cred_files_from_env() -> set[str]:
"""Extract credential file paths from environment variables."""
return {
os.environ[env_var]
@@ -25,7 +24,7 @@ def get_aws_cred_files_from_env() -> Set[str]:
}
-def get_aws_secrets_from_env() -> Set[str]:
+def get_aws_secrets_from_env() -> set[str]:
"""Extract AWS secrets from environment variables."""
keys = set()
for env_var in (
@@ -36,7 +35,7 @@ def get_aws_secrets_from_env() -> Set[str]:
return keys
-def get_aws_secrets_from_file(credentials_file: str) -> Set[str]:
+def get_aws_secrets_from_file(credentials_file: str) -> set[str]:
"""Extract AWS secrets from configuration files.
Read an ini-style configuration file and return a set with all found AWS
@@ -69,8 +68,8 @@ def get_aws_secrets_from_file(credentials_file: str) -> Set[str]:
def check_file_for_aws_keys(
filenames: Sequence[str],
- keys: Set[bytes],
-) -> List[BadFile]:
+ keys: set[bytes],
+) -> list[BadFile]:
"""Check if files contain AWS secrets.
Return a list of all files containing AWS secrets and keys found, with all
@@ -90,7 +89,7 @@ def check_file_for_aws_keys(
return bad_files
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='+', help='Filenames to run')
parser.add_argument(
@@ -119,7 +118,7 @@ def main(argv: Optional[Sequence[str]] = None) -> int:
# of files to to gather AWS secrets from.
credential_files |= get_aws_cred_files_from_env()
- keys: Set[str] = set()
+ keys: set[str] = set()
for credential_file in credential_files:
keys |= get_aws_secrets_from_file(credential_file)
diff --git a/pre_commit_hooks/detect_private_key.py b/pre_commit_hooks/detect_private_key.py
index 18f9539..cd51f90 100644
--- a/pre_commit_hooks/detect_private_key.py
+++ b/pre_commit_hooks/detect_private_key.py
@@ -1,5 +1,6 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
from typing import Sequence
BLACKLIST = [
@@ -16,7 +17,7 @@ BLACKLIST = [
]
-def main(argv: Optional[Sequence[str]] = None) -> int:
+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)
diff --git a/pre_commit_hooks/end_of_file_fixer.py b/pre_commit_hooks/end_of_file_fixer.py
index 40e8821..a30dce9 100644
--- a/pre_commit_hooks/end_of_file_fixer.py
+++ b/pre_commit_hooks/end_of_file_fixer.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import argparse
import os
from typing import IO
-from typing import Optional
from typing import Sequence
@@ -48,7 +49,7 @@ def fix_file(file_obj: IO[bytes]) -> int:
return 0
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/file_contents_sorter.py b/pre_commit_hooks/file_contents_sorter.py
index 392e226..c5691f0 100644
--- a/pre_commit_hooks/file_contents_sorter.py
+++ b/pre_commit_hooks/file_contents_sorter.py
@@ -9,12 +9,13 @@ per line. Various users are adding/removing lines from this file; using
this hook on that file should reduce the instances of git merge
conflicts and keep the file nicely ordered.
"""
+from __future__ import annotations
+
import argparse
from typing import Any
from typing import Callable
from typing import IO
from typing import Iterable
-from typing import Optional
from typing import Sequence
PASS = 0
@@ -23,7 +24,7 @@ FAIL = 1
def sort_file_contents(
f: IO[bytes],
- key: Optional[Callable[[bytes], Any]],
+ key: Callable[[bytes], Any] | None,
*,
unique: bool = False,
) -> int:
@@ -47,7 +48,7 @@ def sort_file_contents(
return FAIL
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='+', help='Files to sort')
parser.add_argument(
diff --git a/pre_commit_hooks/fix_byte_order_marker.py b/pre_commit_hooks/fix_byte_order_marker.py
index 51b94e1..22a4990 100644
--- a/pre_commit_hooks/fix_byte_order_marker.py
+++ b/pre_commit_hooks/fix_byte_order_marker.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import argparse
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+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)
diff --git a/pre_commit_hooks/fix_encoding_pragma.py b/pre_commit_hooks/fix_encoding_pragma.py
index c704774..60c71ee 100644
--- a/pre_commit_hooks/fix_encoding_pragma.py
+++ b/pre_commit_hooks/fix_encoding_pragma.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import argparse
from typing import IO
from typing import NamedTuple
-from typing import Optional
from typing import Sequence
DEFAULT_PRAGMA = b'# -*- coding: utf-8 -*-'
@@ -26,7 +27,7 @@ class ExpectedContents(NamedTuple):
# True: has exactly the coding pragma expected
# False: missing coding pragma entirely
# None: has a coding pragma, but it does not match
- pragma_status: Optional[bool]
+ pragma_status: bool | None
ending: bytes
@property
@@ -55,7 +56,7 @@ def _get_expected_contents(
rest = second_line + rest
if potential_coding.rstrip(b'\r\n') == expected_pragma:
- pragma_status: Optional[bool] = True
+ pragma_status: bool | None = True
elif has_coding(potential_coding):
pragma_status = None
else:
@@ -105,7 +106,7 @@ def _normalize_pragma(pragma: str) -> bytes:
return pragma.encode().rstrip()
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
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 0275808..b806cad 100644
--- a/pre_commit_hooks/forbid_new_submodules.py
+++ b/pre_commit_hooks/forbid_new_submodules.py
@@ -1,12 +1,13 @@
+from __future__ import annotations
+
import argparse
import os
-from typing import Optional
from typing import Sequence
from pre_commit_hooks.util import cmd_output
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/mixed_line_ending.py b/pre_commit_hooks/mixed_line_ending.py
index 4e07ed9..0328e86 100644
--- a/pre_commit_hooks/mixed_line_ending.py
+++ b/pre_commit_hooks/mixed_line_ending.py
@@ -1,7 +1,7 @@
+from __future__ import annotations
+
import argparse
import collections
-from typing import Dict
-from typing import Optional
from typing import Sequence
@@ -25,7 +25,7 @@ def fix_filename(filename: str, fix: str) -> int:
with open(filename, 'rb') as f:
contents = f.read()
- counts: Dict[bytes, int] = collections.defaultdict(int)
+ counts: dict[bytes, int] = collections.defaultdict(int)
for line in contents.splitlines(True):
for ending in ALL_ENDINGS:
@@ -62,7 +62,7 @@ def fix_filename(filename: str, fix: str) -> int:
return other_endings
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'-f', '--fix',
diff --git a/pre_commit_hooks/no_commit_to_branch.py b/pre_commit_hooks/no_commit_to_branch.py
index db84850..741f726 100644
--- a/pre_commit_hooks/no_commit_to_branch.py
+++ b/pre_commit_hooks/no_commit_to_branch.py
@@ -1,7 +1,8 @@
+from __future__ import annotations
+
import argparse
import re
from typing import AbstractSet
-from typing import Optional
from typing import Sequence
from pre_commit_hooks.util import CalledProcessError
@@ -23,7 +24,7 @@ def is_on_branch(
)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'-b', '--branch', action='append',
diff --git a/pre_commit_hooks/pretty_format_json.py b/pre_commit_hooks/pretty_format_json.py
index 33ad5a1..627a11c 100644
--- a/pre_commit_hooks/pretty_format_json.py
+++ b/pre_commit_hooks/pretty_format_json.py
@@ -1,13 +1,11 @@
+from __future__ import annotations
+
import argparse
import json
import sys
from difflib import unified_diff
-from typing import List
from typing import Mapping
-from typing import Optional
from typing import Sequence
-from typing import Tuple
-from typing import Union
def _get_pretty_format(
@@ -17,7 +15,7 @@ def _get_pretty_format(
sort_keys: bool = True,
top_keys: Sequence[str] = (),
) -> str:
- def pairs_first(pairs: Sequence[Tuple[str, str]]) -> Mapping[str, str]:
+ def pairs_first(pairs: Sequence[tuple[str, str]]) -> Mapping[str, str]:
before = [pair for pair in pairs if pair[0] in top_keys]
before = sorted(before, key=lambda x: top_keys.index(x[0]))
after = [pair for pair in pairs if pair[0] not in top_keys]
@@ -38,7 +36,7 @@ def _autofix(filename: str, new_contents: str) -> None:
f.write(new_contents)
-def parse_num_to_int(s: str) -> Union[int, str]:
+def parse_num_to_int(s: str) -> int | str:
"""Convert string numbers to int, leaving strings as is."""
try:
return int(s)
@@ -46,7 +44,7 @@ def parse_num_to_int(s: str) -> Union[int, str]:
return s
-def parse_topkeys(s: str) -> List[str]:
+def parse_topkeys(s: str) -> list[str]:
return s.split(',')
@@ -57,7 +55,7 @@ def get_diff(source: str, target: str, file: str) -> str:
return ''.join(diff)
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'--autofix',
diff --git a/pre_commit_hooks/removed.py b/pre_commit_hooks/removed.py
index 236cbf8..6f6c7b7 100644
--- a/pre_commit_hooks/removed.py
+++ b/pre_commit_hooks/removed.py
@@ -1,9 +1,10 @@
+from __future__ import annotations
+
import sys
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
argv = argv if argv is not None else sys.argv[1:]
hookid, new_hookid, url = argv[:3]
raise SystemExit(
diff --git a/pre_commit_hooks/requirements_txt_fixer.py b/pre_commit_hooks/requirements_txt_fixer.py
index 63f891f..5884394 100644
--- a/pre_commit_hooks/requirements_txt_fixer.py
+++ b/pre_commit_hooks/requirements_txt_fixer.py
@@ -1,8 +1,8 @@
+from __future__ import annotations
+
import argparse
import re
from typing import IO
-from typing import List
-from typing import Optional
from typing import Sequence
@@ -15,8 +15,8 @@ class Requirement:
UNTIL_SEP = re.compile(rb'[^;\s]+')
def __init__(self) -> None:
- self.value: Optional[bytes] = None
- self.comments: List[bytes] = []
+ self.value: bytes | None = None
+ self.comments: list[bytes] = []
@property
def name(self) -> bytes:
@@ -36,7 +36,7 @@ class Requirement:
return name[:m.start()]
- def __lt__(self, requirement: 'Requirement') -> bool:
+ def __lt__(self, requirement: Requirement) -> bool:
# \n means top of file comment, so always return True,
# otherwise just do a string comparison with value.
assert self.value is not None, self.value
@@ -61,9 +61,9 @@ class Requirement:
def fix_requirements(f: IO[bytes]) -> int:
- requirements: List[Requirement] = []
+ requirements: list[Requirement] = []
before = list(f)
- after: List[bytes] = []
+ after: list[bytes] = []
before_string = b''.join(before)
@@ -130,7 +130,7 @@ def fix_requirements(f: IO[bytes]) -> int:
return FAIL
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/sort_simple_yaml.py b/pre_commit_hooks/sort_simple_yaml.py
index 39f683e..116b5c1 100644
--- a/pre_commit_hooks/sort_simple_yaml.py
+++ b/pre_commit_hooks/sort_simple_yaml.py
@@ -17,16 +17,16 @@ We assume a strict subset of YAML that looks like:
In other words, we don't sort deeper than the top layer, and might corrupt
complicated YAML files.
"""
+from __future__ import annotations
+
import argparse
-from typing import List
-from typing import Optional
from typing import Sequence
QUOTES = ["'", '"']
-def sort(lines: List[str]) -> List[str]:
+def sort(lines: list[str]) -> list[str]:
"""Sort a YAML file in alphabetical order, keeping blocks together.
:param lines: array of strings (without newlines)
@@ -44,7 +44,7 @@ def sort(lines: List[str]) -> List[str]:
return new_lines
-def parse_block(lines: List[str], header: bool = False) -> List[str]:
+def parse_block(lines: list[str], header: bool = False) -> list[str]:
"""Parse and return a single block, popping off the start of `lines`.
If parsing a header block, we stop after we reach a line that is not a
@@ -60,7 +60,7 @@ def parse_block(lines: List[str], header: bool = False) -> List[str]:
return block_lines
-def parse_blocks(lines: List[str]) -> List[List[str]]:
+def parse_blocks(lines: list[str]) -> list[list[str]]:
"""Parse and return all possible blocks, popping off the start of `lines`.
:param lines: list of lines
@@ -77,7 +77,7 @@ def parse_blocks(lines: List[str]) -> List[List[str]]:
return blocks
-def first_key(lines: List[str]) -> str:
+def first_key(lines: list[str]) -> str:
"""Returns a string representing the sort key of a block.
The sort key is the first YAML key we encounter, ignoring comments, and
@@ -99,7 +99,7 @@ def first_key(lines: List[str]) -> str:
return '' # not actually reached in reality
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/string_fixer.py b/pre_commit_hooks/string_fixer.py
index a08a5f7..0ef9bc7 100644
--- a/pre_commit_hooks/string_fixer.py
+++ b/pre_commit_hooks/string_fixer.py
@@ -1,9 +1,9 @@
+from __future__ import annotations
+
import argparse
import io
import re
import tokenize
-from typing import List
-from typing import Optional
from typing import Sequence
START_QUOTE_RE = re.compile('^[a-zA-Z]*"')
@@ -24,7 +24,7 @@ def handle_match(token_text: str) -> str:
return token_text
-def get_line_offsets_by_line_no(src: str) -> List[int]:
+def get_line_offsets_by_line_no(src: str) -> list[int]:
# Padded so we can index with line number
offsets = [-1, 0]
for line in src.splitlines(True):
@@ -60,7 +60,7 @@ def fix_strings(filename: str) -> int:
return 0
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*', help='Filenames to fix')
args = parser.parse_args(argv)
diff --git a/pre_commit_hooks/tests_should_end_in_test.py b/pre_commit_hooks/tests_should_end_in_test.py
index bffb0c4..e7842af 100644
--- a/pre_commit_hooks/tests_should_end_in_test.py
+++ b/pre_commit_hooks/tests_should_end_in_test.py
@@ -1,30 +1,50 @@
+from __future__ import annotations
+
import argparse
import os.path
import re
-from typing import Optional
from typing import Sequence
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument('filenames', nargs='*')
- parser.add_argument(
- '--django', default=False, action='store_true',
- help='Use Django-style test naming pattern (test*.py)',
+ mutex = parser.add_mutually_exclusive_group()
+ mutex.add_argument(
+ '--pytest',
+ dest='pattern',
+ action='store_const',
+ const=r'.*_test\.py',
+ default=r'.*_test\.py',
+ help='(the default) ensure tests match %(const)s',
+ )
+ mutex.add_argument(
+ '--pytest-test-first',
+ dest='pattern',
+ action='store_const',
+ const=r'test_.*\.py',
+ help='ensure tests match %(const)s',
+ )
+ mutex.add_argument(
+ '--django', '--unittest',
+ dest='pattern',
+ action='store_const',
+ const=r'test.*\.py',
+ help='ensure tests match %(const)s',
)
args = parser.parse_args(argv)
retcode = 0
- test_name_pattern = r'test.*\.py' if args.django else r'.*_test\.py'
+ reg = re.compile(args.pattern)
for filename in args.filenames:
base = os.path.basename(filename)
if (
- not re.match(test_name_pattern, base) and
+ not reg.fullmatch(base) and
not base == '__init__.py' and
not base == 'conftest.py'
):
retcode = 1
- print(f'{filename} does not match pattern "{test_name_pattern}"')
+ print(f'{filename} does not match pattern "{args.pattern}"')
return retcode
diff --git a/pre_commit_hooks/trailing_whitespace_fixer.py b/pre_commit_hooks/trailing_whitespace_fixer.py
index 82faa2d..84f5067 100644
--- a/pre_commit_hooks/trailing_whitespace_fixer.py
+++ b/pre_commit_hooks/trailing_whitespace_fixer.py
@@ -1,13 +1,14 @@
+from __future__ import annotations
+
import argparse
import os
-from typing import Optional
from typing import Sequence
def _fix_file(
filename: str,
is_markdown: bool,
- chars: Optional[bytes],
+ chars: bytes | None,
) -> bool:
with open(filename, mode='rb') as file_processed:
lines = file_processed.readlines()
@@ -24,7 +25,7 @@ def _fix_file(
def _process_line(
line: bytes,
is_markdown: bool,
- chars: Optional[bytes],
+ chars: bytes | None,
) -> bytes:
if line[-2:] == b'\r\n':
eol = b'\r\n'
@@ -40,7 +41,7 @@ def _process_line(
return line.rstrip(chars) + eol
-def main(argv: Optional[Sequence[str]] = None) -> int:
+def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
'--no-markdown-linebreak-ext',
diff --git a/pre_commit_hooks/util.py b/pre_commit_hooks/util.py
index 402e33e..d6c90ae 100644
--- a/pre_commit_hooks/util.py
+++ b/pre_commit_hooks/util.py
@@ -1,20 +1,19 @@
+from __future__ import annotations
+
import subprocess
from typing import Any
-from typing import List
-from typing import Optional
-from typing import Set
class CalledProcessError(RuntimeError):
pass
-def added_files() -> Set[str]:
+def added_files() -> set[str]:
cmd = ('git', 'diff', '--staged', '--name-only', '--diff-filter=A')
return set(cmd_output(*cmd).splitlines())
-def cmd_output(*cmd: str, retcode: Optional[int] = 0, **kwargs: Any) -> str:
+def cmd_output(*cmd: str, retcode: int | None = 0, **kwargs: Any) -> str:
kwargs.setdefault('stdout', subprocess.PIPE)
kwargs.setdefault('stderr', subprocess.PIPE)
proc = subprocess.Popen(cmd, **kwargs)
@@ -25,7 +24,7 @@ def cmd_output(*cmd: str, retcode: Optional[int] = 0, **kwargs: Any) -> str:
return stdout
-def zsplit(s: str) -> List[str]:
+def zsplit(s: str) -> list[str]:
s = s.strip('\0')
if s:
return s.split('\0')
diff --git a/setup.cfg b/setup.cfg
index 45498eb..f501571 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = pre_commit_hooks
-version = 4.1.0
+version = 4.3.0
description = Some out-of-the-box hooks for pre-commit.
long_description = file: README.md
long_description_content_type = text/markdown
@@ -13,7 +13,6 @@ classifiers =
License :: OSI Approved :: MIT License
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
- Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
@@ -25,8 +24,8 @@ classifiers =
packages = find:
install_requires =
ruamel.yaml>=0.15
- toml
-python_requires = >=3.6.1
+ tomli>=1.1.0;python_version<"3.11"
+python_requires = >=3.7
[options.packages.find]
exclude =
diff --git a/setup.py b/setup.py
index 8bf1ba9..3d93aef 100644
--- a/setup.py
+++ b/setup.py
@@ -1,2 +1,4 @@
+from __future__ import annotations
+
from setuptools import setup
setup()
diff --git a/testing/util.py b/testing/util.py
index 5043754..2bbbe64 100644
--- a/testing/util.py
+++ b/testing/util.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os.path
import subprocess
diff --git a/tests/check_added_large_files_test.py b/tests/check_added_large_files_test.py
index c16bf5a..54c4e68 100644
--- a/tests/check_added_large_files_test.py
+++ b/tests/check_added_large_files_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import shutil
import pytest
diff --git a/tests/check_ast_test.py b/tests/check_ast_test.py
index 686fd11..6243966 100644
--- a/tests/check_ast_test.py
+++ b/tests/check_ast_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit_hooks.check_ast import main
from testing.util import get_resource_path
diff --git a/tests/check_builtin_literals_test.py b/tests/check_builtin_literals_test.py
index e936798..1b18257 100644
--- a/tests/check_builtin_literals_test.py
+++ b/tests/check_builtin_literals_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import ast
import pytest
diff --git a/tests/check_byte_order_marker_test.py b/tests/check_byte_order_marker_test.py
index 4c40247..909a39b 100644
--- a/tests/check_byte_order_marker_test.py
+++ b/tests/check_byte_order_marker_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit_hooks import check_byte_order_marker
diff --git a/tests/check_case_conflict_test.py b/tests/check_case_conflict_test.py
index d9211b5..a914f45 100644
--- a/tests/check_case_conflict_test.py
+++ b/tests/check_case_conflict_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import sys
import pytest
diff --git a/tests/check_docstring_first_test.py b/tests/check_docstring_first_test.py
index ed5c08e..8bafae8 100644
--- a/tests/check_docstring_first_test.py
+++ b/tests/check_docstring_first_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.check_docstring_first import check_docstring_first
@@ -15,7 +17,7 @@ TESTS = (
b'from __future__ import unicode_literals\n'
b'"foo"\n',
1,
- '{filename}:2 Module docstring appears after code '
+ '{filename}:2: Module docstring appears after code '
'(code seen on line 1).\n',
),
# Test double docstring
@@ -24,7 +26,7 @@ TESTS = (
b'from __future__ import absolute_import\n'
b'"fake docstring"\n',
1,
- '{filename}:3 Multiple module docstrings '
+ '{filename}:3: Multiple module docstrings '
'(first docstring on line 1).\n',
),
# Test multiple lines of code above
@@ -33,7 +35,7 @@ TESTS = (
b'import sys\n'
b'"docstring"\n',
1,
- '{filename}:3 Module docstring appears after code '
+ '{filename}:3: Module docstring appears after code '
'(code seen on line 1).\n',
),
# String literals in expressions are ok.
diff --git a/tests/check_executables_have_shebangs_test.py b/tests/check_executables_have_shebangs_test.py
index 5703ede..82d03e3 100644
--- a/tests/check_executables_have_shebangs_test.py
+++ b/tests/check_executables_have_shebangs_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import sys
diff --git a/tests/check_json_test.py b/tests/check_json_test.py
index 3ec67f1..53e1f52 100644
--- a/tests/check_json_test.py
+++ b/tests/check_json_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.check_json import main
diff --git a/tests/check_merge_conflict_test.py b/tests/check_merge_conflict_test.py
index 79c1b11..76c4283 100644
--- a/tests/check_merge_conflict_test.py
+++ b/tests/check_merge_conflict_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import shutil
@@ -99,8 +101,14 @@ def repository_pending_merge(tmpdir):
@pytest.mark.usefixtures('f1_is_a_conflict_file')
-def test_merge_conflicts_git():
+def test_merge_conflicts_git(capsys):
assert main(['f1']) == 1
+ out, _ = capsys.readouterr()
+ assert out == (
+ "f1:1: Merge conflict string '<<<<<<<' found\n"
+ "f1:3: Merge conflict string '=======' found\n"
+ "f1:5: Merge conflict string '>>>>>>>' found\n"
+ )
@pytest.mark.parametrize(
@@ -137,7 +145,7 @@ def test_care_when_assumed_merge(tmpdir):
assert main([str(f.realpath()), '--assume-in-merge']) == 1
-def test_worktree_merge_conflicts(f1_is_a_conflict_file, tmpdir):
+def test_worktree_merge_conflicts(f1_is_a_conflict_file, tmpdir, capsys):
worktree = tmpdir.join('worktree')
cmd_output('git', 'worktree', 'add', str(worktree))
with worktree.as_cwd():
@@ -146,4 +154,4 @@ def test_worktree_merge_conflicts(f1_is_a_conflict_file, tmpdir):
)
msg = f1_is_a_conflict_file.join('.git/worktrees/worktree/MERGE_MSG')
assert msg.exists()
- test_merge_conflicts_git()
+ test_merge_conflicts_git(capsys)
diff --git a/tests/check_shebang_scripts_are_executable_test.py b/tests/check_shebang_scripts_are_executable_test.py
index 9e78b06..e4bd07c 100644
--- a/tests/check_shebang_scripts_are_executable_test.py
+++ b/tests/check_shebang_scripts_are_executable_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import pytest
diff --git a/tests/check_symlinks_test.py b/tests/check_symlinks_test.py
index 07c1168..e2c2c78 100644
--- a/tests/check_symlinks_test.py
+++ b/tests/check_symlinks_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import pytest
diff --git a/tests/check_toml_test.py b/tests/check_toml_test.py
index c7251eb..d594f81 100644
--- a/tests/check_toml_test.py
+++ b/tests/check_toml_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit_hooks.check_toml import main
diff --git a/tests/check_vcs_permalinks_test.py b/tests/check_vcs_permalinks_test.py
index ad59151..01ce94d 100644
--- a/tests/check_vcs_permalinks_test.py
+++ b/tests/check_vcs_permalinks_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit_hooks.check_vcs_permalinks import main
diff --git a/tests/check_xml_test.py b/tests/check_xml_test.py
index 357bad6..767619f 100644
--- a/tests/check_xml_test.py
+++ b/tests/check_xml_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.check_xml import main
diff --git a/tests/check_yaml_test.py b/tests/check_yaml_test.py
index 1a017a1..54eb16e 100644
--- a/tests/check_yaml_test.py
+++ b/tests/check_yaml_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.check_yaml import main
diff --git a/tests/conftest.py b/tests/conftest.py
index f92cfc1..807f15b 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.util import cmd_output
diff --git a/tests/debug_statement_hook_test.py b/tests/debug_statement_hook_test.py
index 428421a..5a8e0bb 100644
--- a/tests/debug_statement_hook_test.py
+++ b/tests/debug_statement_hook_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import ast
from pre_commit_hooks.debug_statement_hook import Debug
@@ -53,7 +55,9 @@ def test_non_utf8_file(tmpdir):
assert main((str(f_py),)) == 0
-def test_py37_breakpoint(tmpdir):
+def test_py37_breakpoint(tmpdir, capsys):
f_py = tmpdir.join('f.py')
f_py.write('def f():\n breakpoint()\n')
assert main((str(f_py),)) == 1
+ out, _ = capsys.readouterr()
+ assert out == f'{f_py}:2:4: breakpoint called\n'
diff --git a/tests/destroyed_symlinks_test.py b/tests/destroyed_symlinks_test.py
index cde06cf..39c474a 100644
--- a/tests/destroyed_symlinks_test.py
+++ b/tests/destroyed_symlinks_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import subprocess
diff --git a/tests/detect_aws_credentials_test.py b/tests/detect_aws_credentials_test.py
index 7212509..afda47a 100644
--- a/tests/detect_aws_credentials_test.py
+++ b/tests/detect_aws_credentials_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from unittest.mock import patch
import pytest
diff --git a/tests/detect_private_key_test.py b/tests/detect_private_key_test.py
index d2c724f..41f8bae 100644
--- a/tests/detect_private_key_test.py
+++ b/tests/detect_private_key_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.detect_private_key import main
diff --git a/tests/end_of_file_fixer_test.py b/tests/end_of_file_fixer_test.py
index 60b9e82..8a5d889 100644
--- a/tests/end_of_file_fixer_test.py
+++ b/tests/end_of_file_fixer_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
import pytest
diff --git a/tests/file_contents_sorter_test.py b/tests/file_contents_sorter_test.py
index 15f1134..5e79e40 100644
--- a/tests/file_contents_sorter_test.py
+++ b/tests/file_contents_sorter_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.file_contents_sorter import FAIL
diff --git a/tests/fix_byte_order_marker_test.py b/tests/fix_byte_order_marker_test.py
index da150e3..d7a6599 100644
--- a/tests/fix_byte_order_marker_test.py
+++ b/tests/fix_byte_order_marker_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit_hooks import fix_byte_order_marker
diff --git a/tests/fix_encoding_pragma_test.py b/tests/fix_encoding_pragma_test.py
index f3afa09..98557e9 100644
--- a/tests/fix_encoding_pragma_test.py
+++ b/tests/fix_encoding_pragma_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import io
import pytest
diff --git a/tests/forbid_new_submodules_test.py b/tests/forbid_new_submodules_test.py
index 0326d94..058a329 100644
--- a/tests/forbid_new_submodules_test.py
+++ b/tests/forbid_new_submodules_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import subprocess
from unittest import mock
diff --git a/tests/mixed_line_ending_test.py b/tests/mixed_line_ending_test.py
index f1c2641..a7e7971 100644
--- a/tests/mixed_line_ending_test.py
+++ b/tests/mixed_line_ending_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.mixed_line_ending import main
diff --git a/tests/no_commit_to_branch_test.py b/tests/no_commit_to_branch_test.py
index 9fcb580..eaae5e6 100644
--- a/tests/no_commit_to_branch_test.py
+++ b/tests/no_commit_to_branch_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.no_commit_to_branch import is_on_branch
diff --git a/tests/pretty_format_json_test.py b/tests/pretty_format_json_test.py
index 7fda23b..5ded724 100644
--- a/tests/pretty_format_json_test.py
+++ b/tests/pretty_format_json_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import shutil
diff --git a/tests/readme_test.py b/tests/readme_test.py
index 7df7fcf..038868d 100644
--- a/tests/readme_test.py
+++ b/tests/readme_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit_hooks.check_yaml import yaml
diff --git a/tests/removed_test.py b/tests/removed_test.py
index d635eb1..cd66957 100644
--- a/tests/removed_test.py
+++ b/tests/removed_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.removed import main
diff --git a/tests/requirements_txt_fixer_test.py b/tests/requirements_txt_fixer_test.py
index e3c6ed5..b725afa 100644
--- a/tests/requirements_txt_fixer_test.py
+++ b/tests/requirements_txt_fixer_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.requirements_txt_fixer import FAIL
diff --git a/tests/sort_simple_yaml_test.py b/tests/sort_simple_yaml_test.py
index a682c15..6cbda85 100644
--- a/tests/sort_simple_yaml_test.py
+++ b/tests/sort_simple_yaml_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import os
import pytest
diff --git a/tests/string_fixer_test.py b/tests/string_fixer_test.py
index 6ddb0ac..9dd7315 100644
--- a/tests/string_fixer_test.py
+++ b/tests/string_fixer_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import textwrap
import pytest
diff --git a/tests/tests_should_end_in_test_test.py b/tests/tests_should_end_in_test_test.py
index 4df2963..2b5a0de 100644
--- a/tests/tests_should_end_in_test_test.py
+++ b/tests/tests_should_end_in_test_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from pre_commit_hooks.tests_should_end_in_test import main
@@ -41,3 +43,8 @@ def test_main_not_django_fails():
def test_main_django_fails():
ret = main(['--django', 'foo_test.py', 'test_bar.py', 'test_baz.py'])
assert ret == 1
+
+
+def test_main_pytest_test_first():
+ assert main(['--pytest-test-first', 'test_foo.py']) == 0
+ assert main(['--pytest-test-first', 'foo_test.py']) == 1
diff --git a/tests/trailing_whitespace_fixer_test.py b/tests/trailing_whitespace_fixer_test.py
index bb3b62d..c07497a 100644
--- a/tests/trailing_whitespace_fixer_test.py
+++ b/tests/trailing_whitespace_fixer_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.trailing_whitespace_fixer import main
diff --git a/tests/util_test.py b/tests/util_test.py
index 7f48816..92473e5 100644
--- a/tests/util_test.py
+++ b/tests/util_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
import pytest
from pre_commit_hooks.util import CalledProcessError
diff --git a/tox.ini b/tox.ini
index 965eba9..cb2b92a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py36,py37,py38,pypy3,pre-commit
+envlist = py37,py38,pypy3,pre-commit
[testenv]
deps = -rrequirements-dev.txt
@@ -11,7 +11,7 @@ setenv =
commands =
coverage erase
coverage run -m pytest {posargs:tests}
- coverage report --fail-under 100
+ coverage report
[testenv:pre-commit]
skip_install = true