pkg-scaffold 3.3.3 → 3.3.5
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/README.md +62 -22
- package/bin/cli.js +7 -7
- package/package.json +5 -4
- package/pnpm-workspace.yaml +2 -0
- package/src/EngineContext.js +47 -19
- package/src/ast/ASTAnalyzer.js +287 -132
- package/src/ast/BarrelParser.js +51 -19
- package/src/ast/DeadCodeDetector.js +73 -0
- package/src/ast/MagicDetector.js +111 -11
- package/src/ast/OxcAnalyzer.js +250 -79
- package/src/healing/GitSandbox.js +44 -122
- package/src/healing/SelfHealer.js +29 -130
- package/src/index.js +124 -108
- package/src/performance/WorkerTaskRunner.js +17 -5
- package/src/plugins/PluginRegistry.js +28 -1
- package/src/plugins/ecosystems/MorePlugins.js +184 -0
- package/src/plugins/ecosystems/PluginLoader.js +20 -0
- package/src/resolution/CircularDetector.js +3 -34
- package/src/resolution/ConfigLoader.js +34 -6
- package/src/resolution/DependencyProfiler.js +261 -9
- package/src/resolution/WorkSpaceGraph.js +148 -35
- package/src/performance/SecretDetector.js +0 -378
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -1,175 +1,203 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import fs from 'fs/promises';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Enterprise AST Syntax Walker & Feature Extractor
|
|
7
|
-
* Upgraded to use full TypeScript Compiler API (ts.createProgram + TypeChecker)
|
|
8
|
-
* for type-aware cross-file analysis.
|
|
9
|
-
*/
|
|
10
|
-
import { OxcAnalyzer } from './OxcAnalyzer.js';
|
|
11
2
|
|
|
12
3
|
export class ASTAnalyzer {
|
|
13
4
|
constructor(context) {
|
|
14
5
|
this.context = context;
|
|
15
|
-
this.
|
|
16
|
-
this.checker = null;
|
|
17
|
-
this.oxc = new OxcAnalyzer(context);
|
|
6
|
+
this.scopeStack = [];
|
|
18
7
|
}
|
|
19
8
|
|
|
20
9
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
10
|
+
* Returns the TypeScript ScriptKind for a given file path.
|
|
11
|
+
* Exposed as a public method so WorkerTaskRunner can call it directly.
|
|
23
12
|
*/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
13
|
+
getScriptKind(filePath) {
|
|
14
|
+
if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx')) return ts.ScriptKind.TSX;
|
|
15
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.mjs') || filePath.endsWith('.cjs')) return ts.ScriptKind.JS;
|
|
16
|
+
return ts.ScriptKind.TS;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
parseFile(filePath, content, fileNode) {
|
|
20
|
+
if (this.context.verbose) {
|
|
21
|
+
console.log(`[AST] Parsing: ${filePath}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const sourceFile = ts.createSourceFile(
|
|
25
|
+
filePath,
|
|
26
|
+
content,
|
|
27
|
+
ts.ScriptTarget.Latest,
|
|
28
|
+
true,
|
|
29
|
+
this.getScriptKind(filePath)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
this.currentScope = { symbols: new Map(), parent: null };
|
|
33
|
+
this.scopeStack.push(this.currentScope);
|
|
34
|
+
|
|
35
|
+
this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
|
|
36
|
+
this.walkAST(sourceFile, fileNode, sourceFile);
|
|
37
|
+
|
|
38
|
+
this.scopeStack.pop(); // Pop the global scope
|
|
39
|
+
this.currentScope = null;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
/**
|
|
39
|
-
*
|
|
43
|
+
* Alias for walkAST used by WorkerTaskRunner (legacy API compatibility).
|
|
40
44
|
*/
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
walkNode(node, sourceFile, fileNode) {
|
|
46
|
+
return this.walkAST(node, fileNode, sourceFile);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
pushScope() {
|
|
50
|
+
const newScope = { symbols: new Map(), parent: this.currentScope };
|
|
51
|
+
this.scopeStack.push(newScope);
|
|
52
|
+
this.currentScope = newScope;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
popScope() {
|
|
56
|
+
if (this.scopeStack.length > 1) {
|
|
57
|
+
this.scopeStack.pop();
|
|
58
|
+
this.currentScope = this.scopeStack[this.scopeStack.length - 1];
|
|
46
59
|
}
|
|
60
|
+
}
|
|
47
61
|
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
addDeclaredSymbol(name, node, sourceFile) {
|
|
63
|
+
if (this.currentScope) {
|
|
64
|
+
const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
65
|
+
this.currentScope.symbols.set(name, { node, line: loc.line + 1, column: loc.character + 1 });
|
|
50
66
|
}
|
|
67
|
+
}
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
resolveSymbol(name) {
|
|
70
|
+
for (let i = this.scopeStack.length - 1; i >= 0; i--) {
|
|
71
|
+
const scope = this.scopeStack[i];
|
|
72
|
+
if (scope.symbols.has(name)) {
|
|
73
|
+
return scope.symbols.get(name);
|
|
56
74
|
}
|
|
57
|
-
return false;
|
|
58
75
|
}
|
|
59
|
-
|
|
60
|
-
this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
|
|
61
|
-
this.walkNode(sourceFile, sourceFile, fileNode);
|
|
62
|
-
|
|
63
|
-
return true;
|
|
76
|
+
return null;
|
|
64
77
|
}
|
|
65
78
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const symbol = this.checker.getSymbolAtLocation(node);
|
|
72
|
-
if (symbol) {
|
|
73
|
-
const declarations = symbol.getDeclarations();
|
|
74
|
-
if (declarations && declarations.length > 0) {
|
|
75
|
-
const declFile = declarations[0].getSourceFile().fileName;
|
|
76
|
-
const symbolName = symbol.getName();
|
|
77
|
-
|
|
78
|
-
// Track sub-symbol usage (Property Access)
|
|
79
|
-
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
|
|
80
|
-
const parentType = this.checker.getTypeAtLocation(node.parent.expression);
|
|
81
|
-
const parentSymbol = parentType.getSymbol() || parentType.aliasSymbol;
|
|
82
|
-
if (parentSymbol) {
|
|
83
|
-
const parentDecl = parentSymbol.getDeclarations()?.[0];
|
|
84
|
-
if (parentDecl) {
|
|
85
|
-
const parentFile = parentDecl.getSourceFile().fileName;
|
|
86
|
-
fileNode.memberUsage = fileNode.memberUsage || new Set();
|
|
87
|
-
fileNode.memberUsage.add(`${parentFile}:${parentSymbol.getName()}.${symbolName}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (declFile !== sourceFile.fileName) {
|
|
93
|
-
fileNode.resolvedReferences = fileNode.resolvedReferences || new Set();
|
|
94
|
-
fileNode.resolvedReferences.add(`${declFile}:${symbolName}`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
79
|
+
walkAST(node, fileNode, sourceFile) {
|
|
80
|
+
// Handle scope entry for blocks, functions, classes, etc.
|
|
81
|
+
const isScopeNode = ts.isBlock(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isModuleDeclaration(node);
|
|
82
|
+
if (isScopeNode) {
|
|
83
|
+
this.pushScope();
|
|
98
84
|
}
|
|
99
85
|
|
|
100
86
|
switch (node.kind) {
|
|
101
|
-
case ts.SyntaxKind.ImportDeclaration:
|
|
102
|
-
this.handleImportDeclaration(node, fileNode);
|
|
87
|
+
case ts.SyntaxKind.ImportDeclaration:
|
|
88
|
+
this.handleImportDeclaration(node, fileNode, sourceFile);
|
|
103
89
|
break;
|
|
104
|
-
|
|
105
|
-
case ts.SyntaxKind.ExportDeclaration: {
|
|
90
|
+
case ts.SyntaxKind.ExportDeclaration:
|
|
106
91
|
this.handleExportDeclaration(node, fileNode, sourceFile);
|
|
107
92
|
break;
|
|
108
|
-
|
|
93
|
+
case ts.SyntaxKind.ExportAssignment:
|
|
94
|
+
fileNode.internalExports.set('default', { type: 'default', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
95
|
+
break;
|
|
96
|
+
case ts.SyntaxKind.VariableStatement:
|
|
97
|
+
this.handleVariableStatement(node, fileNode, sourceFile);
|
|
98
|
+
break;
|
|
109
99
|
case ts.SyntaxKind.FunctionDeclaration:
|
|
110
100
|
case ts.SyntaxKind.ClassDeclaration:
|
|
111
101
|
case ts.SyntaxKind.InterfaceDeclaration:
|
|
112
102
|
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
113
103
|
case ts.SyntaxKind.EnumDeclaration:
|
|
114
|
-
case ts.SyntaxKind.ModuleDeclaration:
|
|
104
|
+
case ts.SyntaxKind.ModuleDeclaration:
|
|
115
105
|
this.handleNamedDeclaration(node, fileNode, sourceFile);
|
|
116
106
|
break;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
this.handleVariableStatement(node, fileNode, sourceFile);
|
|
107
|
+
case ts.SyntaxKind.Identifier:
|
|
108
|
+
fileNode.instantiatedIdentifiers.add(node.text);
|
|
120
109
|
break;
|
|
121
|
-
|
|
122
|
-
case ts.SyntaxKind.
|
|
110
|
+
case ts.SyntaxKind.StringLiteral:
|
|
111
|
+
case ts.SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
112
|
+
fileNode.rawStringReferences.add(node.text);
|
|
113
|
+
break;
|
|
114
|
+
case ts.SyntaxKind.PropertyAccessExpression:
|
|
115
|
+
fileNode.propertyAccessChains.add(node.getText(sourceFile));
|
|
116
|
+
break;
|
|
117
|
+
case ts.SyntaxKind.CallExpression:
|
|
123
118
|
this.handleCallExpression(node, fileNode, sourceFile);
|
|
124
119
|
break;
|
|
125
|
-
|
|
120
|
+
case ts.SyntaxKind.JsxElement:
|
|
121
|
+
case ts.SyntaxKind.JsxSelfClosingElement:
|
|
122
|
+
this.handleJsxElement(node, fileNode, sourceFile);
|
|
123
|
+
break;
|
|
124
|
+
case ts.SyntaxKind.Decorator:
|
|
125
|
+
this.handleDecorator(node, fileNode, sourceFile);
|
|
126
|
+
break;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
ts.forEachChild(node, child => this.
|
|
129
|
+
ts.forEachChild(node, child => this.walkAST(child, fileNode, sourceFile));
|
|
130
|
+
|
|
131
|
+
if (isScopeNode) {
|
|
132
|
+
this.popScope();
|
|
133
|
+
}
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
handleImportDeclaration(node, fileNode) {
|
|
136
|
+
handleImportDeclaration(node, fileNode, sourceFile) {
|
|
132
137
|
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
133
138
|
const specifier = node.moduleSpecifier.text;
|
|
134
139
|
fileNode.explicitImports.add(specifier);
|
|
135
140
|
|
|
141
|
+
// Track external package usage for dependency analysis
|
|
142
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
143
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
144
|
+
}
|
|
145
|
+
|
|
136
146
|
if (node.importClause) {
|
|
147
|
+
if (node.importClause.name) {
|
|
148
|
+
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
149
|
+
}
|
|
137
150
|
if (node.importClause.namedBindings) {
|
|
138
|
-
if (ts.
|
|
151
|
+
if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
152
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
153
|
+
} else if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
139
154
|
node.importClause.namedBindings.elements.forEach(element => {
|
|
140
|
-
const importedName = element.name.text;
|
|
141
|
-
|
|
142
|
-
fileNode.importedSymbols.add(`${specifier}:${propertyName}`);
|
|
155
|
+
const importedName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
156
|
+
fileNode.importedSymbols.add(`${specifier}:${importedName}`);
|
|
143
157
|
});
|
|
144
|
-
} else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
145
|
-
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
146
158
|
}
|
|
147
159
|
}
|
|
148
|
-
if (node.importClause.name) {
|
|
149
|
-
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
150
|
-
}
|
|
151
160
|
}
|
|
152
161
|
}
|
|
153
162
|
}
|
|
154
163
|
|
|
155
164
|
handleExportDeclaration(node, fileNode, sourceFile) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
const specifier = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier) ? node.moduleSpecifier.text : null;
|
|
166
|
+
|
|
167
|
+
if (specifier) {
|
|
168
|
+
// Re-export from source: export * from './module' or export { x } from './module'
|
|
169
|
+
fileNode.explicitImports.add(specifier);
|
|
170
|
+
|
|
171
|
+
// Track external package usage from re-exports
|
|
172
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
173
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!node.exportClause) {
|
|
177
|
+
// export * from './module'
|
|
178
|
+
fileNode.internalExports.set('*', { type: 're-export-all', source: specifier });
|
|
179
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
180
|
+
} else if (ts.isNamespaceExport(node.exportClause)) {
|
|
181
|
+
// export * as name from './module'
|
|
182
|
+
const name = node.exportClause.name.text;
|
|
183
|
+
fileNode.internalExports.set(name, { type: 're-export-namespace', source: specifier, originalName: '*', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
184
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
185
|
+
} else if (ts.isNamedExports(node.exportClause)) {
|
|
186
|
+
// export { x, y as z } from './module'
|
|
160
187
|
node.exportClause.elements.forEach(element => {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
start: node.getStart(sourceFile),
|
|
166
|
-
end: node.getEnd()
|
|
167
|
-
});
|
|
188
|
+
const originalName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
189
|
+
const exportedName = element.name.text;
|
|
190
|
+
fileNode.internalExports.set(exportedName, { type: 're-export', source: specifier, originalName, start: element.getStart(sourceFile), end: element.getEnd() });
|
|
191
|
+
fileNode.importedSymbols.add(`${specifier}:${originalName}`);
|
|
168
192
|
});
|
|
169
|
-
} else {
|
|
170
|
-
// export * from './y'
|
|
171
|
-
fileNode.internalExports.set('*', { type: 're-export-all', source: specifier });
|
|
172
193
|
}
|
|
194
|
+
} else if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
195
|
+
// Local named exports: export { x, y as z }
|
|
196
|
+
node.exportClause.elements.forEach(element => {
|
|
197
|
+
const localName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
198
|
+
const exportedName = element.name.text;
|
|
199
|
+
fileNode.internalExports.set(exportedName, { type: 'export', originalName: localName, start: element.getStart(sourceFile), end: element.getEnd() });
|
|
200
|
+
});
|
|
173
201
|
}
|
|
174
202
|
}
|
|
175
203
|
|
|
@@ -183,35 +211,56 @@ export class ASTAnalyzer {
|
|
|
183
211
|
start: node.getStart(sourceFile),
|
|
184
212
|
end: node.getEnd()
|
|
185
213
|
};
|
|
186
|
-
|
|
187
|
-
fileNode.internalExports.set(name, exportInfo);
|
|
188
214
|
|
|
189
|
-
|
|
215
|
+
fileNode.internalExports.set(name, exportInfo);
|
|
216
|
+
|
|
190
217
|
if (ts.isEnumDeclaration(node)) {
|
|
191
|
-
exportInfo.members = node.members.map(m =>
|
|
218
|
+
exportInfo.members = node.members.map(m => ({
|
|
219
|
+
name: m.name.getText(sourceFile),
|
|
220
|
+
type: 'enumMember',
|
|
221
|
+
start: m.getStart(sourceFile),
|
|
222
|
+
end: m.getEnd()
|
|
223
|
+
}));
|
|
192
224
|
} else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
|
|
193
225
|
exportInfo.members = node.members
|
|
194
226
|
.filter(m => m.name)
|
|
195
|
-
.map(m =>
|
|
227
|
+
.map(m => ({
|
|
228
|
+
name: m.name.getText(sourceFile),
|
|
229
|
+
type: ts.SyntaxKind[m.kind].toLowerCase(),
|
|
230
|
+
start: m.getStart(sourceFile),
|
|
231
|
+
end: m.getEnd()
|
|
232
|
+
}));
|
|
196
233
|
} else if (ts.isModuleDeclaration(node)) {
|
|
197
|
-
// Handle Namespaces
|
|
198
234
|
const members = [];
|
|
199
235
|
if (node.body && ts.isModuleBlock(node.body)) {
|
|
200
236
|
node.body.statements.forEach(stmt => {
|
|
201
237
|
if (this.hasExportModifier(stmt) && (ts.isVariableStatement(stmt) || ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt))) {
|
|
202
238
|
if (ts.isVariableStatement(stmt)) {
|
|
203
|
-
stmt.declarationList.declarations.forEach(d => members.push(
|
|
239
|
+
stmt.declarationList.declarations.forEach(d => members.push({
|
|
240
|
+
name: d.name.getText(sourceFile),
|
|
241
|
+
type: 'variable',
|
|
242
|
+
start: d.getStart(sourceFile),
|
|
243
|
+
end: d.getEnd()
|
|
244
|
+
}));
|
|
204
245
|
} else if (stmt.name) {
|
|
205
|
-
members.push(
|
|
246
|
+
members.push({
|
|
247
|
+
name: stmt.name.getText(sourceFile),
|
|
248
|
+
type: ts.SyntaxKind[stmt.kind].toLowerCase().replace('declaration', ''),
|
|
249
|
+
start: stmt.getStart(sourceFile),
|
|
250
|
+
end: stmt.getEnd()
|
|
251
|
+
});
|
|
206
252
|
}
|
|
207
253
|
}
|
|
208
254
|
});
|
|
209
255
|
}
|
|
210
256
|
exportInfo.members = members;
|
|
211
257
|
}
|
|
212
|
-
|
|
258
|
+
|
|
213
259
|
const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
214
260
|
fileNode.symbolSourceLocations.set(name, { line: loc.line + 1, column: loc.character + 1 });
|
|
261
|
+
this.addDeclaredSymbol(name, node, sourceFile);
|
|
262
|
+
} else if (node.name && ts.isIdentifier(node.name)) {
|
|
263
|
+
this.addDeclaredSymbol(node.name.text, node, sourceFile);
|
|
215
264
|
}
|
|
216
265
|
}
|
|
217
266
|
|
|
@@ -225,32 +274,124 @@ export class ASTAnalyzer {
|
|
|
225
274
|
start: decl.getStart(sourceFile),
|
|
226
275
|
end: decl.getEnd()
|
|
227
276
|
});
|
|
277
|
+
this.addDeclaredSymbol(name, decl, sourceFile);
|
|
278
|
+
} else if (decl.name && ts.isObjectBindingPattern(decl.name)) {
|
|
279
|
+
decl.name.elements.forEach(element => {
|
|
280
|
+
if(element.name && ts.isIdentifier(element.name)) {
|
|
281
|
+
const name = element.name.text;
|
|
282
|
+
fileNode.internalExports.set(name, {
|
|
283
|
+
type: 'variable',
|
|
284
|
+
start: element.getStart(sourceFile),
|
|
285
|
+
end: element.getEnd()
|
|
286
|
+
});
|
|
287
|
+
this.addDeclaredSymbol(name, element, sourceFile);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
} else if (decl.name && ts.isArrayBindingPattern(decl.name)) {
|
|
291
|
+
decl.name.elements.forEach(element => {
|
|
292
|
+
if(ts.isBindingElement(element) && element.name && ts.isIdentifier(element.name)) {
|
|
293
|
+
const name = element.name.text;
|
|
294
|
+
fileNode.internalExports.set(name, {
|
|
295
|
+
type: 'variable',
|
|
296
|
+
start: element.getStart(sourceFile),
|
|
297
|
+
end: element.getEnd()
|
|
298
|
+
});
|
|
299
|
+
this.addDeclaredSymbol(name, element, sourceFile);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
} else {
|
|
305
|
+
// Non-exported variable declarations also need to be added to scope
|
|
306
|
+
node.declarationList.declarations.forEach(decl => {
|
|
307
|
+
if (decl.name && ts.isIdentifier(decl.name)) {
|
|
308
|
+
this.addDeclaredSymbol(decl.name.text, decl, sourceFile);
|
|
309
|
+
} else if (decl.name && ts.isObjectBindingPattern(decl.name)) {
|
|
310
|
+
decl.name.elements.forEach(element => {
|
|
311
|
+
if (element.name && ts.isIdentifier(element.name)) {
|
|
312
|
+
this.addDeclaredSymbol(element.name.text, element, sourceFile);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
} else if (decl.name && ts.isArrayBindingPattern(decl.name)) {
|
|
316
|
+
decl.name.elements.forEach(element => {
|
|
317
|
+
if (ts.isBindingElement(element) && element.name && ts.isIdentifier(element.name)) {
|
|
318
|
+
this.addDeclaredSymbol(element.name.text, element, sourceFile);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
228
321
|
}
|
|
229
322
|
});
|
|
230
323
|
}
|
|
231
324
|
}
|
|
232
325
|
|
|
233
326
|
handleCallExpression(node, fileNode, sourceFile) {
|
|
234
|
-
//
|
|
327
|
+
// Dynamic import(): import('./module').then(...)
|
|
235
328
|
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
329
|
+
const arg = node.arguments[0];
|
|
330
|
+
if (arg) {
|
|
331
|
+
if (ts.isStringLiteral(arg)) {
|
|
332
|
+
fileNode.explicitImports.add(arg.text);
|
|
333
|
+
fileNode.dynamicImports.add(arg.text);
|
|
334
|
+
// Track external package usage from dynamic imports
|
|
335
|
+
if (!arg.text.startsWith('.') && !arg.text.startsWith('/')) {
|
|
336
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(arg.text));
|
|
337
|
+
}
|
|
338
|
+
} else {
|
|
339
|
+
// Dynamic import with a non-literal expression (e.g., variable or template literal).
|
|
340
|
+
if (fileNode.calculatedDynamicImports) {
|
|
341
|
+
fileNode.calculatedDynamicImports.push({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
} else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|
|
236
346
|
const arg = node.arguments[0];
|
|
237
347
|
if (arg && ts.isStringLiteral(arg)) {
|
|
238
348
|
fileNode.explicitImports.add(arg.text);
|
|
239
|
-
|
|
349
|
+
// Track external package usage from require() calls
|
|
350
|
+
if (!arg.text.startsWith('.') && !arg.text.startsWith('/')) {
|
|
351
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(arg.text));
|
|
352
|
+
}
|
|
240
353
|
}
|
|
241
354
|
}
|
|
242
355
|
}
|
|
243
356
|
|
|
244
|
-
|
|
245
|
-
|
|
357
|
+
handleJsxElement(node, fileNode, sourceFile) {
|
|
358
|
+
const getElementName = (name) => {
|
|
359
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
360
|
+
if (ts.isPropertyAccessExpression(name)) return name.name.text;
|
|
361
|
+
return 'unknown';
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const tagName = getElementName(node.openingElement.tagName);
|
|
365
|
+
fileNode.jsxComponents.add(tagName);
|
|
366
|
+
|
|
367
|
+
node.openingElement.attributes.properties.forEach(attr => {
|
|
368
|
+
if (ts.isJsxAttribute(attr) && ts.isIdentifier(attr.name)) {
|
|
369
|
+
fileNode.jsxProps.add(`${tagName}:${attr.name.text}`);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
handleDecorator(node, fileNode, sourceFile) {
|
|
375
|
+
const getDecoratorName = (expr) => {
|
|
376
|
+
if (ts.isIdentifier(expr)) return expr.text;
|
|
377
|
+
if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression)) return expr.expression.text;
|
|
378
|
+
if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) return expr.expression.name.text;
|
|
379
|
+
return 'unknown';
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const decoratorName = getDecoratorName(node.expression);
|
|
383
|
+
fileNode.decorators.add(decoratorName);
|
|
384
|
+
|
|
385
|
+
// Optionally, extract decorator arguments
|
|
386
|
+
if (ts.isCallExpression(node.expression)) {
|
|
387
|
+
node.expression.arguments.forEach(arg => {
|
|
388
|
+
// Further analysis of arguments can be done here if needed
|
|
389
|
+
});
|
|
390
|
+
}
|
|
246
391
|
}
|
|
247
392
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (!parent) return false;
|
|
251
|
-
return (ts.isVariableDeclaration(parent) || ts.isFunctionDeclaration(parent) ||
|
|
252
|
-
ts.isClassDeclaration(parent) || ts.isInterfaceDeclaration(parent) ||
|
|
253
|
-
ts.isEnumDeclaration(parent) || ts.isModuleDeclaration(parent)) && parent.name === node;
|
|
393
|
+
hasExportModifier(node) {
|
|
394
|
+
return node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
254
395
|
}
|
|
255
396
|
|
|
256
397
|
extractTopLevelJSDocSuppreessions(sourceFile, fileNode) {
|
|
@@ -264,4 +405,18 @@ export class ASTAnalyzer {
|
|
|
264
405
|
}
|
|
265
406
|
}
|
|
266
407
|
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Extracts the root npm package name from an import specifier.
|
|
411
|
+
* Handles scoped packages (@scope/pkg) and subpath imports (pkg/utils, @scope/pkg/utils).
|
|
412
|
+
*/
|
|
413
|
+
_extractPackageName(specifier) {
|
|
414
|
+
if (specifier.startsWith('@')) {
|
|
415
|
+
// Scoped package: @scope/name or @scope/name/subpath
|
|
416
|
+
const parts = specifier.split('/');
|
|
417
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
|
|
418
|
+
}
|
|
419
|
+
// Regular package: name or name/subpath
|
|
420
|
+
return specifier.split('/')[0];
|
|
421
|
+
}
|
|
267
422
|
}
|
package/src/ast/BarrelParser.js
CHANGED
|
@@ -99,6 +99,18 @@ export class BarrelParser {
|
|
|
99
99
|
node.declarationList.declarations.forEach(decl => {
|
|
100
100
|
if (ts.isIdentifier(decl.name)) {
|
|
101
101
|
spec.declaredLocalExports.add(decl.name.text);
|
|
102
|
+
} else if (ts.isObjectBindingPattern(decl.name)) {
|
|
103
|
+
decl.name.elements.forEach(element => {
|
|
104
|
+
if (element.name && ts.isIdentifier(element.name)) {
|
|
105
|
+
spec.declaredLocalExports.add(element.name.text);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
} else if (ts.isArrayBindingPattern(decl.name)) {
|
|
109
|
+
decl.name.elements.forEach(element => {
|
|
110
|
+
if (ts.isBindingElement(element) && element.name && ts.isIdentifier(element.name)) {
|
|
111
|
+
spec.declaredLocalExports.add(element.name.text);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
102
114
|
}
|
|
103
115
|
});
|
|
104
116
|
}
|
|
@@ -148,37 +160,57 @@ export class BarrelParser {
|
|
|
148
160
|
if (spec.forwardedNamedExports.has(targetSymbolName)) {
|
|
149
161
|
const routingRule = spec.forwardedNamedExports.get(targetSymbolName);
|
|
150
162
|
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, routingRule.targetModule);
|
|
151
|
-
|
|
163
|
+
if (fullyResolvedPath) {
|
|
164
|
+
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, routingRule.sourceSymbol, activeProjectGraph, protectionStack);
|
|
165
|
+
}
|
|
152
166
|
}
|
|
153
167
|
|
|
154
|
-
// Rule C: Evaluate structural namespace alias groupings
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
168
|
+
// Rule C: Evaluate structural namespace alias groupings (export * as name from 'module')
|
|
169
|
+
for (const [namespaceAlias, relativeModule] of spec.namespacedWildcardExports.entries()) {
|
|
170
|
+
// If the targetSymbolName is prefixed with the namespaceAlias, then it's a member of this namespace re-export
|
|
171
|
+
if (targetSymbolName.startsWith(`${namespaceAlias}.`)) {
|
|
172
|
+
const originalSymbol = targetSymbolName.substring(namespaceAlias.length + 1);
|
|
173
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
|
|
174
|
+
if (fullyResolvedPath) {
|
|
175
|
+
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, originalSymbol, activeProjectGraph, protectionStack);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
159
178
|
}
|
|
160
179
|
|
|
161
|
-
// Rule D: Sweep through anonymous star re-exports vectors
|
|
180
|
+
// Rule D: Sweep through anonymous star re-exports vectors (export * from 'module')
|
|
181
|
+
//
|
|
182
|
+
// Algorithm:
|
|
183
|
+
// 1. For each `export * from './child'`, recursively resolve the symbol in the child.
|
|
184
|
+
// 2. A non-barrel child immediately returns `{ originFile: childPath }` regardless of
|
|
185
|
+
// whether it actually declares the symbol. We therefore must verify that the
|
|
186
|
+
// returned origin file actually contains the symbol in its declaredLocalExports
|
|
187
|
+
// before accepting the result.
|
|
188
|
+
// 3. If the child is itself a barrel, the recursive call already performs the full
|
|
189
|
+
// chain walk, so we only need to verify the final origin.
|
|
162
190
|
for (const relativePath of spec.wildcardExports) {
|
|
163
191
|
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
|
|
164
192
|
|
|
165
|
-
|
|
166
|
-
const targetSubSpec = await this.parseBarrelSpecification(fullyResolvedPath);
|
|
167
|
-
|
|
168
|
-
if (targetSubSpec.declaredLocalExports.has(targetSymbolName) ||
|
|
169
|
-
targetSubSpec.forwardedNamedExports.has(targetSymbolName)) {
|
|
170
|
-
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, targetSymbolName, activeProjectGraph, protectionStack);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Dynamic recursive trace for multi-tier nested barrel file layouts
|
|
174
|
-
if (targetSubSpec.isBarrelInstance) {
|
|
193
|
+
if (fullyResolvedPath) {
|
|
175
194
|
const continuousResolutionTrace = await this.determineSymbolDeclarationOrigin(
|
|
176
195
|
fullyResolvedPath,
|
|
177
196
|
targetSymbolName,
|
|
178
197
|
activeProjectGraph,
|
|
179
|
-
protectionStack
|
|
198
|
+
new Set(protectionStack) // Use a copy so sibling branches don't block each other
|
|
180
199
|
);
|
|
181
|
-
|
|
200
|
+
|
|
201
|
+
if (!continuousResolutionTrace) continue;
|
|
202
|
+
if (continuousResolutionTrace.originFile === contextFilePath) continue;
|
|
203
|
+
|
|
204
|
+
// Verify that the resolved origin actually declares the symbol.
|
|
205
|
+
// This prevents a non-barrel sibling (e.g. constants.ts) from being
|
|
206
|
+
// incorrectly returned for a symbol it does not export (e.g. formatData).
|
|
207
|
+
const originSpec = await this.parseBarrelSpecification(continuousResolutionTrace.originFile);
|
|
208
|
+
if (originSpec.declaredLocalExports.has(continuousResolutionTrace.originSymbol)) {
|
|
209
|
+
return continuousResolutionTrace;
|
|
210
|
+
}
|
|
211
|
+
// The origin spec is itself a barrel (isBarrelInstance = true) and the
|
|
212
|
+
// recursive call already resolved through it – accept the result.
|
|
213
|
+
if (originSpec.isBarrelInstance) {
|
|
182
214
|
return continuousResolutionTrace;
|
|
183
215
|
}
|
|
184
216
|
}
|