# -*- 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 which from mozprocess import ProcessHandler from mozlint import result FLAKE8_NOT_FOUND = """ Could not find flake8! Install flake8 and try again. $ pip install flake8 """.strip() 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 lint(files, **lintargs): binary = os.environ.get('FLAKE8') if not binary: try: binary = which.which('flake8') except which.WhichError: pass if not binary: print(FLAKE8_NOT_FOUND) return [] 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/mozbase', 'testing/mochitest', 'testing/puppeteer', 'testing/talos/', 'tools/lint', ], 'exclude': ["testing/mozbase/mozdevice/mozdevice/Zeroconf.py", 'testing/mochitest/pywebsocket'], 'extensions': EXTENSIONS, 'type': 'external', 'payload': lint, }