summaryrefslogtreecommitdiffstats
path: root/tools/lint/libpref/__init__.py
blob: e986be39d093c95fbc2742c64b52a5f5562f596c (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
# -*- 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 re
import sys

import yaml
from mozlint import result
from mozlint.pathutils import expand_exclusions

# This simple linter checks for duplicates from
# modules/libpref/init/StaticPrefList.yaml against modules/libpref/init/all.js

# If for any reason a pref needs to appear in both files, add it to this set.
IGNORE_PREFS = {
    "devtools.console.stdout.chrome",  # Uses the 'sticky' attribute.
    "devtools.console.stdout.content",  # Uses the 'sticky' attribute.
    "fission.autostart",  # Uses the 'locked' attribute.
    "browser.dom.window.dump.enabled",  # Uses the 'sticky' attribute.
    "apz.fling_curve_function_y2",  # This pref is a part of a series.
    "dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled",  # NOQA: E501; Uses the 'locked' attribute.
}
PATTERN = re.compile(r"\s*pref\(\s*\"(?P<pref>.+)\"\s*,\s*(?P<val>.+)\)\s*;.*")


def get_names(pref_list_filename):
    pref_names = {}
    # We want to transform patterns like 'foo: @VAR@' into valid yaml. This
    # pattern does not happen in 'name', so it's fine to ignore these.
    # We also want to evaluate all branches of #ifdefs for pref names, so we
    # ignore anything else preprocessor related.
    file = open(pref_list_filename).read().replace("@", "")
    try:
        pref_list = yaml.safe_load(file)
    except (IOError, ValueError) as e:
        print("{}: error:\n  {}".format(pref_list_filename, e), file=sys.stderr)
        sys.exit(1)

    for pref in pref_list:
        if pref["name"] not in IGNORE_PREFS:
            pref_names[pref["name"]] = pref["value"]

    return pref_names


# Check the names of prefs against each other, and if the pref is a duplicate
# that has not previously been noted, add that name to the list of errors.
def check_against(path, pref_names):
    errors = []
    prefs = read_prefs(path)
    for pref in prefs:
        if pref["name"] in pref_names:
            errors.extend(check_value_for_pref(pref, pref_names[pref["name"]], path))
    return errors


def check_value_for_pref(some_pref, some_value, path):
    errors = []
    if some_pref["value"] == some_value:
        errors.append(
            {
                "path": path,
                "message": some_pref["raw"],
                "lineno": some_pref["line"],
                "hint": "Remove the duplicate pref or add it to IGNORE_PREFS.",
                "level": "error",
            }
        )
    return errors


# The entries in the *.js pref files are regular enough to use simple pattern
# matching to load in prefs.
def read_prefs(path):
    prefs = []
    with open(path) as source:
        for lineno, line in enumerate(source, start=1):
            match = PATTERN.match(line)
            if match:
                prefs.append(
                    {
                        "name": match.group("pref"),
                        "value": evaluate_pref(match.group("val")),
                        "line": lineno,
                        "raw": line,
                    }
                )
    return prefs


def evaluate_pref(value):
    bools = {"true": True, "false": False}
    if value in bools:
        return bools[value]
    elif value.isdigit():
        return int(value)
    return value


def checkdupes(paths, config, **kwargs):
    results = []
    errors = []
    pref_names = get_names(config["support-files"][0])
    files = list(expand_exclusions(paths, config, kwargs["root"]))
    for file in files:
        errors.extend(check_against(file, pref_names))
    for error in errors:
        results.append(result.from_config(config, **error))
    return results