@ts-stack/cycle-detector 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +365 -88
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
- package/src/index.ts +401 -91
- package/eslint.config.mjs +0 -60
package/dist/index.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import ts from 'typescript';
|
|
5
|
+
// Global shared structures for maximum performance
|
|
5
6
|
const graph = new Map();
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/
|
|
7
|
+
const allUniqueCycles = [];
|
|
8
|
+
const globalDetectedCycles = new Set();
|
|
9
|
+
const packageMetaCache = new Map();
|
|
10
|
+
const compilerOptionsCache = new Map();
|
|
11
|
+
let globalProjectPath;
|
|
12
12
|
function parseArgs() {
|
|
13
13
|
const args = [...process.argv.slice(2)];
|
|
14
14
|
let projectPath;
|
|
@@ -24,10 +24,6 @@ function parseArgs() {
|
|
|
24
24
|
}
|
|
25
25
|
return { entryPatterns, projectPath };
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
28
|
-
* Checks if an import/export declaration is type-only.
|
|
29
|
-
* Type-only imports are stripped during compilation and don't cause runtime cycles.
|
|
30
|
-
*/
|
|
31
27
|
function isRuntimeImport(node) {
|
|
32
28
|
if (ts.isExportDeclaration(node)) {
|
|
33
29
|
if (!node.moduleSpecifier)
|
|
@@ -36,10 +32,9 @@ function isRuntimeImport(node) {
|
|
|
36
32
|
}
|
|
37
33
|
if (ts.isImportDeclaration(node)) {
|
|
38
34
|
if (!node.importClause)
|
|
39
|
-
return true;
|
|
40
|
-
if (node.importClause.
|
|
41
|
-
return false;
|
|
42
|
-
// Check individual specifiers: import { type A, B } from './foo'
|
|
35
|
+
return true;
|
|
36
|
+
if (node.importClause.phaseModifier)
|
|
37
|
+
return false;
|
|
43
38
|
const namedBindings = node.importClause.namedBindings;
|
|
44
39
|
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
45
40
|
return !namedBindings.elements.every((el) => el.isTypeOnly);
|
|
@@ -48,92 +43,300 @@ function isRuntimeImport(node) {
|
|
|
48
43
|
}
|
|
49
44
|
return false;
|
|
50
45
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
function getCompilerOptionsForFile(filePath) {
|
|
47
|
+
const currentDir = path.dirname(filePath);
|
|
48
|
+
const cachedOptions = compilerOptionsCache.get(currentDir);
|
|
49
|
+
if (cachedOptions !== undefined) {
|
|
50
|
+
return cachedOptions;
|
|
51
|
+
}
|
|
52
|
+
const configPath = ts.findConfigFile(currentDir, ts.sys.fileExists, 'tsconfig.json') || globalProjectPath;
|
|
53
|
+
if (configPath) {
|
|
54
|
+
const resolvedConfigPath = path.resolve(configPath);
|
|
55
|
+
const configDir = path.dirname(resolvedConfigPath);
|
|
56
|
+
const cachedConfig = compilerOptionsCache.get(resolvedConfigPath);
|
|
57
|
+
if (cachedConfig !== undefined) {
|
|
58
|
+
compilerOptionsCache.set(currentDir, cachedConfig);
|
|
59
|
+
return cachedConfig;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const configFile = ts.readConfigFile(resolvedConfigPath, ts.sys.readFile);
|
|
63
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, configDir);
|
|
64
|
+
const options = parsedConfig.options || {};
|
|
65
|
+
compilerOptionsCache.set(resolvedConfigPath, options);
|
|
66
|
+
compilerOptionsCache.set(currentDir, options);
|
|
67
|
+
return options;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Fallback
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
function getPackageMeta(filePath) {
|
|
76
|
+
let currentDir = path.dirname(filePath);
|
|
77
|
+
const visitedDirs = [];
|
|
78
|
+
while (currentDir && currentDir !== path.parse(currentDir).root) {
|
|
79
|
+
const cached = packageMetaCache.get(currentDir);
|
|
80
|
+
if (cached !== undefined) {
|
|
81
|
+
for (const d of visitedDirs)
|
|
82
|
+
packageMetaCache.set(d, cached);
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
|
85
|
+
const pkgJsonPath = path.join(currentDir, 'package.json');
|
|
86
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
87
|
+
try {
|
|
88
|
+
const content = fs.readFileSync(pkgJsonPath, 'utf8');
|
|
89
|
+
const pkg = JSON.parse(content);
|
|
90
|
+
let outDirName = 'dist';
|
|
91
|
+
const mainField = pkg.main || pkg.types || pkg.typings || '';
|
|
92
|
+
if (mainField) {
|
|
93
|
+
const parts = path.normalize(mainField).split(path.sep);
|
|
94
|
+
if (parts.length > 1 && parts[0] !== '.' && parts[0] !== '..') {
|
|
95
|
+
outDirName = parts[0];
|
|
96
|
+
}
|
|
97
|
+
else if (parts.length > 2 && (parts[0] === '.' || parts[0] === '..')) {
|
|
98
|
+
outDirName = parts[1];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
let srcDirName = 'src';
|
|
102
|
+
if (fs.existsSync(path.join(currentDir, 'source')))
|
|
103
|
+
srcDirName = 'source';
|
|
104
|
+
else if (fs.existsSync(path.join(currentDir, 'lib')))
|
|
105
|
+
srcDirName = 'lib';
|
|
106
|
+
const meta = { pkgDir: currentDir, srcDirName, outDirName };
|
|
107
|
+
packageMetaCache.set(currentDir, meta);
|
|
108
|
+
for (const d of visitedDirs)
|
|
109
|
+
packageMetaCache.set(d, meta);
|
|
110
|
+
return meta;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
packageMetaCache.set(currentDir, null);
|
|
114
|
+
for (const d of visitedDirs)
|
|
115
|
+
packageMetaCache.set(d, null);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
visitedDirs.push(currentDir);
|
|
120
|
+
currentDir = path.dirname(currentDir);
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
55
124
|
function resolveModule(moduleName, containingFile, options) {
|
|
56
125
|
const result = ts.resolveModuleName(moduleName, containingFile, options, ts.sys);
|
|
57
|
-
if (
|
|
58
|
-
return
|
|
126
|
+
if (!result.resolvedModule)
|
|
127
|
+
return null;
|
|
128
|
+
const resolvedFileName = path.resolve(result.resolvedModule.resolvedFileName);
|
|
129
|
+
if (resolvedFileName.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
130
|
+
return null;
|
|
59
131
|
}
|
|
60
|
-
|
|
132
|
+
const meta = getPackageMeta(resolvedFileName);
|
|
133
|
+
if (meta) {
|
|
134
|
+
const { pkgDir, srcDirName, outDirName } = meta;
|
|
135
|
+
const srcDirPath = path.join(pkgDir, srcDirName);
|
|
136
|
+
if (resolvedFileName.startsWith(srcDirPath + path.sep)) {
|
|
137
|
+
return resolvedFileName;
|
|
138
|
+
}
|
|
139
|
+
const outDirPath = path.join(pkgDir, outDirName);
|
|
140
|
+
if (resolvedFileName.startsWith(outDirPath + path.sep) || resolvedFileName === outDirPath) {
|
|
141
|
+
const relativeToOut = path.relative(outDirPath, resolvedFileName);
|
|
142
|
+
let baseName = relativeToOut;
|
|
143
|
+
if (baseName.endsWith('.d.ts'))
|
|
144
|
+
baseName = baseName.slice(0, -5);
|
|
145
|
+
else if (baseName.endsWith('.d.mts'))
|
|
146
|
+
baseName = baseName.slice(0, -6);
|
|
147
|
+
else if (baseName.endsWith('.d.cts'))
|
|
148
|
+
baseName = baseName.slice(0, -6);
|
|
149
|
+
else if (baseName.endsWith('.js'))
|
|
150
|
+
baseName = baseName.slice(0, -3);
|
|
151
|
+
else if (baseName.endsWith('.mjs'))
|
|
152
|
+
baseName = baseName.slice(0, -4);
|
|
153
|
+
else if (baseName.endsWith('.cjs'))
|
|
154
|
+
baseName = baseName.slice(0, -4);
|
|
155
|
+
else if (baseName.endsWith('.jsx'))
|
|
156
|
+
baseName = baseName.slice(0, -4);
|
|
157
|
+
const extensions = ['.ts', '.tsx', '.mts', '.cts'];
|
|
158
|
+
for (const ext of extensions) {
|
|
159
|
+
const targetSrcFile = path.join(srcDirPath, baseName + ext);
|
|
160
|
+
if (fs.existsSync(targetSrcFile)) {
|
|
161
|
+
return targetSrcFile;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (result.resolvedModule.isExternalLibraryImport) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
return resolvedFileName;
|
|
170
|
+
}
|
|
171
|
+
function getCanonicalCycleKey(cycle) {
|
|
172
|
+
const nodes = cycle.slice(0, -1);
|
|
173
|
+
if (nodes.length === 0)
|
|
174
|
+
return '';
|
|
175
|
+
let minIdx = 0;
|
|
176
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
177
|
+
if (nodes[i] < nodes[minIdx]) {
|
|
178
|
+
minIdx = i;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const rotated = [...nodes.slice(minIdx), ...nodes.slice(0, minIdx)];
|
|
182
|
+
rotated.push(rotated[0]);
|
|
183
|
+
return rotated.join('|');
|
|
184
|
+
}
|
|
185
|
+
function parseFile(filePath) {
|
|
186
|
+
if (graph.has(filePath) || !fs.existsSync(filePath))
|
|
187
|
+
return;
|
|
188
|
+
graph.set(filePath, []);
|
|
189
|
+
const options = getCompilerOptionsForFile(filePath);
|
|
190
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
191
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
192
|
+
const imports = [];
|
|
193
|
+
function walk(node) {
|
|
194
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
195
|
+
if (isRuntimeImport(node)) {
|
|
196
|
+
const specifier = node.moduleSpecifier;
|
|
197
|
+
if (specifier && ts.isStringLiteral(specifier)) {
|
|
198
|
+
const resolved = resolveModule(specifier.text, filePath, options);
|
|
199
|
+
if (resolved && !imports.includes(resolved))
|
|
200
|
+
imports.push(resolved);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
ts.forEachChild(node, walk);
|
|
205
|
+
}
|
|
206
|
+
walk(sourceFile);
|
|
207
|
+
graph.set(filePath, imports);
|
|
208
|
+
for (const dep of imports)
|
|
209
|
+
parseFile(dep);
|
|
61
210
|
}
|
|
62
211
|
/**
|
|
63
|
-
*
|
|
212
|
+
* Checks if a specific file import creates an immediate execution (top-level) risk.
|
|
64
213
|
*/
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
214
|
+
function hasTopLevelUsage(fromFile, toFile) {
|
|
215
|
+
if (!fs.existsSync(fromFile))
|
|
216
|
+
return false;
|
|
217
|
+
const options = getCompilerOptionsForFile(fromFile);
|
|
218
|
+
const content = fs.readFileSync(fromFile, 'utf8');
|
|
219
|
+
const sourceFile = ts.createSourceFile(fromFile, content, ts.ScriptTarget.Latest, true);
|
|
220
|
+
const importedSymbols = new Set();
|
|
221
|
+
let hasSideEffectOrReExport = false;
|
|
222
|
+
// 1. Collect all names imported from 'toFile'
|
|
223
|
+
function findImports(node) {
|
|
224
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
225
|
+
if (isRuntimeImport(node)) {
|
|
226
|
+
const specifier = node.moduleSpecifier;
|
|
227
|
+
if (specifier && ts.isStringLiteral(specifier)) {
|
|
228
|
+
const resolved = resolveModule(specifier.text, fromFile, options);
|
|
229
|
+
if (resolved === toFile) {
|
|
230
|
+
if (ts.isImportDeclaration(node) && node.importClause) {
|
|
231
|
+
const clause = node.importClause;
|
|
232
|
+
if (clause.name)
|
|
233
|
+
importedSymbols.add(clause.name.text); // default import
|
|
234
|
+
if (clause.namedBindings) {
|
|
235
|
+
if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
236
|
+
importedSymbols.add(clause.namedBindings.name.text); // import * as namespace
|
|
237
|
+
}
|
|
238
|
+
else if (ts.isNamedImports(clause.namedBindings)) {
|
|
239
|
+
for (const el of clause.namedBindings.elements) {
|
|
240
|
+
importedSymbols.add(el.name.text); // named imports
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Re-exports (export * from...) or side-effect imports (import './file')
|
|
247
|
+
// trigger top-level evaluation instantly.
|
|
248
|
+
hasSideEffectOrReExport = true;
|
|
249
|
+
}
|
|
96
250
|
}
|
|
97
251
|
}
|
|
98
252
|
}
|
|
99
|
-
ts.forEachChild(node, walk);
|
|
100
253
|
}
|
|
101
|
-
|
|
102
|
-
graph.set(filePath, imports);
|
|
103
|
-
for (const dep of imports)
|
|
104
|
-
parseFile(dep);
|
|
254
|
+
ts.forEachChild(node, findImports);
|
|
105
255
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
256
|
+
findImports(sourceFile);
|
|
257
|
+
if (hasSideEffectOrReExport)
|
|
258
|
+
return true;
|
|
259
|
+
if (importedSymbols.size === 0)
|
|
260
|
+
return false;
|
|
261
|
+
let dangerousTopLevelUsage = false;
|
|
262
|
+
// 2. Check if collected symbols are used outside of lazy blocks (functions/methods)
|
|
263
|
+
function checkNodeUsage(node, isInsideLazyScope) {
|
|
264
|
+
if (dangerousTopLevelUsage)
|
|
265
|
+
return;
|
|
266
|
+
let currentScopeLazy = isInsideLazyScope;
|
|
267
|
+
if (ts.isFunctionDeclaration(node) ||
|
|
268
|
+
ts.isFunctionExpression(node) ||
|
|
269
|
+
ts.isArrowFunction(node) ||
|
|
270
|
+
ts.isMethodDeclaration(node) ||
|
|
271
|
+
ts.isConstructorDeclaration(node) ||
|
|
272
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
273
|
+
ts.isSetAccessorDeclaration(node)) {
|
|
274
|
+
currentScopeLazy = true;
|
|
275
|
+
}
|
|
276
|
+
if (ts.isPropertyDeclaration(node)) {
|
|
277
|
+
const isStatic = node.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
278
|
+
if (!isStatic) {
|
|
279
|
+
currentScopeLazy = true;
|
|
117
280
|
}
|
|
118
|
-
|
|
119
|
-
|
|
281
|
+
}
|
|
282
|
+
if (!currentScopeLazy && ts.isIdentifier(node)) {
|
|
283
|
+
if (importedSymbols.has(node.text)) {
|
|
284
|
+
const parent = node.parent;
|
|
285
|
+
// Verify it's an actual usage reference, not the import clause definition itself
|
|
286
|
+
const isImportDeclarationRef = ts.isImportSpecifier(parent) ||
|
|
287
|
+
ts.isImportClause(parent) ||
|
|
288
|
+
ts.isNamespaceImport(parent);
|
|
289
|
+
if (!isImportDeclarationRef) {
|
|
290
|
+
// Verify it's not just a TypeScript Type usage context (which is safe in runtime)
|
|
291
|
+
let isInTypeContext = false;
|
|
292
|
+
let checkParent = parent;
|
|
293
|
+
while (checkParent && checkParent !== sourceFile) {
|
|
294
|
+
if (ts.isTypeNode(checkParent) ||
|
|
295
|
+
ts.isTypeReferenceNode(checkParent) ||
|
|
296
|
+
ts.isTypeAliasDeclaration(checkParent) ||
|
|
297
|
+
ts.isInterfaceDeclaration(checkParent)) {
|
|
298
|
+
isInTypeContext = true;
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
checkParent = checkParent.parent;
|
|
302
|
+
}
|
|
303
|
+
if (!isInTypeContext) {
|
|
304
|
+
dangerousTopLevelUsage = true;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
120
308
|
}
|
|
121
309
|
}
|
|
122
|
-
|
|
123
|
-
|
|
310
|
+
ts.forEachChild(node, (n) => checkNodeUsage(n, currentScopeLazy));
|
|
311
|
+
}
|
|
312
|
+
checkNodeUsage(sourceFile, false);
|
|
313
|
+
return dangerousTopLevelUsage;
|
|
314
|
+
}
|
|
315
|
+
function canReach(start, target) {
|
|
316
|
+
const seen = new Set();
|
|
317
|
+
const stack = [start];
|
|
318
|
+
while (stack.length > 0) {
|
|
319
|
+
const current = stack.pop();
|
|
320
|
+
if (current === target)
|
|
321
|
+
return true;
|
|
322
|
+
if (seen.has(current))
|
|
323
|
+
continue;
|
|
324
|
+
seen.add(current);
|
|
325
|
+
const deps = graph.get(current) || [];
|
|
326
|
+
for (const dep of deps) {
|
|
327
|
+
stack.push(dep);
|
|
328
|
+
}
|
|
124
329
|
}
|
|
125
|
-
|
|
126
|
-
findCycles(entryPoint);
|
|
127
|
-
return detectedCycles;
|
|
330
|
+
return false;
|
|
128
331
|
}
|
|
129
332
|
function main() {
|
|
130
333
|
const { entryPatterns, projectPath } = parseArgs();
|
|
131
|
-
|
|
132
|
-
|
|
334
|
+
globalProjectPath = projectPath;
|
|
335
|
+
if (entryPatterns.length === 0) {
|
|
336
|
+
console.error('❌ Error: Please specify at least one entry point or glob pattern.');
|
|
133
337
|
process.exit(1);
|
|
134
338
|
}
|
|
135
339
|
const entryPoints = [];
|
|
136
|
-
// Expand glob patterns or fall back to direct paths
|
|
137
340
|
for (const pattern of entryPatterns) {
|
|
138
341
|
const matches = fs.globSync ? fs.globSync(pattern) : [pattern];
|
|
139
342
|
for (const match of matches) {
|
|
@@ -152,30 +355,104 @@ function main() {
|
|
|
152
355
|
console.error('❌ Error: No entry files found matching the provided paths or patterns.');
|
|
153
356
|
process.exit(1);
|
|
154
357
|
}
|
|
155
|
-
console.log(`🔍 Found ${entryPoints.length} entry point(s) for analysis...\n`);
|
|
358
|
+
console.log(`🔍 Found ${entryPoints.length} entry point(s) for analysis. Building graph...\n`);
|
|
359
|
+
// Phase 1: Deep parse all files globally across all entry points
|
|
360
|
+
for (const entryPoint of entryPoints) {
|
|
361
|
+
parseFile(entryPoint);
|
|
362
|
+
}
|
|
363
|
+
// Phase 2: Traverse the global graph to discover all unique cycles
|
|
364
|
+
const visited = new Map();
|
|
365
|
+
const currentStack = [];
|
|
366
|
+
function findCycles(node) {
|
|
367
|
+
visited.set(node, 'VISITING');
|
|
368
|
+
currentStack.push(node);
|
|
369
|
+
for (const neighbor of graph.get(node) || []) {
|
|
370
|
+
const state = visited.get(neighbor);
|
|
371
|
+
if (state === 'VISITING') {
|
|
372
|
+
const startIdx = currentStack.indexOf(neighbor);
|
|
373
|
+
const cycle = currentStack.slice(startIdx);
|
|
374
|
+
cycle.push(neighbor);
|
|
375
|
+
const key = getCanonicalCycleKey(cycle);
|
|
376
|
+
if (!globalDetectedCycles.has(key)) {
|
|
377
|
+
globalDetectedCycles.add(key);
|
|
378
|
+
allUniqueCycles.push(cycle);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else if (!state) {
|
|
382
|
+
findCycles(neighbor);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
currentStack.pop();
|
|
386
|
+
visited.set(node, 'VISITED');
|
|
387
|
+
}
|
|
388
|
+
for (const entryPoint of entryPoints) {
|
|
389
|
+
if (!visited.has(entryPoint)) {
|
|
390
|
+
findCycles(entryPoint);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// Phase 2.5: Smart AST Filtering (Filter out runtime-safe / lazy cycles)
|
|
394
|
+
const criticalCycles = [];
|
|
395
|
+
for (const cycle of allUniqueCycles) {
|
|
396
|
+
let isHarmfulCycle = false;
|
|
397
|
+
// A cycle is harmful if AT LEAST ONE link in it uses imports on top-level
|
|
398
|
+
for (let i = 0; i < cycle.length - 1; i++) {
|
|
399
|
+
if (hasTopLevelUsage(cycle[i], cycle[i + 1])) {
|
|
400
|
+
isHarmfulCycle = true;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (isHarmfulCycle) {
|
|
405
|
+
criticalCycles.push(cycle);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Phase 3: Intelligently distribute critical cycles to their native entry points
|
|
409
|
+
const entryPointCycles = new Map();
|
|
410
|
+
for (const ep of entryPoints) {
|
|
411
|
+
entryPointCycles.set(ep, []);
|
|
412
|
+
}
|
|
413
|
+
for (const cycle of criticalCycles) {
|
|
414
|
+
const firstFile = cycle[0];
|
|
415
|
+
const matchedEp = entryPoints.find((ep) => {
|
|
416
|
+
const epDir = path.dirname(ep);
|
|
417
|
+
return firstFile.startsWith(epDir + path.sep) || firstFile === ep;
|
|
418
|
+
});
|
|
419
|
+
if (matchedEp) {
|
|
420
|
+
entryPointCycles.get(matchedEp).push(cycle);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
const reachingEp = entryPoints.find((ep) => canReach(ep, firstFile));
|
|
424
|
+
if (reachingEp) {
|
|
425
|
+
entryPointCycles.get(reachingEp).push(cycle);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
entryPointCycles.get(entryPoints[0]).push(cycle);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Phase 4: Output the clean, perfectly targeted report
|
|
156
433
|
let globalHasCycles = false;
|
|
157
434
|
for (const entryPoint of entryPoints) {
|
|
158
|
-
const
|
|
159
|
-
const cycles =
|
|
435
|
+
const absoluteEntry = path.resolve(entryPoint);
|
|
436
|
+
const cycles = entryPointCycles.get(entryPoint) || [];
|
|
160
437
|
if (cycles.length > 0) {
|
|
161
438
|
globalHasCycles = true;
|
|
162
|
-
console.error(`❌ [${
|
|
439
|
+
console.error(`❌ [${absoluteEntry}] — Found ${cycles.length} critical circular dependencies:`);
|
|
163
440
|
cycles.forEach((cycle, index) => {
|
|
164
|
-
const readableCycle = cycle.map((p) => path.
|
|
441
|
+
const readableCycle = cycle.map((p) => path.resolve(p)).join('\n -> ');
|
|
165
442
|
console.error(` ${index + 1}) ${readableCycle}`);
|
|
166
443
|
});
|
|
167
444
|
console.error('');
|
|
168
445
|
}
|
|
169
446
|
else {
|
|
170
|
-
console.log(`✅ [${
|
|
447
|
+
console.log(`✅ [${absoluteEntry}] — Clean!`);
|
|
171
448
|
}
|
|
172
449
|
}
|
|
173
|
-
if (globalHasCycles) {
|
|
174
|
-
console.error('💥 Validation failed.
|
|
450
|
+
if (globalHasCycles || criticalCycles.length > 0) {
|
|
451
|
+
console.error('💥 Validation failed. Critical circular dependencies detected.');
|
|
175
452
|
process.exit(1);
|
|
176
453
|
}
|
|
177
454
|
else {
|
|
178
|
-
console.log('🎉 All packages checked. No circular dependencies found!');
|
|
455
|
+
console.log('🎉 All packages checked. No critical circular dependencies found!');
|
|
179
456
|
process.exit(0);
|
|
180
457
|
}
|
|
181
458
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ts-stack/cycle-detector",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.4",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cycle-detector": "./dist/index.js"
|
|
7
7
|
},
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"clean": "rimraf dist*"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
|
-
"
|
|
31
|
+
"ts-stack",
|
|
32
|
+
"dependencies",
|
|
33
|
+
"circular-dependencies",
|
|
32
34
|
"circular dependencies"
|
|
33
35
|
],
|
|
34
36
|
"author": "Kostia Tretiak",
|
|
@@ -45,6 +47,7 @@
|
|
|
45
47
|
"nodemon": "^3.1.14",
|
|
46
48
|
"prettier": "^3.8.4",
|
|
47
49
|
"rimraf": "^5.0.10",
|
|
50
|
+
"ts-node": "^10.9.2",
|
|
48
51
|
"typescript": "^5.9.3",
|
|
49
52
|
"typescript-eslint": "^8.62.0"
|
|
50
53
|
},
|
package/src/index.ts
CHANGED
|
@@ -6,14 +6,16 @@ import ts from 'typescript';
|
|
|
6
6
|
|
|
7
7
|
type NodeState = 'VISITING' | 'VISITED';
|
|
8
8
|
|
|
9
|
+
// Global shared structures for maximum performance
|
|
9
10
|
const graph = new Map<string, string[]>();
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
const allUniqueCycles: string[][] = [];
|
|
12
|
+
const globalDetectedCycles = new Set<string>();
|
|
13
|
+
|
|
14
|
+
const packageMetaCache = new Map<string, { pkgDir: string; srcDirName: string; outDirName: string; } | null>();
|
|
15
|
+
const compilerOptionsCache = new Map<string, ts.CompilerOptions>();
|
|
16
|
+
|
|
17
|
+
let globalProjectPath: string | undefined;
|
|
13
18
|
|
|
14
|
-
/**
|
|
15
|
-
* Parses command line arguments to separate options from entry point patterns.
|
|
16
|
-
*/
|
|
17
19
|
function parseArgs() {
|
|
18
20
|
const args = [...process.argv.slice(2)];
|
|
19
21
|
let projectPath: string | undefined;
|
|
@@ -31,10 +33,6 @@ function parseArgs() {
|
|
|
31
33
|
return { entryPatterns, projectPath };
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
/**
|
|
35
|
-
* Checks if an import/export declaration is type-only.
|
|
36
|
-
* Type-only imports are stripped during compilation and don't cause runtime cycles.
|
|
37
|
-
*/
|
|
38
36
|
function isRuntimeImport(node: ts.ImportDeclaration | ts.ExportDeclaration): boolean {
|
|
39
37
|
if (ts.isExportDeclaration(node)) {
|
|
40
38
|
if (!node.moduleSpecifier) return false;
|
|
@@ -42,10 +40,9 @@ function isRuntimeImport(node: ts.ImportDeclaration | ts.ExportDeclaration): boo
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
if (ts.isImportDeclaration(node)) {
|
|
45
|
-
if (!node.importClause) return true;
|
|
46
|
-
if (node.importClause.
|
|
43
|
+
if (!node.importClause) return true;
|
|
44
|
+
if (node.importClause.phaseModifier) return false;
|
|
47
45
|
|
|
48
|
-
// Check individual specifiers: import { type A, B } from './foo'
|
|
49
46
|
const namedBindings = node.importClause.namedBindings;
|
|
50
47
|
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
|
51
48
|
return !namedBindings.elements.every((el) => el.isTypeOnly);
|
|
@@ -56,106 +53,333 @@ function isRuntimeImport(node: ts.ImportDeclaration | ts.ExportDeclaration): boo
|
|
|
56
53
|
return false;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
function getCompilerOptionsForFile(filePath: string): ts.CompilerOptions {
|
|
57
|
+
const currentDir = path.dirname(filePath);
|
|
58
|
+
|
|
59
|
+
const cachedOptions = compilerOptionsCache.get(currentDir);
|
|
60
|
+
if (cachedOptions !== undefined) {
|
|
61
|
+
return cachedOptions;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const configPath = ts.findConfigFile(currentDir, ts.sys.fileExists, 'tsconfig.json') || globalProjectPath;
|
|
65
|
+
|
|
66
|
+
if (configPath) {
|
|
67
|
+
const resolvedConfigPath = path.resolve(configPath);
|
|
68
|
+
const configDir = path.dirname(resolvedConfigPath);
|
|
69
|
+
|
|
70
|
+
const cachedConfig = compilerOptionsCache.get(resolvedConfigPath);
|
|
71
|
+
if (cachedConfig !== undefined) {
|
|
72
|
+
compilerOptionsCache.set(currentDir, cachedConfig);
|
|
73
|
+
return cachedConfig;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const configFile = ts.readConfigFile(resolvedConfigPath, ts.sys.readFile);
|
|
78
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, configDir);
|
|
79
|
+
const options = parsedConfig.options || {};
|
|
80
|
+
|
|
81
|
+
compilerOptionsCache.set(resolvedConfigPath, options);
|
|
82
|
+
compilerOptionsCache.set(currentDir, options);
|
|
83
|
+
return options;
|
|
84
|
+
} catch {
|
|
85
|
+
// Fallback
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getPackageMeta(filePath: string) {
|
|
93
|
+
let currentDir = path.dirname(filePath);
|
|
94
|
+
const visitedDirs: string[] = [];
|
|
95
|
+
|
|
96
|
+
while (currentDir && currentDir !== path.parse(currentDir).root) {
|
|
97
|
+
const cached = packageMetaCache.get(currentDir);
|
|
98
|
+
if (cached !== undefined) {
|
|
99
|
+
for (const d of visitedDirs) packageMetaCache.set(d, cached);
|
|
100
|
+
return cached;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const pkgJsonPath = path.join(currentDir, 'package.json');
|
|
104
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
105
|
+
try {
|
|
106
|
+
const content = fs.readFileSync(pkgJsonPath, 'utf8');
|
|
107
|
+
const pkg = JSON.parse(content);
|
|
108
|
+
|
|
109
|
+
let outDirName = 'dist';
|
|
110
|
+
const mainField = pkg.main || pkg.types || pkg.typings || '';
|
|
111
|
+
if (mainField) {
|
|
112
|
+
const parts = path.normalize(mainField).split(path.sep);
|
|
113
|
+
if (parts.length > 1 && parts[0] !== '.' && parts[0] !== '..') {
|
|
114
|
+
outDirName = parts[0];
|
|
115
|
+
} else if (parts.length > 2 && (parts[0] === '.' || parts[0] === '..')) {
|
|
116
|
+
outDirName = parts[1];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let srcDirName = 'src';
|
|
121
|
+
if (fs.existsSync(path.join(currentDir, 'source'))) srcDirName = 'source';
|
|
122
|
+
else if (fs.existsSync(path.join(currentDir, 'lib'))) srcDirName = 'lib';
|
|
123
|
+
|
|
124
|
+
const meta = { pkgDir: currentDir, srcDirName, outDirName };
|
|
125
|
+
packageMetaCache.set(currentDir, meta);
|
|
126
|
+
for (const d of visitedDirs) packageMetaCache.set(d, meta);
|
|
127
|
+
return meta;
|
|
128
|
+
} catch {
|
|
129
|
+
packageMetaCache.set(currentDir, null);
|
|
130
|
+
for (const d of visitedDirs) packageMetaCache.set(d, null);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
visitedDirs.push(currentDir);
|
|
136
|
+
currentDir = path.dirname(currentDir);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
63
142
|
function resolveModule(moduleName: string, containingFile: string, options: ts.CompilerOptions): string | null {
|
|
64
143
|
const result = ts.resolveModuleName(moduleName, containingFile, options, ts.sys);
|
|
65
|
-
if (
|
|
66
|
-
|
|
144
|
+
if (!result.resolvedModule) return null;
|
|
145
|
+
|
|
146
|
+
const resolvedFileName = path.resolve(result.resolvedModule.resolvedFileName);
|
|
147
|
+
|
|
148
|
+
if (resolvedFileName.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
149
|
+
return null;
|
|
67
150
|
}
|
|
68
|
-
|
|
151
|
+
|
|
152
|
+
const meta = getPackageMeta(resolvedFileName);
|
|
153
|
+
if (meta) {
|
|
154
|
+
const { pkgDir, srcDirName, outDirName } = meta;
|
|
155
|
+
const srcDirPath = path.join(pkgDir, srcDirName);
|
|
156
|
+
|
|
157
|
+
if (resolvedFileName.startsWith(srcDirPath + path.sep)) {
|
|
158
|
+
return resolvedFileName;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const outDirPath = path.join(pkgDir, outDirName);
|
|
162
|
+
if (resolvedFileName.startsWith(outDirPath + path.sep) || resolvedFileName === outDirPath) {
|
|
163
|
+
const relativeToOut = path.relative(outDirPath, resolvedFileName);
|
|
164
|
+
let baseName = relativeToOut;
|
|
165
|
+
|
|
166
|
+
if (baseName.endsWith('.d.ts')) baseName = baseName.slice(0, -5);
|
|
167
|
+
else if (baseName.endsWith('.d.mts')) baseName = baseName.slice(0, -6);
|
|
168
|
+
else if (baseName.endsWith('.d.cts')) baseName = baseName.slice(0, -6);
|
|
169
|
+
else if (baseName.endsWith('.js')) baseName = baseName.slice(0, -3);
|
|
170
|
+
else if (baseName.endsWith('.mjs')) baseName = baseName.slice(0, -4);
|
|
171
|
+
else if (baseName.endsWith('.cjs')) baseName = baseName.slice(0, -4);
|
|
172
|
+
else if (baseName.endsWith('.jsx')) baseName = baseName.slice(0, -4);
|
|
173
|
+
|
|
174
|
+
const extensions = ['.ts', '.tsx', '.mts', '.cts'];
|
|
175
|
+
for (const ext of extensions) {
|
|
176
|
+
const targetSrcFile = path.join(srcDirPath, baseName + ext);
|
|
177
|
+
if (fs.existsSync(targetSrcFile)) {
|
|
178
|
+
return targetSrcFile;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (result.resolvedModule.isExternalLibraryImport) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return resolvedFileName;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function getCanonicalCycleKey(cycle: string[]): string {
|
|
192
|
+
const nodes = cycle.slice(0, -1);
|
|
193
|
+
if (nodes.length === 0) return '';
|
|
194
|
+
|
|
195
|
+
let minIdx = 0;
|
|
196
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
197
|
+
if (nodes[i] < nodes[minIdx]) {
|
|
198
|
+
minIdx = i;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const rotated = [...nodes.slice(minIdx), ...nodes.slice(0, minIdx)];
|
|
203
|
+
rotated.push(rotated[0]);
|
|
204
|
+
return rotated.join('|');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function parseFile(filePath: string) {
|
|
208
|
+
if (graph.has(filePath) || !fs.existsSync(filePath)) return;
|
|
209
|
+
graph.set(filePath, []);
|
|
210
|
+
|
|
211
|
+
const options = getCompilerOptionsForFile(filePath);
|
|
212
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
213
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true);
|
|
214
|
+
const imports: string[] = [];
|
|
215
|
+
|
|
216
|
+
function walk(node: ts.Node) {
|
|
217
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
218
|
+
if (isRuntimeImport(node)) {
|
|
219
|
+
const specifier = node.moduleSpecifier;
|
|
220
|
+
if (specifier && ts.isStringLiteral(specifier)) {
|
|
221
|
+
const resolved = resolveModule(specifier.text, filePath, options);
|
|
222
|
+
if (resolved && !imports.includes(resolved)) imports.push(resolved);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
ts.forEachChild(node, walk);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
walk(sourceFile);
|
|
230
|
+
graph.set(filePath, imports);
|
|
231
|
+
|
|
232
|
+
for (const dep of imports) parseFile(dep);
|
|
69
233
|
}
|
|
70
234
|
|
|
71
235
|
/**
|
|
72
|
-
*
|
|
236
|
+
* Checks if a specific file import creates an immediate execution (top-level) risk.
|
|
73
237
|
*/
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
238
|
+
function hasTopLevelUsage(fromFile: string, toFile: string): boolean {
|
|
239
|
+
if (!fs.existsSync(fromFile)) return false;
|
|
240
|
+
|
|
241
|
+
const options = getCompilerOptionsForFile(fromFile);
|
|
242
|
+
const content = fs.readFileSync(fromFile, 'utf8');
|
|
243
|
+
const sourceFile = ts.createSourceFile(fromFile, content, ts.ScriptTarget.Latest, true);
|
|
244
|
+
|
|
245
|
+
const importedSymbols = new Set<string>();
|
|
246
|
+
let hasSideEffectOrReExport = false;
|
|
247
|
+
|
|
248
|
+
// 1. Collect all names imported from 'toFile'
|
|
249
|
+
function findImports(node: ts.Node) {
|
|
250
|
+
if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) {
|
|
251
|
+
if (isRuntimeImport(node)) {
|
|
252
|
+
const specifier = node.moduleSpecifier;
|
|
253
|
+
if (specifier && ts.isStringLiteral(specifier)) {
|
|
254
|
+
const resolved = resolveModule(specifier.text, fromFile, options);
|
|
255
|
+
if (resolved === toFile) {
|
|
256
|
+
if (ts.isImportDeclaration(node) && node.importClause) {
|
|
257
|
+
const clause = node.importClause;
|
|
258
|
+
if (clause.name) importedSymbols.add(clause.name.text); // default import
|
|
259
|
+
if (clause.namedBindings) {
|
|
260
|
+
if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
261
|
+
importedSymbols.add(clause.namedBindings.name.text); // import * as namespace
|
|
262
|
+
} else if (ts.isNamedImports(clause.namedBindings)) {
|
|
263
|
+
for (const el of clause.namedBindings.elements) {
|
|
264
|
+
importedSymbols.add(el.name.text); // named imports
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
// Re-exports (export * from...) or side-effect imports (import './file')
|
|
270
|
+
// trigger top-level evaluation instantly.
|
|
271
|
+
hasSideEffectOrReExport = true;
|
|
272
|
+
}
|
|
109
273
|
}
|
|
110
274
|
}
|
|
111
275
|
}
|
|
112
|
-
ts.forEachChild(node, walk);
|
|
113
276
|
}
|
|
277
|
+
ts.forEachChild(node, findImports);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
findImports(sourceFile);
|
|
281
|
+
|
|
282
|
+
if (hasSideEffectOrReExport) return true;
|
|
283
|
+
if (importedSymbols.size === 0) return false;
|
|
284
|
+
|
|
285
|
+
let dangerousTopLevelUsage = false;
|
|
114
286
|
|
|
115
|
-
|
|
116
|
-
|
|
287
|
+
// 2. Check if collected symbols are used outside of lazy blocks (functions/methods)
|
|
288
|
+
function checkNodeUsage(node: ts.Node, isInsideLazyScope: boolean) {
|
|
289
|
+
if (dangerousTopLevelUsage) return;
|
|
117
290
|
|
|
118
|
-
|
|
291
|
+
let currentScopeLazy = isInsideLazyScope;
|
|
292
|
+
|
|
293
|
+
if (
|
|
294
|
+
ts.isFunctionDeclaration(node) ||
|
|
295
|
+
ts.isFunctionExpression(node) ||
|
|
296
|
+
ts.isArrowFunction(node) ||
|
|
297
|
+
ts.isMethodDeclaration(node) ||
|
|
298
|
+
ts.isConstructorDeclaration(node) ||
|
|
299
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
300
|
+
ts.isSetAccessorDeclaration(node)
|
|
301
|
+
) {
|
|
302
|
+
currentScopeLazy = true;
|
|
119
303
|
}
|
|
120
304
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
305
|
+
if (ts.isPropertyDeclaration(node)) {
|
|
306
|
+
const isStatic = node.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword);
|
|
307
|
+
if (!isStatic) {
|
|
308
|
+
currentScopeLazy = true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
125
311
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
312
|
+
if (!currentScopeLazy && ts.isIdentifier(node)) {
|
|
313
|
+
if (importedSymbols.has(node.text)) {
|
|
314
|
+
const parent = node.parent;
|
|
315
|
+
|
|
316
|
+
// Verify it's an actual usage reference, not the import clause definition itself
|
|
317
|
+
const isImportDeclarationRef =
|
|
318
|
+
ts.isImportSpecifier(parent) ||
|
|
319
|
+
ts.isImportClause(parent) ||
|
|
320
|
+
ts.isNamespaceImport(parent);
|
|
321
|
+
|
|
322
|
+
if (!isImportDeclarationRef) {
|
|
323
|
+
// Verify it's not just a TypeScript Type usage context (which is safe in runtime)
|
|
324
|
+
let isInTypeContext = false;
|
|
325
|
+
let checkParent: ts.Node | undefined = parent;
|
|
326
|
+
while (checkParent && checkParent !== sourceFile) {
|
|
327
|
+
if (
|
|
328
|
+
ts.isTypeNode(checkParent) ||
|
|
329
|
+
ts.isTypeReferenceNode(checkParent) ||
|
|
330
|
+
ts.isTypeAliasDeclaration(checkParent) ||
|
|
331
|
+
ts.isInterfaceDeclaration(checkParent)
|
|
332
|
+
) {
|
|
333
|
+
isInTypeContext = true;
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
checkParent = checkParent.parent;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (!isInTypeContext) {
|
|
340
|
+
dangerousTopLevelUsage = true;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
135
343
|
}
|
|
136
344
|
}
|
|
137
|
-
|
|
138
|
-
currentStack.pop();
|
|
139
|
-
visited.set(node, 'VISITED');
|
|
140
345
|
}
|
|
141
346
|
|
|
142
|
-
|
|
143
|
-
|
|
347
|
+
ts.forEachChild(node, (n) => checkNodeUsage(n, currentScopeLazy));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
checkNodeUsage(sourceFile, false);
|
|
351
|
+
return dangerousTopLevelUsage;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function canReach(start: string, target: string): boolean {
|
|
355
|
+
const seen = new Set<string>();
|
|
356
|
+
const stack = [start];
|
|
144
357
|
|
|
145
|
-
|
|
358
|
+
while (stack.length > 0) {
|
|
359
|
+
const current = stack.pop()!;
|
|
360
|
+
if (current === target) return true;
|
|
361
|
+
if (seen.has(current)) continue;
|
|
362
|
+
seen.add(current);
|
|
363
|
+
|
|
364
|
+
const deps = graph.get(current) || [];
|
|
365
|
+
for (const dep of deps) {
|
|
366
|
+
stack.push(dep);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
146
370
|
}
|
|
147
371
|
|
|
148
372
|
function main() {
|
|
149
373
|
const { entryPatterns, projectPath } = parseArgs();
|
|
374
|
+
globalProjectPath = projectPath;
|
|
150
375
|
|
|
151
|
-
if (entryPatterns.length
|
|
152
|
-
console.error('❌ Error: Please specify at least one entry point or glob pattern
|
|
376
|
+
if (entryPatterns.length === 0) {
|
|
377
|
+
console.error('❌ Error: Please specify at least one entry point or glob pattern.');
|
|
153
378
|
process.exit(1);
|
|
154
379
|
}
|
|
155
380
|
|
|
156
381
|
const entryPoints: string[] = [];
|
|
157
382
|
|
|
158
|
-
// Expand glob patterns or fall back to direct paths
|
|
159
383
|
for (const pattern of entryPatterns) {
|
|
160
384
|
const matches = fs.globSync ? fs.globSync(pattern) : [pattern];
|
|
161
385
|
|
|
@@ -179,32 +403,118 @@ function main() {
|
|
|
179
403
|
process.exit(1);
|
|
180
404
|
}
|
|
181
405
|
|
|
182
|
-
console.log(`🔍 Found ${entryPoints.length} entry point(s) for analysis...\n`);
|
|
406
|
+
console.log(`🔍 Found ${entryPoints.length} entry point(s) for analysis. Building graph...\n`);
|
|
407
|
+
|
|
408
|
+
// Phase 1: Deep parse all files globally across all entry points
|
|
409
|
+
for (const entryPoint of entryPoints) {
|
|
410
|
+
parseFile(entryPoint);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Phase 2: Traverse the global graph to discover all unique cycles
|
|
414
|
+
const visited = new Map<string, NodeState>();
|
|
415
|
+
const currentStack: string[] = [];
|
|
416
|
+
|
|
417
|
+
function findCycles(node: string) {
|
|
418
|
+
visited.set(node, 'VISITING');
|
|
419
|
+
currentStack.push(node);
|
|
420
|
+
|
|
421
|
+
for (const neighbor of graph.get(node) || []) {
|
|
422
|
+
const state = visited.get(neighbor);
|
|
423
|
+
if (state === 'VISITING') {
|
|
424
|
+
const startIdx = currentStack.indexOf(neighbor);
|
|
425
|
+
const cycle = currentStack.slice(startIdx);
|
|
426
|
+
cycle.push(neighbor);
|
|
427
|
+
|
|
428
|
+
const key = getCanonicalCycleKey(cycle);
|
|
429
|
+
if (!globalDetectedCycles.has(key)) {
|
|
430
|
+
globalDetectedCycles.add(key);
|
|
431
|
+
allUniqueCycles.push(cycle);
|
|
432
|
+
}
|
|
433
|
+
} else if (!state) {
|
|
434
|
+
findCycles(neighbor);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
currentStack.pop();
|
|
439
|
+
visited.set(node, 'VISITED');
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
for (const entryPoint of entryPoints) {
|
|
443
|
+
if (!visited.has(entryPoint)) {
|
|
444
|
+
findCycles(entryPoint);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Phase 2.5: Smart AST Filtering (Filter out runtime-safe / lazy cycles)
|
|
449
|
+
const criticalCycles: string[][] = [];
|
|
450
|
+
|
|
451
|
+
for (const cycle of allUniqueCycles) {
|
|
452
|
+
let isHarmfulCycle = false;
|
|
453
|
+
|
|
454
|
+
// A cycle is harmful if AT LEAST ONE link in it uses imports on top-level
|
|
455
|
+
for (let i = 0; i < cycle.length - 1; i++) {
|
|
456
|
+
if (hasTopLevelUsage(cycle[i], cycle[i + 1])) {
|
|
457
|
+
isHarmfulCycle = true;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (isHarmfulCycle) {
|
|
463
|
+
criticalCycles.push(cycle);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Phase 3: Intelligently distribute critical cycles to their native entry points
|
|
468
|
+
const entryPointCycles = new Map<string, string[][]>();
|
|
469
|
+
for (const ep of entryPoints) {
|
|
470
|
+
entryPointCycles.set(ep, []);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
for (const cycle of criticalCycles) {
|
|
474
|
+
const firstFile = cycle[0];
|
|
475
|
+
|
|
476
|
+
const matchedEp = entryPoints.find((ep) => {
|
|
477
|
+
const epDir = path.dirname(ep);
|
|
478
|
+
return firstFile.startsWith(epDir + path.sep) || firstFile === ep;
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
if (matchedEp) {
|
|
482
|
+
entryPointCycles.get(matchedEp)!.push(cycle);
|
|
483
|
+
} else {
|
|
484
|
+
const reachingEp = entryPoints.find((ep) => canReach(ep, firstFile));
|
|
485
|
+
if (reachingEp) {
|
|
486
|
+
entryPointCycles.get(reachingEp)!.push(cycle);
|
|
487
|
+
} else {
|
|
488
|
+
entryPointCycles.get(entryPoints[0])!.push(cycle);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
183
492
|
|
|
493
|
+
// Phase 4: Output the clean, perfectly targeted report
|
|
184
494
|
let globalHasCycles = false;
|
|
185
495
|
|
|
186
496
|
for (const entryPoint of entryPoints) {
|
|
187
|
-
const
|
|
188
|
-
const cycles =
|
|
497
|
+
const absoluteEntry = path.resolve(entryPoint);
|
|
498
|
+
const cycles = entryPointCycles.get(entryPoint) || [];
|
|
189
499
|
|
|
190
500
|
if (cycles.length > 0) {
|
|
191
501
|
globalHasCycles = true;
|
|
192
|
-
console.error(`❌ [${
|
|
502
|
+
console.error(`❌ [${absoluteEntry}] — Found ${cycles.length} critical circular dependencies:`);
|
|
193
503
|
cycles.forEach((cycle, index) => {
|
|
194
|
-
const readableCycle = cycle.map((p) => path.
|
|
504
|
+
const readableCycle = cycle.map((p) => path.resolve(p)).join('\n -> ');
|
|
195
505
|
console.error(` ${index + 1}) ${readableCycle}`);
|
|
196
506
|
});
|
|
197
507
|
console.error('');
|
|
198
508
|
} else {
|
|
199
|
-
console.log(`✅ [${
|
|
509
|
+
console.log(`✅ [${absoluteEntry}] — Clean!`);
|
|
200
510
|
}
|
|
201
511
|
}
|
|
202
512
|
|
|
203
|
-
if (globalHasCycles) {
|
|
204
|
-
console.error('💥 Validation failed.
|
|
513
|
+
if (globalHasCycles || criticalCycles.length > 0) {
|
|
514
|
+
console.error('💥 Validation failed. Critical circular dependencies detected.');
|
|
205
515
|
process.exit(1);
|
|
206
516
|
} else {
|
|
207
|
-
console.log('🎉 All packages checked. No circular dependencies found!');
|
|
517
|
+
console.log('🎉 All packages checked. No critical circular dependencies found!');
|
|
208
518
|
process.exit(0);
|
|
209
519
|
}
|
|
210
520
|
}
|
package/eslint.config.mjs
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import eslint from '@eslint/js';
|
|
4
|
-
import { defineConfig } from 'eslint/config';
|
|
5
|
-
import tseslint from 'typescript-eslint';
|
|
6
|
-
|
|
7
|
-
export default defineConfig([
|
|
8
|
-
eslint.configs.recommended,
|
|
9
|
-
...tseslint.configs.recommended,
|
|
10
|
-
{
|
|
11
|
-
languageOptions: {
|
|
12
|
-
parserOptions: {
|
|
13
|
-
projectService: true,
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
rules: {
|
|
19
|
-
semi: ['error', 'always'],
|
|
20
|
-
quotes: ['error', 'single', { avoidEscape: true }],
|
|
21
|
-
'@typescript-eslint/no-floating-promises': 'error',
|
|
22
|
-
'@typescript-eslint/no-misused-promises': 1,
|
|
23
|
-
'@typescript-eslint/no-unused-expressions': 'warn',
|
|
24
|
-
'@typescript-eslint/consistent-type-imports': 1,
|
|
25
|
-
'@typescript-eslint/no-empty-object-type': 0,
|
|
26
|
-
'@typescript-eslint/no-non-null-assertion': 0,
|
|
27
|
-
'@typescript-eslint/no-empty-function': 0,
|
|
28
|
-
'@typescript-eslint/no-empty-interface': 0,
|
|
29
|
-
'@typescript-eslint/explicit-function-return-type': 0,
|
|
30
|
-
'@typescript-eslint/explicit-module-boundary-types': 0,
|
|
31
|
-
'@typescript-eslint/no-explicit-any': 0,
|
|
32
|
-
'@typescript-eslint/no-inferrable-types': 0,
|
|
33
|
-
'@typescript-eslint/no-non-null-asserted-optional-chain': 0,
|
|
34
|
-
'@typescript-eslint/no-unused-vars': 0,
|
|
35
|
-
'@typescript-eslint/triple-slash-reference': 0,
|
|
36
|
-
'@typescript-eslint/no-unsafe-function-type': 0,
|
|
37
|
-
'no-unused-private-class-members': 'warn',
|
|
38
|
-
'no-useless-assignment': 'warn',
|
|
39
|
-
'no-restricted-imports': ['error', 'fs'],
|
|
40
|
-
'prefer-const': 'warn',
|
|
41
|
-
'no-async-promise-executor': 0,
|
|
42
|
-
'no-prototype-builtins': 0,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
ignores: [
|
|
47
|
-
'**/dist*',
|
|
48
|
-
'**/*.d.ts',
|
|
49
|
-
'website/*',
|
|
50
|
-
'node_modules/*',
|
|
51
|
-
'eslint.config.mjs',
|
|
52
|
-
'**/jest.config.ts',
|
|
53
|
-
'**/vitest.config.ts',
|
|
54
|
-
'**/jest.setup.js',
|
|
55
|
-
'**/jest.d.ts',
|
|
56
|
-
'**/jest.matchers.ts',
|
|
57
|
-
'packages/openapi/ui/*',
|
|
58
|
-
],
|
|
59
|
-
},
|
|
60
|
-
]);
|