summaryrefslogtreecommitdiffstats
path: root/tools/lint/spell
diff options
context:
space:
mode:
Diffstat (limited to 'tools/lint/spell')
-rw-r--r--tools/lint/spell/__init__.py168
-rw-r--r--tools/lint/spell/codespell_requirements.in1
-rw-r--r--tools/lint/spell/codespell_requirements.txt10
-rw-r--r--tools/lint/spell/exclude-list.txt23
4 files changed, 202 insertions, 0 deletions
diff --git a/tools/lint/spell/__init__.py b/tools/lint/spell/__init__.py
new file mode 100644
index 0000000000..65712acdd7
--- /dev/null
+++ b/tools/lint/spell/__init__.py
@@ -0,0 +1,168 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import re
+import subprocess
+
+# py2-compat
+try:
+ from json.decoder import JSONDecodeError
+except ImportError:
+ JSONDecodeError = ValueError
+
+from mozfile import which
+from mozlint import result
+from mozlint.util.implementation import LintProcess
+
+here = os.path.abspath(os.path.dirname(__file__))
+CODESPELL_REQUIREMENTS_PATH = os.path.join(here, "codespell_requirements.txt")
+
+CODESPELL_NOT_FOUND = """
+Could not find codespell! Install codespell and try again.
+
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ CODESPELL_REQUIREMENTS_PATH
+)
+
+
+CODESPELL_INSTALL_ERROR = """
+Unable to install correct version of codespell
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ CODESPELL_REQUIREMENTS_PATH
+)
+
+results = []
+
+CODESPELL_FORMAT_REGEX = re.compile(r"(.*):(.*): (.*) ==> (.*)$")
+
+
+class CodespellProcess(LintProcess):
+ fixed = 0
+ _fix = None
+
+ def process_line(self, line):
+ try:
+ match = CODESPELL_FORMAT_REGEX.match(line)
+ abspath, line, typo, correct = match.groups()
+ except AttributeError:
+ if "FIXED: " not in line:
+ print("Unable to match regex against output: {}".format(line))
+ return
+
+ if CodespellProcess._fix:
+ CodespellProcess.fixed += 1
+
+ # Ignore false positive like aParent (which would be fixed to apparent)
+ # See https://github.com/lucasdemarchi/codespell/issues/314
+ m = re.match(r"^[a-z][A-Z][a-z]*", typo)
+ if m:
+ return
+ res = {
+ "path": abspath,
+ "message": typo.strip() + " ==> " + correct,
+ "level": "error",
+ "lineno": line,
+ }
+ results.append(result.from_config(self.config, **res))
+
+
+def run_process(config, cmd):
+ proc = CodespellProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+
+def get_codespell_binary():
+ """
+ Returns the path of the first codespell binary available
+ if not found returns None
+ """
+ binary = os.environ.get("CODESPELL")
+ if binary:
+ return binary
+
+ return which("codespell")
+
+
+def setup(root, **lintargs):
+ virtualenv_manager = lintargs["virtualenv_manager"]
+ try:
+ virtualenv_manager.install_pip_requirements(
+ CODESPELL_REQUIREMENTS_PATH, quiet=True
+ )
+ except subprocess.CalledProcessError:
+ print(CODESPELL_INSTALL_ERROR)
+ return 1
+
+
+def get_codespell_version(binary):
+ return subprocess.check_output(
+ [which("python"), binary, "--version"],
+ universal_newlines=True,
+ stderr=subprocess.STDOUT,
+ )
+
+
+def get_ignored_words_file(config):
+ config_root = os.path.dirname(config["path"])
+ return os.path.join(config_root, "spell", "exclude-list.txt")
+
+
+def lint(paths, config, fix=None, **lintargs):
+ log = lintargs["log"]
+ binary = get_codespell_binary()
+ if not binary:
+ print(CODESPELL_NOT_FOUND)
+ if "MOZ_AUTOMATION" in os.environ:
+ return 1
+ return []
+
+ config["root"] = lintargs["root"]
+
+ exclude_list = get_ignored_words_file(config)
+ cmd_args = [
+ which("python"),
+ binary,
+ "--disable-colors",
+ # Silence some warnings:
+ # 1: disable warnings about wrong encoding
+ # 2: disable warnings about binary file
+ # 4: shut down warnings about automatic fixes
+ # that were disabled in dictionary.
+ "--quiet-level=7",
+ "--ignore-words=" + exclude_list,
+ ]
+
+ if "exclude" in config:
+ cmd_args.append("--skip=*.dic,{}".format(",".join(config["exclude"])))
+
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ log.debug("Version: {}".format(get_codespell_version(binary)))
+
+ if fix:
+ CodespellProcess._fix = True
+
+ base_command = cmd_args + paths
+ run_process(config, base_command)
+
+ if fix:
+ global results
+ results = []
+ cmd_args.append("--write-changes")
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ log.debug("Version: {}".format(get_codespell_version(binary)))
+ base_command = cmd_args + paths
+ run_process(config, base_command)
+ CodespellProcess.fixed = CodespellProcess.fixed - len(results)
+ else:
+ CodespellProcess.fixed = 0
+
+ return {"results": results, "fixed": CodespellProcess.fixed}
diff --git a/tools/lint/spell/codespell_requirements.in b/tools/lint/spell/codespell_requirements.in
new file mode 100644
index 0000000000..407f17489c
--- /dev/null
+++ b/tools/lint/spell/codespell_requirements.in
@@ -0,0 +1 @@
+codespell==2.2.4
diff --git a/tools/lint/spell/codespell_requirements.txt b/tools/lint/spell/codespell_requirements.txt
new file mode 100644
index 0000000000..d8f0be543b
--- /dev/null
+++ b/tools/lint/spell/codespell_requirements.txt
@@ -0,0 +1,10 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# pip-compile --generate-hashes tools/lint/spell/codespell_requirements.in
+#
+codespell==2.2.4 \
+ --hash=sha256:0b4620473c257d9cde1ff8998b26b2bb209a35c2b7489f5dc3436024298ce83a \
+ --hash=sha256:7d984b8130108e6f82524b7d09f8b7bf2fb1e398c5d4b37d9e2bd310145b3e29
+ # via -r tools/lint/spell/codespell_requirements.in
diff --git a/tools/lint/spell/exclude-list.txt b/tools/lint/spell/exclude-list.txt
new file mode 100644
index 0000000000..b5c0290882
--- /dev/null
+++ b/tools/lint/spell/exclude-list.txt
@@ -0,0 +1,23 @@
+cas
+optin
+aparent
+acount
+te
+wasn
+incrementall
+aare
+whats
+crate
+files'
+thru
+referer
+dur
+ue
+tring
+delink
+warmup
+aNumber
+falsy
+rduce
+complies
+ehr