Source code

Revision control

Copy as Markdown

Other Tools

# 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 difflib
import os
import re
import yaml
from mozlint import result
from mozlint.pathutils import expand_exclusions
here = os.path.dirname(__file__)
with open(os.path.join(here, "..", "..", "..", "mfbt", "api.yml")) as fd:
description = yaml.safe_load(fd)
def generate_diff(path, raw_content, line_to_delete):
prev_content = raw_content.split("\n")
new_content = [
raw_line
for lineno, raw_line in enumerate(prev_content, start=1)
if lineno != line_to_delete
]
diff = "\n".join(
difflib.unified_diff(prev_content, new_content, fromfile=path, tofile=path)
)
return diff
def fix(path, raw_content, line_to_delete):
prev_content = raw_content.split("\n")
new_content = [
raw_line
for lineno, raw_line in enumerate(prev_content, start=1)
if lineno != line_to_delete
]
with open(path, "w") as outfd:
outfd.write("\n".join(new_content))
symbol_pattern = r"\b{}\b"
literal_pattern = r'[0-9."\']{}\b'
categories_pattern = {
"variables": symbol_pattern,
"functions": symbol_pattern,
"macros": symbol_pattern,
"types": symbol_pattern,
"literals": literal_pattern,
}
def lint(paths, config, **lintargs):
results = {"results": [], "fixed": 0}
paths = list(expand_exclusions(paths, config, lintargs["root"]))
for path in paths:
try:
with open(path) as fd:
raw_content = fd.read()
except UnicodeDecodeError:
continue
supported_keys = "variables", "functions", "macros", "types", "literals"
for header, categories in description.items():
assert set(categories.keys()).issubset(supported_keys)
if path.endswith(f"mfbt/{header}") or path.endswith(
f"mfbt/{header[:-1]}.cpp"
):
continue
headerline = rf'#\s*include "mozilla/{header}"'
if not re.search(headerline, raw_content):
continue
content = raw_content.replace(f'"mozilla/{header}"', "")
for category, pattern in categories_pattern.items():
identifiers = categories.get(category, [])
if any(
re.search(pattern.format(identifier), content)
for identifier in identifiers
):
break
else:
msg = f"{path} includes {header} but does not reference any of its API"
for lineno, raw_line in enumerate(raw_content.split("\n"), start=1):
if re.search(headerline, raw_line):
break
if lintargs.get("fix"):
fix(path, raw_content, lineno)
results["fixed"] += 1
else:
diff = generate_diff(path, raw_content, lineno)
results["results"].append(
result.from_config(
config,
path=path,
message=msg,
level="error",
lineno=lineno,
diff=diff,
)
)
return results