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 138 139 140 141 142 143 144 145 146 147
|
# -*- 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.
"extensions.backgroundServiceWorker.enabled", # NOQA: E501; Uses the 'locked' attribute.
"general.smoothScroll", # Uses the 'sticky` attribute.
}
# A regular expression to match preference names and values from js preference
# files. This is not an exact parser, but close enough for our purposes.
# The exact parser grammar is defined at https://searchfox.org/mozilla-central/rev/5c2888b35d56928d252acf84e8816fa89a8a6a61/modules/libpref/parser/src/lib.rs#5-30
PATTERN = re.compile(
r"""
\s*pref\(
\s*"
(?P<pref>[^"]+)
"
\s*,
\s*
(?P<val>
"( # String value
[^"\\]+ # Any unescaped string character.
|
\\. # An escaped character.
)*"
|
[^,)]+ # other literals: true, false, integers
)
(\s*,.*)? # optional pref-attr: "sticky" | "locked"
\s*\)
\s*;.*
""",
re.VERBOSE,
)
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, encoding="utf-8").read().replace("@", "")
try:
pref_list = yaml.safe_load(file)
except (OSError, ValueError) as e:
print(f"{pref_list_filename}: error:\n {e}", file=sys.stderr)
sys.exit(1)
# Caveats on pref["value"]:
# - StaticPrefList.yaml may contain expressions such as 10*1000, 0.0f, and
# even float(M_PI / 6.0). We don't parse these and may therefore fail to
# report these prefs if their value in the js pref file still matches.
# - Some prefs have values dependent on preprocessor directives. In these
# cases, the last value takes precedence over values declared at an
# earlier line.
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, encoding="utf-8") 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
|