Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions lint.ignore
Original file line number Diff line number Diff line change
Expand Up @@ -847,3 +847,62 @@ PROMISE_REJECTS: wasm/core/js/harness/testharness.js

# Legitimate use of test_driver_internal
TEST DRIVER INTERNAL: resources/testdriver.js

# Intentional overlapping web feature mappings
OVERLAPPING-WEB-FEATURES-FILE: css/CSS2/backgrounds/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/CSS2/normal-flow/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/CSS2/page-box/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/CSS2/tables/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/CSS2/text/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-backgrounds/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-break/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-cascade/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-contain/content-visibility/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-display/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-display/parsing/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-flexbox/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-images/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-lists/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-masking/clip-path/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-masking/clip-path/animations/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-multicol/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-overflow/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-page/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-pseudo/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-scrollbars/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-sizing/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-text/text-indent/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-typed-om/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-ui/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-values/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-variables/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-view-transitions/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-writing-modes/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/css-writing-modes/parsing/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/cssom/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/mediaqueries/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/selectors/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/selectors/invalidation/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: css/selectors/parsing/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: custom-elements/reactions/customized-builtins/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: dom/abort/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: dom/nodes/moveBefore/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: fetch/api/basic/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: fetch/api/credentials/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: fetch/http-cache/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: file-system-access/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/editing/the-hidden-attribute/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/rendering/non-replaced-elements/phrasing-content-0/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/rendering/non-replaced-elements/the-fieldset-and-legend-elements/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/rendering/the-details-element/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/semantics/document-metadata/the-link-element/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/semantics/embedded-content/the-img-element/srcset/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/semantics/forms/the-button-element/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/semantics/forms/the-input-element/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/semantics/interactive-elements/the-dialog-element/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: html/semantics/scripting-1/the-script-element/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: shadow-dom/declarative/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: url/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: webmessaging/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: workers/interfaces/WorkerUtils/navigator/WEB_FEATURES.yml
OVERLAPPING-WEB-FEATURES-FILE: xhr/WEB_FEATURES.yml
24 changes: 24 additions & 0 deletions tools/lint/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,10 @@ def check_web_features_file(repo_root: Text, path: Text, f: IO[bytes]) -> List[r
base_dir = os.path.join(repo_root, os.path.dirname(path))
files_in_directory = [
f for f in os.listdir(base_dir) if os.path.isfile(os.path.join(base_dir, f))]
# Track which files are claimed by which feature.
file_to_feature: Dict[str, str] = {}
# Track overlaps grouped by feature pair: (earlier_feature, later_feature) -> [filenames]
overlap_pairs: Dict[Tuple[str, str], List[str]] = {}
for feature in web_features_file.features:
if isinstance(feature.files, list):
# Resolve inclusion patterns to files, then subtract exclusions.
Expand All @@ -802,6 +806,26 @@ def check_web_features_file(repo_root: Text, path: Text, f: IO[bytes]) -> List[r
errors.append(rules.UnnecessaryExclusionInWebFeaturesFile.error(path, (
f"'{file}' in feature '{feature.name}'",)))
included -= excluded
elif feature.does_feature_apply_recursively():
included = set(files_in_directory)
else:
continue
for filename in sorted(included):
if filename in file_to_feature:
pair = (file_to_feature[filename], feature.name)
overlap_pairs.setdefault(pair, []).append(filename)
else:
file_to_feature[filename] = feature.name

for (feature_a, feature_b), filenames in overlap_pairs.items():
max_files = 5
file_list = ", ".join(filenames[:max_files])
if len(filenames) > max_files:
file_list += f", and {len(filenames) - max_files} more"
errors.append(rules.OverlappingWebFeaturesFile.error(path, (
f"Features '{feature_a}' and '{feature_b}' share "
f"{len(filenames)} overlapping test file{'s' if len(filenames) > 1 else ''}: "
f"{file_list}",)))

return errors

Expand Down
7 changes: 7 additions & 0 deletions tools/lint/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,13 @@ class UnnecessaryExclusionInWebFeaturesFile(Rule):
""")


class OverlappingWebFeaturesFile(Rule):
name = "OVERLAPPING-WEB-FEATURES-FILE"
description = collapse("""
The WEB_FEATURES.yml file maps the same file to multiple features: %s
""")


class WebFeaturesFileInNonTestDirectory(Rule):
name = "WEB-FEATURES-FILE-IN-NON-TEST-DIRECTORY"
description = collapse("""
Expand Down
186 changes: 186 additions & 0 deletions tools/lint/tests/test_file_lints.py
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,192 @@ def test_duplicate_keys_invalid_web_features_file():

assert errors == []

@pytest.mark.parametrize("files,yml,expected_errors", [
# ** before explicit files — 1 overlapping file
(
["file-1.html", "file-2.html"],
b"""\
features:
- name: feature-a
files: "**"
- name: feature-b
files:
- file-1.html
""",
[
("OVERLAPPING-WEB-FEATURES-FILE",
"The WEB_FEATURES.yml file maps the same file to multiple features: "
"Features 'feature-a' and 'feature-b' share "
"1 overlapping test file: file-1.html",
"css/WEB_FEATURES.yml",
None),
]
),
# Explicit files before ** — 1 overlapping file
(
["file-1.html", "file-2.html"],
b"""\
features:
- name: feature-a
files:
- file-1.html
- name: feature-b
files: "**"
""",
[
("OVERLAPPING-WEB-FEATURES-FILE",
"The WEB_FEATURES.yml file maps the same file to multiple features: "
"Features 'feature-a' and 'feature-b' share "
"1 overlapping test file: file-1.html",
"css/WEB_FEATURES.yml",
None),
]
),
# ** before wildcard patterns — 2 overlapping files
(
["file-1.html", "file-2.html"],
b"""\
features:
- name: feature-a
files: "**"
- name: feature-b
files:
- file-*
""",
[
("OVERLAPPING-WEB-FEATURES-FILE",
"The WEB_FEATURES.yml file maps the same file to multiple features: "
"Features 'feature-a' and 'feature-b' share "
"2 overlapping test files: file-1.html, file-2.html",
"css/WEB_FEATURES.yml",
None),
]
),
# Same explicit file in two features
(
["file-1.html", "file-2.html"],
b"""\
features:
- name: feature-a
files:
- file-1.html
- name: feature-b
files:
- file-1.html
""",
[
("OVERLAPPING-WEB-FEATURES-FILE",
"The WEB_FEATURES.yml file maps the same file to multiple features: "
"Features 'feature-a' and 'feature-b' share "
"1 overlapping test file: file-1.html",
"css/WEB_FEATURES.yml",
None),
]
),
# Same wildcard pattern in two features — 2 overlapping files
(
["file-1.html", "file-2.html"],
b"""\
features:
- name: feature-a
files:
- file-*
- name: feature-b
files:
- file-*
""",
[
("OVERLAPPING-WEB-FEATURES-FILE",
"The WEB_FEATURES.yml file maps the same file to multiple features: "
"Features 'feature-a' and 'feature-b' share "
"2 overlapping test files: file-1.html, file-2.html",
"css/WEB_FEATURES.yml",
None),
]
),
# Non-overlapping wildcard patterns — no errors
(
["file-1.html", "file-2.html", "bar-1.html"],
b"""\
features:
- name: feature-a
files:
- file-*
- name: feature-b
files:
- bar-*
""",
[]
),
# Non-overlapping explicit files — no errors
(
["file-1.html", "file-2.html"],
b"""\
features:
- name: feature-a
files:
- file-1.html
- name: feature-b
files:
- file-2.html
""",
[]
),
# Overlapping wildcards with exclusions that prevent actual overlap — no errors
(
["file-1.html", "file-2.html"],
b"""\
features:
- name: feature-a
files:
- file-*
- "!file-2.html"
- name: feature-b
files:
- file-2.html
""",
[]
),
# Many overlapping files — capped at 5 in the message
(
["f-1.html", "f-2.html", "f-3.html", "f-4.html", "f-5.html", "f-6.html", "f-7.html"],
b"""\
features:
- name: feature-a
files:
- f-*
- name: feature-b
files:
- f-*
""",
[
("OVERLAPPING-WEB-FEATURES-FILE",
"The WEB_FEATURES.yml file maps the same file to multiple features: "
"Features 'feature-a' and 'feature-b' share "
"7 overlapping test files: f-1.html, f-2.html, f-3.html, f-4.html, f-5.html, and 2 more",
"css/WEB_FEATURES.yml",
None),
]
),
])
def test_overlapping_web_features_file(monkeypatch, files, yml, expected_errors):
def listdir(dir):
if dir.endswith("css"):
return files

def is_file(file):
if os.path.basename(file) in files:
return True
return False

monkeypatch.setattr(os.path, "isfile", is_file)
monkeypatch.setattr(os, "listdir", listdir)
errors = check_file_contents("", "css/WEB_FEATURES.yml", io.BytesIO(yml))
check_errors(errors)

assert errors == expected_errors


def test_css_missing_file_manual():
errors = check_file_contents("", "css/foo/bar-manual.html", io.BytesIO(b""))
check_errors(errors)
Expand Down
Loading