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
import difflib
import os
import re
import yaml
from mozlint import result
from mozlint.pathutils import expand_exclusions
from .std import api as std_api
from .std import capi as std_capi
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_includes(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_mfbt_headers(results, path, raw_content, config, fix):
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 (match := 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"
lineno = 1 + raw_content.count("\n", 0, match.start())
if fix:
fix_includes(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,
)
)
def lint_std_headers(results, path, raw_content, config, fix):
if re.search(r"using\s+namespace\s+std", raw_content):
return
symbol_pattern = r"\bstd::{}\b"
for header, symbols in std_api.items():
headerline = rf"#\s*include <{header}>"
if not (match := re.search(headerline, raw_content)):
continue
if re.search(
"|".join(symbol_pattern.format(symbol) for symbol in symbols), raw_content
):
continue
msg = f"{path} includes <{header}> but does not reference any of its API"
lineno = 1 + raw_content.count("\n", 0, match.start())
if fix:
fix_includes(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,
)
)
def lint_cstd_headers(results, path, raw_content, config, fix):
symbol_pattern = r"\b((std)?::)?{}\b"
for header, symbols in std_capi.items():
headerline = rf"#\s*include <({header}|c{header[:-2]})>"
if not (match := re.search(headerline, raw_content)):
continue
if re.search(
"|".join(symbol_pattern.format(symbol) for symbol in symbols), raw_content
):
continue
msg = (
f"{path} includes <{match.group(1)}> but does not reference any of its API"
)
lineno = 1 + raw_content.count("\n", 0, match.start())
if fix:
fix_includes(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,
)
)
def lint(paths, config, **lintargs):
results = {"results": [], "fixed": 0}
paths = list(expand_exclusions(paths, config, lintargs["root"]))
fix = lintargs.get("fix")
for path in paths:
try:
with open(path) as fd:
raw_content = fd.read()
except UnicodeDecodeError:
continue
lint_mfbt_headers(results, path, raw_content, config, fix)
lint_std_headers(results, path, raw_content, config, fix)
lint_cstd_headers(results, path, raw_content, config, fix)
return results