

























@@ -1,21 +1,19 @@
11#!/usr/bin/env node
223+import { promises as fs } from "node:fs";
34import path from "node:path";
45import { fileURLToPath } from "node:url";
5-import ts from "typescript";
66import { BUNDLED_PLUGIN_PATH_PREFIX } from "./lib/bundled-plugin-paths.mjs";
77import {
8-collectTypeScriptInventory,
8+collectModuleReferencesFromSource,
99normalizeRepoPath,
1010resolveRepoSpecifier,
11-visitModuleSpecifiers,
1211writeLine,
1312} from "./lib/guard-inventory-utils.mjs";
1413import {
1514collectTypeScriptFilesFromRoots,
1615resolveSourceRoots,
1716runAsScript,
18-toLine,
1917} from "./lib/ts-guard-utils.mjs";
20182119const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
@@ -37,76 +35,69 @@ function pushEntry(entries, entry) {
3735entries.push(entry);
3836}
393740-function scanPluginSdkExtensionFacadeSmells(sourceFile, filePath) {
38+function scanPluginSdkExtensionFacadeSmells(source, filePath) {
4139const relativeFile = normalizeRepoPath(repoRoot, filePath);
4240if (!relativeFile.startsWith("src/plugin-sdk/")) {
4341return [];
4442}
45434644const entries = [];
474548-visitModuleSpecifiers(ts, sourceFile, ({ kind, specifier, specifierNode }) => {
46+for (const { kind, line, specifier } of collectModuleReferencesFromSource(source)) {
4947if (kind !== "export") {
50-return;
48+continue;
5149}
5250const resolvedPath = resolveRepoSpecifier(repoRoot, specifier, filePath);
5351if (!resolvedPath?.startsWith(BUNDLED_PLUGIN_PATH_PREFIX)) {
54-return;
52+continue;
5553}
5654pushEntry(entries, {
5755category: "plugin-sdk-extension-facade",
5856file: relativeFile,
59-line: toLine(sourceFile, specifierNode),
57+ line,
6058 kind,
6159 specifier,
6260 resolvedPath,
6361reason: "plugin-sdk public surface re-exports extension-owned implementation",
6462});
65-});
63+}
6664return entries;
6765}
686669-function scanRuntimeTypeImplementationSmells(sourceFile, filePath) {
67+function scanRuntimeTypeImplementationSmells(source, filePath) {
7068const relativeFile = normalizeRepoPath(repoRoot, filePath);
7169if (!/^src\/plugins\/runtime\/types(?:-[^/]+)?\.ts$/.test(relativeFile)) {
7270return [];
7371}
74727573const entries = [];
767477-function visit(node) {
75+for (const { kind, line, specifier } of collectModuleReferencesFromSource(source)) {
76+if (kind !== "dynamic-import") {
77+continue;
78+}
79+const resolvedPath = resolveRepoSpecifier(repoRoot, specifier, filePath);
7880if (
79-ts.isImportTypeNode(node) &&
80-ts.isLiteralTypeNode(node.argument) &&
81-ts.isStringLiteral(node.argument.literal)
81+resolvedPath &&
82+(/^src\/plugins\/runtime\/runtime-[^/]+\.ts$/.test(resolvedPath) ||
83+ /^extensions\/[^/]+\/runtime-api\.[^/]+$/.test(resolvedPath))
8284) {
83-const specifier = node.argument.literal.text;
84-const resolvedPath = resolveRepoSpecifier(repoRoot, specifier, filePath);
85-if (
86-resolvedPath &&
87-(/^src\/plugins\/runtime\/runtime-[^/]+\.ts$/.test(resolvedPath) ||
88-/^extensions\/[^/]+\/runtime-api\.[^/]+$/.test(resolvedPath))
89-) {
90-pushEntry(entries, {
91-category: "runtime-type-implementation-edge",
92-file: relativeFile,
93-line: toLine(sourceFile, node.argument.literal),
94-kind: "import-type",
95- specifier,
96- resolvedPath,
97-reason: "runtime type file references implementation shim directly",
98-});
99-}
85+pushEntry(entries, {
86+category: "runtime-type-implementation-edge",
87+file: relativeFile,
88+ line,
89+kind: "import-type",
90+ specifier,
91+ resolvedPath,
92+reason: "runtime type file references implementation shim directly",
93+});
10094}
101-102-ts.forEachChild(node, visit);
10395}
10496105-visit(sourceFile);
10697return entries;
10798}
10899109-function scanRuntimeServiceLocatorSmells(sourceFile, filePath) {
100+function scanRuntimeServiceLocatorSmells(source, filePath) {
110101const relativeFile = normalizeRepoPath(repoRoot, filePath);
111102if (
112103!relativeFile.startsWith("src/plugin-sdk/") &&
@@ -120,47 +111,25 @@ function scanRuntimeServiceLocatorSmells(sourceFile, filePath) {
120111const runtimeStoreCalls = [];
121112const mutableStateNodes = [];
122113123-for (const statement of sourceFile.statements) {
124-if (ts.isFunctionDeclaration(statement) && statement.name) {
125-const isExported = statement.modifiers?.some(
126-(modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
127-);
128-if (isExported) {
129-exportedNames.add(statement.name.text);
130-}
131-} else if (ts.isVariableStatement(statement)) {
132-const isExported = statement.modifiers?.some(
133-(modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
134-);
135-for (const declaration of statement.declarationList.declarations) {
136-if (ts.isIdentifier(declaration.name) && isExported) {
137-exportedNames.add(declaration.name.text);
138-}
139-if (
140-!isExported &&
141-(statement.declarationList.flags & ts.NodeFlags.Let) !== 0 &&
142-ts.isIdentifier(declaration.name)
143-) {
144-mutableStateNodes.push(declaration.name);
145-}
146-}
114+const lines = source.split(/\r?\n/);
115+for (const [index, line] of lines.entries()) {
116+const lineNumber = index + 1;
117+const exportedFunction = line.match(/^\s*export\s+function\s+([A-Za-z_$][\w$]*)/);
118+if (exportedFunction) {
119+exportedNames.add(exportedFunction[1]);
147120}
148-}
149-150-function visit(node) {
151-if (
152- ts.isCallExpression(node) &&
153-ts.isIdentifier(node.expression) &&
154- node.expression.text === "createPluginRuntimeStore"
155-) {
156-runtimeStoreCalls.push(node.expression);
121+ const exportedVariable = line.match(/^\s*export\s+(?:const|let|var)\s+([A-Za-z_$][\w$]*)/);
122+ if (exportedVariable) {
123+ exportedNames.add(exportedVariable[1]);
124+}
125+for (const mutableMatch of line.matchAll(/^\s*let\s+([A-Za-z_$][\w$]*)/g)) {
126+mutableStateNodes.push({ line: lineNumber, text: mutableMatch[1] });
127+}
128+if (line.includes("createPluginRuntimeStore")) {
129+runtimeStoreCalls.push({ line: lineNumber });
157130}
158-159-ts.forEachChild(node, visit);
160131}
161132162-visit(sourceFile);
163-164133const getterNames = [...exportedNames].filter((name) => /^get[A-Z]/.test(name));
165134const setterNames = [...exportedNames].filter((name) => /^set[A-Z]/.test(name));
166135@@ -169,7 +138,7 @@ function scanRuntimeServiceLocatorSmells(sourceFile, filePath) {
169138pushEntry(entries, {
170139category: "runtime-service-locator",
171140file: relativeFile,
172-line: toLine(sourceFile, callNode),
141+line: callNode.line,
173142kind: "runtime-store",
174143specifier: "createPluginRuntimeStore",
175144resolvedPath: relativeFile,
@@ -183,7 +152,7 @@ function scanRuntimeServiceLocatorSmells(sourceFile, filePath) {
183152pushEntry(entries, {
184153category: "runtime-service-locator",
185154file: relativeFile,
186-line: toLine(sourceFile, identifier),
155+line: identifier.line,
187156kind: "mutable-state",
188157specifier: identifier.text,
189158resolvedPath: relativeFile,
@@ -201,18 +170,16 @@ export async function collectArchitectureSmells() {
201170const files = (await collectTypeScriptFilesFromRoots(scanRoots)).toSorted((left, right) =>
202171normalizeRepoPath(repoRoot, left).localeCompare(normalizeRepoPath(repoRoot, right)),
203172);
204-return await collectTypeScriptInventory({
205- ts,
206- files,
207- compareEntries,
208-collectEntries(sourceFile, filePath) {
209-return [
210- ...scanPluginSdkExtensionFacadeSmells(sourceFile, filePath),
211- ...scanRuntimeTypeImplementationSmells(sourceFile, filePath),
212- ...scanRuntimeServiceLocatorSmells(sourceFile, filePath),
213-];
214-},
215-});
173+const entriesByFile = await Promise.all(
174+files.map(async (filePath) => {
175+const source = await fs.readFile(filePath, "utf8");
176+const entries = scanPluginSdkExtensionFacadeSmells(source, filePath);
177+entries.push(...scanRuntimeTypeImplementationSmells(source, filePath));
178+entries.push(...scanRuntimeServiceLocatorSmells(source, filePath));
179+return entries;
180+}),
181+);
182+return entriesByFile.flat().toSorted(compareEntries);
216183})();
217184try {
218185return await architectureSmellsPromise;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。