Files
AI/참고/playwright-main/utils/build_deps_true.js
2026-05-12 19:40:31 +09:00

263 lines
8.5 KiB
JavaScript

#!/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<string, string[]>} 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<string, Set<string>>} */
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);
});