From 0d1b013f8f4dd55fbe1f5ef8898dfc3be20b0c81 Mon Sep 17 00:00:00 2001 From: Filip Skokan Date: Sun, 1 Feb 2026 17:11:37 +0100 Subject: [PATCH] tools: lint new WPTRunner() paths for no duplicates --- test/eslint.config_partial.mjs | 8 ++ .../no-duplicate-wpt-runner-paths.js | 100 ++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 tools/eslint-rules/no-duplicate-wpt-runner-paths.js diff --git a/test/eslint.config_partial.mjs b/test/eslint.config_partial.mjs index 6fbdf277044679..18f33c59f21c40 100644 --- a/test/eslint.config_partial.mjs +++ b/test/eslint.config_partial.mjs @@ -210,6 +210,14 @@ export default [ 'node-core/must-call-assert': 'off', }, }, + { + files: [ + 'test/wpt/test-*.js', + ], + rules: { + 'node-core/no-duplicate-wpt-runner-paths': 'error', + }, + }, { files: [ 'test/es-module/test-esm-example-loader.js', diff --git a/tools/eslint-rules/no-duplicate-wpt-runner-paths.js b/tools/eslint-rules/no-duplicate-wpt-runner-paths.js new file mode 100644 index 00000000000000..e60a680e1e4593 --- /dev/null +++ b/tools/eslint-rules/no-duplicate-wpt-runner-paths.js @@ -0,0 +1,100 @@ +/** + * @file Ensure WPTRunner paths do not overlap across test files. + * Prevents running the same WPT tests multiple times by detecting + * when one WPTRunner path is a parent or subset of another. + */ +'use strict'; + +const { isString } = require('./rules-utils.js'); + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +// Module-level state to track paths across all files in a lint run. +// Map +const registeredPaths = new Map(); + +/** + * Check if pathA contains pathB (i.e., pathB is a subdirectory of pathA) + * @param {string} pathA - The potential parent path + * @param {string} pathB - The potential child path + * @returns {boolean} + */ +function isSubdirectory(pathA, pathB) { + // Normalize: remove trailing slashes + const normalizedA = pathA.replace(/\/+$/, ''); + const normalizedB = pathB.replace(/\/+$/, ''); + + if (normalizedA === normalizedB) { + return true; + } + + // pathB is a subdirectory of pathA if pathB starts with pathA/ + return normalizedB.startsWith(normalizedA + '/'); +} + +module.exports = { + meta: { + type: 'problem', + docs: { + description: 'Disallow overlapping WPTRunner paths across test files', + }, + schema: [], + messages: { + overlappingPath: + "WPTRunner path '{{ currentPath }}' overlaps with '{{ existingPath }}' in {{ existingFile }}. " + + 'One path is a subset of the other, which would run the same tests multiple times.', + }, + }, + + create(context) { + return { + NewExpression(node) { + // Check if this is a `new WPTRunner(...)` call + if (node.callee.type !== 'Identifier' || node.callee.name !== 'WPTRunner') { + return; + } + + // Get the first argument (the path) + const [firstArg] = node.arguments; + if (!isString(firstArg)) { + return; + } + + const currentPath = firstArg.value; + const currentFilename = context.filename; + + // Check against all registered paths for overlaps + for (const [existingPath, existingInfo] of registeredPaths) { + // Skip if same file (could be legitimate multiple runners in same file) + if (existingInfo.filename === currentFilename) { + continue; + } + + // Check if either path is a subdirectory of the other + const currentIsSubOfExisting = isSubdirectory(existingPath, currentPath); + const existingIsSubOfCurrent = isSubdirectory(currentPath, existingPath); + + if (currentIsSubOfExisting || existingIsSubOfCurrent) { + context.report({ + node: firstArg, + messageId: 'overlappingPath', + data: { + currentPath, + existingPath, + existingFile: existingInfo.filename, + }, + }); + } + } + + // Register this path + registeredPaths.set(currentPath, { + filename: currentFilename, + loc: firstArg.loc, + }); + }, + }; + }, +};