From 14a52c51cb7d808aa3a0b7518d96c9673694928d Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 10 Apr 2026 11:49:25 -0700 Subject: [PATCH] chore: remove stale DEPS.list entries and add validation tooling Add build_deps_true.js and validate_deps.js scripts to detect declared dependencies in DEPS.list files that are not actually used by any source file. Remove ~110 stale entries found across 38 DEPS.list files. --- .gitignore | 1 + packages/html-reporter/src/DEPS.list | 1 - packages/isomorphic/trace/DEPS.list | 2 - packages/playwright-core/src/DEPS.list | 12 - packages/playwright-core/src/cli/DEPS.list | 5 - packages/playwright-core/src/common/DEPS.list | 1 - .../playwright-core/src/protocol/DEPS.list | 1 - packages/playwright-core/src/remote/DEPS.list | 2 - packages/playwright-core/src/server/DEPS.list | 4 - .../src/server/android/DEPS.list | 2 - .../playwright-core/src/server/bidi/DEPS.list | 2 - .../src/server/codegen/DEPS.list | 1 - .../src/server/electron/DEPS.list | 3 +- .../src/server/recorder/DEPS.list | 3 - .../src/server/registry/DEPS.list | 1 - .../src/server/trace/recorder/DEPS.list | 3 - .../src/server/trace/viewer/DEPS.list | 2 - .../src/tools/backend/DEPS.list | 2 - .../src/tools/cli-client/DEPS.list | 5 +- .../src/tools/cli-daemon/DEPS.list | 2 - .../src/tools/dashboard/DEPS.list | 1 - .../playwright-core/src/tools/mcp/DEPS.list | 3 - .../playwright-core/src/tools/trace/DEPS.list | 2 - packages/playwright-ct-core/src/DEPS.list | 2 - packages/playwright/src/DEPS.list | 7 - packages/playwright/src/agents/DEPS.list | 2 - packages/playwright/src/cli/DEPS.list | 1 - packages/playwright/src/loader/DEPS.list | 3 +- packages/playwright/src/matchers/DEPS.list | 1 - packages/playwright/src/mcp/test/DEPS.list | 3 - packages/playwright/src/plugins/DEPS.list | 2 - packages/playwright/src/reporters/DEPS.list | 2 +- packages/playwright/src/runner/DEPS.list | 3 - packages/playwright/src/transform/DEPS.list | 17 -- packages/playwright/src/worker/DEPS.list | 1 - packages/trace-viewer/src/DEPS.list | 2 - packages/trace-viewer/src/ui/DEPS.list | 6 +- packages/utils/DEPS.list | 7 - packages/web/src/DEPS.list | 3 - utils/build_deps_true.js | 262 ++++++++++++++++++ utils/validate_deps.js | 233 ++++++++++++++++ 41 files changed, 502 insertions(+), 116 deletions(-) create mode 100644 utils/build_deps_true.js create mode 100644 utils/validate_deps.js diff --git a/.gitignore b/.gitignore index 13ff5dec04afe..ee3c339313e6a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ node_modules/ .DS_Store *.swp *.pyc +DEPS.true /.vscode .mono .idea diff --git a/packages/html-reporter/src/DEPS.list b/packages/html-reporter/src/DEPS.list index 7d6766edb953c..058dd4d82b631 100644 --- a/packages/html-reporter/src/DEPS.list +++ b/packages/html-reporter/src/DEPS.list @@ -1,5 +1,4 @@ [*] -@playwright/experimental-ct-react @web/** @isomorphic/** diff --git a/packages/isomorphic/trace/DEPS.list b/packages/isomorphic/trace/DEPS.list index 4192108ed5425..e43dcb5d9f30b 100644 --- a/packages/isomorphic/trace/DEPS.list +++ b/packages/isomorphic/trace/DEPS.list @@ -1,3 +1 @@ [*] -./** -../** diff --git a/packages/playwright-core/src/DEPS.list b/packages/playwright-core/src/DEPS.list index 0b88a2b5c2f31..3174f52bc8994 100644 --- a/packages/playwright-core/src/DEPS.list +++ b/packages/playwright-core/src/DEPS.list @@ -3,14 +3,12 @@ remote/ server/ @utils/** @isomorphic/** -protocol/validator.ts protocol/validatorPrimitives.ts [androidServerImpl.ts] remote/ server/ @utils/** -@isomorphic/** [inProcessFactory.ts] ** @@ -21,16 +19,12 @@ server/ [outofprocess.ts] client/ -package.ts -protocol/ @isomorphic/** @utils/** [utilsBundle.ts] -node_modules/@modelcontextprotocol/sdk node_modules/chokidar node_modules/colors/safe -node_modules/commander node_modules/debug node_modules/diff node_modules/dotenv @@ -45,9 +39,7 @@ node_modules/json5 node_modules/mime node_modules/minimatch node_modules/open -node_modules/pngjs node_modules/progress -node_modules/proxy-from-env node_modules/retry node_modules/signal-exit node_modules/socks-proxy-agent @@ -55,10 +47,6 @@ node_modules/source-map-support node_modules/stoppable node_modules/ws node_modules/yaml -node_modules/yauzl -node_modules/yazl -node_modules/zod -node_modules/zod-to-json-schema [package.ts] "strict" diff --git a/packages/playwright-core/src/cli/DEPS.list b/packages/playwright-core/src/cli/DEPS.list index 17191792c1a86..956f4210d5952 100644 --- a/packages/playwright-core/src/cli/DEPS.list +++ b/packages/playwright-core/src/cli/DEPS.list @@ -6,14 +6,9 @@ ../server/ ../server/registry/ ../remote/ -../utils/** -../utilsBundle.ts -../client/ ../server/trace/viewer/ ../tools/cli-client/program.ts ../tools/trace/traceCli.ts -./browserActions.ts -./installActions.ts ../bootstrap.ts node_modules/commander node_modules/debug diff --git a/packages/playwright-core/src/common/DEPS.list b/packages/playwright-core/src/common/DEPS.list index 047840f50927c..e43dcb5d9f30b 100644 --- a/packages/playwright-core/src/common/DEPS.list +++ b/packages/playwright-core/src/common/DEPS.list @@ -1,2 +1 @@ [*] -@isomorphic/** diff --git a/packages/playwright-core/src/protocol/DEPS.list b/packages/playwright-core/src/protocol/DEPS.list index 047840f50927c..e43dcb5d9f30b 100644 --- a/packages/playwright-core/src/protocol/DEPS.list +++ b/packages/playwright-core/src/protocol/DEPS.list @@ -1,2 +1 @@ [*] -@isomorphic/** diff --git a/packages/playwright-core/src/remote/DEPS.list b/packages/playwright-core/src/remote/DEPS.list index f8c54fd5a8110..6452d73f13de1 100644 --- a/packages/playwright-core/src/remote/DEPS.list +++ b/packages/playwright-core/src/remote/DEPS.list @@ -3,6 +3,4 @@ ../server/android/ ../server/dispatchers/ @utils/** -../utils/** @isomorphic/** -../utilsBundle.ts diff --git a/packages/playwright-core/src/server/DEPS.list b/packages/playwright-core/src/server/DEPS.list index 89daa5f713a5c..f9cf05a0b315c 100644 --- a/packages/playwright-core/src/server/DEPS.list +++ b/packages/playwright-core/src/server/DEPS.list @@ -4,11 +4,8 @@ ../generated/ ../package.ts ../protocol/ -../utils -../utilsBundle.ts ./ ./codegen/ -./isomorphic ./har/ ./recorder/ ./registry/ @@ -23,7 +20,6 @@ node_modules/yauzl node_modules/yazl [devtoolsController.ts] -./chromium/ [playwright.ts] ./android/ diff --git a/packages/playwright-core/src/server/android/DEPS.list b/packages/playwright-core/src/server/android/DEPS.list index facdfb55d51d2..24642c2c6497d 100644 --- a/packages/playwright-core/src/server/android/DEPS.list +++ b/packages/playwright-core/src/server/android/DEPS.list @@ -2,9 +2,7 @@ @isomorphic/** @utils/** ../ -../../protocol/ ../../utilsBundle.ts ../chromium/ -../utils ../registry/ node_modules/debug \ No newline at end of file diff --git a/packages/playwright-core/src/server/bidi/DEPS.list b/packages/playwright-core/src/server/bidi/DEPS.list index b7afb4d4b25d7..ea0bea7e1ae50 100644 --- a/packages/playwright-core/src/server/bidi/DEPS.list +++ b/packages/playwright-core/src/server/bidi/DEPS.list @@ -2,8 +2,6 @@ @utils/** @isomorphic/** ../ -../isomorphic/ -../utils ./third_party/ [bidiOverCdp.ts] diff --git a/packages/playwright-core/src/server/codegen/DEPS.list b/packages/playwright-core/src/server/codegen/DEPS.list index c95fe3ff089fd..2258dc1a0431e 100644 --- a/packages/playwright-core/src/server/codegen/DEPS.list +++ b/packages/playwright-core/src/server/codegen/DEPS.list @@ -1,4 +1,3 @@ [*] @isomorphic/** -@utils/** ../deviceDescriptors.ts diff --git a/packages/playwright-core/src/server/electron/DEPS.list b/packages/playwright-core/src/server/electron/DEPS.list index df64932cb0016..d918ec75671b3 100644 --- a/packages/playwright-core/src/server/electron/DEPS.list +++ b/packages/playwright-core/src/server/electron/DEPS.list @@ -3,5 +3,4 @@ ../../package.ts @utils/** @isomorphic/** -../chromium/ -../utils \ No newline at end of file +../chromium/ \ No newline at end of file diff --git a/packages/playwright-core/src/server/recorder/DEPS.list b/packages/playwright-core/src/server/recorder/DEPS.list index da2c4f3372797..a2d3fe744f93d 100644 --- a/packages/playwright-core/src/server/recorder/DEPS.list +++ b/packages/playwright-core/src/server/recorder/DEPS.list @@ -4,8 +4,5 @@ ../ ../codegen/language.ts ../codegen/languages.ts -../registry/** -../../generated/pollingRecorderSource.ts ../../package.ts -../../protocol/ node_modules/mime diff --git a/packages/playwright-core/src/server/registry/DEPS.list b/packages/playwright-core/src/server/registry/DEPS.list index a6f38b8e7591b..a6aa9a2c65cd0 100644 --- a/packages/playwright-core/src/server/registry/DEPS.list +++ b/packages/playwright-core/src/server/registry/DEPS.list @@ -1,7 +1,6 @@ [*] @utils/** @isomorphic/** -../../utilsBundle.ts ../../package.ts ../utils.ts ../userAgent.ts diff --git a/packages/playwright-core/src/server/trace/recorder/DEPS.list b/packages/playwright-core/src/server/trace/recorder/DEPS.list index 8f4dddaa6e558..944c7dc164060 100644 --- a/packages/playwright-core/src/server/trace/recorder/DEPS.list +++ b/packages/playwright-core/src/server/trace/recorder/DEPS.list @@ -3,8 +3,5 @@ @utils/** ../../ ../../har/ -../../../protocol/ ../../dispatchers/dispatcher.ts -../../utils -../common/ node_modules/mime diff --git a/packages/playwright-core/src/server/trace/viewer/DEPS.list b/packages/playwright-core/src/server/trace/viewer/DEPS.list index b667fa3405f7b..c2dee419d6506 100644 --- a/packages/playwright-core/src/server/trace/viewer/DEPS.list +++ b/packages/playwright-core/src/server/trace/viewer/DEPS.list @@ -1,8 +1,6 @@ [*] ../../ @utils/** -../../registry/ -../../../generated/ ../../../package.ts @utils/** node_modules/open diff --git a/packages/playwright-core/src/tools/backend/DEPS.list b/packages/playwright-core/src/tools/backend/DEPS.list index 0e9ba37d4584f..00720253e24a7 100644 --- a/packages/playwright-core/src/tools/backend/DEPS.list +++ b/packages/playwright-core/src/tools/backend/DEPS.list @@ -4,8 +4,6 @@ @utils/** @utils/** @isomorphic/** -../utils/** -node_modules/@modelcontextprotocol/sdk/types.js node_modules/debug node_modules/jpeg-js node_modules/pngjs diff --git a/packages/playwright-core/src/tools/cli-client/DEPS.list b/packages/playwright-core/src/tools/cli-client/DEPS.list index 73a5463045484..c36e724141a74 100644 --- a/packages/playwright-core/src/tools/cli-client/DEPS.list +++ b/packages/playwright-core/src/tools/cli-client/DEPS.list @@ -1,14 +1,13 @@ [program.ts] "strict" +../../package.ts +../../serverRegistry.ts ./minimist.ts ./session.ts ./registry.ts -../../package.ts -../../serverRegistry.ts [session.ts] "strict" -./minimist.ts ../../package.ts ../utils/socketConnection.ts ./registry.ts diff --git a/packages/playwright-core/src/tools/cli-daemon/DEPS.list b/packages/playwright-core/src/tools/cli-daemon/DEPS.list index 9362d7c6587e5..2c911112cd918 100644 --- a/packages/playwright-core/src/tools/cli-daemon/DEPS.list +++ b/packages/playwright-core/src/tools/cli-daemon/DEPS.list @@ -4,9 +4,7 @@ ../cli-client/registry.ts ../backend/ ../mcp/ -../../utilsBundle.ts @utils/** ../../server/registry/index.ts @utils/** -../../serverRegistry.ts node_modules/zod diff --git a/packages/playwright-core/src/tools/dashboard/DEPS.list b/packages/playwright-core/src/tools/dashboard/DEPS.list index d6d3af79f7661..b5330959fa65a 100644 --- a/packages/playwright-core/src/tools/dashboard/DEPS.list +++ b/packages/playwright-core/src/tools/dashboard/DEPS.list @@ -1,7 +1,6 @@ [*] ../../.. ../../ -../../client/connect.ts ../../server/registry/index.ts @utils/** ../../serverRegistry.ts diff --git a/packages/playwright-core/src/tools/mcp/DEPS.list b/packages/playwright-core/src/tools/mcp/DEPS.list index 4e148131bb74b..187312272b193 100644 --- a/packages/playwright-core/src/tools/mcp/DEPS.list +++ b/packages/playwright-core/src/tools/mcp/DEPS.list @@ -5,12 +5,10 @@ ../backend/ @utils/** @isomorphic/** -../../utilsBundle.ts ../../server/ ../../server/registry/ @utils/** ../../serverRegistry.ts -node_modules/@modelcontextprotocol/sdk/server/index.js node_modules/commander node_modules/debug node_modules/dotenv @@ -22,4 +20,3 @@ node_modules/ws ../utils/connect.ts [program.ts] -../../cli/program.ts diff --git a/packages/playwright-core/src/tools/trace/DEPS.list b/packages/playwright-core/src/tools/trace/DEPS.list index fcbcf9fe6ea27..cf63107215805 100644 --- a/packages/playwright-core/src/tools/trace/DEPS.list +++ b/packages/playwright-core/src/tools/trace/DEPS.list @@ -2,11 +2,9 @@ ../../package.ts @isomorphic/** @utils/** -./*.ts [traceSnapshot.ts] ../../inprocess.ts -../../utils ../backend/browserBackend.ts ../backend/tools.ts ../cli-daemon/command.ts diff --git a/packages/playwright-ct-core/src/DEPS.list b/packages/playwright-ct-core/src/DEPS.list index ef95f390882a1..58cf2e435df02 100644 --- a/packages/playwright-ct-core/src/DEPS.list +++ b/packages/playwright-ct-core/src/DEPS.list @@ -2,8 +2,6 @@ generated/indexSource.ts [viteDevPlugin.ts] -generated/indexSource.ts [mount.ts] -generated/serializers.ts injected/** diff --git a/packages/playwright/src/DEPS.list b/packages/playwright/src/DEPS.list index 81ae6218f6b4e..a67d9689d0619 100644 --- a/packages/playwright/src/DEPS.list +++ b/packages/playwright/src/DEPS.list @@ -2,7 +2,6 @@ @isomorphic/** @utils/** common/ -./util.ts node_modules/commander node_modules/debug node_modules/mime @@ -18,12 +17,6 @@ node_modules/minimatch ** [index.ts] -@testIsomorphic/** -./prompt.ts -./errorContext.ts -./worker/testTracing.ts -./mcp/sdk/ ./mcp/test/ -./transform/babelBundle.ts [errorContext.ts] diff --git a/packages/playwright/src/agents/DEPS.list b/packages/playwright/src/agents/DEPS.list index f4d799271f0a7..f064674bf4a89 100644 --- a/packages/playwright/src/agents/DEPS.list +++ b/packages/playwright/src/agents/DEPS.list @@ -1,8 +1,6 @@ [*] @utils/** -../mcp/sdk/ ../mcp/test/ -../common/ node_modules/colors/safe node_modules/yaml diff --git a/packages/playwright/src/cli/DEPS.list b/packages/playwright/src/cli/DEPS.list index 74a8123ffffa3..e24bde458056c 100644 --- a/packages/playwright/src/cli/DEPS.list +++ b/packages/playwright/src/cli/DEPS.list @@ -1,5 +1,4 @@ [*] ../runner/ -../reporters/ @utils/** ../common/ diff --git a/packages/playwright/src/loader/DEPS.list b/packages/playwright/src/loader/DEPS.list index 1e987ef87c3c6..ea70a1635100a 100644 --- a/packages/playwright/src/loader/DEPS.list +++ b/packages/playwright/src/loader/DEPS.list @@ -1,3 +1,2 @@ [*] -../common/ -../transform/ \ No newline at end of file +../common/ \ No newline at end of file diff --git a/packages/playwright/src/matchers/DEPS.list b/packages/playwright/src/matchers/DEPS.list index 0ed320d3a43a2..8a96baf06c96d 100644 --- a/packages/playwright/src/matchers/DEPS.list +++ b/packages/playwright/src/matchers/DEPS.list @@ -1,6 +1,5 @@ [*] @isomorphic/** @utils/** -../package.ts node_modules/colors/safe node_modules/expect diff --git a/packages/playwright/src/mcp/test/DEPS.list b/packages/playwright/src/mcp/test/DEPS.list index e50e0ae051f08..87d67db765ce8 100644 --- a/packages/playwright/src/mcp/test/DEPS.list +++ b/packages/playwright/src/mcp/test/DEPS.list @@ -1,12 +1,9 @@ [*] @isomorphic/** @utils/** -../../reporters ../../runner -../../transform ../../util ../../common ../../util.ts -node_modules/@modelcontextprotocol/sdk/types.js node_modules/debug node_modules/zod diff --git a/packages/playwright/src/plugins/DEPS.list b/packages/playwright/src/plugins/DEPS.list index c44346b2311de..af3c8c1c201b2 100644 --- a/packages/playwright/src/plugins/DEPS.list +++ b/packages/playwright/src/plugins/DEPS.list @@ -1,7 +1,5 @@ [*] @isomorphic/** @utils/** -../common/ -../util.ts node_modules/colors/safe node_modules/debug diff --git a/packages/playwright/src/reporters/DEPS.list b/packages/playwright/src/reporters/DEPS.list index 0703852e3efe5..d832203a0a13c 100644 --- a/packages/playwright/src/reporters/DEPS.list +++ b/packages/playwright/src/reporters/DEPS.list @@ -1,7 +1,7 @@ [*] @isomorphic/** @utils/** -../common/** +../common/ ../isomorphic/** ../util.ts node_modules/colors/safe diff --git a/packages/playwright/src/runner/DEPS.list b/packages/playwright/src/runner/DEPS.list index 805280a750963..84132df6f5896 100644 --- a/packages/playwright/src/runner/DEPS.list +++ b/packages/playwright/src/runner/DEPS.list @@ -1,15 +1,12 @@ [*] @isomorphic/** @utils/** -../../types.ts ../common/ ../reporters/ -../third_party/ ../transform/ ../plugins/ ../util.ts ../isomorphic/ -../fsWatcher.ts node_modules/chokidar node_modules/colors/safe node_modules/debug diff --git a/packages/playwright/src/transform/DEPS.list b/packages/playwright/src/transform/DEPS.list index f40162ec997be..d862d31b37c48 100644 --- a/packages/playwright/src/transform/DEPS.list +++ b/packages/playwright/src/transform/DEPS.list @@ -8,22 +8,5 @@ node_modules/json5 node_modules/source-map-support [babelBundle.ts] -node_modules/@babel/code-frame node_modules/@babel/core -node_modules/@babel/helper-plugin-utils -node_modules/@babel/plugin-proposal-decorators -node_modules/@babel/plugin-syntax-import-attributes -node_modules/@babel/plugin-transform-class-properties -node_modules/@babel/plugin-transform-class-static-block -node_modules/@babel/plugin-transform-export-namespace-from -node_modules/@babel/plugin-transform-logical-assignment-operators -node_modules/@babel/plugin-transform-modules-commonjs -node_modules/@babel/plugin-transform-nullish-coalescing-operator -node_modules/@babel/plugin-transform-numeric-separator -node_modules/@babel/plugin-transform-optional-chaining -node_modules/@babel/plugin-transform-private-methods -node_modules/@babel/plugin-transform-private-property-in-object -node_modules/@babel/plugin-transform-react-jsx -node_modules/@babel/preset-typescript node_modules/@babel/traverse -node_modules/@babel/types diff --git a/packages/playwright/src/worker/DEPS.list b/packages/playwright/src/worker/DEPS.list index f30726589a195..72ccc32a7361d 100644 --- a/packages/playwright/src/worker/DEPS.list +++ b/packages/playwright/src/worker/DEPS.list @@ -5,7 +5,6 @@ ../common/ ../util.ts ../matchers/** -../isomorphic/util.ts node_modules/colors/safe node_modules/yauzl node_modules/yazl diff --git a/packages/trace-viewer/src/DEPS.list b/packages/trace-viewer/src/DEPS.list index f52c0a024e556..a30fe9005e312 100644 --- a/packages/trace-viewer/src/DEPS.list +++ b/packages/trace-viewer/src/DEPS.list @@ -1,6 +1,4 @@ [*] -@isomorphic/** -@trace/** @web/** ui/ diff --git a/packages/trace-viewer/src/ui/DEPS.list b/packages/trace-viewer/src/ui/DEPS.list index af02e8bfad41a..d96a82cab8a06 100644 --- a/packages/trace-viewer/src/ui/DEPS.list +++ b/packages/trace-viewer/src/ui/DEPS.list @@ -1,10 +1,6 @@ [*] @injected/** @isomorphic/** -@trace/** @web/** -../entries.ts -../geometry.ts ../../../playwright/src/isomorphic/** -../third_party/devtools.ts -./shared/** \ No newline at end of file +../third_party/devtools.ts \ No newline at end of file diff --git a/packages/utils/DEPS.list b/packages/utils/DEPS.list index 6567ca502c06d..8904c06d0311b 100644 --- a/packages/utils/DEPS.list +++ b/packages/utils/DEPS.list @@ -1,8 +1,5 @@ [*] -../../package.ts -../../utils @isomorphic/** -../../utilsBundle.ts node_modules/colors/safe node_modules/debug node_modules/diff @@ -12,10 +9,6 @@ node_modules/mime node_modules/pngjs node_modules/proxy-from-env node_modules/socks-proxy-agent -node_modules/get-stream -node_modules/graceful-fs -node_modules/retry -node_modules/signal-exit node_modules/ws node_modules/yauzl node_modules/yazl diff --git a/packages/web/src/DEPS.list b/packages/web/src/DEPS.list index 2903883d573fa..e43dcb5d9f30b 100644 --- a/packages/web/src/DEPS.list +++ b/packages/web/src/DEPS.list @@ -1,4 +1 @@ [*] -@isomorphic/** -@playwright/experimental-ct-react -third_party/** diff --git a/utils/build_deps_true.js b/utils/build_deps_true.js new file mode 100644 index 0000000000000..857017e305dce --- /dev/null +++ b/utils/build_deps_true.js @@ -0,0 +1,262 @@ +#!/usr/bin/env node +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * Builds DEPS.true files next to every DEPS.list. + * Each DEPS.true has a [filename] section per source file in the directory, + * listing the actual cross-directory dependencies (resolved import targets) + * that the file uses. Type-only imports are excluded. + */ + +const fs = require('fs'); +const ts = require('typescript'); +const path = require('path').posix; +const Module = require('module'); +const builtins = new Set(Module.builtinModules); +const packagesDir = path.resolve(path.join(__dirname, '..', 'packages')); + +const packages = new Map(); +packages.set('web', packagesDir + '/web/src/'); +packages.set('injected', packagesDir + '/injected/src/'); +packages.set('isomorphic', packagesDir + '/isomorphic/'); +packages.set('utils', packagesDir + '/utils/'); +packages.set('testIsomorphic', packagesDir + '/playwright/src/isomorphic/'); + +async function main() { + // Find every DEPS.list under packages/. + const depsListDirs = []; + findDepsListDirs(packagesDir, depsListDirs); + + // Group DEPS.list dirs by the top-level source root they belong to, so we + // only create one TS program per root. + /** @type {Map} sourceRoot -> depsListDirs */ + const rootToDirs = new Map(); + for (const dir of depsListDirs) { + const sourceRoot = findSourceRoot(dir); + if (!rootToDirs.has(sourceRoot)) + rootToDirs.set(sourceRoot, []); + rootToDirs.get(sourceRoot).push(dir); + } + + const allDepsListDirSet = new Set(depsListDirs); + for (const [sourceRoot, dirs] of rootToDirs) + buildDepsTrue(sourceRoot, dirs, allDepsListDirSet); +} + +/** + * Walk up from a DEPS.list directory to find the source root (the directory + * containing all the TS files we need to parse). This is typically `src/` or + * the package root itself (for packages like utils/ or isomorphic/). + */ +function findSourceRoot(dir) { + // Walk up until we hit a package root (directory directly under packagesDir) + // or a `src/` boundary. + let current = dir; + while (current !== packagesDir) { + const parent = path.dirname(current); + if (parent === packagesDir) + return current; // package root (e.g., packages/utils) + if (path.basename(current) === 'src') + return current; + current = parent; + } + return dir; +} + +function buildDepsTrue(sourceRoot, depsListDirs, allDepsListDirSet) { + const allFiles = listAllFiles(sourceRoot); + if (allFiles.length === 0) + return; + + const program = ts.createProgram({ + options: { + allowJs: true, + target: ts.ScriptTarget.ESNext, + strict: true, + }, + rootNames: allFiles, + }); + + const sourceFiles = program.getSourceFiles().filter( + x => !x.fileName.includes(path.sep + 'node_modules' + path.sep) && + !x.fileName.includes(path.sep + 'bundles' + path.sep) + ); + + // Collect imports per source file. + /** @type {Map>} */ + const fileImports = new Map(); + for (const sf of sourceFiles) { + const imports = new Set(); + fileImports.set(sf.fileName, imports); + collectImports(sf, sf.fileName, sf.getFullText(), imports); + } + + for (const depsDir of depsListDirs) + writeDepsTrue(depsDir, fileImports, allDepsListDirSet); +} + +function collectImports(node, fileName, text, imports) { + if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { + // Skip type-only imports. + if (node.importClause) { + if (node.importClause.isTypeOnly) + return; + if (node.importClause.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) { + if (node.importClause.namedBindings.elements.every(e => e.isTypeOnly)) + return; + } + } + + // Skip @no-check-deps. + const fullStart = node.getFullStart(); + const commentRanges = ts.getLeadingCommentRanges(text, fullStart); + for (const range of commentRanges || []) { + const comment = text.substring(range.pos, range.end); + if (comment.includes('@no-check-deps')) + return; + } + + const importName = node.moduleSpecifier.text; + let importPath; + if (importName.startsWith('.')) { + importPath = path.resolve(path.dirname(fileName), importName); + } else if (importName.startsWith('@')) { + const tokens = importName.substring(1).split('/'); + const pkg = tokens[0]; + if (packages.has(pkg)) + importPath = packages.get(pkg) + tokens.slice(1).join('/'); + } + + if (importPath) { + // Resolve to actual file. + if (!fs.existsSync(importPath)) { + if (fs.existsSync(importPath + '.ts')) + importPath = importPath + '.ts'; + else if (fs.existsSync(importPath + '.tsx')) + importPath = importPath + '.tsx'; + else if (fs.existsSync(importPath + '.d.ts')) + importPath = importPath + '.d.ts'; + } + imports.add(importPath); + } else if (!builtins.has(importName)) { + // External (node_modules) import. + imports.add('node_modules/' + importName); + } + } + ts.forEachChild(node, x => collectImports(x, fileName, text, imports)); +} + +function writeDepsTrue(depsDir, fileImports, allDepsListDirs) { + // Find source files governed by this DEPS.list: files in this directory + // plus files in subdirectories that don't have their own DEPS.list. + const governedFiles = []; + collectGovernedFiles(depsDir, governedFiles, allDepsListDirs); + governedFiles.sort(); + + const lines = []; + for (const filePath of governedFiles) { + const imports = fileImports.get(filePath); + if (!imports || imports.size === 0) + continue; + + // Include all deps: cross-directory and same-directory (strict mode needs both). + const deps = []; + for (const imp of imports) + deps.push(imp); + + if (deps.length === 0) + continue; + + // Format deps relative to the DEPS.list directory. + const formatted = deps + .map(dep => { + if (dep.startsWith('node_modules/')) + return dep; + return path.relative(depsDir, dep); + }) + .sort(); + + const fileName = path.relative(depsDir, filePath); + lines.push(`[${fileName}]`); + for (const dep of formatted) + lines.push(dep); + lines.push(''); + } + + const outPath = path.join(depsDir, 'DEPS.true'); + if (lines.length === 0) { + // Remove stale DEPS.true if no cross-dir deps exist. + if (fs.existsSync(outPath)) + fs.unlinkSync(outPath); + return; + } + + fs.writeFileSync(outPath, lines.join('\n') + '\n'); + console.log('Wrote ' + path.relative(packagesDir, outPath)); +} + +/** + * Collect source files governed by this DEPS.list: files directly in depsDir + * plus files in subdirectories that don't have their own DEPS.list. + */ +function collectGovernedFiles(dir, result, allDepsListDirs) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.resolve(dir, entry.name); + if (entry.isDirectory()) { + if (entry.name === 'node_modules' || entry.name === 'bundles') + continue; + // Stop recursion at subdirectories that have their own DEPS.list. + if (allDepsListDirs.has(full)) + continue; + collectGovernedFiles(full, result, allDepsListDirs); + } else if (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) { + result.push(full); + } + } +} + +function findDepsListDirs(dir, result) { + if (fs.existsSync(path.join(dir, 'DEPS.list'))) + result.push(dir); + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'bundles') + findDepsListDirs(path.resolve(dir, entry.name), result); + } +} + +function listAllFiles(dir) { + const result = []; + for (const d of fs.readdirSync(dir, { withFileTypes: true })) { + const res = path.resolve(dir, d.name); + if (d.isDirectory() && d.name !== 'node_modules' && d.name !== 'bundles') + result.push(...listAllFiles(res)); + else if (d.name.endsWith('.ts') || d.name.endsWith('.tsx')) + result.push(res); + } + return result; +} + +function isDirectory(p) { + return fs.existsSync(p) && fs.statSync(p).isDirectory(); +} + +main().catch(e => { + console.error(e && e.stack ? e.stack : e); + process.exit(1); +}); diff --git a/utils/validate_deps.js b/utils/validate_deps.js new file mode 100644 index 0000000000000..8f84c480a6f2e --- /dev/null +++ b/utils/validate_deps.js @@ -0,0 +1,233 @@ +#!/usr/bin/env node +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// @ts-check + +/** + * Validates DEPS.list against DEPS.true (generated by build_deps_true.js). + * Reports declared dependencies in DEPS.list that are not actually used by + * any file, according to DEPS.true. + * + * Usage: node utils/build_deps_true.js && node utils/validate_deps.js + */ + +const fs = require('fs'); +const path = require('path').posix; +const packagesDir = path.resolve(path.join(__dirname, '..', 'packages')); + +const packages = new Map(); +packages.set('web', packagesDir + '/web/src/'); +packages.set('injected', packagesDir + '/injected/src/'); +packages.set('isomorphic', packagesDir + '/isomorphic/'); +packages.set('utils', packagesDir + '/utils/'); +packages.set('testIsomorphic', packagesDir + '/playwright/src/isomorphic/'); + +let hasErrors = false; + +function main() { + const depsListFiles = []; + findFiles(packagesDir, 'DEPS.list', depsListFiles); + + for (const depsListPath of depsListFiles.sort()) { + const depsDir = path.dirname(depsListPath); + const depsTruePath = path.join(depsDir, 'DEPS.true'); + + // Parse DEPS.list into groups. + const declared = parseDepsFile(depsListPath, depsDir); + // Parse DEPS.true (actual imports). Missing file means no cross-dir deps. + const actual = fs.existsSync(depsTruePath) ? parseDepsTrue(depsTruePath) : new Map(); + + validateDirectory(depsDir, declared, actual); + } + + if (hasErrors) + process.exit(1); +} + +/** + * Parse a DEPS.list file into a Map. + * Each entry is { original, resolved } where resolved is the absolute path + * (or node_modules/... or special token). + */ +function parseDepsFile(filePath, depsDir) { + /** @type {Map>} */ + const groups = new Map(); + let currentGroup = '*'; + groups.set('*', []); + + for (const line of fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean).filter(l => !l.startsWith('#'))) { + const groupMatch = line.match(/^\[(.*)\]$/); + if (groupMatch) { + currentGroup = groupMatch[1]; + if (!groups.has(currentGroup)) + groups.set(currentGroup, []); + continue; + } + + let resolved; + if (line === '***' || line === '**' || line === '"strict"') { + resolved = line; + } else if (line.startsWith('node_modules/')) { + resolved = line; + } else if (line.startsWith('@')) { + resolved = line.replace(/@([\w-]+)\/(.*)/, (_, arg1, arg2) => { + const base = packages.get(arg1); + return base ? base + arg2 : '/' + arg1 + '/' + arg2; + }); + } else { + resolved = path.resolve(depsDir, line); + } + + groups.get(currentGroup).push({ original: line, resolved }); + } + return groups; +} + +/** + * Parse a DEPS.true file into a Map>. + * Import paths are relative to the DEPS.true directory (same as they appear in the file). + */ +function parseDepsTrue(filePath) { + /** @type {Map>} */ + const files = new Map(); + let currentFile = null; + + for (const line of fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean)) { + const groupMatch = line.match(/^\[(.*)\]$/); + if (groupMatch) { + currentFile = groupMatch[1]; + files.set(currentFile, new Set()); + continue; + } + if (currentFile) + files.get(currentFile).add(line); + } + return files; +} + +/** + * Check each entry in DEPS.list: is it matched by at least one actual import + * from a file the entry governs? + */ +function validateDirectory(depsDir, declared, actual) { + // Collect all actual import paths (resolved to absolute) for files governed by each group. + // [*] group governs all files; [file.ts] governs only that file. + const errors = []; + + for (const [group, entries] of declared) { + // Skip groups with wildcard — everything is allowed. + if (entries.some(e => e.resolved === '***' || e.resolved === '**')) + continue; + + // Determine which files this group governs. + /** @type {Set} absolute import paths from governed files */ + const governedImports = new Set(); + if (group === '*') { + // All files in the directory. + for (const [, imports] of actual) { + for (const imp of imports) + governedImports.add(imp); + } + } else { + // Specific file. + const fileImports = actual.get(group); + if (fileImports) { + for (const imp of fileImports) + governedImports.add(imp); + } + } + + for (const entry of entries) { + if (entry.resolved === '"strict"') + continue; + if (!isEntryUsed(entry, governedImports, depsDir)) + errors.push(` Unused: '${entry.original}' in [${group}]`); + } + } + + if (errors.length) { + hasErrors = true; + const rel = path.relative(packagesDir, path.join(depsDir, 'DEPS.list')); + console.log(rel + ':'); + for (const error of errors) + console.log(error); + } +} + +/** + * Check if a DEPS.list entry is matched by at least one actual import. + * The entry.resolved is an absolute path (possibly with ** glob suffix), + * or a node_modules/ specifier. + * governedImports are relative paths from DEPS.true (relative to depsDir). + */ +function isEntryUsed(entry, governedImports, depsDir) { + const { resolved } = entry; + + for (const imp of governedImports) { + const absImp = imp.startsWith('node_modules/') ? imp : path.resolve(depsDir, imp); + + if (resolved.startsWith('node_modules/')) { + if (absImp === resolved || absImp.startsWith(resolved + '/')) + return true; + continue; + } + + // Glob: foo/** matches anything under foo/. + if (resolved.endsWith('**')) { + const parent = resolved.substring(0, resolved.length - 2); + if (absImp.startsWith(parent)) + return true; + continue; + } + + // Directory dep (e.g., ./codegen/ or ../protocol/): matches imports whose + // directory is at or under the resolved path. + if (isDirectory(resolved)) { + if (absImp.startsWith(resolved + '/') || absImp === resolved) + return true; + continue; + } + + // Exact file match. + if (absImp === resolved) + return true; + // Match without extension. + if (absImp === resolved + '.ts' || absImp === resolved + '.tsx' || absImp === resolved + '.d.ts') + return true; + // The dep might point to a directory (with index.ts) that we resolved as a file. + const impDir = path.dirname(absImp); + if (impDir === resolved) + return true; + } + return false; +} + +function isDirectory(p) { + return fs.existsSync(p) && fs.statSync(p).isDirectory(); +} + +function findFiles(dir, name, result) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.resolve(dir, entry.name); + if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'bundles') + findFiles(full, name, result); + else if (entry.name === name) + result.push(full); + } +} + +main();