pkg-scaffold 2.3.0 → 2.4.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/.scaffold-ignore +22 -0
- package/bin/cli.js +91 -0
- package/index.js +0 -0
- package/package.json +18 -6
- package/src/EngineContext.js +282 -0
- package/src/ast/ASTAnalyzer.js +313 -0
- package/src/ast/BarrelParser.js +177 -0
- package/src/ast/MagicDetector.js +154 -0
- package/src/healing/GitSandbox.js +160 -0
- package/src/healing/SelfHealer.js +150 -0
- package/src/index.js +343 -0
- package/src/performance/GraphCache.js +82 -0
- package/src/performance/SupplyChainGuard.js +106 -0
- package/src/performance/WorkerPool.js +89 -0
- package/src/performance/WorkerTaskRunner.js +64 -0
- package/src/refractor/ImpactAnalyzer.js +92 -0
- package/src/refractor/SourceRewriter.js +86 -0
- package/src/refractor/TransactionManager.js +131 -0
- package/src/refractor/TypeIntegrity.js +75 -0
- package/src/resolution/DepencyResolver.js +120 -0
- package/src/resolution/PathMapper.js +115 -0
- package/src/resolution/WorkSpaceGraph.js +171 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Enterprise AST Syntax Walker & Feature Extractor
|
|
7
|
+
* Utilizes the official TypeScript Compiler infrastructure to execute deeply nested
|
|
8
|
+
* node classification without falling back to high-risk regular expression approximations.
|
|
9
|
+
*/
|
|
10
|
+
export class ASTAnalyzer {
|
|
11
|
+
constructor(context) {
|
|
12
|
+
this.context = context;
|
|
13
|
+
// Standard high-entropy baseline selectors for AST variable tracking
|
|
14
|
+
this.entropyThreshold = 4.3;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parses target file data into an isolated AST representation and populates metadata structures.
|
|
19
|
+
* @param {string} filePath - Absolute path to on-disk component
|
|
20
|
+
* @param {Object} fileNode - In-memory structural graph reference node
|
|
21
|
+
*/
|
|
22
|
+
async processFile(filePath, fileNode) {
|
|
23
|
+
try {
|
|
24
|
+
const sourceText = await fs.readFile(filePath, 'utf8');
|
|
25
|
+
|
|
26
|
+
// Configure target extraction structures to parse TS, JSX, and modern TC39 specifications
|
|
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
|
+
);
|
|
34
|
+
|
|
35
|
+
this.walkNode(sourceFile, sourceFile, fileNode);
|
|
36
|
+
this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
} catch (parseError) {
|
|
40
|
+
if (this.context.verbose) {
|
|
41
|
+
console.error(`[AST Open Error] Failed compilation validation mapping on element: ${filePath}. Reason: ${parseError.message}`);
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Primary node walker loop executing atomic switch classifications.
|
|
49
|
+
* Challenge #7: Resolves conditional/destructured references to prevent cascading breakages.
|
|
50
|
+
*/
|
|
51
|
+
walkNode(sourceFile, node, fileNode) {
|
|
52
|
+
if (!node) return;
|
|
53
|
+
|
|
54
|
+
switch (node.kind) {
|
|
55
|
+
// Handle Explicit Named or Absolute Star Namespace Imports
|
|
56
|
+
case ts.SyntaxKind.ImportDeclaration: {
|
|
57
|
+
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
58
|
+
const specifier = node.moduleSpecifier.text;
|
|
59
|
+
fileNode.explicitImports.add(specifier);
|
|
60
|
+
|
|
61
|
+
if (node.importClause) {
|
|
62
|
+
// Trace named bounds: import { activeToken } from 'module';
|
|
63
|
+
if (node.importClause.namedBindings) {
|
|
64
|
+
if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
65
|
+
node.importClause.namedBindings.elements.forEach(element => {
|
|
66
|
+
const importedName = element.name.text;
|
|
67
|
+
const propertyName = element.propertyName ? element.propertyName.text : importedName;
|
|
68
|
+
fileNode.importedSymbols.add(`${specifier}:${propertyName}`);
|
|
69
|
+
});
|
|
70
|
+
} else if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
71
|
+
// Tracking total wildcard imports: import * as layout from 'module';
|
|
72
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Trace default bounds: import React from 'react';
|
|
76
|
+
if (node.importClause.name) {
|
|
77
|
+
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Handle Explicit Namespace Requirements: import config = require('./config');
|
|
85
|
+
case ts.SyntaxKind.ImportEqualsDeclaration: {
|
|
86
|
+
if (node.moduleReference && ts.isExternalModuleReference(node.moduleReference)) {
|
|
87
|
+
if (node.moduleReference.expression && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
88
|
+
fileNode.explicitImports.add(node.moduleReference.expression.text);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
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
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle Variable Declaration Assignments & Challenge #11 (AST Secret Scanning)
|
|
118
|
+
case ts.SyntaxKind.VariableDeclaration: {
|
|
119
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
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
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle Named Function Node Exports Matrix Configurations
|
|
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
|
+
}
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Handle Structural Class Definitions and Class Export Signatures
|
|
144
|
+
case ts.SyntaxKind.ClassDeclaration: {
|
|
145
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
146
|
+
const name = node.name.text;
|
|
147
|
+
if (this.hasExportModifier(node)) {
|
|
148
|
+
fileNode.internalExports.set(name, { type: 'class', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Handle Interface Definitions (Crucial for Challenge #10: Type Integrity Mapping)
|
|
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
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Handle Type Invocations and Declarations Aliases
|
|
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
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Handle Explicit Export Assignments: export default baselineConfiguration;
|
|
177
|
+
case ts.SyntaxKind.ExportAssignment: {
|
|
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
|
+
}
|
|
187
|
+
|
|
188
|
+
// Handle Arbitrary String References to catch deep framework routing or dynamic keys
|
|
189
|
+
case ts.SyntaxKind.StringLiteral: {
|
|
190
|
+
const text = node.text;
|
|
191
|
+
if (text.length > 2 && text.length < 120) {
|
|
192
|
+
fileNode.rawStringReferences.add(text);
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
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);
|
|
203
|
+
}
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Traverse recursively down the Node structural tree
|
|
209
|
+
ts.forEachChild(node, child => this.walkNode(sourceFile, child, fileNode));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Challenge #1: Evaluates math operations and template configurations inside dynamic imports.
|
|
214
|
+
*/
|
|
215
|
+
traceStringExpressions(node, collector) {
|
|
216
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.PlusToken) {
|
|
217
|
+
this.traceStringExpressions(node.left, collector);
|
|
218
|
+
this.traceStringExpressions(node.right, collector);
|
|
219
|
+
} else if (ts.isStringLiteral(node)) {
|
|
220
|
+
collector.push({ type: 'literal', val: node.text });
|
|
221
|
+
} else if (ts.isTemplateExpression(node)) {
|
|
222
|
+
if (node.head) collector.push({ type: 'template-slice', val: node.head.text });
|
|
223
|
+
node.templateSpans.forEach(span => {
|
|
224
|
+
collector.push({ type: 'dynamic-var', val: span.expression.getText() });
|
|
225
|
+
if (span.literal) collector.push({ type: 'template-slice', val: span.literal.text });
|
|
226
|
+
});
|
|
227
|
+
} else {
|
|
228
|
+
collector.push({ type: 'computed-variable', val: node.getText() });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Challenge #11: AST Secret Scanning. Evaluates entropy and patterns directly via assignments.
|
|
234
|
+
*/
|
|
235
|
+
auditAssignmentSafety(variableName, initializer, fileNode, sourceFile) {
|
|
236
|
+
// Process variable mapping target indicators first
|
|
237
|
+
if (this.hasExportModifier(variableName?.parent)) {
|
|
238
|
+
fileNode.internalExports.set(variableName, { type: 'variable', start: variableName.parent.getStart(sourceFile), end: variableName.parent.getEnd() });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (!initializer || !ts.isStringLiteral(initializer)) return;
|
|
242
|
+
const value = initializer.text;
|
|
243
|
+
|
|
244
|
+
// Challenge #11 Heuristic validation parameters matching variable patterns or contents values
|
|
245
|
+
const isSuspiciousKeyName = /api_?key|secret|token|password|auth_?token|private_?key/i.test(variableName);
|
|
246
|
+
const entropy = this.calculateShannonEntropy(value);
|
|
247
|
+
|
|
248
|
+
if ((isSuspiciousKeyName && value.length > 8) || (entropy > this.entropyThreshold && value.length > 16)) {
|
|
249
|
+
fileNode.securityThreats.push({
|
|
250
|
+
identifier: variableName,
|
|
251
|
+
entropy: parseFloat(entropy.toFixed(2)),
|
|
252
|
+
position: initializer.getStart(sourceFile),
|
|
253
|
+
riskCode: 'HIGH_RISK_SECRET_LEAK'
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
calculateShannonEntropy(str) {
|
|
259
|
+
const map = {};
|
|
260
|
+
for (let i = 0; i < str.length; i++) {
|
|
261
|
+
const char = str[i];
|
|
262
|
+
map[char] = (map[char] || 0) + 1;
|
|
263
|
+
}
|
|
264
|
+
let entropy = 0;
|
|
265
|
+
for (const char in map) {
|
|
266
|
+
const p = map[char] / str.length;
|
|
267
|
+
entropy -= p * Math.log2(p);
|
|
268
|
+
}
|
|
269
|
+
return entropy;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Challenge #18 & #8: Parse JSDoc suppression blocks right out of code statements.
|
|
274
|
+
*/
|
|
275
|
+
extractTopLevelJSDocSuppreessions(sourceFile, fileNode) {
|
|
276
|
+
const fullText = sourceFile.text;
|
|
277
|
+
const commentRanges = ts.getLeadingCommentRanges(fullText, 0) || [];
|
|
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
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
hasExportModifier(node) {
|
|
292
|
+
if (!node || !node.modifiers) return false;
|
|
293
|
+
return node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
isNodeDeclarationName(node) {
|
|
297
|
+
const parent = node.parent;
|
|
298
|
+
if (!parent) return false;
|
|
299
|
+
if (ts.isVariableDeclaration(parent) && parent.name === node) return true;
|
|
300
|
+
if (ts.isFunctionDeclaration(parent) && parent.name === node) return true;
|
|
301
|
+
if (ts.isClassDeclaration(parent) && parent.name === node) return true;
|
|
302
|
+
if (ts.isInterfaceDeclaration(parent) && parent.name === node) return true;
|
|
303
|
+
if (ts.isImportSpecifier(parent) && parent.name === node) return true;
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
getScriptKind(filePath) {
|
|
308
|
+
if (filePath.endsWith('.ts')) return ts.ScriptKind.TS;
|
|
309
|
+
if (filePath.endsWith('.tsx')) return ts.ScriptKind.TSX;
|
|
310
|
+
if (filePath.endsWith('.jsx')) return ts.ScriptKind.JSX;
|
|
311
|
+
return ts.ScriptKind.JS;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enterprise Wildcard Optimization & Flattening Layer for Barrel Operations
|
|
6
|
+
* Traces unreferenced paths through redistribution entries.
|
|
7
|
+
*/
|
|
8
|
+
export class BarrelParser {
|
|
9
|
+
constructor(context, resolver) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
this.resolver = resolver;
|
|
12
|
+
this.cachedSpecifications = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compiles the structural export layout for a specific index or barrel target.
|
|
17
|
+
* @param {string} filePath - Absolute path to on-disk target element
|
|
18
|
+
*/
|
|
19
|
+
async parseBarrelSpecification(filePath) {
|
|
20
|
+
if (this.cachedSpecifications.has(filePath)) {
|
|
21
|
+
return this.cachedSpecifications.get(filePath);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const specification = {
|
|
25
|
+
isBarrelInstance: false,
|
|
26
|
+
wildcardExports: new Set(), // export * from './module';
|
|
27
|
+
namespacedWildcardExports: new Map(), // export * as Utils from './module';
|
|
28
|
+
forwardedNamedExports: new Map(), // export { token as alias } from './module';
|
|
29
|
+
declaredLocalExports: new Set() // export const a = 1;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const code = await fs.readFile(filePath, 'utf8');
|
|
34
|
+
const sourceFile = ts.createSourceFile(filePath, code, ts.ScriptTarget.Latest, true);
|
|
35
|
+
|
|
36
|
+
this.harvestExportSignatures(sourceFile, specification);
|
|
37
|
+
|
|
38
|
+
if (specification.wildcardExports.size > 0 ||
|
|
39
|
+
specification.namespacedWildcardExports.size > 0 ||
|
|
40
|
+
specification.forwardedNamedExports.size > 0) {
|
|
41
|
+
specification.isBarrelInstance = true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.cachedSpecifications.set(filePath, specification);
|
|
45
|
+
return specification;
|
|
46
|
+
} catch {
|
|
47
|
+
return specification; // Error state defaults to safe boundaries layout manifest
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
harvestExportSignatures(node, spec) {
|
|
52
|
+
if (!node) return;
|
|
53
|
+
|
|
54
|
+
switch (node.kind) {
|
|
55
|
+
case ts.SyntaxKind.ExportDeclaration: {
|
|
56
|
+
const targetSpecifier = node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)
|
|
57
|
+
? node.moduleSpecifier.text
|
|
58
|
+
: null;
|
|
59
|
+
|
|
60
|
+
if (targetSpecifier) {
|
|
61
|
+
if (!node.exportClause) {
|
|
62
|
+
// Standard Wildcard Transfer: export * from './module';
|
|
63
|
+
spec.wildcardExports.add(targetSpecifier);
|
|
64
|
+
} else if (ts.isNamespaceExport(node.exportClause)) {
|
|
65
|
+
// Namespaced Wildcard Transfer: export * as Core from './module';
|
|
66
|
+
const namespaceAlias = node.exportClause.name.text;
|
|
67
|
+
spec.namespacedWildcardExports.set(namespaceAlias, targetSpecifier);
|
|
68
|
+
} else if (ts.isNamedExports(node.exportClause)) {
|
|
69
|
+
// Selective Re-export Forwarding: export { x, y as z } from './module';
|
|
70
|
+
node.exportClause.elements.forEach(element => {
|
|
71
|
+
const originalSymbol = element.propertyName ? element.propertyName.text : element.name.text;
|
|
72
|
+
const exposedAlias = element.name.text;
|
|
73
|
+
spec.forwardedNamedExports.set(exposedAlias, {
|
|
74
|
+
targetModule: targetSpecifier,
|
|
75
|
+
sourceSymbol: originalSymbol
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Track items declared directly within the file boundary
|
|
84
|
+
case ts.SyntaxKind.FunctionDeclaration:
|
|
85
|
+
case ts.SyntaxKind.ClassDeclaration:
|
|
86
|
+
case ts.SyntaxKind.InterfaceDeclaration:
|
|
87
|
+
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
88
|
+
case ts.SyntaxKind.EnumDeclaration: {
|
|
89
|
+
if (node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
90
|
+
if (node.name && ts.isIdentifier(node.name)) {
|
|
91
|
+
spec.declaredLocalExports.add(node.name.text);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case ts.SyntaxKind.VariableStatement: {
|
|
98
|
+
if (node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
99
|
+
node.declarationList.declarations.forEach(decl => {
|
|
100
|
+
if (ts.isIdentifier(decl.name)) {
|
|
101
|
+
spec.declaredLocalExports.add(decl.name.text);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
ts.forEachChild(node, child => this.harvestExportSignatures(child, spec));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Challenge #3 Resolution Logic. Unwraps nested redistribution links to find origin source nodes.
|
|
114
|
+
* @param {string} contextFilePath - Current position filename vector pointer
|
|
115
|
+
* @param {string} targetSymbolName - Targeted semantic variable signature name
|
|
116
|
+
* @param {Map} activeProjectGraph - Global active module maps directory registry
|
|
117
|
+
* @param {Set} [protectionStack] - Avoids cyclic validation traps inside self-referencing links
|
|
118
|
+
*/
|
|
119
|
+
async determineSymbolDeclarationOrigin(contextFilePath, targetSymbolName, activeProjectGraph, protectionStack = new Set()) {
|
|
120
|
+
if (protectionStack.has(contextFilePath)) {
|
|
121
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
122
|
+
}
|
|
123
|
+
protectionStack.add(contextFilePath);
|
|
124
|
+
|
|
125
|
+
const spec = await this.parseBarrelSpecification(contextFilePath);
|
|
126
|
+
if (!spec.isBarrelInstance) {
|
|
127
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Rule A: Settle boundary immediately if local declaration matches token signature name
|
|
131
|
+
if (spec.declaredLocalExports.has(targetSymbolName)) {
|
|
132
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Rule B: Evaluate explicit named re-export mappings
|
|
136
|
+
if (spec.forwardedNamedExports.has(targetSymbolName)) {
|
|
137
|
+
const routingRule = spec.forwardedNamedExports.get(targetSymbolName);
|
|
138
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, routingRule.targetModule);
|
|
139
|
+
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, routingRule.sourceSymbol, activeProjectGraph, protectionStack);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Rule C: Evaluate structural namespace alias groupings
|
|
143
|
+
if (spec.namespacedWildcardExports.has(targetSymbolName)) {
|
|
144
|
+
const relativeModule = spec.namespacedWildcardExports.get(targetSymbolName);
|
|
145
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativeModule);
|
|
146
|
+
return { originFile: fullyResolvedPath, originSymbol: '*' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Rule D: Sweep through anonymous star re-exports vectors
|
|
150
|
+
for (const relativePath of spec.wildcardExports) {
|
|
151
|
+
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
|
|
152
|
+
|
|
153
|
+
// Look inside the graph metadata map configuration to verify child target availability
|
|
154
|
+
const targetSubSpec = await this.parseBarrelSpecification(fullyResolvedPath);
|
|
155
|
+
|
|
156
|
+
if (targetSubSpec.declaredLocalExports.has(targetSymbolName) ||
|
|
157
|
+
targetSubSpec.forwardedNamedExports.has(targetSymbolName)) {
|
|
158
|
+
return this.determineSymbolDeclarationOrigin(fullyResolvedPath, targetSymbolName, activeProjectGraph, protectionStack);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Dynamic recursive trace for multi-tier nested barrel file layouts
|
|
162
|
+
if (targetSubSpec.isBarrelInstance) {
|
|
163
|
+
const continuousResolutionTrace = await this.determineSymbolDeclarationOrigin(
|
|
164
|
+
fullyResolvedPath,
|
|
165
|
+
targetSymbolName,
|
|
166
|
+
activeProjectGraph,
|
|
167
|
+
protectionStack
|
|
168
|
+
);
|
|
169
|
+
if (continuousResolutionTrace && continuousResolutionTrace.originFile !== fullyResolvedPath) {
|
|
170
|
+
return continuousResolutionTrace;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { originFile: contextFilePath, originSymbol: targetSymbolName };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Ecosystem Entry Point Manifest & Dynamic Framework Router Heuristic Validator
|
|
6
|
+
* Intercepts implicit conventions to handle cases where direct import statements are absent.
|
|
7
|
+
*/
|
|
8
|
+
export class MagicDetector {
|
|
9
|
+
constructor(context) {
|
|
10
|
+
this.context = context;
|
|
11
|
+
this.manifestSchemaRules = this.compileEcosystemSchemaMatrices();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Compiles explicit layout definitions to handle various web development environments.
|
|
16
|
+
*/
|
|
17
|
+
compileEcosystemSchemaMatrices() {
|
|
18
|
+
return {
|
|
19
|
+
nextjs: {
|
|
20
|
+
configFiles: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
|
|
21
|
+
routePatterns: [
|
|
22
|
+
/\/pages\/api\//,
|
|
23
|
+
/\/pages\/[a-zA-Z0-9_\-\[\]]+/i,
|
|
24
|
+
/\/app\/([\w\-\[\]]+\/)+(page|route|layout|loading|error|not-found)\.(ts|tsx|js|jsx)$/
|
|
25
|
+
],
|
|
26
|
+
requiredSystemContracts: ['default', 'getServerSideProps', 'getStaticProps', 'getStaticPaths', 'generateMetadata', 'middleware']
|
|
27
|
+
},
|
|
28
|
+
nuxt: {
|
|
29
|
+
configFiles: ['nuxt.config.js', 'nuxt.config.ts'],
|
|
30
|
+
routePatterns: [
|
|
31
|
+
/\/pages\//,
|
|
32
|
+
/\/server\/(api|routes|middleware)\//,
|
|
33
|
+
/\/components\/[a-zA-Z0-9_\-\/]+\.vue$/
|
|
34
|
+
],
|
|
35
|
+
requiredSystemContracts: ['default']
|
|
36
|
+
},
|
|
37
|
+
remix: {
|
|
38
|
+
configFiles: ['remix.config.js', 'vite.config.js', 'vite.config.ts'],
|
|
39
|
+
routePatterns: [
|
|
40
|
+
/\/app\/routes\//,
|
|
41
|
+
/\/app\/root\.(tsx|jsx)$/
|
|
42
|
+
],
|
|
43
|
+
requiredSystemContracts: ['default', 'loader', 'action', 'meta', 'links']
|
|
44
|
+
},
|
|
45
|
+
sveltekit: {
|
|
46
|
+
configFiles: ['svelte.config.js', 'vite.config.ts'],
|
|
47
|
+
routePatterns: [
|
|
48
|
+
/\+page\.(svelte|ts|js)$/,
|
|
49
|
+
/\+page\.server\.(ts|js)$/,
|
|
50
|
+
/\+layout\.(svelte|ts|js)$/,
|
|
51
|
+
/\+server\.(ts|js)$/
|
|
52
|
+
],
|
|
53
|
+
requiredSystemContracts: ['load', 'actions', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH']
|
|
54
|
+
},
|
|
55
|
+
astro: {
|
|
56
|
+
configFiles: ['astro.config.mjs', 'astro.config.cjs', 'astro.config.ts'],
|
|
57
|
+
routePatterns: [
|
|
58
|
+
/\/src\/pages\/.*\.astro$/,
|
|
59
|
+
/\/src\/pages\/.*\.(ts|js)$/
|
|
60
|
+
],
|
|
61
|
+
requiredSystemContracts: ['default', 'getStaticPaths']
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Audits the project context to map active micro-framework ecosystems.
|
|
68
|
+
* @param {string} baseContextDirectory - Root file workspace context execution vector path
|
|
69
|
+
*/
|
|
70
|
+
async identifyActiveProjectEcosystems(baseContextDirectory) {
|
|
71
|
+
const activeFrameworkFlags = [];
|
|
72
|
+
|
|
73
|
+
for (const [frameworkKey, criteria] of Object.entries(this.manifestSchemaRules)) {
|
|
74
|
+
for (const configFile of criteria.configFiles) {
|
|
75
|
+
try {
|
|
76
|
+
await fs.access(path.join(baseContextDirectory, configFile));
|
|
77
|
+
activeFrameworkFlags.push(frameworkKey);
|
|
78
|
+
break; // Stop scanning once config criteria is found
|
|
79
|
+
} catch {
|
|
80
|
+
// File path criteria absent; proceed to standard verification loops
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Universal infrastructure overrides (testing platforms and common bundlers)
|
|
86
|
+
activeFrameworkFlags.push('universal-tooling-vectors');
|
|
87
|
+
return activeFrameworkFlags;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Assesses if a file path acts as an implicit route entry point.
|
|
92
|
+
*/
|
|
93
|
+
isImplicitlyRequiredByEcosystem(absolutePath, activeFrameworks) {
|
|
94
|
+
const normalizedSystemPath = absolutePath.replace(/\\/g, '/');
|
|
95
|
+
|
|
96
|
+
for (const framework of activeFrameworks) {
|
|
97
|
+
const frameworkRules = this.manifestSchemaRules[framework];
|
|
98
|
+
if (!frameworkRules) continue;
|
|
99
|
+
|
|
100
|
+
const matchesPattern = frameworkRules.routePatterns.some(regex => regex.test(normalizedSystemPath));
|
|
101
|
+
if (matchesPattern) return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Apply baseline platform rules (Test suites, lint parameters, continuous integration files)
|
|
105
|
+
if (this.isCoreToolingSuiteElement(normalizedSystemPath)) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
isCoreToolingSuiteElement(normalizedPath) {
|
|
113
|
+
// Testing and execution matrices rules configuration keys
|
|
114
|
+
if (/\.(test|spec|e2e|cy)\.(js|ts|tsx|jsx|stories\.tsx|stories\.ts)$/i.test(normalizedPath)) return true;
|
|
115
|
+
|
|
116
|
+
// Testing tools and structural environment frameworks configuration keys
|
|
117
|
+
const testEnvironments = [
|
|
118
|
+
'jest.config.', 'vitest.config.', 'playwright.config.', 'cypress.config.',
|
|
119
|
+
'webpack.config.', 'vite.config.', 'rollup.config.', 'tailwind.config.',
|
|
120
|
+
'.eslintrc.', 'prettier.config.', '.postcssrc.', 'postcss.config.'
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
return testEnvironments.some(matchPattern => normalizedPath.includes(matchPattern));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Challenge #4 Framework Overrides. Protects interface boundaries from false positive report flags.
|
|
128
|
+
*/
|
|
129
|
+
injectVirtualConsumerEdges(filePath, fileNode, activeFrameworks) {
|
|
130
|
+
if (!this.isImplicitlyRequiredByEcosystem(filePath, activeFrameworks)) return;
|
|
131
|
+
|
|
132
|
+
// Retain entry point elements within memory to keep verification safe
|
|
133
|
+
fileNode.isLibraryEntry = true;
|
|
134
|
+
|
|
135
|
+
// Apply dynamic exports coverage metrics based on active platform contracts
|
|
136
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
137
|
+
|
|
138
|
+
for (const framework of activeFrameworks) {
|
|
139
|
+
const frameworkRules = this.manifestSchemaRules[framework];
|
|
140
|
+
if (!frameworkRules) continue;
|
|
141
|
+
|
|
142
|
+
// If the file path matches the active framework schema, protect its interface keywords
|
|
143
|
+
const appliesToFramework = frameworkRules.routePatterns.some(regex => regex.test(normalizedPath));
|
|
144
|
+
if (appliesToFramework) {
|
|
145
|
+
frameworkRules.requiredSystemContracts.forEach(contractMethodToken => {
|
|
146
|
+
if (fileNode.internalExports.has(contractMethodToken)) {
|
|
147
|
+
// Emulate active local reference linkages to protect the export
|
|
148
|
+
fileNode.instantiatedIdentifiers.add(contractMethodToken);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|