summaryrefslogtreecommitdiffstats
path: root/tools/lint/rst/__init__.py
blob: 7151c09a590ca585b98936a04eac3598c4cb05dc (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
# 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 os
import re
import subprocess

from mozfile import which
from mozlint import result
from mozlint.pathutils import expand_exclusions

# Error Levels
# (0, 'debug')
# (1, 'info')
# (2, 'warning')
# (3, 'error')
# (4, 'severe')

abspath = os.path.abspath(os.path.dirname(__file__))
rstcheck_requirements_file = os.path.join(abspath, "requirements.txt")

results = []

RSTCHECK_NOT_FOUND = """
Could not find rstcheck! Install rstcheck and try again.

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

RSTCHECK_INSTALL_ERROR = """
Unable to install required version of rstcheck
Try to install it manually with:
    $ pip install -U --require-hashes -r {}
""".strip().format(
    rstcheck_requirements_file
)

RSTCHECK_FORMAT_REGEX = re.compile(r"(.*):(.*): \(.*/([0-9]*)\) (.*)$")
IGNORE_NOT_REF_LINK_UPSTREAM_BUG = re.compile(
    r"Hyperlink target (.*) is not referenced."
)


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


def get_rstcheck_binary():
    """
    Returns the path of the first rstcheck binary available
    if not found returns None
    """
    binary = os.environ.get("RSTCHECK")
    if binary:
        return binary

    return which("rstcheck")


def parse_with_split(errors):
    match = RSTCHECK_FORMAT_REGEX.match(errors)
    filename, lineno, level, message = match.groups()

    return filename, lineno, level, message


def lint(files, config, **lintargs):
    log = lintargs["log"]
    config["root"] = lintargs["root"]
    paths = expand_exclusions(files, config, config["root"])
    paths = list(paths)
    chunk_size = 50
    binary = get_rstcheck_binary()
    rstcheck_options = [
        "--ignore-language=cpp,json",
        "--ignore-roles=searchfox",
    ]

    while paths:
        cmdargs = [which("python"), binary] + rstcheck_options + paths[:chunk_size]
        log.debug("Command: {}".format(" ".join(cmdargs)))

        proc = subprocess.Popen(
            cmdargs,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            env=os.environ,
            universal_newlines=True,
        )
        all_errors = proc.communicate()[1]
        for errors in all_errors.split("\n"):
            if len(errors) > 1:
                filename, lineno, level, message = parse_with_split(errors)
                if not IGNORE_NOT_REF_LINK_UPSTREAM_BUG.match(message):
                    # Ignore an upstream bug
                    # https://github.com/myint/rstcheck/issues/19
                    res = {
                        "path": filename,
                        "message": message,
                        "lineno": lineno,
                        "level": "error" if int(level) >= 2 else "warning",
                    }
                    results.append(result.from_config(config, **res))
        paths = paths[chunk_size:]

    return results