pkg-scaffold 3.0.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 +62 -52
- package/bin/cli.js +81 -1
- 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 +40 -3
- package/src/ast/ASTAnalyzer.js +192 -251
- package/src/ast/MagicDetector.js +39 -86
- package/src/ast/OxcAnalyzer.js +114 -0
- package/src/index.js +26 -0
- package/src/performance/GraphCache.js +2 -1
- package/src/performance/SupplyChainGuard.js +41 -55
- package/src/performance/WorkerPool.js +8 -0
- package/src/performance/WorkerTaskRunner.js +7 -2
- 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/DependencyProfiler.js +90 -0
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -1,325 +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
96
|
}
|
|
97
|
+
}
|
|
93
98
|
|
|
94
|
-
|
|
95
|
-
case ts.SyntaxKind.
|
|
96
|
-
|
|
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
|
-
}
|
|
99
|
+
switch (node.kind) {
|
|
100
|
+
case ts.SyntaxKind.ImportDeclaration: {
|
|
101
|
+
this.handleImportDeclaration(node, fileNode);
|
|
114
102
|
break;
|
|
115
103
|
}
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
}
|
|
104
|
+
case ts.SyntaxKind.ExportDeclaration: {
|
|
105
|
+
this.handleExportDeclaration(node, fileNode, sourceFile);
|
|
129
106
|
break;
|
|
130
107
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (this.hasExportModifier(node)) {
|
|
139
|
-
const isDefaultExport = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
|
|
140
|
-
if (isDefaultExport) {
|
|
141
|
-
// Register under 'default'; also store the real name as referencedSymbol for diagnostics
|
|
142
|
-
const realName = node.name && ts.isIdentifier(node.name) ? node.name.text : 'anonymous';
|
|
143
|
-
fileNode.internalExports.set('default', { type: 'default-function', referencedSymbol: realName, start: node.getStart(sourceFile), end: node.getEnd() });
|
|
144
|
-
} else if (node.name && ts.isIdentifier(node.name)) {
|
|
145
|
-
fileNode.internalExports.set(node.name.text, { type: 'function', start: node.getStart(sourceFile), end: node.getEnd() });
|
|
146
|
-
}
|
|
147
|
-
}
|
|
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);
|
|
148
115
|
break;
|
|
149
116
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
case ts.SyntaxKind.ClassDeclaration: {
|
|
153
|
-
if (this.hasExportModifier(node)) {
|
|
154
|
-
const isDefaultExport = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
|
|
155
|
-
if (isDefaultExport) {
|
|
156
|
-
const realName = node.name && ts.isIdentifier(node.name) ? node.name.text : 'anonymous';
|
|
157
|
-
fileNode.internalExports.set('default', { type: 'default-class', referencedSymbol: realName, start: node.getStart(sourceFile), end: node.getEnd() });
|
|
158
|
-
} else if (node.name && ts.isIdentifier(node.name)) {
|
|
159
|
-
fileNode.internalExports.set(node.name.text, { type: 'class', 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.InterfaceDeclaration: {
|
|
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: 'interface', 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
|
+
}
|
|
126
|
+
|
|
127
|
+
ts.forEachChild(node, child => this.walkNode(sourceFile, child, fileNode));
|
|
128
|
+
}
|
|
175
129
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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}:*`);
|
|
182
145
|
}
|
|
183
146
|
}
|
|
184
|
-
|
|
147
|
+
if (node.importClause.name) {
|
|
148
|
+
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
149
|
+
}
|
|
185
150
|
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
186
153
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
});
|
|
195
167
|
});
|
|
196
|
-
|
|
168
|
+
} else {
|
|
169
|
+
// export * from './y'
|
|
170
|
+
fileNode.internalExports.set('*', { type: 're-export-all', source: specifier });
|
|
197
171
|
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
198
174
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
+
};
|
|
207
185
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
+
});
|
|
214
208
|
}
|
|
215
|
-
|
|
209
|
+
exportInfo.members = members;
|
|
216
210
|
}
|
|
217
|
-
}
|
|
218
211
|
|
|
219
|
-
|
|
220
|
-
|
|
212
|
+
const loc = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
213
|
+
fileNode.symbolSourceLocations.set(name, { line: loc.line + 1, column: loc.character + 1 });
|
|
214
|
+
}
|
|
221
215
|
}
|
|
222
216
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
node.templateSpans.forEach(span => {
|
|
235
|
-
collector.push({ type: 'dynamic-var', val: span.expression.getText() });
|
|
236
|
-
if (span.literal) collector.push({ type: 'template-slice', val: span.literal.text });
|
|
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
|
+
}
|
|
237
228
|
});
|
|
238
|
-
} else {
|
|
239
|
-
collector.push({ type: 'computed-variable', val: node.getText() });
|
|
240
229
|
}
|
|
241
230
|
}
|
|
242
231
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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);
|
|
239
|
+
}
|
|
250
240
|
}
|
|
241
|
+
}
|
|
251
242
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
// Challenge #11 Heuristic validation parameters matching variable patterns or contents values
|
|
256
|
-
const isSuspiciousKeyName = /api_?key|secret|token|password|auth_?token|private_?key/i.test(variableName);
|
|
257
|
-
const entropy = this.calculateShannonEntropy(value);
|
|
258
|
-
|
|
259
|
-
if ((isSuspiciousKeyName && value.length > 8) || (entropy > this.entropyThreshold && value.length > 16)) {
|
|
260
|
-
fileNode.securityThreats.push({
|
|
261
|
-
identifier: variableName,
|
|
262
|
-
entropy: parseFloat(entropy.toFixed(2)),
|
|
263
|
-
position: initializer.getStart(sourceFile),
|
|
264
|
-
riskCode: 'HIGH_RISK_SECRET_LEAK'
|
|
265
|
-
});
|
|
266
|
-
}
|
|
243
|
+
hasExportModifier(node) {
|
|
244
|
+
return node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
267
245
|
}
|
|
268
246
|
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
let entropy = 0;
|
|
276
|
-
for (const char in map) {
|
|
277
|
-
const p = map[char] / str.length;
|
|
278
|
-
entropy -= p * Math.log2(p);
|
|
279
|
-
}
|
|
280
|
-
return entropy;
|
|
247
|
+
isNodeDeclarationName(node) {
|
|
248
|
+
const parent = node.parent;
|
|
249
|
+
if (!parent) 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;
|
|
281
253
|
}
|
|
282
254
|
|
|
283
|
-
/**
|
|
284
|
-
* Challenge #18 & #8: Parse JSDoc suppression blocks right out of code statements.
|
|
285
|
-
*/
|
|
286
255
|
extractTopLevelJSDocSuppreessions(sourceFile, fileNode) {
|
|
287
256
|
const fullText = sourceFile.text;
|
|
288
|
-
// Scan all comments in the file for @scaffold-suppress
|
|
289
257
|
const commentRegex = /\/\*\*?[\s\S]*?\*\/|\/\/.*/g;
|
|
290
258
|
let match;
|
|
291
259
|
while ((match = commentRegex.exec(fullText)) !== null) {
|
|
292
|
-
const
|
|
293
|
-
const suppressMatches = comment.match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
|
|
260
|
+
const suppressMatches = match[0].match(/@scaffold-suppress\s+([a-zA-Z0-9_\-*:]+)/g);
|
|
294
261
|
if (suppressMatches) {
|
|
295
|
-
suppressMatches.forEach(m =>
|
|
296
|
-
const directive = m.replace('@scaffold-suppress', '').trim();
|
|
297
|
-
fileNode.localSuppressedRules.add(directive);
|
|
298
|
-
});
|
|
262
|
+
suppressMatches.forEach(m => fileNode.localSuppressedRules.add(m.replace('@scaffold-suppress', '').trim()));
|
|
299
263
|
}
|
|
300
264
|
}
|
|
301
265
|
}
|
|
302
|
-
|
|
303
|
-
hasExportModifier(node) {
|
|
304
|
-
if (!node || !node.modifiers) return false;
|
|
305
|
-
return node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
isNodeDeclarationName(node) {
|
|
309
|
-
const parent = node.parent;
|
|
310
|
-
if (!parent) return false;
|
|
311
|
-
if (ts.isVariableDeclaration(parent) && parent.name === node) return true;
|
|
312
|
-
if (ts.isFunctionDeclaration(parent) && parent.name === node) return true;
|
|
313
|
-
if (ts.isClassDeclaration(parent) && parent.name === node) return true;
|
|
314
|
-
if (ts.isInterfaceDeclaration(parent) && parent.name === node) return true;
|
|
315
|
-
if (ts.isImportSpecifier(parent) && parent.name === node) return true;
|
|
316
|
-
return false;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
getScriptKind(filePath) {
|
|
320
|
-
if (filePath.endsWith('.ts')) return ts.ScriptKind.TS;
|
|
321
|
-
if (filePath.endsWith('.tsx')) return ts.ScriptKind.TSX;
|
|
322
|
-
if (filePath.endsWith('.jsx')) return ts.ScriptKind.JSX;
|
|
323
|
-
return ts.ScriptKind.JS;
|
|
324
|
-
}
|
|
325
266
|
}
|