pkg-scaffold 3.3.3 → 3.3.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.
@@ -1,138 +1,123 @@
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.program = null;
16
- this.checker = null;
17
- this.oxc = new OxcAnalyzer(context);
18
- }
19
-
20
- /**
21
- * Initializes the TypeScript program for the entire project.
22
- * This is crucial for cross-file type resolution.
23
- */
24
- initProgram(filePaths, options = {}) {
25
- const defaultOptions = {
26
- target: ts.ScriptTarget.Latest,
27
- module: ts.ModuleKind.CommonJS,
28
- allowJs: true,
29
- checkJs: true,
30
- esModuleInterop: true,
31
- skipLibCheck: true
32
- };
33
-
34
- this.program = ts.createProgram(filePaths, { ...defaultOptions, ...options });
35
- this.checker = this.program.getTypeChecker();
6
+ this.scopeStack = [];
36
7
  }
37
8
 
38
- /**
39
- * Processes a file using the initialized program and type checker.
40
- */
41
- async processFile(filePath, fileNode) {
42
- // Fast Path: Use OXC for rapid scanning if type checking is not strictly required for this file
43
- if (this.context.fastMode && this.oxc.isAvailable) {
44
- const success = await this.oxc.processFile(filePath, fileNode);
45
- if (success) return true;
9
+ parseFile(filePath, content, fileNode) {
10
+ if (this.context.verbose) {
11
+ console.log(`[AST] Parsing: ${filePath}`);
46
12
  }
47
13
 
48
- if (!this.program) {
49
- throw new Error('ASTAnalyzer must be initialized with initProgram() before processing files.');
50
- }
14
+ const sourceFile = ts.createSourceFile(
15
+ filePath,
16
+ content,
17
+ ts.ScriptTarget.Latest,
18
+ true,
19
+ filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS
20
+ );
51
21
 
52
- const sourceFile = this.program.getSourceFile(filePath);
53
- if (!sourceFile) {
54
- if (this.context.verbose) {
55
- console.error(`[AST Error] Source file not found in program: ${filePath}`);
56
- }
57
- return false;
58
- }
22
+ this.currentScope = { symbols: new Map(), parent: null };
23
+ this.scopeStack.push(this.currentScope);
59
24
 
60
25
  this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
61
- this.walkNode(sourceFile, sourceFile, fileNode);
62
-
63
- return true;
26
+ this.walkAST(sourceFile, fileNode, sourceFile);
27
+
28
+ this.scopeStack.pop(); // Pop the global scope
29
+ this.currentScope = null;
64
30
  }
65
31
 
66
- walkNode(sourceFile, node, fileNode) {
67
- if (!node) return;
32
+ pushScope() {
33
+ const newScope = { symbols: new Map(), parent: this.currentScope };
34
+ this.scopeStack.push(newScope);
35
+ this.currentScope = newScope;
36
+ }
68
37
 
69
- // Use type checker to resolve symbols if needed
70
- if (ts.isIdentifier(node) && !this.isNodeDeclarationName(node)) {
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
- }
38
+ popScope() {
39
+ if (this.scopeStack.length > 1) {
40
+ this.scopeStack.pop();
41
+ this.currentScope = this.scopeStack[this.scopeStack.length - 1];
42
+ }
43
+ }
91
44
 
92
- if (declFile !== sourceFile.fileName) {
93
- fileNode.resolvedReferences = fileNode.resolvedReferences || new Set();
94
- fileNode.resolvedReferences.add(`${declFile}:${symbolName}`);
95
- }
96
- }
45
+ addDeclaredSymbol(name, node, sourceFile) {
46
+ if (this.currentScope) {
47
+ const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
48
+ this.currentScope.symbols.set(name, { node, line: loc.line + 1, column: loc.character + 1 });
49
+ }
50
+ }
51
+
52
+ resolveSymbol(name) {
53
+ for (let i = this.scopeStack.length - 1; i >= 0; i--) {
54
+ const scope = this.scopeStack[i];
55
+ if (scope.symbols.has(name)) {
56
+ return scope.symbols.get(name);
97
57
  }
98
58
  }
59
+ return null;
60
+ }
61
+
62
+ walkAST(node, fileNode, sourceFile) {
63
+ // Handle scope entry for blocks, functions, classes, etc.
64
+ const isScopeNode = ts.isBlock(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isModuleDeclaration(node);
65
+ if (isScopeNode) {
66
+ this.pushScope();
67
+ }
99
68
 
100
69
  switch (node.kind) {
101
- case ts.SyntaxKind.ImportDeclaration: {
102
- this.handleImportDeclaration(node, fileNode);
70
+ case ts.SyntaxKind.ImportDeclaration:
71
+ this.handleImportDeclaration(node, fileNode, sourceFile);
103
72
  break;
104
- }
105
- case ts.SyntaxKind.ExportDeclaration: {
73
+ case ts.SyntaxKind.ExportDeclaration:
106
74
  this.handleExportDeclaration(node, fileNode, sourceFile);
107
75
  break;
108
- }
76
+ case ts.SyntaxKind.ExportAssignment:
77
+ fileNode.internalExports.set('default', { type: 'default', start: node.getStart(sourceFile), end: node.getEnd() });
78
+ break;
79
+ case ts.SyntaxKind.VariableStatement:
80
+ this.handleVariableStatement(node, fileNode, sourceFile);
81
+ break;
109
82
  case ts.SyntaxKind.FunctionDeclaration:
110
83
  case ts.SyntaxKind.ClassDeclaration:
111
84
  case ts.SyntaxKind.InterfaceDeclaration:
112
85
  case ts.SyntaxKind.TypeAliasDeclaration:
113
86
  case ts.SyntaxKind.EnumDeclaration:
114
- case ts.SyntaxKind.ModuleDeclaration: {
87
+ case ts.SyntaxKind.ModuleDeclaration:
115
88
  this.handleNamedDeclaration(node, fileNode, sourceFile);
116
89
  break;
117
- }
118
- case ts.SyntaxKind.VariableStatement: {
119
- this.handleVariableStatement(node, fileNode, sourceFile);
120
- break;
121
- }
122
- case ts.SyntaxKind.CallExpression: {
90
+ case ts.SyntaxKind.CallExpression:
123
91
  this.handleCallExpression(node, fileNode, sourceFile);
124
92
  break;
125
- }
93
+ case ts.SyntaxKind.JsxElement:
94
+ case ts.SyntaxKind.JsxSelfClosingElement:
95
+ this.handleJsxElement(node, fileNode, sourceFile);
96
+ break;
97
+ case ts.SyntaxKind.Decorator:
98
+ this.handleDecorator(node, fileNode, sourceFile);
99
+ break;
100
+ case ts.SyntaxKind.Identifier:
101
+ // Track usage of identifiers
102
+ const symbol = this.resolveSymbol(node.text);
103
+ if (symbol) {
104
+ // fileNode.usedSymbols.add(`${symbol.node.parent.kind}:${node.text}`); // More granular tracking needed
105
+ }
106
+ break;
126
107
  }
127
108
 
128
- ts.forEachChild(node, child => this.walkNode(sourceFile, child, fileNode));
109
+ ts.forEachChild(node, child => this.walkAST(child, fileNode, sourceFile));
110
+
111
+ if (isScopeNode) {
112
+ this.popScope();
113
+ }
129
114
  }
130
115
 
131
- handleImportDeclaration(node, fileNode) {
116
+ handleImportDeclaration(node, fileNode, sourceFile) {
132
117
  if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
133
118
  const specifier = node.moduleSpecifier.text;
134
119
  fileNode.explicitImports.add(specifier);
135
-
120
+
136
121
  if (node.importClause) {
137
122
  if (node.importClause.namedBindings) {
138
123
  if (ts.isNamedImports(node.importClause.namedBindings)) {
@@ -140,36 +125,80 @@ export class ASTAnalyzer {
140
125
  const importedName = element.name.text;
141
126
  const propertyName = element.propertyName ? element.propertyName.text : importedName;
142
127
  fileNode.importedSymbols.add(`${specifier}:${propertyName}`);
128
+ this.addDeclaredSymbol(element.name.text, element, sourceFile); // Add local import name to scope
143
129
  });
144
130
  } else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
145
131
  fileNode.importedSymbols.add(`${specifier}:*`);
132
+ this.addDeclaredSymbol(node.importClause.namedBindings.name.text, node.importClause.namedBindings, sourceFile); // Add namespace import to scope
146
133
  }
147
134
  }
148
135
  if (node.importClause.name) {
149
136
  fileNode.importedSymbols.add(`${specifier}:default`);
137
+ this.addDeclaredSymbol(node.importClause.name.text, node.importClause.name, sourceFile); // Add default import to scope
150
138
  }
151
139
  }
152
140
  }
153
141
  }
154
142
 
155
143
  handleExportDeclaration(node, fileNode, sourceFile) {
156
- // Handle re-exports: export { x } from './y'
157
144
  if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
158
145
  const specifier = node.moduleSpecifier.text;
159
146
  if (node.exportClause && ts.isNamedExports(node.exportClause)) {
160
147
  node.exportClause.elements.forEach(element => {
161
148
  const name = element.name.text;
149
+ const propertyName = element.propertyName ? element.propertyName.text : name;
162
150
  fileNode.internalExports.set(name, {
163
151
  type: 're-export',
164
152
  source: specifier,
153
+ originalName: propertyName,
165
154
  start: node.getStart(sourceFile),
166
155
  end: node.getEnd()
167
156
  });
168
157
  });
158
+ } else if (node.exportClause && ts.isNamespaceExport(node.exportClause)) {
159
+ // export * as name from 'module'
160
+ const name = node.exportClause.name.text;
161
+ fileNode.internalExports.set(name, { type: 're-export-namespace', source: specifier, originalName: '*', start: node.getStart(sourceFile), end: node.getEnd() });
169
162
  } else {
170
- // export * from './y'
163
+ // export * from 'module'
171
164
  fileNode.internalExports.set('*', { type: 're-export-all', source: specifier });
172
165
  }
166
+ } else if (node.exportClause && ts.isNamedExports(node.exportClause)) {
167
+ node.exportClause.elements.forEach(element => {
168
+ const name = element.name.text;
169
+ const propertyName = element.propertyName ? element.propertyName.text : name;
170
+ fileNode.internalExports.set(name, {
171
+ type: 'export',
172
+ originalName: propertyName,
173
+ start: node.getStart(sourceFile),
174
+ end: node.getEnd()
175
+ });
176
+ });
177
+ } else if (node.declaration) {
178
+ // Direct export of a declaration (e.g., export const x = 1;)
179
+ if (ts.isVariableStatement(node.declaration)) {
180
+ node.declaration.declarationList.declarations.forEach(decl => {
181
+ if (decl.name && ts.isIdentifier(decl.name)) {
182
+ const name = decl.name.text;
183
+ fileNode.internalExports.set(name, {
184
+ type: 'variable',
185
+ start: decl.getStart(sourceFile),
186
+ end: decl.getEnd()
187
+ });
188
+ this.addDeclaredSymbol(name, decl, sourceFile);
189
+ }
190
+ });
191
+ } else if (ts.isFunctionDeclaration(node.declaration) || ts.isClassDeclaration(node.declaration)) {
192
+ const name = node.declaration.name?.text;
193
+ if (name) {
194
+ fileNode.internalExports.set(name, {
195
+ type: ts.SyntaxKind[node.declaration.kind].toLowerCase().replace('declaration', ''),
196
+ start: node.declaration.getStart(sourceFile),
197
+ end: node.declaration.getEnd()
198
+ });
199
+ this.addDeclaredSymbol(name, node.declaration, sourceFile);
200
+ }
201
+ }
173
202
  }
174
203
  }
175
204
 
@@ -183,35 +212,56 @@ export class ASTAnalyzer {
183
212
  start: node.getStart(sourceFile),
184
213
  end: node.getEnd()
185
214
  };
186
-
187
- fileNode.internalExports.set(name, exportInfo);
188
215
 
189
- // Phase 4: Drill down into members
216
+ fileNode.internalExports.set(name, exportInfo);
217
+
190
218
  if (ts.isEnumDeclaration(node)) {
191
- exportInfo.members = node.members.map(m => m.name.getText(sourceFile));
219
+ exportInfo.members = node.members.map(m => ({
220
+ name: m.name.getText(sourceFile),
221
+ type: 'enumMember',
222
+ start: m.getStart(sourceFile),
223
+ end: m.getEnd()
224
+ }));
192
225
  } else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
193
226
  exportInfo.members = node.members
194
227
  .filter(m => m.name)
195
- .map(m => m.name.getText(sourceFile));
228
+ .map(m => ({
229
+ name: m.name.getText(sourceFile),
230
+ type: ts.SyntaxKind[m.kind].toLowerCase(),
231
+ start: m.getStart(sourceFile),
232
+ end: m.getEnd()
233
+ }));
196
234
  } else if (ts.isModuleDeclaration(node)) {
197
- // Handle Namespaces
198
235
  const members = [];
199
236
  if (node.body && ts.isModuleBlock(node.body)) {
200
237
  node.body.statements.forEach(stmt => {
201
238
  if (this.hasExportModifier(stmt) && (ts.isVariableStatement(stmt) || ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt))) {
202
239
  if (ts.isVariableStatement(stmt)) {
203
- stmt.declarationList.declarations.forEach(d => members.push(d.name.getText(sourceFile)));
240
+ stmt.declarationList.declarations.forEach(d => members.push({
241
+ name: d.name.getText(sourceFile),
242
+ type: 'variable',
243
+ start: d.getStart(sourceFile),
244
+ end: d.getEnd()
245
+ }));
204
246
  } else if (stmt.name) {
205
- members.push(stmt.name.getText(sourceFile));
247
+ members.push({
248
+ name: stmt.name.getText(sourceFile),
249
+ type: ts.SyntaxKind[stmt.kind].toLowerCase().replace('declaration', ''),
250
+ start: stmt.getStart(sourceFile),
251
+ end: stmt.getEnd()
252
+ });
206
253
  }
207
254
  }
208
255
  });
209
256
  }
210
257
  exportInfo.members = members;
211
258
  }
212
-
259
+
213
260
  const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
214
261
  fileNode.symbolSourceLocations.set(name, { line: loc.line + 1, column: loc.character + 1 });
262
+ this.addDeclaredSymbol(name, node, sourceFile);
263
+ } else if (node.name && ts.isIdentifier(node.name)) {
264
+ this.addDeclaredSymbol(node.name.text, node, sourceFile);
215
265
  }
216
266
  }
217
267
 
@@ -225,32 +275,109 @@ export class ASTAnalyzer {
225
275
  start: decl.getStart(sourceFile),
226
276
  end: decl.getEnd()
227
277
  });
278
+ this.addDeclaredSymbol(name, decl, sourceFile);
279
+ } else if (decl.name && ts.isObjectBindingPattern(decl.name)) {
280
+ decl.name.elements.forEach(element => {
281
+ if(element.name && ts.isIdentifier(element.name)) {
282
+ const name = element.name.text;
283
+ fileNode.internalExports.set(name, {
284
+ type: 'variable',
285
+ start: element.getStart(sourceFile),
286
+ end: element.getEnd()
287
+ });
288
+ this.addDeclaredSymbol(name, element, sourceFile);
289
+ }
290
+ });
291
+ } else if (decl.name && ts.isArrayBindingPattern(decl.name)) {
292
+ decl.name.elements.forEach(element => {
293
+ if(ts.isBindingElement(element) && element.name && ts.isIdentifier(element.name)) {
294
+ const name = element.name.text;
295
+ fileNode.internalExports.set(name, {
296
+ type: 'variable',
297
+ start: element.getStart(sourceFile),
298
+ end: element.getEnd()
299
+ });
300
+ this.addDeclaredSymbol(name, element, sourceFile);
301
+ }
302
+ });
303
+ }
304
+ });
305
+ } else {
306
+ // Non-exported variable declarations also need to be added to scope
307
+ node.declarationList.declarations.forEach(decl => {
308
+ if (decl.name && ts.isIdentifier(decl.name)) {
309
+ this.addDeclaredSymbol(decl.name.text, decl, sourceFile);
310
+ } else if (decl.name && ts.isObjectBindingPattern(decl.name)) {
311
+ decl.name.elements.forEach(element => {
312
+ if (element.name && ts.isIdentifier(element.name)) {
313
+ this.addDeclaredSymbol(element.name.text, element, sourceFile);
314
+ }
315
+ });
316
+ } else if (decl.name && ts.isArrayBindingPattern(decl.name)) {
317
+ decl.name.elements.forEach(element => {
318
+ if (ts.isBindingElement(element) && element.name && ts.isIdentifier(element.name)) {
319
+ this.addDeclaredSymbol(element.name.text, element, sourceFile);
320
+ }
321
+ });
228
322
  }
229
323
  });
230
324
  }
231
325
  }
232
326
 
233
327
  handleCallExpression(node, fileNode, sourceFile) {
234
- // Trace dynamic imports
235
328
  if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
236
329
  const arg = node.arguments[0];
237
330
  if (arg && ts.isStringLiteral(arg)) {
238
331
  fileNode.explicitImports.add(arg.text);
239
332
  fileNode.dynamicImports.add(arg.text);
240
333
  }
334
+ } else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
335
+ const arg = node.arguments[0];
336
+ if (arg && ts.isStringLiteral(arg)) {
337
+ fileNode.explicitImports.add(arg.text);
338
+ }
241
339
  }
242
340
  }
243
341
 
244
- hasExportModifier(node) {
245
- return node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
342
+ handleJsxElement(node, fileNode, sourceFile) {
343
+ const getElementName = (name) => {
344
+ if (ts.isIdentifier(name)) return name.text;
345
+ if (ts.isPropertyAccessExpression(name)) return name.name.text;
346
+ return 'unknown';
347
+ };
348
+
349
+ const tagName = getElementName(node.openingElement.tagName);
350
+ fileNode.jsxComponents.add(tagName);
351
+
352
+ node.openingElement.attributes.properties.forEach(attr => {
353
+ if (ts.isJsxAttribute(attr) && ts.isIdentifier(attr.name)) {
354
+ fileNode.jsxProps.add(`${tagName}:${attr.name.text}`);
355
+ }
356
+ });
246
357
  }
247
358
 
248
- isNodeDeclarationName(node) {
249
- const parent = node.parent;
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;
359
+ handleDecorator(node, fileNode, sourceFile) {
360
+ const getDecoratorName = (expr) => {
361
+ if (ts.isIdentifier(expr)) return expr.text;
362
+ if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression)) return expr.expression.text;
363
+ if (ts.isCallExpression(expr) && ts.isPropertyAccessExpression(expr.expression)) return expr.expression.name.text;
364
+ return 'unknown';
365
+ };
366
+
367
+ const decoratorName = getDecoratorName(node.expression);
368
+ fileNode.decorators.add(decoratorName);
369
+
370
+ // Optionally, extract decorator arguments
371
+ if (ts.isCallExpression(node.expression)) {
372
+ node.expression.arguments.forEach(arg => {
373
+ // Further analysis of arguments can be done here if needed
374
+ // e.g., if (ts.isStringLiteral(arg)) fileNode.decoratorArgs.add(`${decoratorName}:${arg.text}`);
375
+ });
376
+ }
377
+ }
378
+
379
+ hasExportModifier(node) {
380
+ return node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
254
381
  }
255
382
 
256
383
  extractTopLevelJSDocSuppreessions(sourceFile, fileNode) {
@@ -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,36 +160,36 @@ 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
- return this.determineSymbolDeclarationOrigin(fullyResolvedPath, routingRule.sourceSymbol, activeProjectGraph, protectionStack);
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
- if (spec.namespacedWildcardExports.has(targetSymbolName)) {
156
- const relativeModule = spec.namespacedWildcardExports.get(targetSymbolName);
157
- const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
158
- return { originFile: fullyResolvedPath, originSymbol: '*' };
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')
162
181
  for (const relativePath of spec.wildcardExports) {
163
182
  const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
164
183
 
165
- // Look inside the graph metadata map configuration to verify child target availability
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) {
184
+ if (fullyResolvedPath) {
185
+ // Look inside the graph metadata map configuration to verify child target availability
175
186
  const continuousResolutionTrace = await this.determineSymbolDeclarationOrigin(
176
187
  fullyResolvedPath,
177
188
  targetSymbolName,
178
189
  activeProjectGraph,
179
190
  protectionStack
180
191
  );
192
+ // If a resolution was found in a child barrel, return it
181
193
  if (continuousResolutionTrace && continuousResolutionTrace.originFile !== fullyResolvedPath) {
182
194
  return continuousResolutionTrace;
183
195
  }
@@ -0,0 +1,73 @@
1
+ export class DeadCodeDetector {
2
+ constructor(context) {
3
+ this.context = context;
4
+ }
5
+
6
+ detectDeadCode(graph) {
7
+ const deadFiles = [];
8
+ const deadExports = [];
9
+
10
+ // Find all entry points
11
+ const entryPoints = new Set();
12
+ for (const [filePath, node] of graph.entries()) {
13
+ if (node.isEntry || node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
14
+ entryPoints.add(filePath);
15
+ }
16
+ }
17
+
18
+ // Traverse from entry points to find used files
19
+ const usedFiles = new Set();
20
+ const queue = Array.from(entryPoints);
21
+
22
+ while (queue.length > 0) {
23
+ const current = queue.shift();
24
+ if (!usedFiles.has(current)) {
25
+ usedFiles.add(current);
26
+ const node = graph.get(current);
27
+ if (node && node.outgoingEdges) {
28
+ for (const edge of node.outgoingEdges) {
29
+ queue.push(edge);
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ // Identify dead files
36
+ for (const filePath of graph.keys()) {
37
+ if (!usedFiles.has(filePath)) {
38
+ deadFiles.push(filePath);
39
+ }
40
+ }
41
+
42
+ // Identify dead exports in used files
43
+ for (const filePath of usedFiles) {
44
+ const node = graph.get(filePath);
45
+ if (!node) continue;
46
+
47
+ // If it's an entry point, we consider its exports used (unless strictly configured otherwise)
48
+ if (node.isEntry) continue;
49
+
50
+ for (const [exportName, exportInfo] of node.internalExports.entries()) {
51
+ if (exportName === '*' || exportName === 'default') continue; // Skip wildcards for now
52
+
53
+ let isUsed = false;
54
+ // Check incoming edges if they import this specific symbol
55
+ for (const [otherPath, otherNode] of graph.entries()) {
56
+ if (otherNode.outgoingEdges.has(filePath)) {
57
+ if (otherNode.importedSymbols.has(`${filePath}:${exportName}`) ||
58
+ otherNode.importedSymbols.has(`${filePath}:*`)) {
59
+ isUsed = true;
60
+ break;
61
+ }
62
+ }
63
+ }
64
+
65
+ if (!isUsed) {
66
+ deadExports.push({ file: filePath, symbol: exportName, line: exportInfo.start });
67
+ }
68
+ }
69
+ }
70
+
71
+ return { deadFiles, deadExports };
72
+ }
73
+ }
@@ -68,7 +68,9 @@ export class MagicDetector {
68
68
  'jest.config.', 'vitest.config.', 'playwright.config.', 'cypress.config.',
69
69
  'webpack.config.', 'vite.config.', 'rollup.config.', 'tailwind.config.',
70
70
  '.eslintrc.', 'prettier.config.', '.postcssrc.', 'postcss.config.',
71
- 'bin/cli.js', 'index.js', 'WorkerTaskRunner.js'
71
+ 'bin/cli.js', 'index.js', 'WorkerTaskRunner.js',
72
+ 'src/server.ts', 'src/main.ts', 'src/app.ts', 'src/index.tsx', 'src/index.ts',
73
+ 'server.ts', 'main.ts', 'app.ts'
72
74
  ];
73
75
 
74
76
  return testEnvironments.some(matchPattern => normalizedPath.includes(matchPattern));