summaryrefslogtreecommitdiffstats
path: root/tools/lint/python/pylint.py
blob: 8bb0c68b8737eb52774f20c1d560f82d27c6a1d5 (plain)
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
133
# 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

from mach.site import InstallPipRequirementsException
from mozlint import result
from mozlint.pathutils import expand_exclusions
from mozprocess import ProcessHandler

here = os.path.abspath(os.path.dirname(__file__))
PYLINT_REQUIREMENTS_PATH = os.path.join(here, "pylint_requirements.txt")

PYLINT_NOT_FOUND = """
Could not find pylint! Install pylint and try again.

    $ pip install -U --require-hashes -r {}
""".strip().format(
    PYLINT_REQUIREMENTS_PATH
)


PYLINT_INSTALL_ERROR = """
Unable to install correct version of pylint
Try to install it manually with:
    $ pip install -U --require-hashes -r {}
""".strip().format(
    PYLINT_REQUIREMENTS_PATH
)


class PylintProcess(ProcessHandler):
    def __init__(self, config, *args, **kwargs):
        self.config = config
        kwargs["stream"] = False
        kwargs["universal_newlines"] = True
        ProcessHandler.__init__(self, *args, **kwargs)

    def run(self, *args, **kwargs):
        orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
        ProcessHandler.run(self, *args, **kwargs)
        signal.signal(signal.SIGINT, orig)


def setup(root, **lintargs):
    virtualenv_manager = lintargs["virtualenv_manager"]
    try:
        virtualenv_manager.install_pip_requirements(
            PYLINT_REQUIREMENTS_PATH,
            quiet=True,
        )
    except (subprocess.CalledProcessError, InstallPipRequirementsException):
        print(PYLINT_INSTALL_ERROR)
        return 1


def get_pylint_binary():
    return "pylint"


def run_process(config, cmd):
    proc = PylintProcess(config, cmd)
    proc.run()
    try:
        proc.wait()
    except KeyboardInterrupt:
        proc.kill()

    return proc.output


def parse_issues(log, config, issues_json, path):
    results = []

    try:
        issues = json.loads(issues_json)
    except json.decoder.JSONDecodeError:
        log.debug("Could not parse the output:")
        log.debug("pylint output: {}".format(issues_json))
        return []

    for issue in issues:
        res = {
            "path": issue["path"],
            "level": issue["type"],
            "lineno": issue["line"],
            "column": issue["column"],
            "message": issue["message"],
            "rule": issue["message-id"],
        }
        results.append(result.from_config(config, **res))
    return results


def get_pylint_version(binary):
    return subprocess.check_output(
        [binary, "--version"],
        universal_newlines=True,
        stderr=subprocess.STDOUT,
    )


def lint(paths, config, **lintargs):
    log = lintargs["log"]

    binary = get_pylint_binary()

    log = lintargs["log"]
    paths = list(expand_exclusions(paths, config, lintargs["root"]))

    cmd_args = [binary]
    results = []

    # list from https://code.visualstudio.com/docs/python/linting#_pylint
    # And ignore a bit more elements
    cmd_args += [
        "-fjson",
        "--disable=all",
        "--enable=F,E,unreachable,duplicate-key,unnecessary-semicolon,global-variable-not-assigned,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode,no-else-return",  # NOQA: E501
        "--disable=import-error,no-member",
    ]

    base_command = cmd_args + paths
    log.debug("Command: {}".format(" ".join(cmd_args)))
    log.debug("pylint version: {}".format(get_pylint_version(binary)))
    output = " ".join(run_process(config, base_command))
    results = parse_issues(log, config, str(output), [])

    return results