pkg-scaffold 2.4.0 → 3.1.0
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/.github/workflows/deploy.yml +55 -0
- package/README.md +104 -107
- package/bin/cli.js +87 -4
- package/docs/.vitepress/config.mts +40 -0
- package/docs/.vitepress/theme/index.ts +17 -0
- package/docs/.vitepress/theme/style.css +139 -0
- package/docs/guide.md +102 -0
- package/docs/index.md +64 -0
- package/docs/reference.md +52 -0
- package/index.js +1 -1
- package/package.json +21 -4
- package/pkg-scaffold/config.json +25 -0
- package/pkg-scaffold/plugins/README.md +19 -0
- package/src/EngineContext.js +46 -4
- package/src/ast/ASTAnalyzer.js +193 -240
- package/src/ast/BarrelParser.js +12 -0
- package/src/ast/MagicDetector.js +41 -87
- package/src/ast/OxcAnalyzer.js +114 -0
- package/src/healing/SelfHealer.js +1 -1
- package/src/index.js +112 -45
- package/src/performance/GraphCache.js +4 -1
- package/src/performance/SupplyChainGuard.js +41 -55
- package/src/performance/WorkerPool.js +12 -1
- package/src/performance/WorkerTaskRunner.js +11 -4
- package/src/plugins/BasePlugin.js +53 -0
- package/src/plugins/PluginRegistry.js +95 -0
- package/src/plugins/ecosystems/GenericPlugins.js +64 -0
- package/src/plugins/ecosystems/NextJsPlugin.js +33 -0
- package/src/resolution/ConfigLoader.js +59 -0
- package/src/resolution/DepencyResolver.js +11 -0
- package/src/resolution/DependencyProfiler.js +90 -0
- package/src/resolution/WorkSpaceGraph.js +1 -1
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -1,313 +1,266 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import fs from 'fs/promises';
|
|
3
|
-
import
|
|
3
|
+
import path from 'path';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Enterprise AST Syntax Walker & Feature Extractor
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Upgraded to use full TypeScript Compiler API (ts.createProgram + TypeChecker)
|
|
8
|
+
* for type-aware cross-file analysis.
|
|
9
9
|
*/
|
|
10
|
+
import { OxcAnalyzer } from './OxcAnalyzer.js';
|
|
11
|
+
|
|
10
12
|
export class ASTAnalyzer {
|
|
11
13
|
constructor(context) {
|
|
12
14
|
this.context = context;
|
|
13
|
-
|
|
14
|
-
this.
|
|
15
|
+
this.program = null;
|
|
16
|
+
this.checker = null;
|
|
17
|
+
this.oxc = new OxcAnalyzer(context);
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
/**
|
|
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();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Processes a file using the initialized program and type checker.
|
|
21
40
|
*/
|
|
22
41
|
async processFile(filePath, fileNode) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const sourceFile = ts.createSourceFile(
|
|
28
|
-
filePath,
|
|
29
|
-
sourceText,
|
|
30
|
-
ts.ScriptTarget.Latest,
|
|
31
|
-
true, // Ensure parent pointers are bound to allow localized subtree walking
|
|
32
|
-
this.getScriptKind(filePath)
|
|
33
|
-
);
|
|
42
|
+
// Fast Path: Use OXC for rapid scanning if type checking is not strictly required for this file
|
|
43
|
+
if (this.context.fastMode) {
|
|
44
|
+
return await this.oxc.processFile(filePath, fileNode);
|
|
45
|
+
}
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
if (!this.program) {
|
|
48
|
+
throw new Error('ASTAnalyzer must be initialized with initProgram() before processing files.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const sourceFile = this.program.getSourceFile(filePath);
|
|
52
|
+
if (!sourceFile) {
|
|
40
53
|
if (this.context.verbose) {
|
|
41
|
-
console.error(`[AST
|
|
54
|
+
console.error(`[AST Error] Source file not found in program: ${filePath}`);
|
|
42
55
|
}
|
|
43
56
|
return false;
|
|
44
57
|
}
|
|
58
|
+
|
|
59
|
+
this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
|
|
60
|
+
this.walkNode(sourceFile, sourceFile, fileNode);
|
|
61
|
+
|
|
62
|
+
return true;
|
|
45
63
|
}
|
|
46
64
|
|
|
47
|
-
/**
|
|
48
|
-
* Primary node walker loop executing atomic switch classifications.
|
|
49
|
-
* Challenge #7: Resolves conditional/destructured references to prevent cascading breakages.
|
|
50
|
-
*/
|
|
51
65
|
walkNode(sourceFile, node, fileNode) {
|
|
52
66
|
if (!node) return;
|
|
53
67
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// Trace default bounds: import React from 'react';
|
|
76
|
-
if (node.importClause.name) {
|
|
77
|
-
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
78
|
-
}
|
|
68
|
+
// Use type checker to resolve symbols if needed
|
|
69
|
+
if (ts.isIdentifier(node) && !this.isNodeDeclarationName(node)) {
|
|
70
|
+
const symbol = this.checker.getSymbolAtLocation(node);
|
|
71
|
+
if (symbol) {
|
|
72
|
+
const declarations = symbol.getDeclarations();
|
|
73
|
+
if (declarations && declarations.length > 0) {
|
|
74
|
+
const declFile = declarations[0].getSourceFile().fileName;
|
|
75
|
+
const symbolName = symbol.getName();
|
|
76
|
+
|
|
77
|
+
// Track sub-symbol usage (Property Access)
|
|
78
|
+
if (ts.isPropertyAccessExpression(node.parent) && node.parent.name === node) {
|
|
79
|
+
const parentType = this.checker.getTypeAtLocation(node.parent.expression);
|
|
80
|
+
const parentSymbol = parentType.getSymbol() || parentType.aliasSymbol;
|
|
81
|
+
if (parentSymbol) {
|
|
82
|
+
const parentDecl = parentSymbol.getDeclarations()?.[0];
|
|
83
|
+
if (parentDecl) {
|
|
84
|
+
const parentFile = parentDecl.getSourceFile().fileName;
|
|
85
|
+
fileNode.memberUsage = fileNode.memberUsage || new Set();
|
|
86
|
+
fileNode.memberUsage.add(`${parentFile}:${parentSymbol.getName()}.${symbolName}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
79
89
|
}
|
|
80
|
-
}
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (node.moduleReference.expression && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
88
|
-
fileNode.explicitImports.add(node.moduleReference.expression.text);
|
|
91
|
+
if (declFile !== sourceFile.fileName) {
|
|
92
|
+
fileNode.resolvedReferences = fileNode.resolvedReferences || new Set();
|
|
93
|
+
fileNode.resolvedReferences.add(`${declFile}:${symbolName}`);
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Challenge #1: Tracking dynamic expressions e.g., import('./chunks/' + variant)
|
|
95
|
-
case ts.SyntaxKind.CallExpression: {
|
|
96
|
-
if (node.expression && node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
97
|
-
const firstArgument = node.arguments[0];
|
|
98
|
-
if (firstArgument) {
|
|
99
|
-
if (ts.isStringLiteral(firstArgument)) {
|
|
100
|
-
fileNode.explicitImports.add(firstArgument.text);
|
|
101
|
-
fileNode.dynamicImports.add(firstArgument.text);
|
|
102
|
-
} else {
|
|
103
|
-
// Deeply trace runtime calculated variables within the import parameters call
|
|
104
|
-
const stringPatterns = [];
|
|
105
|
-
this.traceStringExpressions(firstArgument, stringPatterns);
|
|
106
|
-
fileNode.calculatedDynamicImports.push({
|
|
107
|
-
rawText: firstArgument.getText(sourceFile),
|
|
108
|
-
heuristics: stringPatterns,
|
|
109
|
-
position: node.getStart(sourceFile)
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
break;
|
|
115
96
|
}
|
|
97
|
+
}
|
|
116
98
|
|
|
117
|
-
|
|
118
|
-
case ts.SyntaxKind.
|
|
119
|
-
|
|
120
|
-
this.auditAssignmentSafety(node.name.text, node.initializer, fileNode, sourceFile);
|
|
121
|
-
} else if (node.name && (ts.isObjectBindingPattern(node.name) || ts.isArrayBindingPattern(node.name))) {
|
|
122
|
-
// Flatten binding properties to map destructured usage accurately
|
|
123
|
-
node.name.elements.forEach(element => {
|
|
124
|
-
if (element.name && ts.isIdentifier(element.name)) {
|
|
125
|
-
this.auditAssignmentSafety(element.name.text, null, fileNode, sourceFile);
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
99
|
+
switch (node.kind) {
|
|
100
|
+
case ts.SyntaxKind.ImportDeclaration: {
|
|
101
|
+
this.handleImportDeclaration(node, fileNode);
|
|
129
102
|
break;
|
|
130
103
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
case ts.SyntaxKind.FunctionDeclaration: {
|
|
134
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
135
|
-
const name = node.name.text;
|
|
136
|
-
if (this.hasExportModifier(node)) {
|
|
137
|
-
fileNode.internalExports.set(name, { type: 'function', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
138
|
-
}
|
|
139
|
-
}
|
|
104
|
+
case ts.SyntaxKind.ExportDeclaration: {
|
|
105
|
+
this.handleExportDeclaration(node, fileNode, sourceFile);
|
|
140
106
|
break;
|
|
141
107
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
case ts.SyntaxKind.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
}
|
|
108
|
+
case ts.SyntaxKind.FunctionDeclaration:
|
|
109
|
+
case ts.SyntaxKind.ClassDeclaration:
|
|
110
|
+
case ts.SyntaxKind.InterfaceDeclaration:
|
|
111
|
+
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
112
|
+
case ts.SyntaxKind.EnumDeclaration:
|
|
113
|
+
case ts.SyntaxKind.ModuleDeclaration: {
|
|
114
|
+
this.handleNamedDeclaration(node, fileNode, sourceFile);
|
|
151
115
|
break;
|
|
152
116
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
case ts.SyntaxKind.InterfaceDeclaration: {
|
|
156
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
157
|
-
const name = node.name.text;
|
|
158
|
-
if (this.hasExportModifier(node)) {
|
|
159
|
-
fileNode.internalExports.set(name, { type: 'interface', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
160
|
-
}
|
|
161
|
-
}
|
|
117
|
+
case ts.SyntaxKind.VariableStatement: {
|
|
118
|
+
this.handleVariableStatement(node, fileNode, sourceFile);
|
|
162
119
|
break;
|
|
163
120
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
case ts.SyntaxKind.TypeAliasDeclaration: {
|
|
167
|
-
if (node.name && ts.isIdentifier(node.name)) {
|
|
168
|
-
const name = node.name.text;
|
|
169
|
-
if (this.hasExportModifier(node)) {
|
|
170
|
-
fileNode.internalExports.set(name, { type: 'type-alias', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
121
|
+
case ts.SyntaxKind.CallExpression: {
|
|
122
|
+
this.handleCallExpression(node, fileNode, sourceFile);
|
|
173
123
|
break;
|
|
174
124
|
}
|
|
125
|
+
}
|
|
175
126
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const name = node.expression ? node.expression.getText(sourceFile) : 'default';
|
|
179
|
-
fileNode.internalExports.set('default', {
|
|
180
|
-
type: 'default-assignment',
|
|
181
|
-
referencedSymbol: name,
|
|
182
|
-
start: node.getStart(sourceFile),
|
|
183
|
-
end: node.getEnd()
|
|
184
|
-
});
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
127
|
+
ts.forEachChild(node, child => this.walkNode(sourceFile, child, fileNode));
|
|
128
|
+
}
|
|
187
129
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
130
|
+
handleImportDeclaration(node, fileNode) {
|
|
131
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
132
|
+
const specifier = node.moduleSpecifier.text;
|
|
133
|
+
fileNode.explicitImports.add(specifier);
|
|
134
|
+
|
|
135
|
+
if (node.importClause) {
|
|
136
|
+
if (node.importClause.namedBindings) {
|
|
137
|
+
if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
138
|
+
node.importClause.namedBindings.elements.forEach(element => {
|
|
139
|
+
const importedName = element.name.text;
|
|
140
|
+
const propertyName = element.propertyName ? element.propertyName.text : importedName;
|
|
141
|
+
fileNode.importedSymbols.add(`${specifier}:${propertyName}`);
|
|
142
|
+
});
|
|
143
|
+
} else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
144
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
145
|
+
}
|
|
193
146
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// Track general identifiers to register references to mapped import keys
|
|
198
|
-
case ts.SyntaxKind.Identifier: {
|
|
199
|
-
const idText = node.text;
|
|
200
|
-
// Avoid adding declarations to usage logs to keep verification accurate
|
|
201
|
-
if (!this.isNodeDeclarationName(node)) {
|
|
202
|
-
fileNode.instantiatedIdentifiers.add(idText);
|
|
147
|
+
if (node.importClause.name) {
|
|
148
|
+
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
203
149
|
}
|
|
204
|
-
break;
|
|
205
150
|
}
|
|
206
151
|
}
|
|
207
|
-
|
|
208
|
-
// Traverse recursively down the Node structural tree
|
|
209
|
-
ts.forEachChild(node, child => this.walkNode(sourceFile, child, fileNode));
|
|
210
152
|
}
|
|
211
153
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
154
|
+
handleExportDeclaration(node, fileNode, sourceFile) {
|
|
155
|
+
// Handle re-exports: export { x } from './y'
|
|
156
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
157
|
+
const specifier = node.moduleSpecifier.text;
|
|
158
|
+
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
159
|
+
node.exportClause.elements.forEach(element => {
|
|
160
|
+
const name = element.name.text;
|
|
161
|
+
fileNode.internalExports.set(name, {
|
|
162
|
+
type: 're-export',
|
|
163
|
+
source: specifier,
|
|
164
|
+
start: node.getStart(sourceFile),
|
|
165
|
+
end: node.getEnd()
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
// export * from './y'
|
|
170
|
+
fileNode.internalExports.set('*', { type: 're-export-all', source: specifier });
|
|
171
|
+
}
|
|
229
172
|
}
|
|
230
173
|
}
|
|
231
174
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const value = initializer.text;
|
|
175
|
+
handleNamedDeclaration(node, fileNode, sourceFile) {
|
|
176
|
+
if (this.hasExportModifier(node)) {
|
|
177
|
+
const isDefault = node.modifiers?.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
|
|
178
|
+
const name = isDefault ? 'default' : (node.name?.text || 'anonymous');
|
|
179
|
+
|
|
180
|
+
const exportInfo = {
|
|
181
|
+
type: ts.SyntaxKind[node.kind].toLowerCase().replace('declaration', ''),
|
|
182
|
+
start: node.getStart(sourceFile),
|
|
183
|
+
end: node.getEnd()
|
|
184
|
+
};
|
|
243
185
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
186
|
+
fileNode.internalExports.set(name, exportInfo);
|
|
187
|
+
|
|
188
|
+
// Phase 4: Drill down into members
|
|
189
|
+
if (ts.isEnumDeclaration(node)) {
|
|
190
|
+
exportInfo.members = node.members.map(m => m.name.getText(sourceFile));
|
|
191
|
+
} else if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
|
|
192
|
+
exportInfo.members = node.members
|
|
193
|
+
.filter(m => m.name)
|
|
194
|
+
.map(m => m.name.getText(sourceFile));
|
|
195
|
+
} else if (ts.isModuleDeclaration(node)) {
|
|
196
|
+
// Handle Namespaces
|
|
197
|
+
const members = [];
|
|
198
|
+
if (node.body && ts.isModuleBlock(node.body)) {
|
|
199
|
+
node.body.statements.forEach(stmt => {
|
|
200
|
+
if (this.hasExportModifier(stmt) && (ts.isVariableStatement(stmt) || ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt))) {
|
|
201
|
+
if (ts.isVariableStatement(stmt)) {
|
|
202
|
+
stmt.declarationList.declarations.forEach(d => members.push(d.name.getText(sourceFile)));
|
|
203
|
+
} else if (stmt.name) {
|
|
204
|
+
members.push(stmt.name.getText(sourceFile));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
exportInfo.members = members;
|
|
210
|
+
}
|
|
247
211
|
|
|
248
|
-
|
|
249
|
-
fileNode.
|
|
250
|
-
identifier: variableName,
|
|
251
|
-
entropy: parseFloat(entropy.toFixed(2)),
|
|
252
|
-
position: initializer.getStart(sourceFile),
|
|
253
|
-
riskCode: 'HIGH_RISK_SECRET_LEAK'
|
|
254
|
-
});
|
|
212
|
+
const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
213
|
+
fileNode.symbolSourceLocations.set(name, { line: loc.line + 1, column: loc.character + 1 });
|
|
255
214
|
}
|
|
256
215
|
}
|
|
257
216
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
217
|
+
handleVariableStatement(node, fileNode, sourceFile) {
|
|
218
|
+
if (this.hasExportModifier(node)) {
|
|
219
|
+
node.declarationList.declarations.forEach(decl => {
|
|
220
|
+
if (decl.name && ts.isIdentifier(decl.name)) {
|
|
221
|
+
const name = decl.name.text;
|
|
222
|
+
fileNode.internalExports.set(name, {
|
|
223
|
+
type: 'variable',
|
|
224
|
+
start: decl.getStart(sourceFile),
|
|
225
|
+
end: decl.getEnd()
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
});
|
|
268
229
|
}
|
|
269
|
-
return entropy;
|
|
270
230
|
}
|
|
271
231
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
for (const range of commentRanges) {
|
|
280
|
-
const comment = fullText.slice(range.pos, range.end);
|
|
281
|
-
const matches = comment.match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
|
|
282
|
-
if (matches) {
|
|
283
|
-
matches.forEach(m => {
|
|
284
|
-
const directive = m.replace('@scaffold-suppress', '').trim();
|
|
285
|
-
fileNode.localSuppressedRules.add(directive);
|
|
286
|
-
});
|
|
232
|
+
handleCallExpression(node, fileNode, sourceFile) {
|
|
233
|
+
// Trace dynamic imports
|
|
234
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
235
|
+
const arg = node.arguments[0];
|
|
236
|
+
if (arg && ts.isStringLiteral(arg)) {
|
|
237
|
+
fileNode.explicitImports.add(arg.text);
|
|
238
|
+
fileNode.dynamicImports.add(arg.text);
|
|
287
239
|
}
|
|
288
240
|
}
|
|
289
241
|
}
|
|
290
242
|
|
|
291
243
|
hasExportModifier(node) {
|
|
292
|
-
|
|
293
|
-
return node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword);
|
|
244
|
+
return node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
294
245
|
}
|
|
295
246
|
|
|
296
247
|
isNodeDeclarationName(node) {
|
|
297
248
|
const parent = node.parent;
|
|
298
249
|
if (!parent) return false;
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (ts.isInterfaceDeclaration(parent) && parent.name === node) return true;
|
|
303
|
-
if (ts.isImportSpecifier(parent) && parent.name === node) return true;
|
|
304
|
-
return false;
|
|
250
|
+
return (ts.isVariableDeclaration(parent) || ts.isFunctionDeclaration(parent) ||
|
|
251
|
+
ts.isClassDeclaration(parent) || ts.isInterfaceDeclaration(parent) ||
|
|
252
|
+
ts.isEnumDeclaration(parent) || ts.isModuleDeclaration(parent)) && parent.name === node;
|
|
305
253
|
}
|
|
306
254
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
255
|
+
extractTopLevelJSDocSuppreessions(sourceFile, fileNode) {
|
|
256
|
+
const fullText = sourceFile.text;
|
|
257
|
+
const commentRegex = /\/\*\*?[\s\S]*?\*\/|\/\/.*/g;
|
|
258
|
+
let match;
|
|
259
|
+
while ((match = commentRegex.exec(fullText)) !== null) {
|
|
260
|
+
const suppressMatches = match[0].match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
|
|
261
|
+
if (suppressMatches) {
|
|
262
|
+
suppressMatches.forEach(m => fileNode.localSuppressedRules.add(m.replace('@scaffold-suppress', '').trim()));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
312
265
|
}
|
|
313
266
|
}
|
package/src/ast/BarrelParser.js
CHANGED
|
@@ -104,6 +104,18 @@ export class BarrelParser {
|
|
|
104
104
|
}
|
|
105
105
|
break;
|
|
106
106
|
}
|
|
107
|
+
|
|
108
|
+
// Track default exports declared directly within the file boundary:
|
|
109
|
+
// Handles both `export default function foo()` and `export default expression`.
|
|
110
|
+
// The canonical symbol name stored is 'default' so that forwardedNamedExports
|
|
111
|
+
// entries whose sourceSymbol is 'default' can resolve correctly via Rule A.
|
|
112
|
+
case ts.SyntaxKind.ExportAssignment: {
|
|
113
|
+
// ExportAssignment covers `export default <expr>` (isExportEquals === false)
|
|
114
|
+
// as well as `export = <expr>` (isExportEquals === true, CommonJS style).
|
|
115
|
+
// We register 'default' for both to ensure the barrel tracer can settle here.
|
|
116
|
+
spec.declaredLocalExports.add('default');
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
107
119
|
}
|
|
108
120
|
|
|
109
121
|
ts.forEachChild(node, child => this.harvestExportSignatures(child, spec));
|