summaryrefslogtreecommitdiffstats
path: root/tools/lint/flake8.lint
diff options
context:
space:
mode:
Diffstat (limited to 'tools/lint/flake8.lint')
-rw-r--r--tools/lint/flake8.lint195
1 files changed, 195 insertions, 0 deletions
diff --git a/tools/lint/flake8.lint b/tools/lint/flake8.lint
new file mode 100644
index 000000000..eaf4a4dff
--- /dev/null
+++ b/tools/lint/flake8.lint
@@ -0,0 +1,195 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 json
+import os
+import signal
+import subprocess
+
+import which
+from mozprocess import ProcessHandler
+
+from mozlint import result
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+FLAKE8_REQUIREMENTS_PATH = os.path.join(here, 'flake8', 'flake8_requirements.txt')
+
+FLAKE8_NOT_FOUND = """
+Could not find flake8! Install flake8 and try again.
+
+ $ pip install -U --require-hashes -r {}
+""".strip().format(FLAKE8_REQUIREMENTS_PATH)
+
+
+FLAKE8_INSTALL_ERROR = """
+Unable to install correct version of flake8
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(FLAKE8_REQUIREMENTS_PATH)
+
+LINE_OFFSETS = {
+ # continuation line under-indented for hanging indent
+ 'E121': (-1, 2),
+ # continuation line missing indentation or outdented
+ 'E122': (-1, 2),
+ # continuation line over-indented for hanging indent
+ 'E126': (-1, 2),
+ # continuation line over-indented for visual indent
+ 'E127': (-1, 2),
+ # continuation line under-indented for visual indent
+ 'E128': (-1, 2),
+ # continuation line unaligned for hanging indend
+ 'E131': (-1, 2),
+ # expected 1 blank line, found 0
+ 'E301': (-1, 2),
+ # expected 2 blank lines, found 1
+ 'E302': (-2, 3),
+}
+"""Maps a flake8 error to a lineoffset tuple.
+
+The offset is of the form (lineno_offset, num_lines) and is passed
+to the lineoffset property of `ResultContainer`.
+"""
+
+EXTENSIONS = ['.py', '.lint']
+results = []
+
+
+def process_line(line):
+ # Escape slashes otherwise JSON conversion will not work
+ line = line.replace('\\', '\\\\')
+ try:
+ res = json.loads(line)
+ except ValueError:
+ print('Non JSON output from linter, will not be processed: {}'.format(line))
+ return
+
+ if 'code' in res:
+ if res['code'].startswith('W'):
+ res['level'] = 'warning'
+
+ if res['code'] in LINE_OFFSETS:
+ res['lineoffset'] = LINE_OFFSETS[res['code']]
+
+ results.append(result.from_linter(LINTER, **res))
+
+
+def run_process(cmdargs):
+ # flake8 seems to handle SIGINT poorly. Handle it here instead
+ # so we can kill the process without a cryptic traceback.
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ proc = ProcessHandler(cmdargs, env=os.environ,
+ processOutputLine=process_line)
+ proc.run()
+ signal.signal(signal.SIGINT, orig)
+
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+
+def get_flake8_binary():
+ """
+ Returns the path of the first flake8 binary available
+ if not found returns None
+ """
+ binary = os.environ.get('FLAKE8')
+ if binary:
+ return binary
+
+ try:
+ return which.which('flake8')
+ except which.WhichError:
+ return None
+
+
+def _run_pip(*args):
+ """
+ Helper function that runs pip with subprocess
+ """
+ try:
+ subprocess.check_output(['pip'] + list(args),
+ stderr=subprocess.STDOUT)
+ return True
+ except subprocess.CalledProcessError as e:
+ print(e.output)
+ return False
+
+
+def reinstall_flake8():
+ """
+ Try to install flake8 at the target version, returns True on success
+ otherwise prints the otuput of the pip command and returns False
+ """
+ if _run_pip('install', '-U',
+ '--require-hashes', '-r',
+ FLAKE8_REQUIREMENTS_PATH):
+ return True
+
+ return False
+
+
+def lint(files, **lintargs):
+
+ if not reinstall_flake8():
+ print(FLAKE8_INSTALL_ERROR)
+ return 1
+
+ binary = get_flake8_binary()
+
+ cmdargs = [
+ binary,
+ '--format', '{"path":"%(path)s","lineno":%(row)s,'
+ '"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
+ ]
+
+ # Run any paths with a .flake8 file in the directory separately so
+ # it gets picked up. This means only .flake8 files that live in
+ # directories that are explicitly included will be considered.
+ # See bug 1277851
+ no_config = []
+ for f in files:
+ if not os.path.isfile(os.path.join(f, '.flake8')):
+ no_config.append(f)
+ continue
+ run_process(cmdargs+[f])
+
+ # XXX For some reason passing in --exclude results in flake8 not using
+ # the local .flake8 file. So for now only pass in --exclude if there
+ # is no local config.
+ exclude = lintargs.get('exclude')
+ if exclude:
+ cmdargs += ['--exclude', ','.join(lintargs['exclude'])]
+
+ if no_config:
+ run_process(cmdargs+no_config)
+
+ return results
+
+
+LINTER = {
+ 'name': "flake8",
+ 'description': "Python linter",
+ 'include': [
+ 'python/mozlint',
+ 'taskcluster',
+ 'testing/firefox-ui',
+ 'testing/marionette/client',
+ 'testing/marionette/harness',
+ 'testing/marionette/puppeteer',
+ 'testing/mozbase',
+ 'testing/mochitest',
+ 'testing/talos/',
+ 'tools/lint',
+ ],
+ 'exclude': ["testing/mozbase/mozdevice/mozdevice/Zeroconf.py",
+ 'testing/mochitest/pywebsocket'],
+ 'extensions': EXTENSIONS,
+ 'type': 'external',
+ 'payload': lint,
+}