Skip to content

Commit 4eea792

Browse files
committed
Add a UNUSED-IGNORELIST lint for unused lines in lint.ignore
1 parent b31a712 commit 4eea792

File tree

7 files changed

+118
-37
lines changed

7 files changed

+118
-37
lines changed

lint.ignore

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ W3C-TEST.ORG: .gitignore
5959
## Documentation ##
6060

6161
W3C-TEST.ORG: README.md
62-
W3C-TEST.ORG: */README.md
6362
W3C-TEST.ORG: docs/*
6463
WEB-PLATFORM.TEST:*/README.md
6564
WEB-PLATFORM.TEST:docs/*
@@ -87,7 +86,6 @@ CR AT EOL: html/semantics/forms/the-textarea-element/multiline-placeholder-crlf.
8786
CR AT EOL: html/semantics/forms/the-input-element/multiline-placeholder-cr.html
8887
CR AT EOL: html/semantics/forms/the-input-element/multiline-placeholder-crlf.html
8988
CR AT EOL: webvtt/parsing/file-parsing/tests/support/newlines.vtt
90-
CR AT EOL: css/css-text/ellisize-rtl-text-crash.html
9189
CR AT EOL: html/syntax/charset/after-head-after-1kb-crlf.html
9290
CR AT EOL: html/syntax/charset/after-head-in-1kb-crlf.html
9391

@@ -173,8 +171,6 @@ SET TIMEOUT: encrypted-media/polyfill/chrome-polyfill.js
173171
SET TIMEOUT: encrypted-media/polyfill/clearkey-polyfill.js
174172
SET TIMEOUT: encrypted-media/scripts/playback-temporary-events.js
175173
SET TIMEOUT: fedcm/support/fedcm-helper.sub.js
176-
SET TIMEOUT: fedcm/support/fedcm-iframe.html
177-
SET TIMEOUT: fedcm/support/fedcm/disconnect-iframe.html
178174
SET TIMEOUT: fedcm/support/login_delay.html
179175
SET TIMEOUT: fetch/fetch-later/resources/fetch-later-helper.js
180176
SET TIMEOUT: fetch/metadata/resources/helper.sub.js
@@ -187,7 +183,6 @@ SET TIMEOUT: focus/support/iframe-contentwindow-focus-with-different-site-interm
187183
SET TIMEOUT: focus/support/iframe-contentwindow-focus-with-different-site-intermediate-frame-middle.sub.html
188184
SET TIMEOUT: focus/support/iframe-focuses-parent-different-site-inner.html
189185
SET TIMEOUT: focus/support/iframe-focuses-parent-same-site-inner.html
190-
SET TIMEOUT: generic-sensor/resources/iframe_sensor_handler.html
191186
SET TIMEOUT: html/browsers/browsing-the-web/back-forward-cache/resources/inflight-fetch-helper.js
192187
SET TIMEOUT: html/browsers/browsing-the-web/history-traversal/*
193188
SET TIMEOUT: html/browsers/browsing-the-web/navigating-across-documents/*
@@ -228,7 +223,6 @@ SET TIMEOUT: mixed-content/generic/sanity-checker.js
228223
SET TIMEOUT: navigation-api/navigation-history-entry/entries-after-bfcache-in-iframe.html
229224
SET TIMEOUT: navigation-timing/*
230225
SET TIMEOUT: old-tests/submission/Microsoft/history/history_000.htm
231-
SET TIMEOUT: paint-timing/resources/subframe-painting.html
232226
SET TIMEOUT: performance-timeline/resources/navigation-id-detached-frame-page.html
233227
SET TIMEOUT: png/apng/acTL-plays-one.html
234228
SET TIMEOUT: png/apng/acTL-plays-two.html
@@ -306,7 +300,6 @@ SET TIMEOUT: web-animations/crashtests/reparent-animating-element-002.html
306300
SET TIMEOUT: web-animations/timing-model/animations/*
307301
SET TIMEOUT: web-locks/crashtests/after-worker-termination.https.html
308302
SET TIMEOUT: webaudio/the-audio-api/the-mediaelementaudiosourcenode-interface/mediaElementAudioSourceToScriptProcessorTest.html
309-
SET TIMEOUT: webauthn/*timeout.https.html
310303
SET TIMEOUT: webdriver/*
311304
SET TIMEOUT: webmessaging/*
312305
SET TIMEOUT: webrtc-encoded-transform/script-metadata-transform-worker.js
@@ -324,15 +317,13 @@ GENERATE_TESTS: resources/test/tests/functional/generate-callback.html
324317
GENERATE_TESTS: resources/testharness.js
325318

326319
# generate_tests usage (should be got rid of)
327-
GENERATE_TESTS: html/canvas/element/drawing-images-to-the-canvas/*
328320
GENERATE_TESTS: html/canvas/element/manual/drawing-images-to-the-canvas/*
329321
GENERATE_TESTS: css/css-shapes/shape-outside/values/*
330322
GENERATE_TESTS: css/css-tables/bounding-box-computation-1.html
331323
GENERATE_TESTS: css/css-tables/bounding-box-computation-2.html
332324
GENERATE_TESTS: css/css-tables/bounding-box-computation-3.html
333325
GENERATE_TESTS: css/css-tables/caption-side-1.html
334326
GENERATE_TESTS: css/css-tables/fixed-layout-1.html
335-
GENERATE_TESTS: css/css-tables/fixed-layout-2.html
336327
GENERATE_TESTS: css/css-tables/height-distribution/computing-row-measure-0.html
337328
GENERATE_TESTS: css/css-tables/height-distribution/computing-row-measure-1.html
338329
GENERATE_TESTS: css/css-tables/height-distribution/percentage-sizing-of-table-cell-children.html
@@ -342,7 +333,6 @@ GENERATE_TESTS: css/css-tables/html5-table-formatting-1.html
342333
GENERATE_TESTS: css/css-tables/html5-table-formatting-2.html
343334
GENERATE_TESTS: css/css-tables/html5-table-formatting-3.html
344335
GENERATE_TESTS: css/css-tables/html5-table-formatting-fixed-layout-1.html
345-
GENERATE_TESTS: css/css-tables/table-model-fixup-2.html
346336
GENERATE_TESTS: css/css-tables/table-model-fixup.html
347337
GENERATE_TESTS: css/css-tables/visibility-collapse-col-001.html
348338
GENERATE_TESTS: css/css-tables/visibility-collapse-row-001.html
@@ -419,7 +409,6 @@ SET TIMEOUT: scheduler/tentative/current-task-signal-async-abort.any.js
419409
SET TIMEOUT: scheduler/tentative/current-task-signal-async-priority.any.js
420410
SET TIMEOUT: screen-capture/tentative/getdisplaymedia-captured-surface-resolution.https.html
421411
SET TIMEOUT: speculation-rules/prerender/resources/activation-start.html
422-
SET TIMEOUT: speculation-rules/prerender/resources/prerender-response-code.html
423412
SET TIMEOUT: speculation-rules/prerender/resources/prerender-while-prerender-outer.html
424413
SET TIMEOUT: speculation-rules/prerender/resources/deferred-promise-utils.js
425414
SET TIMEOUT: speculation-rules/prerender/resources/session-history-harness.js
@@ -447,9 +436,6 @@ SET TIMEOUT: acid/acid3/test.html
447436
*: resources/.gitignore
448437
*: webaudio/.gitignore
449438

450-
## Third party data files
451-
TRAILING WHITESPACE: resources/chromium/*
452-
453439
## Test plans and implementation reports
454440
*: css/*/test-plan/*
455441

@@ -496,8 +482,6 @@ TRAILING WHITESPACE: css/css-fonts/support/fonts/gsubtest-lookup3.ufo/features.f
496482
SET TIMEOUT: css/compositing/mix-blend-mode/mix-blend-mode-parent-with-3D-transform-and-transition.html
497483
SET TIMEOUT: css/compositing/mix-blend-mode/mix-blend-mode-sibling-with-3D-transform-and-transition.html
498484
SET TIMEOUT: css/css-transitions/events-007.html
499-
SET TIMEOUT: css/css-transitions/support/generalParallelTest.js
500-
SET TIMEOUT: css/css-transitions/support/runParallelAsyncHarness.js
501485
SET TIMEOUT: css/css-transitions/transitioncancel-001.html
502486
SET TIMEOUT: css/css-values/reference/vh-update-and-transition-in-subframe-ref.html
503487
SET TIMEOUT: css/css-values/reference/vh-update-and-transition-in-subframe-iframe-ref.html
@@ -545,8 +529,6 @@ SET TIMEOUT: css/CSS2/selectors/dom-hover-002.xht
545529
SET TIMEOUT: css/CSS2/tables/tables-102.xht
546530
SET TIMEOUT: css/mediaqueries/min-width-tables-001.html
547531
SET TIMEOUT: css/css-text/crashtests/rendering-rtl-bidi-override-crash.html
548-
SET TIMEOUT: css/css-backgrounds/color-mix-currentcolor-border-repaint-parent.html
549-
SET TIMEOUT: svg/painting/currentcolor-fill-stroke-repaint.html
550532
SET TIMEOUT: resource-timing/resources/run-async-tasks-promise.js
551533

552534
# CSS tests that used to be at the top level and weren't subject to lints
@@ -611,7 +593,6 @@ MISSING-LINK: css/geometry/*.any.js
611593
MISSING-LINK: css/filter-effects/*.any.js
612594

613595
# Tests that use WebKit/Blink testing APIs
614-
LAYOUTTESTS APIS: import-maps/data-driven/resources/test-helper-iframe.js
615596
LAYOUTTESTS APIS: resources/chromium/enable-hyperlink-auditing.js
616597
LAYOUTTESTS APIS: resources/chromium/webxr-test.js
617598
LAYOUTTESTS APIS: webxr/resources/webxr_util.js
@@ -626,7 +607,6 @@ WEB-PLATFORM.TEST:web-bundle/resources/generate-test-wbns.sh
626607
WEB-PLATFORM.TEST:web-bundle/resources/nested/*.wbn
627608
WEB-PLATFORM.TEST:web-bundle/resources/wbn/*.wbn
628609
WEB-PLATFORM.TEST:web-bundle/subresource-loading/*.html
629-
WEB-PLATFORM.TEST:web-bundle/subresource-loading/resources/*.js
630610

631611
# The /.well-known/shared-stored/trusted-origins file uses hard-coded hosts
632612
WEB-PLATFORM.TEST:shared-storage/resources/trusted-origins.py
@@ -669,20 +649,16 @@ AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-012-ref.html
669649
AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-013.html
670650
AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-013-ref.html
671651
AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-014.html
672-
AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-014-ref.html
673652
AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-metrics-override.html
674653
AHEM SYSTEM FONT: css/css-fonts/font-size-adjust-metrics-override-ref.html
675654
AHEM SYSTEM FONT: css/css-fonts/line-gap-override.html
676655
AHEM SYSTEM FONT: css/css-fonts/matching/font-unicode-presented-as-emoji-outline.html
677656
AHEM SYSTEM FONT: css/css-fonts/matching/font-unicode-presented-as-emoji-outline-ref.html
678657
AHEM SYSTEM FONT: css/css-fonts/parsing/font-size-adjust-computed.html
679-
AHEM SYSTEM FONT: css/css-fonts/size-adjust-unicode-range-system-fallback.html
680658
AHEM SYSTEM FONT: css/css-text/zwnj-renders-invisible.html
681659
AHEM SYSTEM FONT: css/css-overflow/line-clamp/block-ellipsis-010.html
682660
AHEM SYSTEM FONT: css/css-overflow/line-clamp/reference/block-ellipsis-010-ref.html
683661
AHEM SYSTEM FONT: css/css-overflow/line-clamp/reference/block-ellipsis-010-alt-ref.html
684-
AHEM SYSTEM FONT: html/dom/render-blocking/remove-attr-unblocks-rendering.optional.html
685-
AHEM SYSTEM FONT: html/dom/render-blocking/remove-element-unblocks-rendering.optional.html
686662

687663
# TODO: The following should be deleted along with the Ahem web font cleanup
688664
# PR (https://github.com/web-platform-tests/wpt/pull/18702)
@@ -774,7 +750,6 @@ SET TIMEOUT: editing/crashtests/make-editable-div-inline-and-set-contenteditable
774750
SET TIMEOUT: editing/crashtests/outdent-across-svg-boundary.html
775751
SET TIMEOUT: editing/crashtests/textarea-will-be-blurred-by-focus-event-listener.html
776752
SET TIMEOUT: mathml/crashtests/mozilla/*
777-
SET TIMEOUT: selection/crashtests/selection-modify-line-boundary-around-empty-details.html
778753
PARSE-FAILED: mathml/crashtests/mozilla/289180-1.xml
779754

780755
# Invalid HTML syntax /> on non-void elements
@@ -834,9 +809,7 @@ TESTDRIVER-IN-UNSUPPORTED-TYPE: speech-api/SpeechRecognition-phrases-manual.http
834809

835810
# Tests automatically imported from the WebAssembly/spec repository that should be removed after getting fixed upstream.
836811
SET TIMEOUT: wasm/core/js/harness/testharness.js
837-
ASSERT_THROWS: wasm/core/js/harness/testharness.js
838812
GENERATE_TESTS: wasm/core/js/harness/testharness.js
839-
PROMISE_REJECTS: wasm/core/js/harness/testharness.js
840813

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

tools/lint/lint.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,55 @@ def check_unique_case_insensitive_paths(repo_root: Text, paths: List[Text]) -> L
279279
return errors
280280

281281

282+
def check_unused_ignorelist(repo_root: Text,
283+
paths: List[Text],
284+
ignorelist: Ignorelist,
285+
used_entries: Dict[str, Dict[str, Set[Optional[int]]]]) -> List[rules.Error]:
286+
"""
287+
Check for ignorelist entries that were never used during this lint run.
288+
Only reports unused entries if the corresponding file pattern matches
289+
files in the currently linted paths.
290+
291+
:param repo_root: the repository root
292+
:param paths: list of currently linted paths
293+
:param ignorelist: the parsed ignorelist
294+
:param used_entries: dict tracking which ignorelist entries were used
295+
:returns: a list of errors for unused ignorelist entries
296+
"""
297+
errors = []
298+
299+
normalized_paths = {os.path.normcase(path) for path in paths}
300+
301+
for error_type, file_patterns in ignorelist.items():
302+
for file_match, allowed_lines in file_patterns.items():
303+
# Allow non-matching patterns like `CR AT EOL: *.png`.
304+
if "/" not in file_match and file_match.startswith("*."):
305+
continue
306+
307+
used_allowed_lines = used_entries.get(error_type, {}).get(file_match, set())
308+
if used_allowed_lines == allowed_lines:
309+
continue
310+
311+
assert used_allowed_lines.issubset(allowed_lines)
312+
313+
matches_linted_path = any(
314+
fnmatch.fnmatchcase(norm_path, file_match)
315+
for norm_path in normalized_paths
316+
)
317+
318+
if not matches_linted_path:
319+
continue
320+
321+
for missing_allowed_line in allowed_lines - used_allowed_lines:
322+
line_suffix = "" if missing_allowed_line is None else f":{missing_allowed_line}"
323+
errors.append(rules.UnusedIgnorelistEntry.error(
324+
"lint.ignore",
325+
(error_type, file_match + line_suffix)
326+
))
327+
328+
return errors
329+
330+
282331
def parse_ignorelist(f: IO[Text]) -> Tuple[Ignorelist, Set[Text]]:
283332
"""
284333
Parse the ignorelist file given by `f`, and return the parsed structure.
@@ -315,9 +364,16 @@ def parse_ignorelist(f: IO[Text]) -> Tuple[Ignorelist, Set[Text]]:
315364
return data, skipped_files
316365

317366

318-
def filter_ignorelist_errors(data: Ignorelist, errors: Sequence[rules.Error]) -> List[rules.Error]:
367+
def filter_ignorelist_errors(data: Ignorelist,
368+
errors: Sequence[rules.Error],
369+
used_entries: Dict[str, Dict[str, Set[Optional[int]]]]) -> List[rules.Error]:
319370
"""
320371
Filter out those errors that are ignored in `data`.
372+
373+
:param data: the ignorelist data structure
374+
:param errors: the list of errors to filter
375+
:param used_entries: dict to track which ignorelist entries were used
376+
:returns: filtered list of errors
321377
"""
322378

323379
if not errors:
@@ -335,6 +391,11 @@ def filter_ignorelist_errors(data: Ignorelist, errors: Sequence[rules.Error]) ->
335391
if None in allowed_lines or line in allowed_lines:
336392
if fnmatch.fnmatchcase(normpath, file_match):
337393
skipped[i] = True
394+
used_lines = used_entries.setdefault(error_type, {}).setdefault(file_match, set())
395+
if None in allowed_lines:
396+
used_lines.add(None)
397+
else:
398+
used_lines.add(line)
338399

339400
return [item for i, item in enumerate(errors) if not skipped[i]]
340401

@@ -1008,6 +1069,8 @@ def lint(repo_root: Text,
10081069
if ignore_glob:
10091070
skipped_files |= set(ignore_glob)
10101071

1072+
used_ignorelist_entries: Dict[str, Dict[str, Set[Optional[int]]]] = {}
1073+
10111074
output_errors = {"json": output_errors_json,
10121075
"markdown": output_errors_markdown,
10131076
"normal": output_errors_text}[output_format]
@@ -1021,7 +1084,7 @@ def process_errors(errors: List[rules.Error]) -> Optional[Tuple[Text, Text]]:
10211084
a tuple of the error type and the path otherwise
10221085
"""
10231086

1024-
errors = filter_ignorelist_errors(ignorelist, errors)
1087+
errors = filter_ignorelist_errors(ignorelist, errors, used_ignorelist_entries)
10251088
if not errors:
10261089
return None
10271090

@@ -1079,6 +1142,9 @@ def process_errors(errors: List[rules.Error]) -> Optional[Tuple[Text, Text]]:
10791142
errors = check_all_paths(repo_root, paths)
10801143
last = process_errors(errors) or last
10811144

1145+
unused_errors = check_unused_ignorelist(repo_root, paths, ignorelist, used_ignorelist_entries)
1146+
last = process_errors(unused_errors) or last
1147+
10821148
if output_format in ("normal", "markdown"):
10831149
output_error_count(error_count)
10841150
if error_count:

tools/lint/rules.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,12 @@ class MissingTestInWebFeaturesFile(Rule):
379379
""")
380380

381381

382+
class UnusedIgnorelistEntry(Rule):
383+
name = "UNUSED-IGNORELIST"
384+
description = "Unused ignorelist entry: %s: %s"
385+
to_fix = "Remove this entry from lint.ignore if it's no longer needed"
386+
387+
382388
EXTENSIONS = {
383389
"html": [".html", ".htm"],
384390
"xhtml": [".xht", ".xhtml"],

tools/lint/tests/dummy/lint.ignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1+
# Totally ignore broken_ignored.html
12
*:broken_ignored.html
3+
4+
# used_entry.html has trailing whitespace
5+
TRAILING WHITESPACE: used_entry.html
6+
7+
# unused_entry.html but file has no errors
8+
TRAILING WHITESPACE: unused_entry.html
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>This file has no lint errors.</p>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>This file has trailing whitespace.</p>

tools/lint/tests/test_lint.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,23 @@ def test_filter_ignorelist_errors():
3232
filteredfile = 'svg/test.html'
3333
unfilteredfile = 'html/test.html'
3434
# Tests for passing no errors
35-
filtered = filter_ignorelist_errors(ignorelist, [])
35+
filtered = filter_ignorelist_errors(ignorelist, [], {})
3636
assert filtered == []
37-
filtered = filter_ignorelist_errors(ignorelist, [])
37+
filtered = filter_ignorelist_errors(ignorelist, [], {})
3838
assert filtered == []
3939
# Tests for filtering on file and line number
40-
filtered = filter_ignorelist_errors(ignorelist, [['CONSOLE', '', filteredfile, 12]])
40+
filtered = filter_ignorelist_errors(ignorelist, [['CONSOLE', '', filteredfile, 12]], {})
4141
assert filtered == []
42-
filtered = filter_ignorelist_errors(ignorelist, [['CONSOLE', '', unfilteredfile, 12]])
42+
filtered = filter_ignorelist_errors(ignorelist, [['CONSOLE', '', unfilteredfile, 12]], {})
4343
assert filtered == [['CONSOLE', '', unfilteredfile, 12]]
44-
filtered = filter_ignorelist_errors(ignorelist, [['CONSOLE', '', filteredfile, 11]])
44+
filtered = filter_ignorelist_errors(ignorelist, [['CONSOLE', '', filteredfile, 11]], {})
4545
assert filtered == [['CONSOLE', '', filteredfile, 11]]
4646
# Tests for filtering on just file
47-
filtered = filter_ignorelist_errors(ignorelist, [['INDENT TABS', '', filteredfile, 12]])
47+
filtered = filter_ignorelist_errors(ignorelist, [['INDENT TABS', '', filteredfile, 12]], {})
4848
assert filtered == []
49-
filtered = filter_ignorelist_errors(ignorelist, [['INDENT TABS', '', filteredfile, 11]])
49+
filtered = filter_ignorelist_errors(ignorelist, [['INDENT TABS', '', filteredfile, 11]], {})
5050
assert filtered == []
51-
filtered = filter_ignorelist_errors(ignorelist, [['INDENT TABS', '', unfilteredfile, 11]])
51+
filtered = filter_ignorelist_errors(ignorelist, [['INDENT TABS', '', unfilteredfile, 11]], {})
5252
assert filtered == [['INDENT TABS', '', unfilteredfile, 11]]
5353

5454

@@ -363,6 +363,33 @@ def test_ignore_glob(caplog):
363363
assert "ABSOLUTE-URL-REF" in caplog.text
364364

365365

366+
def test_unused_ignorelist_entry(caplog):
367+
"""Test that unused ignorelist entries are detected."""
368+
# This test should detect that the ignorelist entry for unused_entry.html
369+
# is not being used (file has no errors)
370+
with _mock_lint("check_path") as mocked_check_path:
371+
with _mock_lint("check_file_contents") as mocked_check_file_contents:
372+
rv = lint(_dummy_repo, ["unused_entry.html"], "normal")
373+
assert rv == 1
374+
assert mocked_check_path.call_count == 1
375+
assert mocked_check_file_contents.call_count == 1
376+
assert "UNUSED-IGNORELIST" in caplog.text
377+
assert "TRAILING WHITESPACE" in caplog.text
378+
379+
380+
def test_used_ignorelist_entry(caplog):
381+
"""Test that used ignorelist entries are not reported."""
382+
# This test should NOT report the ignorelist entry for used_entry.html
383+
# because it's actually being used (file has trailing whitespace that is ignored)
384+
with _mock_lint("check_path") as mocked_check_path:
385+
with _mock_lint("check_file_contents") as mocked_check_file_contents:
386+
rv = lint(_dummy_repo, ["used_entry.html"], "normal")
387+
assert rv == 0
388+
assert mocked_check_path.call_count == 1
389+
assert mocked_check_file_contents.call_count == 1
390+
assert caplog.text == ""
391+
392+
366393
def test_all_filesystem_paths():
367394
with mock.patch(
368395
'tools.lint.lint.walk',

0 commit comments

Comments
 (0)