From f010d35ad322ea6fa189b20f747f87f1c3c4ecb2 Mon Sep 17 00:00:00 2001 From: MDW Date: Fri, 9 Dec 2022 17:26:29 +0100 Subject: [PATCH] Add pre-commit configuration (#2459) --- .github/workflows/codespell-private.yml | 6 ++ .pre-commit-config.yaml | 111 ++++++++++++++++++++++++ MANIFEST.in | 1 + codespell_lib/_codespell.py | 40 +++++---- codespell_lib/tests/test_dictionary.py | 47 ++++++---- pyproject-codespell.precommit-toml | 7 ++ pyproject.toml | 62 +++++++++++++ setup.cfg | 3 - 8 files changed, 241 insertions(+), 36 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 pyproject-codespell.precommit-toml delete mode 100644 setup.cfg diff --git a/.github/workflows/codespell-private.yml b/.github/workflows/codespell-private.yml index 9aee75d231..f58feeae36 100644 --- a/.github/workflows/codespell-private.yml +++ b/.github/workflows/codespell-private.yml @@ -63,6 +63,12 @@ jobs: flake8-annotation: runs-on: ubuntu-latest steps: + - name: Setup python + uses: actions/setup-python@v4 + with: + python-version: 3.x - uses: actions/checkout@v3 + - name: Install codespell dependencies + run: pip install -e ".[dev]" - name: Flake8 with annotations uses: TrueBrain/actions-flake8@v2 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..570d846a57 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,111 @@ +--- +files: ^(.*\.(py|json|md|sh|yaml|cfg|txt))$ +exclude: ^(\.[^/]*cache/.*)$ +repos: + - repo: https://github.com/executablebooks/mdformat + # Do this before other tools "fixing" the line endings + rev: 0.7.16 + hooks: + - id: mdformat + name: Format Markdown + entry: mdformat # Executable to run, with fixed options + language: python + types: [markdown] + args: [--wrap, '75', --number] + additional_dependencies: + - mdformat-toc + - mdformat-beautysh + # -mdformat-shfmt + # -mdformat-tables + - mdformat-config + - mdformat-black + - mdformat-web + - mdformat-gfm + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: [--py37-plus] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: no-commit-to-branch + args: [--branch, main] + - id: check-yaml + args: [--unsafe] + - id: debug-statements + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-json + - id: mixed-line-ending + - id: check-builtin-literals + - id: check-ast + - id: check-merge-conflict + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: check-docstring-first + - id: fix-byte-order-marker + - id: check-case-conflict + - id: check-toml + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.28.0 + hooks: + - id: yamllint + args: + - --no-warnings + - -d + - '{extends: relaxed, rules: {line-length: {max: 90}}}' + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + - repo: https://github.com/Lucas-C/pre-commit-hooks-bandit + rev: v1.0.6 + hooks: + - id: python-bandit-vulnerability-check + - repo: https://github.com/PyCQA/autoflake + rev: v2.0.0 + hooks: + - id: autoflake + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + additional_dependencies: + - flake8-pyproject>=1.2.2 + - flake8-bugbear>=22.7.1 + - flake8-comprehensions>=3.10.0 + - flake8-2020>=1.7.0 + - mccabe>=0.7.0 + - pycodestyle>=2.9.1 + - pyflakes>=2.5.0 + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + - repo: https://github.com/codespell-project/codespell + rev: v2.2.2 + hooks: + - id: codespell + args: [--toml, pyproject-codespell.precommit-toml] + additional_dependencies: + - tomli + - repo: https://github.com/pre-commit/mirrors-pylint + rev: v3.0.0a5 + hooks: + - id: pylint + additional_dependencies: + - chardet + - pytest + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.991 + hooks: + - id: mypy + args: [--no-warn-unused-ignores, --config-file, pyproject.toml, --disable-error-code, + import] + additional_dependencies: + - chardet + - pytest + - pytest-cov + - pytest-dependency + - types-chardet diff --git a/MANIFEST.in b/MANIFEST.in index d4cf114e40..f5358f6819 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,3 +9,4 @@ exclude .git-blame-ignore-revs exclude example example/* snap snap/* tools tools/* exclude Makefile exclude codespell.1.include +exclude pyproject-codespell.precommit-toml \ No newline at end of file diff --git a/codespell_lib/_codespell.py b/codespell_lib/_codespell.py index 8811a049e6..e1783b5a2a 100644 --- a/codespell_lib/_codespell.py +++ b/codespell_lib/_codespell.py @@ -352,7 +352,7 @@ def parse_options( "This option can be specified multiple times.", ) builtin_opts = "\n- ".join( - [""] + ["%r %s" % (d[0], d[1]) for d in _builtin_dictionaries] + [""] + [f"{d[0]!r} {d[1]}" for d in _builtin_dictionaries] ) parser.add_argument( "--builtin", @@ -693,7 +693,7 @@ def ask_for_word_fix( r = "" fixword = fix_case(wrongword, misspelling.data) while not r: - print("%s\t%s ==> %s (Y/n) " % (line, wrongword, fixword), end="") + print(f"{line}\t{wrongword} ==> {fixword} (Y/n) ", end="") r = sys.stdin.readline().strip().upper() if not r: r = "Y" @@ -743,7 +743,7 @@ def print_context( # context = (context_before, context_after) for i in range(index - context[0], index + context[1] + 1): if 0 <= i < len(lines): - print("%s %s" % (">" if i == index else ":", lines[i].rstrip())) + print("{} {}".format(">" if i == index else ":", lines[i].rstrip())) def extract_words( @@ -806,14 +806,14 @@ def parse_file( if summary and fix: summary.update(lword) - cfilename = "%s%s%s" % (colors.FILE, filename, colors.DISABLE) - cwrongword = "%s%s%s" % (colors.WWORD, word, colors.DISABLE) - crightword = "%s%s%s" % (colors.FWORD, fixword, colors.DISABLE) + cfilename = f"{colors.FILE}{filename}{colors.DISABLE}" + cwrongword = f"{colors.WWORD}{word}{colors.DISABLE}" + crightword = f"{colors.FWORD}{fixword}{colors.DISABLE}" if misspellings[lword].reason: if options.quiet_level & QuietLevels.DISABLED_FIXES: continue - creason = " | %s%s%s" % ( + creason = " | {}{}{}".format( colors.FILE, misspellings[lword].reason, colors.DISABLE, @@ -843,7 +843,7 @@ def parse_file( try: text = is_text_file(filename) except PermissionError as e: - print("WARNING: %s: %s" % (e.strerror, filename), file=sys.stderr) + print(f"WARNING: {e.strerror}: {filename}", file=sys.stderr) return bad_count except OSError: return bad_count @@ -917,16 +917,16 @@ def parse_file( ): continue - cfilename = "%s%s%s" % (colors.FILE, filename, colors.DISABLE) + cfilename = f"{colors.FILE}{filename}{colors.DISABLE}" cline = "%s%d%s" % (colors.FILE, i + 1, colors.DISABLE) - cwrongword = "%s%s%s" % (colors.WWORD, word, colors.DISABLE) - crightword = "%s%s%s" % (colors.FWORD, fixword, colors.DISABLE) + cwrongword = f"{colors.WWORD}{word}{colors.DISABLE}" + crightword = f"{colors.FWORD}{fixword}{colors.DISABLE}" if misspellings[lword].reason: if options.quiet_level & QuietLevels.DISABLED_FIXES: continue - creason = " | %s%s%s" % ( + creason = " | {}{}{}".format( colors.FILE, misspellings[lword].reason, colors.DISABLE, @@ -976,7 +976,7 @@ def parse_file( else: if not options.quiet_level & QuietLevels.FIXES: print( - "%sFIXED:%s %s" % (colors.FWORD, colors.DISABLE, filename), + f"{colors.FWORD}FIXED:{colors.DISABLE} {filename}", file=sys.stderr, ) with open(filename, "w", encoding=encoding, newline="") as f: @@ -1010,7 +1010,7 @@ def main(*args: str) -> int: try: word_regex = re.compile(word_regex) except re.error as err: - print('ERROR: invalid --regex "%s" (%s)' % (word_regex, err), file=sys.stderr) + print(f'ERROR: invalid --regex "{word_regex}" ({err})', file=sys.stderr) parser.print_help() return EX_USAGE @@ -1019,7 +1019,9 @@ def main(*args: str) -> int: ignore_word_regex = re.compile(options.ignore_regex) except re.error as err: print( - 'ERROR: invalid --ignore-regex "%s" (%s)' % (options.ignore_regex, err), + 'ERROR: invalid --ignore-regex "{}" ({})'.format( + options.ignore_regex, err + ), file=sys.stderr, ) parser.print_help() @@ -1044,7 +1046,8 @@ def main(*args: str) -> int: uri_regex = re.compile(uri_regex) except re.error as err: print( - 'ERROR: invalid --uri-regex "%s" (%s)' % (uri_regex, err), file=sys.stderr + f'ERROR: invalid --uri-regex "{uri_regex}" ({err})', + file=sys.stderr, ) parser.print_help() return EX_USAGE @@ -1063,12 +1066,13 @@ def main(*args: str) -> int: for builtin in _builtin_dictionaries: if builtin[0] == u: use_dictionaries.append( - os.path.join(_data_root, "dictionary%s.txt" % (builtin[2],)) + os.path.join(_data_root, f"dictionary{builtin[2]}.txt") ) break else: print( - "ERROR: Unknown builtin dictionary: %s" % (u,), file=sys.stderr + f"ERROR: Unknown builtin dictionary: {u}", + file=sys.stderr, ) parser.print_help() return EX_USAGE diff --git a/codespell_lib/tests/test_dictionary.py b/codespell_lib/tests/test_dictionary.py index 56c373e311..515c3d2d0a 100644 --- a/codespell_lib/tests/test_dictionary.py +++ b/codespell_lib/tests/test_dictionary.py @@ -89,14 +89,14 @@ def _check_aspell( spellers[lang].check(phrase.encode(spellers[lang].ConfigKeys()["encoding"][1])) for lang in languages ) - end = "be in aspell dictionaries (%s) for dictionary %s" % ( + end = "be in aspell dictionaries ({}) for dictionary {}".format( ", ".join(languages), fname, ) if in_aspell: # should be an error in aspell - assert this_in_aspell, "%s should %s" % (msg, end) + assert this_in_aspell, f"{msg} should {end}" else: # shouldn't be - assert not this_in_aspell, "%s should not %s" % (msg, end) + assert not this_in_aspell, f"{msg} should not {end}" whitespace = re.compile(r"\s") @@ -118,15 +118,18 @@ def _check_err_rep( ) -> None: assert whitespace.search(err) is None, "error %r has whitespace" % err assert "," not in err, "error %r has a comma" % err - assert len(rep) > 0, "error %s: correction %r must be non-empty" % (err, rep) + assert len(rep) > 0, f"error {err}: correction {rep!r} must be non-empty" assert not start_whitespace.match( rep - ), "error %s: correction %r cannot start with whitespace" % (err, rep) - _check_aspell(err, "error %r" % (err,), in_aspell[0], fname, languages[0]) - prefix = "error %s: correction %r" % (err, rep) + ), f"error {err}: correction {rep!r} cannot start with whitespace" + _check_aspell(err, f"error {err!r}", in_aspell[0], fname, languages[0]) + prefix = f"error {err}: correction {rep!r}" for (regex, msg) in [ (start_comma, "%s starts with a comma"), - (whitespace_comma, "%s contains a whitespace character followed by a comma"), + ( + whitespace_comma, + "%s contains a whitespace character followed by a comma", + ), ( comma_whitespaces, "%s contains a comma followed by multiple whitespace characters", @@ -144,9 +147,13 @@ def _check_err_rep( reps = [r.strip() for r in rep.split(",")] reps = [r for r in reps if len(r)] for r in reps: - assert err != r.lower(), "error %r corrects to itself amongst others" % (err,) + assert err != r.lower(), f"error {err!r} corrects to itself amongst others" _check_aspell( - r, "error %s: correction %r" % (err, r), in_aspell[1], fname, languages[1] + r, + f"error {err}: correction {r!r}", + in_aspell[1], + fname, + languages[1], ) # aspell dictionary is case sensitive, so pass the original case into there @@ -180,7 +187,11 @@ def test_error_checking(err: str, rep: str, match: str) -> None: """Test that our error checking works.""" with pytest.raises(AssertionError, match=match): _check_err_rep( - err, rep, (None, None), "dummy", (supported_languages, supported_languages) + err, + rep, + (None, None), + "dummy", + (supported_languages, supported_languages), ) @@ -205,7 +216,13 @@ def test_error_checking(err: str, rep: str, match: str) -> None: ("a", "bar back", None, False, "should not be in aspell"), ("a", "bar back Wednesday", None, False, "should not be in aspell"), # Second multi-word, both parts - ("a", "bar back, abcdef uvwxyz, bar,", None, True, "should be in aspell"), + ( + "a", + "bar back, abcdef uvwxyz, bar,", + None, + True, + "should be in aspell", + ), ( "a", "abcdef uvwxyz, bar back, ghijkl,", @@ -263,7 +280,7 @@ def test_dictionary_looping( for line in fid: err, rep = line.split("->") err = err.lower() - assert err not in this_err_dict, "error %r already exists in %s" % ( + assert err not in this_err_dict, "error {!r} already exists in {}".format( err, short_fname, ) @@ -286,7 +303,7 @@ def test_dictionary_looping( for err in this_err_dict: assert ( err not in other_err_dict - ), "error %r in dictionary %s already exists in dictionary %s" % ( + ), "error {!r} in dictionary {} already exists in dictionary {}".format( err, short_fname, other_fname, @@ -297,7 +314,7 @@ def test_dictionary_looping( for err in this_err_dict: assert ( err not in other_err_dict - ), "error %r in dictionary %s already exists in dictionary %s" % ( + ), "error {!r} in dictionary {} already exists in dictionary {}".format( err, short_fname, other_fname, diff --git a/pyproject-codespell.precommit-toml b/pyproject-codespell.precommit-toml new file mode 100644 index 0000000000..41a15c09dc --- /dev/null +++ b/pyproject-codespell.precommit-toml @@ -0,0 +1,7 @@ +[tool.codespell] +#builtin = ["clear","rare","informal","usage","code","names"] +builtin = "clear,rare,informal,usage,code,names" +#ignore-words-list = ["uint"] +ignore-words-list = "uint" +#skip=[ "./.*","codespell_lib/data/*","codespell_lib/tests/*"] +skip="./.*,codespell_lib/data/*,codespell_lib/tests/*" diff --git a/pyproject.toml b/pyproject.toml index 32c9e55404..4dc98082c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ dynamic = ["version"] dev = [ "check-manifest", "flake8", + "flake8-pyproject", "pytest", "pytest-cov", "pytest-dependency", @@ -49,6 +50,8 @@ toml = [ types = [ "mypy", "pytest", + "pytest-cov", + "pytest-dependency", "types-chardet", ] @@ -79,9 +82,28 @@ codespell_lib = [ "py.typed", ] +[tool.autoflake] +in-place = true +recursive = true +expand-star-imports = true + +[tool.bandit] +skip = "B101,B404,B603" +recursive = true + [tool.check-manifest] ignore = ["codespell_lib/_version.py"] +# TODO: reintegrate codespell configuration after updating test cases +#[tool.codespell] +#builtin = ["clear","rare","informal","usage","code","names"] +#ignore-words-list = ["uint"] +#skip=[ "./.*","codespell_lib/data/*","codespell_lib/tests/*"] + +[tool.flake8] +max-line-length = "88" +extend-ignore = "E203" + [tool.isort] profile = "black" @@ -90,5 +112,45 @@ pretty = true show_error_codes = true strict = true +[tool.pylint] +reports=false +py-version="3.7" +disable = [ + "broad-except", + "consider-using-f-string", + "consider-using-dict-items", + "consider-using-with", + "fixme", + "import-error", + "import-outside-toplevel", + "invalid-name", + "line-too-long", + "missing-class-docstring", + "missing-module-docstring", + "missing-function-docstring", + "no-else-raise", + "no-else-return", + "raise-missing-from", + "redefined-outer-name", + "subprocess-run-check", + "too-many-arguments", + "too-many-lines", + "too-many-locals", + "too-many-branches", + "too-many-statements", + "too-many-return-statements", + "too-few-public-methods", + "unneeded-not", + "unspecified-encoding", + "unused-argument", + "unused-variable", + "use-maxsplit-arg" +] + + +[tool.pylint.FORMAT] +good-names=["F","r","i","n"] +# include-naming-hint=yes + [tool.pytest.ini_options] addopts = "--cov=codespell_lib -rs --cov-report= --tb=short --junit-xml=junit-results.xml" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 8dd399ab55..0000000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -max-line-length = 88 -extend-ignore = E203