summaryrefslogtreecommitdiffstats
path: root/tools/lint/python/pylint.py
blob: ba95be1f0b301c630967efbca45bcdd29eeb08f5 (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
134
135
136
137
# 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 subprocess

import signal

from mozprocess import ProcessHandler

from mozlint import result
from mozlint.pathutils import expand_exclusions

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,
            # The defined versions of astroid and lazy-object-proxy conflict and fail to
            # install with the new 2020 pip resolver (bug 1682959)
            legacy_resolver=True,
        )
    except subprocess.CalledProcessError:
        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