summaryrefslogtreecommitdiffstats
path: root/tools/lint/clippy/__init__.py
blob: 1facb246fcb251047f0323a246e6ed4d7c30aeb9 (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
# 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 bisect
import json
import os
import subprocess
import sys

from mozlint import result
from mozlint.pathutils import expand_exclusions


def in_sorted_list(l, x):
    i = bisect.bisect_left(l, x)
    return i < len(l) and l[i] == x


def handle_clippy_msg(config, line, log, base_path, files):
    try:
        detail = json.loads(line)
        if "message" in detail:
            p = detail["target"]["src_path"]
            detail = detail["message"]
            if "level" in detail:
                if (
                    detail["level"] == "error" or detail["level"] == "failure-note"
                ) and not detail["code"]:
                    log.debug(
                        "Error outside of clippy."
                        "This means that the build failed. Therefore, skipping this"
                    )
                    log.debug("File = {} / Detail = {}".format(p, detail))
                    return
                # We are in a clippy warning
                if len(detail["spans"]) == 0:
                    # For some reason, at the end of the summary, we can
                    # get the following line
                    # {'rendered': 'warning: 5 warnings emitted\n\n', 'children':
                    # [], 'code': None, 'level': 'warning', 'message':
                    # '5 warnings emitted', 'spans': []}
                    # if this is the case, skip it
                    log.debug(
                        "Skipping the summary line {} for file {}".format(detail, p)
                    )
                    return

                l = detail["spans"][0]
                if not in_sorted_list(files, p):
                    return
                p = os.path.join(base_path, l["file_name"])
                res = {
                    "path": p,
                    "level": detail["level"],
                    "lineno": l["line_start"],
                    "column": l["column_start"],
                    "message": detail["message"],
                    "hint": detail["rendered"],
                    "rule": detail["code"]["code"],
                    "lineoffset": l["line_end"] - l["line_start"],
                }
                return result.from_config(config, **res)

    except json.decoder.JSONDecodeError:
        log.debug("Could not parse the output:")
        log.debug("clippy output: {}".format(line))
        return


def lint(paths, config, fix=None, **lintargs):
    files = list(expand_exclusions(paths, config, lintargs["root"]))
    files.sort()
    log = lintargs["log"]
    results = []
    mach_path = lintargs["root"] + "/mach"
    march_cargo_process = subprocess.Popen(
        [
            sys.executable,
            mach_path,
            "--log-no-times",
            "cargo",
            "clippy",
            "--",
            "--message-format=json",
        ],
        stdout=subprocess.PIPE,
        text=True,
    )
    for l in march_cargo_process.stdout:
        r = handle_clippy_msg(config, l, log, lintargs["root"], files)
        if r is not None:
            results.append(r)
    march_cargo_process.wait()

    if fix:
        log.error("Rust linting in mach does not support --fix")

    return results