Files
sousa-gecko/testing/web-platform/tests/tools/lint/rules.py
T
Jan Varga 0badc62744 Bug 2050852 - Run CSSKeywordValue error handling test in worker contexts; r=emilio,jgraham
Convert the CSSKeywordValue constructor error-handling test from `.html` to
`.any.js` so it runs in Window, DedicatedWorker, SharedWorker, and
ServiceWorker contexts.

This provides basic coverage for CSS Typed OM in worker contexts and helps
highlight the current interop differences between browser engines.

Also adjusts WPT tooling so spec links can be declared via `// META: spec=...`
in script-based tests, which allows `*.any.js` tests under `css/` to satisfy
the `MISSING-LINK` lint. See https://github.com/w3c/csswg-drafts/issues/14090.

Differential Revision: https://phabricator.services.mozilla.com/D309243
2026-06-29 14:01:07 +00:00

595 lines
18 KiB
Python

import abc
import inspect
import os
import re
from typing import Any, List, Match, Optional, Pattern, Text, Tuple, cast
Error = Tuple[str, str, str, Optional[int]]
def collapse(text: Text) -> Text:
return inspect.cleandoc(str(text)).replace("\n", " ")
class Rule(metaclass=abc.ABCMeta):
@abc.abstractproperty
def name(self) -> Text:
pass
@abc.abstractproperty
def description(self) -> Text:
pass
to_fix: Optional[Text] = None
@classmethod
def error(cls, path: Text, context: Tuple[Any, ...] = (), line_no: Optional[int] = None) -> Error:
name = cast(str, cls.name)
description = cast(str, cls.description) % context
return (name, description, path, line_no)
class MissingLink(Rule):
name = "MISSING-LINK"
description = "Testcase file must have a link to a spec"
to_fix = """
Ensure that there is a `<link rel="help" href="[url]">` for the spec
in HTML tests, or a `// META: spec=[url]` line in script-based tests
(`*.any.js`, `*.window.js`, `*.worker.js`). `MISSING-LINK` is designed
to ensure that tests can be associated with the relevant spec.
"""
class PathLength(Rule):
name = "PATH LENGTH"
description = "/%s longer than maximum path length (%d > 150)"
to_fix = "use shorter filename to rename the test file"
class FileType(Rule):
name = "FILE TYPE"
description = "/%s is an unsupported file type (%s)"
class WorkerCollision(Rule):
name = "WORKER COLLISION"
description = collapse("""
path ends with %s which collides with generated tests from %s files
""")
class GitIgnoreFile(Rule):
name = "GITIGNORE"
description = ".gitignore found outside the root"
class MojomJSFile(Rule):
name = "MOJOM-JS"
description = "Don't check *.mojom.js files into WPT"
to_fix = """
Check if the file is already included in mojojs.zip:
https://source.chromium.org/chromium/chromium/src/+/master:chrome/tools/build/linux/FILES.cfg
If yes, use `loadMojoResources` from `resources/test-only-api.js` to load
it; if not, contact ecosystem-infra@chromium.org for adding new files
to mojojs.zip.
"""
class AhemCopy(Rule):
name = "AHEM COPY"
description = "Don't add extra copies of Ahem, use /fonts/Ahem.ttf"
class AhemSystemFont(Rule):
name = "AHEM SYSTEM FONT"
description = "Don't use Ahem as a system font, use /fonts/ahem.css"
# TODO: Add tests for this rule
class IgnoredPath(Rule):
name = "IGNORED PATH"
description = collapse("""
%s matches an ignore filter in .gitignore - please add a .gitignore
exception
""")
class ParseFailed(Rule):
name = "PARSE-FAILED"
description = "Unable to parse file"
to_fix = """
examine the file to find the causes of any parse errors, and fix them.
"""
class ContentManual(Rule):
name = "CONTENT-MANUAL"
description = "Manual test whose filename doesn't end in '-manual'"
class ContentVisual(Rule):
name = "CONTENT-VISUAL"
description = "Visual test whose filename doesn't end in '-visual'"
class AbsoluteUrlRef(Rule):
name = "ABSOLUTE-URL-REF"
description = collapse("""
Reference test with a reference file specified via an absolute URL:
'%s'
""")
class SameFileRef(Rule):
name = "SAME-FILE-REF"
description = "Reference test which points at itself as a reference"
class NonexistentRef(Rule):
name = "NON-EXISTENT-REF"
description = collapse("""
Reference test with a non-existent '%s' relationship reference: '%s'
""")
class MultipleTimeout(Rule):
name = "MULTIPLE-TIMEOUT"
description = "More than one meta name='timeout'"
to_fix = """
ensure each test file has only one instance of a `<meta
name="timeout"...>` element
"""
class InvalidTimeout(Rule):
name = "INVALID-TIMEOUT"
description = collapse("""
Test file with `<meta name='timeout'...>` element that has a `content`
attribute whose value is not `long`: %s
""")
to_fix = "replace the value of the `content` attribute with `long`"
class MultipleTestharness(Rule):
name = "MULTIPLE-TESTHARNESS"
description = "More than one `<script src='/resources/testharness.js'>`"
to_fix = """
Ensure each test has only one `<script
src='/resources/testharness.js'>` instance.
For `.js` tests, remove `// META: script=/resources/testharness.js`,
which wptserve already adds to the boilerplate markup.
"""
class MissingReftestWait(Rule):
name = "MISSING-REFTESTWAIT"
description = "Missing `class=reftest-wait`"
to_fix = """
ensure tests that include reftest-wait.js also use class=reftest-wait on the root element.
"""
class MissingTestharnessReport(Rule):
name = "MISSING-TESTHARNESSREPORT"
description = "Missing `<script src='/resources/testharnessreport.js'>`"
to_fix = """
ensure each test file contains `<script
src='/resources/testharnessreport.js'>`
"""
class MultipleTestharnessReport(Rule):
name = "MULTIPLE-TESTHARNESSREPORT"
description = "More than one `<script src='/resources/testharnessreport.js'>`"
to_fix = """
Ensure each test has only one `<script
src='/resources/testharnessreport.js'>` instance.
For `.js` tests, remove `// META: script=/resources/testharnessreport.js`,
which wptserve already adds to the boilerplate markup.
"""
class VariantMissing(Rule):
name = "VARIANT-MISSING"
description = collapse("""
Test file with a `<meta name='variant'...>` element that's missing a
`content` attribute
""")
to_fix = """
add a `content` attribute with an appropriate value to the `<meta
name='variant'...>` element
"""
class MalformedVariant(Rule):
name = "MALFORMED-VARIANT"
description = collapse("""
%s must be a non empty string
and start with '?' or '#'
""")
class LateTimeout(Rule):
name = "LATE-TIMEOUT"
description = "`<meta name=timeout>` seen after testharness.js script"
description = collapse("""
Test file with `<meta name='timeout'...>` element after `<script
src='/resources/testharnessreport.js'>` element
""")
to_fix = """
move the `<meta name="timeout"...>` element to precede the `script`
element.
"""
class EarlyTestharnessReport(Rule):
name = "EARLY-TESTHARNESSREPORT"
description = collapse("""
Test file has an instance of
`<script src='/resources/testharnessreport.js'>` prior to
`<script src='/resources/testharness.js'>`
""")
to_fix = "flip the order"
class EarlyTestdriverVendor(Rule):
name = "EARLY-TESTDRIVER-VENDOR"
description = collapse("""
Test file has an instance of
`<script src='/resources/testdriver-vendor.js'>` prior to
`<script src='/resources/testdriver.js'>`
""")
to_fix = "flip the order"
class MultipleTestdriver(Rule):
name = "MULTIPLE-TESTDRIVER"
description = "More than one `<script src='/resources/testdriver.js'>`"
class MissingTestdriverVendor(Rule):
name = "MISSING-TESTDRIVER-VENDOR"
description = "Missing `<script src='/resources/testdriver-vendor.js'>`"
class MultipleTestdriverVendor(Rule):
name = "MULTIPLE-TESTDRIVER-VENDOR"
description = "More than one `<script src='/resources/testdriver-vendor.js'>`"
class TestharnessPath(Rule):
name = "TESTHARNESS-PATH"
description = "testharness.js script seen with incorrect path"
class TestharnessReportPath(Rule):
name = "TESTHARNESSREPORT-PATH"
description = "testharnessreport.js script seen with incorrect path"
class TestdriverPath(Rule):
name = "TESTDRIVER-PATH"
description = "testdriver.js script seen with incorrect path"
class TestdriverUnsupportedQueryParameter(Rule):
name = "TESTDRIVER-UNSUPPORTED-QUERY-PARAMETER"
description = "testdriver.js script seen with unsupported query parameters"
class TestdriverVendorPath(Rule):
name = "TESTDRIVER-VENDOR-PATH"
description = "testdriver-vendor.js script seen with incorrect path"
class TestdriverInUnsupportedType(Rule):
name = "TESTDRIVER-IN-UNSUPPORTED-TYPE"
description = "testdriver.js included in a %s test, which doesn't support testdriver.js"
class OpenNoMode(Rule):
name = "OPEN-NO-MODE"
description = "File opened without providing an explicit mode (note: binary files must be read with 'b' in the mode flags)"
class UnknownGlobalMetadata(Rule):
name = "UNKNOWN-GLOBAL-METADATA"
description = "Unexpected value for global metadata"
class BrokenGlobalMetadata(Rule):
name = "BROKEN-GLOBAL-METADATA"
description = "Invalid global metadata: %s"
class UnknownTimeoutMetadata(Rule):
name = "UNKNOWN-TIMEOUT-METADATA"
description = "Unexpected value for timeout metadata"
class UnknownMetadata(Rule):
name = "UNKNOWN-METADATA"
description = "Unexpected kind of metadata"
class StrayMetadata(Rule):
name = "STRAY-METADATA"
description = "Metadata comments should start the file"
class IndentedMetadata(Rule):
name = "INDENTED-METADATA"
description = "Metadata comments should start the line"
class BrokenMetadata(Rule):
name = "BROKEN-METADATA"
description = "Metadata comment is not formatted correctly"
class TestharnessInOtherType(Rule):
name = "TESTHARNESS-IN-OTHER-TYPE"
description = "testharness.js included in a %s test"
class ReferenceInOtherType(Rule):
name = "REFERENCE-IN-OTHER-TYPE"
description = "Reference link included in a %s test"
class DuplicateBasenamePath(Rule):
name = "DUPLICATE-BASENAME-PATH"
description = collapse("""
File has identical basename path (path excluding extension) as
other file(s) (found extensions: %s)
""")
to_fix = "rename files so they have unique basename paths"
class DuplicatePathCaseInsensitive(Rule):
name = "DUPLICATE-CASE-INSENSITIVE-PATH"
description = collapse("""
Path differs from path %s only in case
""")
to_fix = "rename files so they are unique irrespective of case"
class TentativeDirectoryName(Rule):
name = "TENTATIVE-DIRECTORY-NAME"
description = "Directories for tentative tests must be named exactly 'tentative'"
to_fix = "rename directory to be called 'tentative'"
class InvalidMetaFile(Rule):
name = "INVALID-META-FILE"
description = "The META.yml is not a YAML file with the expected structure"
class InvalidWebFeaturesFile(Rule):
name = "INVALID-WEB-FEATURES-FILE"
description = "The WEB_FEATURES.yml file contains an invalid structure: %s"
class MissingTestInWebFeaturesFile(Rule):
name = "MISSING-WEB-FEATURES-FILE"
description = collapse("""
The WEB_FEATURES.yml file references a test that does not exist: '%s'
""")
class UnnecessaryExclusionInWebFeaturesFile(Rule):
name = "UNNECESSARY-EXCLUSION-IN-WEB-FEATURES-FILE"
description = collapse("""
The WEB_FEATURES.yml file contains a redundant or inoperable exclusion pattern: %s
""")
class WebFeaturesFileInNonTestDirectory(Rule):
name = "WEB-FEATURES-FILE-IN-NON-TEST-DIRECTORY"
description = collapse("""
WEB_FEATURES.yml is located in a non-test directory: '%s'
""")
class NonTestFileInWebFeaturesFile(Rule):
name = "NON-TEST-FILE-IN-WEB-FEATURES-FILE"
description = collapse("""
The WEB_FEATURES.yml file references a non-test file: '%s' in feature '%s'
""")
EXTENSIONS = {
"html": [".html", ".htm"],
"xhtml": [".xht", ".xhtml"],
"svg": [".svg"],
"js": [".js", ".mjs"],
"python": [".py"]
}
EXTENSIONS["markup"] = EXTENSIONS["html"] + EXTENSIONS["xhtml"] + EXTENSIONS["svg"]
EXTENSIONS["js_all"] = EXTENSIONS["markup"] + EXTENSIONS["js"]
class Regexp(metaclass=abc.ABCMeta):
@abc.abstractproperty
def pattern(self) -> bytes:
pass
@abc.abstractproperty
def name(self) -> Text:
pass
@abc.abstractproperty
def description(self) -> Text:
pass
file_extensions: Optional[List[Text]] = None
def __init__(self) -> None:
self._re: Pattern[bytes] = re.compile(self.pattern)
def applies(self, path: Text) -> bool:
return (self.file_extensions is None or
os.path.splitext(path)[1] in self.file_extensions)
def search(self, line: bytes) -> Optional[Match[bytes]]:
return self._re.search(line)
class TabsRegexp(Regexp):
pattern = b"^\t"
name = "INDENT TABS"
description = "Test-file line starts with one or more tab characters"
to_fix = "use spaces to replace any tab characters at beginning of lines"
class CRRegexp(Regexp):
pattern = b"\r$"
name = "CR AT EOL"
description = "Test-file line ends with CR (U+000D) character"
to_fix = """
reformat file so each line just has LF (U+000A) line ending (standard,
cross-platform "Unix" line endings instead of, e.g., DOS line endings).
"""
class SetTimeoutRegexp(Regexp):
pattern = br"setTimeout\s*\("
name = "SET TIMEOUT"
file_extensions = EXTENSIONS["js_all"]
description = "setTimeout used"
to_fix = """
replace all `setTimeout(...)` calls with `step_timeout(...)` calls
"""
class W3CTestOrgRegexp(Regexp):
pattern = br"w3c\-test\.org"
name = "W3C-TEST.ORG"
description = "Test-file line has the string `w3c-test.org`"
to_fix = """
either replace the `w3c-test.org` string with the expression
`{{host}}:{{ports[http][0]}}` or a generic hostname like `example.org`
"""
class WebPlatformTestRegexp(Regexp):
pattern = br"web\-platform\.test"
name = "WEB-PLATFORM.TEST"
description = "Internal web-platform.test domain used"
to_fix = """
use [server-side substitution](https://web-platform-tests.org/writing-tests/server-pipes.html#sub),
along with the [`.sub` filename-flag](https://web-platform-tests.org/writing-tests/file-names.html#test-features),
to replace web-platform.test with `{{domains[]}}`
"""
class Webidl2Regexp(Regexp):
pattern = br"webidl2\.js"
name = "WEBIDL2.JS"
description = "Legacy webidl2.js script used"
class ConsoleRegexp(Regexp):
pattern = br"console\.[a-zA-Z]+\s*\("
name = "CONSOLE"
file_extensions = EXTENSIONS["js_all"]
description = "Test-file line has a `console.*(...)` call"
to_fix = """
remove the `console.*(...)` call (and in some cases, consider adding an
`assert_*` of some kind in place of it)
"""
class GenerateTestsRegexp(Regexp):
pattern = br"generate_tests\s*\("
name = "GENERATE_TESTS"
file_extensions = EXTENSIONS["js_all"]
description = "Test file line has a generate_tests call"
to_fix = "remove the call and call `test()` a number of times instead"
class PrintRegexp(Regexp):
pattern = br"print(?:\s|\s*\()"
name = "PRINT STATEMENT"
file_extensions = EXTENSIONS["python"]
description = collapse("""
A server-side python support file contains a `print` statement
""")
to_fix = """
remove the `print` statement or replace it with something else that
achieves the intended effect (e.g., a logging call)
"""
class LayoutTestsRegexp(Regexp):
pattern = br"(eventSender|testRunner|internals)\."
name = "LAYOUTTESTS APIS"
file_extensions = EXTENSIONS["js_all"]
description = "eventSender/testRunner/internals used; these are LayoutTests-specific APIs (WebKit/Blink)"
class MissingDepsRegexp(Regexp):
pattern = br"[^\w]/gen/"
name = "MISSING DEPENDENCY"
file_extensions = EXTENSIONS["js_all"]
description = "Chromium-specific content referenced"
to_fix = "Reimplement the test to use well-documented testing interfaces"
class SpecialPowersRegexp(Regexp):
pattern = b"SpecialPowers"
name = "SPECIALPOWERS API"
file_extensions = EXTENSIONS["js_all"]
description = "SpecialPowers used; this is gecko-specific and not supported in wpt"
class TrailingWhitespaceRegexp(Regexp):
name = "TRAILING WHITESPACE"
description = "Whitespace at EOL"
pattern = b"[ \t\f\v]$"
to_fix = """Remove trailing whitespace from all lines in the file."""
class AssertThrowsRegexp(Regexp):
pattern = br"[^.]assert_throws\("
name = "ASSERT_THROWS"
file_extensions = EXTENSIONS["js_all"]
description = "Test-file line has an `assert_throws(...)` call"
to_fix = """Replace with `assert_throws_dom` or `assert_throws_js` or `assert_throws_exactly`"""
class PromiseRejectsRegexp(Regexp):
pattern = br"promise_rejects\("
name = "PROMISE_REJECTS"
file_extensions = EXTENSIONS["js_all"]
description = "Test-file line has a `promise_rejects(...)` call"
to_fix = """Replace with promise_rejects_dom or promise_rejects_js or `promise_rejects_exactly`"""
class AssertPreconditionRegexp(Regexp):
pattern = br"[^.]assert_precondition\("
name = "ASSERT-PRECONDITION"
file_extensions = EXTENSIONS["js_all"]
description = "Test-file line has an `assert_precondition(...)` call"
to_fix = """Replace with `assert_implements` or `assert_implements_optional`"""
class HTMLInvalidSyntaxRegexp(Regexp):
pattern = (br"<(a|abbr|article|audio|b|bdi|bdo|blockquote|body|button|canvas|caption|cite|code|colgroup|data|datalist|dd|del|details|"
br"dfn|dialog|div|dl|dt|em|fieldset|figcaption|figure|footer|form|h[1-6]|head|header|html|i|iframe|ins|kbd|label|legend|li|"
br"main|map|mark|menu|meter|nav|noscript|object|ol|optgroup|option|output|p|picture|pre|progress|q|rp|rt|ruby|s|samp|script|"
br"search|section|select|slot|small|span|strong|style|sub|summary|sup|table|tbody|td|template|textarea|tfoot|th|thead|time|"
br"title|tr|u|ul|var|video)(\s+[^>]+)?\s*/>")
name = "HTML INVALID SYNTAX"
file_extensions = EXTENSIONS["html"]
description = "Test-file line has a non-void HTML tag with /> syntax"
to_fix = """Replace with start tag and end tag"""
class TestDriverInternalRegexp(Regexp):
pattern = br"test_driver_internal"
name = "TEST DRIVER INTERNAL"
file_extensions = EXTENSIONS["js_all"]
description = "Test-file uses test_driver_internal API"
to_fix = """Only use test_driver public API"""