Bug 1433410 - Add codespell support for mach lint r?ahal draft
authorSylvestre Ledru <sledru@mozilla.com>
Sat, 27 Jan 2018 10:35:31 +0100
changeset 747963 b77ce3b3342b649106347b073d3a8050f62f79da
parent 724080 0e62eb7804c00c0996a9bdde5350328a384fb7af
push id97043
push userbmo:sledru@mozilla.com
push dateSat, 27 Jan 2018 09:38:42 +0000
reviewersahal
bugs1433410
milestone60.0a1
Bug 1433410 - Add codespell support for mach lint r?ahal MozReview-Commit-ID: Ii6QjPMN0Ks
taskcluster/docker/lint/system-setup.sh
tools/lint/codespell.yml
tools/lint/spell/__init__.py
--- a/taskcluster/docker/lint/system-setup.sh
+++ b/taskcluster/docker/lint/system-setup.sh
@@ -5,16 +5,17 @@ export DEBIAN_FRONTEND=noninteractive
 set -ve
 
 test `whoami` == 'root'
 
 mkdir -p /setup
 cd /setup
 
 apt_packages=()
+apt_packages+=('codespell')
 apt_packages+=('curl')
 apt_packages+=('locales')
 apt_packages+=('git')
 apt_packages+=('python')
 apt_packages+=('python-pip')
 apt_packages+=('python3')
 apt_packages+=('python3-pip')
 apt_packages+=('shellcheck')
new file mode 100644
--- /dev/null
+++ b/tools/lint/codespell.yml
@@ -0,0 +1,27 @@
+---
+codespell:
+    description: Check code for common misspellings
+    include: ['.']
+    exclude:
+        - third_party
+    # List of extensions coming from:
+    # tools/lint/{flake8,eslint}.yml
+    # tools/mach_commands.py (clang-format)
+    # + documentation
+    # + localization files
+    extensions:
+        - js
+        - jsm
+        - jxs
+        - xml
+        - html
+        - xhtml
+        - cpp
+        - c
+        - h
+        - configure
+        - py
+        - properties
+        - rst
+    type: external
+    payload: spell:lint
new file mode 100644
--- /dev/null
+++ b/tools/lint/spell/__init__.py
@@ -0,0 +1,117 @@
+# 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/.
+
+from __future__ import absolute_import, print_function
+
+import os
+import signal
+import which
+import re
+
+# Py3/Py2 compatibility.
+try:
+    from json.decoder import JSONDecodeError
+except ImportError:
+    JSONDecodeError = ValueError
+
+from mozlint import result
+from mozprocess import ProcessHandlerMixin
+
+
+CODESPELL_NOT_FOUND = """
+Unable to locate codespell, please ensure it is installed and in
+your PATH or set the CODESPELL environment variable.
+
+https://github.com/lucasdemarchi/codespell or your system's package manager.
+""".strip()
+
+results = []
+
+CODESPELL_FORMAT_REGEX = re.compile(r'(.*):(.*): (.*) ==> (.*)$')
+
+
+class CodespellProcess(ProcessHandlerMixin):
+    def __init__(self, config, *args, **kwargs):
+        self.config = config
+        kwargs['processOutputLine'] = [self.process_line]
+        ProcessHandlerMixin.__init__(self, *args, **kwargs)
+
+    def process_line(self, line):
+        try:
+            match = CODESPELL_FORMAT_REGEX.match(line)
+            abspath, line, typo, correct = match.groups()
+        except AttributeError:
+            print('Unable to match regex against output: {}'.format(line))
+            return
+
+        # 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': os.path.relpath(abspath, self.config['root']),
+               'message': typo + " ==> " + correct,
+               'level': "warning",
+               'lineno': line,
+               }
+        results.append(result.from_config(self.config, **res))
+
+    def run(self, *args, **kwargs):
+        orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+        ProcessHandlerMixin.run(self, *args, **kwargs)
+        signal.signal(signal.SIGINT, orig)
+
+
+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
+
+    try:
+        return which.which('codespell')
+    except which.WhichError:
+        return None
+
+
+def lint(paths, config, fix=None, **lintargs):
+
+    binary = get_codespell_binary()
+
+    if not binary:
+        print(CODESPELL_NOT_FOUND)
+        if 'MOZ_AUTOMATION' in os.environ:
+            return 1
+        return []
+
+    config['root'] = lintargs['root']
+    cmd_args = [binary,
+                '--disable-colors',
+                # Silence some warnings:
+                # 1: disable warnings about wrong encoding
+                # 2: disable warnings about binary file
+                '--quiet-level=3',
+                ]
+
+# Disabled for now because of
+# https://github.com/lucasdemarchi/codespell/issues/314
+#    if fix:
+#        cmd_args.append('--write-changes')
+
+    base_command = cmd_args + paths
+
+    run_process(config, base_command)
+    return results