DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
# -*- Mode: python; c-basic-offset: 4; 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`.
"""

results = []


def process_line(line):
    try:
        res = json.loads(line)
    except ValueError:
        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"}',
    ]

    exclude = lintargs.get('exclude')
    if exclude:
        cmdargs += ['--exclude', ','.join(lintargs['exclude'])]

    # 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])

    if no_config:
        run_process(cmdargs+no_config)

    return results


LINTER = {
    'name': "flake8",
    'description': "Python linter",
    'include': [
        'python/mozlint',
        'tools/lint',
        'testing/marionette/client'
    ],
    'exclude': [],
    'type': 'external',
    'payload': lint,
}