참고소스 수정본
This commit is contained in:
233
참고/playwright-main/utils/validate_deps.js
Normal file
233
참고/playwright-main/utils/validate_deps.js
Normal file
@@ -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<group, entry[]>.
|
||||
* Each entry is { original, resolved } where resolved is the absolute path
|
||||
* (or node_modules/... or special token).
|
||||
*/
|
||||
function parseDepsFile(filePath, depsDir) {
|
||||
/** @type {Map<string, Array<{original: string, resolved: string}>>} */
|
||||
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 : '<unknown>/' + 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<fileName, Set<importPath>>.
|
||||
* Import paths are relative to the DEPS.true directory (same as they appear in the file).
|
||||
*/
|
||||
function parseDepsTrue(filePath) {
|
||||
/** @type {Map<string, Set<string>>} */
|
||||
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<string>} 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();
|
||||
Reference in New Issue
Block a user