driftdetect-core 0.4.1 → 0.4.2
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/dist/boundaries/boundary-scanner.d.ts +76 -0
- package/dist/boundaries/boundary-scanner.d.ts.map +1 -0
- package/dist/boundaries/boundary-scanner.js +801 -0
- package/dist/boundaries/boundary-scanner.js.map +1 -0
- package/dist/boundaries/data-access-learner.d.ts +126 -0
- package/dist/boundaries/data-access-learner.d.ts.map +1 -0
- package/dist/boundaries/data-access-learner.js +486 -0
- package/dist/boundaries/data-access-learner.js.map +1 -0
- package/dist/boundaries/index.d.ts +6 -0
- package/dist/boundaries/index.d.ts.map +1 -1
- package/dist/boundaries/index.js +6 -0
- package/dist/boundaries/index.js.map +1 -1
- package/dist/boundaries/security-prioritizer.d.ts +118 -0
- package/dist/boundaries/security-prioritizer.d.ts.map +1 -0
- package/dist/boundaries/security-prioritizer.js +316 -0
- package/dist/boundaries/security-prioritizer.js.map +1 -0
- package/dist/call-graph/analysis/coverage-analyzer.d.ts +201 -0
- package/dist/call-graph/analysis/coverage-analyzer.d.ts.map +1 -0
- package/dist/call-graph/analysis/coverage-analyzer.js +553 -0
- package/dist/call-graph/analysis/coverage-analyzer.js.map +1 -0
- package/dist/call-graph/analysis/dead-code-detector.d.ts +145 -0
- package/dist/call-graph/analysis/dead-code-detector.d.ts.map +1 -0
- package/dist/call-graph/analysis/dead-code-detector.js +391 -0
- package/dist/call-graph/analysis/dead-code-detector.js.map +1 -0
- package/dist/call-graph/analysis/graph-builder.d.ts +142 -0
- package/dist/call-graph/analysis/graph-builder.d.ts.map +1 -0
- package/dist/call-graph/analysis/graph-builder.js +624 -0
- package/dist/call-graph/analysis/graph-builder.js.map +1 -0
- package/dist/call-graph/analysis/impact-analyzer.d.ts +150 -0
- package/dist/call-graph/analysis/impact-analyzer.d.ts.map +1 -0
- package/dist/call-graph/analysis/impact-analyzer.js +329 -0
- package/dist/call-graph/analysis/impact-analyzer.js.map +1 -0
- package/dist/call-graph/analysis/index.d.ts +11 -0
- package/dist/call-graph/analysis/index.d.ts.map +1 -0
- package/dist/call-graph/analysis/index.js +9 -0
- package/dist/call-graph/analysis/index.js.map +1 -0
- package/dist/call-graph/analysis/path-finder.d.ts +117 -0
- package/dist/call-graph/analysis/path-finder.d.ts.map +1 -0
- package/dist/call-graph/analysis/path-finder.js +360 -0
- package/dist/call-graph/analysis/path-finder.js.map +1 -0
- package/dist/call-graph/analysis/reachability.d.ts +56 -0
- package/dist/call-graph/analysis/reachability.d.ts.map +1 -0
- package/dist/call-graph/analysis/reachability.js +357 -0
- package/dist/call-graph/analysis/reachability.js.map +1 -0
- package/dist/call-graph/demo.d.ts +11 -0
- package/dist/call-graph/demo.d.ts.map +1 -0
- package/dist/call-graph/demo.js +339 -0
- package/dist/call-graph/demo.js.map +1 -0
- package/dist/call-graph/enrichment/enrichment-engine.d.ts +126 -0
- package/dist/call-graph/enrichment/enrichment-engine.d.ts.map +1 -0
- package/dist/call-graph/enrichment/enrichment-engine.js +760 -0
- package/dist/call-graph/enrichment/enrichment-engine.js.map +1 -0
- package/dist/call-graph/enrichment/impact-scorer.d.ts +59 -0
- package/dist/call-graph/enrichment/impact-scorer.d.ts.map +1 -0
- package/dist/call-graph/enrichment/impact-scorer.js +328 -0
- package/dist/call-graph/enrichment/impact-scorer.js.map +1 -0
- package/dist/call-graph/enrichment/index.d.ts +12 -0
- package/dist/call-graph/enrichment/index.d.ts.map +1 -0
- package/dist/call-graph/enrichment/index.js +15 -0
- package/dist/call-graph/enrichment/index.js.map +1 -0
- package/dist/call-graph/enrichment/remediation-generator.d.ts +41 -0
- package/dist/call-graph/enrichment/remediation-generator.d.ts.map +1 -0
- package/dist/call-graph/enrichment/remediation-generator.js +609 -0
- package/dist/call-graph/enrichment/remediation-generator.js.map +1 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.d.ts +71 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.d.ts.map +1 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.js +454 -0
- package/dist/call-graph/enrichment/sensitivity-classifier.js.map +1 -0
- package/dist/call-graph/enrichment/types.d.ts +402 -0
- package/dist/call-graph/enrichment/types.d.ts.map +1 -0
- package/dist/call-graph/enrichment/types.js +9 -0
- package/dist/call-graph/enrichment/types.js.map +1 -0
- package/dist/call-graph/extractors/base-extractor.d.ts +112 -0
- package/dist/call-graph/extractors/base-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/base-extractor.js +140 -0
- package/dist/call-graph/extractors/base-extractor.js.map +1 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts +76 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.js +387 -0
- package/dist/call-graph/extractors/csharp-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/csharp-extractor.d.ts +87 -0
- package/dist/call-graph/extractors/csharp-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/csharp-extractor.js +470 -0
- package/dist/call-graph/extractors/csharp-extractor.js.map +1 -0
- package/dist/call-graph/extractors/data-access-extractor.d.ts +76 -0
- package/dist/call-graph/extractors/data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/data-access-extractor.js +234 -0
- package/dist/call-graph/extractors/data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/index.d.ts +26 -0
- package/dist/call-graph/extractors/index.d.ts.map +1 -0
- package/dist/call-graph/extractors/index.js +36 -0
- package/dist/call-graph/extractors/index.js.map +1 -0
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts +101 -0
- package/dist/call-graph/extractors/java-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/java-data-access-extractor.js +611 -0
- package/dist/call-graph/extractors/java-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/java-extractor.d.ts +87 -0
- package/dist/call-graph/extractors/java-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/java-extractor.js +510 -0
- package/dist/call-graph/extractors/java-extractor.js.map +1 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts +93 -0
- package/dist/call-graph/extractors/php-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/php-data-access-extractor.js +589 -0
- package/dist/call-graph/extractors/php-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/php-extractor.d.ts +104 -0
- package/dist/call-graph/extractors/php-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/php-extractor.js +619 -0
- package/dist/call-graph/extractors/php-extractor.js.map +1 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts +90 -0
- package/dist/call-graph/extractors/python-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/python-data-access-extractor.js +537 -0
- package/dist/call-graph/extractors/python-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/python-extractor.d.ts +98 -0
- package/dist/call-graph/extractors/python-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/python-extractor.js +681 -0
- package/dist/call-graph/extractors/python-extractor.js.map +1 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts +91 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.d.ts.map +1 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.js +498 -0
- package/dist/call-graph/extractors/semantic-data-access-scanner.js.map +1 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts +122 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.js +788 -0
- package/dist/call-graph/extractors/typescript-data-access-extractor.js.map +1 -0
- package/dist/call-graph/extractors/typescript-extractor.d.ts +145 -0
- package/dist/call-graph/extractors/typescript-extractor.d.ts.map +1 -0
- package/dist/call-graph/extractors/typescript-extractor.js +904 -0
- package/dist/call-graph/extractors/typescript-extractor.js.map +1 -0
- package/dist/call-graph/index.d.ts +127 -0
- package/dist/call-graph/index.d.ts.map +1 -0
- package/dist/call-graph/index.js +247 -0
- package/dist/call-graph/index.js.map +1 -0
- package/dist/call-graph/store/call-graph-store.d.ts +70 -0
- package/dist/call-graph/store/call-graph-store.d.ts.map +1 -0
- package/dist/call-graph/store/call-graph-store.js +210 -0
- package/dist/call-graph/store/call-graph-store.js.map +1 -0
- package/dist/call-graph/store/index.d.ts +7 -0
- package/dist/call-graph/store/index.d.ts.map +1 -0
- package/dist/call-graph/store/index.js +7 -0
- package/dist/call-graph/store/index.js.map +1 -0
- package/dist/call-graph/types.d.ts +376 -0
- package/dist/call-graph/types.d.ts.map +1 -0
- package/dist/call-graph/types.js +8 -0
- package/dist/call-graph/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/lake/callgraph-shard-store.d.ts +168 -0
- package/dist/lake/callgraph-shard-store.d.ts.map +1 -0
- package/dist/lake/callgraph-shard-store.js +466 -0
- package/dist/lake/callgraph-shard-store.js.map +1 -0
- package/dist/lake/examples-store.d.ts +127 -0
- package/dist/lake/examples-store.d.ts.map +1 -0
- package/dist/lake/examples-store.js +389 -0
- package/dist/lake/examples-store.js.map +1 -0
- package/dist/lake/index-store.d.ts +82 -0
- package/dist/lake/index-store.d.ts.map +1 -0
- package/dist/lake/index-store.js +359 -0
- package/dist/lake/index-store.js.map +1 -0
- package/dist/lake/index.d.ts +93 -0
- package/dist/lake/index.d.ts.map +1 -0
- package/dist/lake/index.js +138 -0
- package/dist/lake/index.js.map +1 -0
- package/dist/lake/lake.bak/index-store.d.ts +82 -0
- package/dist/lake/lake.bak/index-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/index-store.js +357 -0
- package/dist/lake/lake.bak/index-store.js.map +1 -0
- package/dist/lake/lake.bak/index.d.ts +81 -0
- package/dist/lake/lake.bak/index.d.ts.map +1 -0
- package/dist/lake/lake.bak/index.js +114 -0
- package/dist/lake/lake.bak/index.js.map +1 -0
- package/dist/lake/lake.bak/manifest-store.d.ts +51 -0
- package/dist/lake/lake.bak/manifest-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/manifest-store.js +347 -0
- package/dist/lake/lake.bak/manifest-store.js.map +1 -0
- package/dist/lake/lake.bak/query-engine.d.ts +112 -0
- package/dist/lake/lake.bak/query-engine.d.ts.map +1 -0
- package/dist/lake/lake.bak/query-engine.js +370 -0
- package/dist/lake/lake.bak/query-engine.js.map +1 -0
- package/dist/lake/lake.bak/types.d.ts +428 -0
- package/dist/lake/lake.bak/types.d.ts.map +1 -0
- package/dist/lake/lake.bak/types.js +46 -0
- package/dist/lake/lake.bak/types.js.map +1 -0
- package/dist/lake/lake.bak/view-materializer.d.ts +70 -0
- package/dist/lake/lake.bak/view-materializer.d.ts.map +1 -0
- package/dist/lake/lake.bak/view-materializer.js +314 -0
- package/dist/lake/lake.bak/view-materializer.js.map +1 -0
- package/dist/lake/lake.bak/view-store.d.ts +57 -0
- package/dist/lake/lake.bak/view-store.d.ts.map +1 -0
- package/dist/lake/lake.bak/view-store.js +348 -0
- package/dist/lake/lake.bak/view-store.js.map +1 -0
- package/dist/lake/manifest-store.d.ts +51 -0
- package/dist/lake/manifest-store.d.ts.map +1 -0
- package/dist/lake/manifest-store.js +348 -0
- package/dist/lake/manifest-store.js.map +1 -0
- package/dist/lake/pattern-shard-store.d.ts +87 -0
- package/dist/lake/pattern-shard-store.d.ts.map +1 -0
- package/dist/lake/pattern-shard-store.js +347 -0
- package/dist/lake/pattern-shard-store.js.map +1 -0
- package/dist/lake/query-engine.d.ts +124 -0
- package/dist/lake/query-engine.d.ts.map +1 -0
- package/dist/lake/query-engine.js +453 -0
- package/dist/lake/query-engine.js.map +1 -0
- package/dist/lake/security-shard-store.d.ts +156 -0
- package/dist/lake/security-shard-store.d.ts.map +1 -0
- package/dist/lake/security-shard-store.js +498 -0
- package/dist/lake/security-shard-store.js.map +1 -0
- package/dist/lake/types.d.ts +428 -0
- package/dist/lake/types.d.ts.map +1 -0
- package/dist/lake/types.js +46 -0
- package/dist/lake/types.js.map +1 -0
- package/dist/lake/view-materializer.d.ts +70 -0
- package/dist/lake/view-materializer.d.ts.map +1 -0
- package/dist/lake/view-materializer.js +314 -0
- package/dist/lake/view-materializer.js.map +1 -0
- package/dist/lake/view-store.d.ts +57 -0
- package/dist/lake/view-store.d.ts.map +1 -0
- package/dist/lake/view-store.js +348 -0
- package/dist/lake/view-store.js.map +1 -0
- package/dist/parsers/tree-sitter/index.d.ts +1 -0
- package/dist/parsers/tree-sitter/index.d.ts.map +1 -1
- package/dist/parsers/tree-sitter/index.js +4 -0
- package/dist/parsers/tree-sitter/index.js.map +1 -1
- package/dist/parsers/tree-sitter/typescript-loader.d.ts +58 -0
- package/dist/parsers/tree-sitter/typescript-loader.d.ts.map +1 -0
- package/dist/parsers/tree-sitter/typescript-loader.js +250 -0
- package/dist/parsers/tree-sitter/typescript-loader.js.map +1 -0
- package/dist/store/project-config.d.ts +154 -0
- package/dist/store/project-config.d.ts.map +1 -0
- package/dist/store/project-config.js +235 -0
- package/dist/store/project-config.js.map +1 -0
- package/dist/store/project-registry.d.ts +241 -0
- package/dist/store/project-registry.d.ts.map +1 -0
- package/dist/store/project-registry.js +557 -0
- package/dist/store/project-registry.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript/JavaScript Call Graph Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts functions, calls, imports, and exports from TypeScript/JavaScript
|
|
5
|
+
* using the TypeScript Compiler API.
|
|
6
|
+
*
|
|
7
|
+
* Handles:
|
|
8
|
+
* - Function declarations, arrow functions, function expressions
|
|
9
|
+
* - Nested functions (closures)
|
|
10
|
+
* - Class methods and constructors
|
|
11
|
+
* - JSX component usage
|
|
12
|
+
* - Module-level calls (top-level code)
|
|
13
|
+
* - Callback patterns (functions passed as arguments)
|
|
14
|
+
* - IIFE patterns
|
|
15
|
+
*/
|
|
16
|
+
import ts from 'typescript';
|
|
17
|
+
import { BaseCallGraphExtractor } from './base-extractor.js';
|
|
18
|
+
/**
|
|
19
|
+
* TypeScript/JavaScript call graph extractor
|
|
20
|
+
*/
|
|
21
|
+
export class TypeScriptCallGraphExtractor extends BaseCallGraphExtractor {
|
|
22
|
+
language = 'typescript';
|
|
23
|
+
extensions = ['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts', '.mjs', '.cjs'];
|
|
24
|
+
/**
|
|
25
|
+
* Extract call graph information from TypeScript/JavaScript source
|
|
26
|
+
*/
|
|
27
|
+
extract(source, filePath) {
|
|
28
|
+
const result = this.createEmptyResult(filePath);
|
|
29
|
+
result.language = this.getLanguageFromPath(filePath);
|
|
30
|
+
try {
|
|
31
|
+
const scriptKind = this.getScriptKind(filePath);
|
|
32
|
+
const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, scriptKind);
|
|
33
|
+
// Extract all information in a single pass
|
|
34
|
+
this.visitNode(sourceFile, result, null, null);
|
|
35
|
+
// Extract module-level calls (top-level code not inside any function)
|
|
36
|
+
this.extractModuleLevelCalls(sourceFile, result);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
result.errors.push(error instanceof Error ? error.message : 'Unknown parse error');
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Extract calls at module level (not inside any function)
|
|
45
|
+
* This catches patterns like: ReactDOM.render(<App />, ...) in main.tsx
|
|
46
|
+
*/
|
|
47
|
+
extractModuleLevelCalls(sourceFile, result) {
|
|
48
|
+
const source = sourceFile.text;
|
|
49
|
+
// Create a synthetic "module" function to hold top-level calls
|
|
50
|
+
const moduleCalls = [];
|
|
51
|
+
// Visit only top-level statements
|
|
52
|
+
for (const statement of sourceFile.statements) {
|
|
53
|
+
// Expression statements at top level (like ReactDOM.render(...))
|
|
54
|
+
if (ts.isExpressionStatement(statement)) {
|
|
55
|
+
this.extractCallsFromNodeWithCallback(statement, moduleCalls, source);
|
|
56
|
+
}
|
|
57
|
+
// Variable declarations with calls (like const x = foo())
|
|
58
|
+
else if (ts.isVariableStatement(statement)) {
|
|
59
|
+
let hasFunction = false;
|
|
60
|
+
for (const decl of statement.declarationList.declarations) {
|
|
61
|
+
if (decl.initializer &&
|
|
62
|
+
(ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
|
|
63
|
+
hasFunction = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (!hasFunction) {
|
|
67
|
+
this.extractCallsFromNodeWithCallback(statement, moduleCalls, source);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Add module-level calls to result
|
|
72
|
+
result.calls.push(...moduleCalls);
|
|
73
|
+
// If there are module-level calls, create a synthetic module function
|
|
74
|
+
if (moduleCalls.length > 0) {
|
|
75
|
+
const moduleFunc = this.createFunction({
|
|
76
|
+
name: '__module__',
|
|
77
|
+
startLine: 1,
|
|
78
|
+
endLine: sourceFile.getLineAndCharacterOfPosition(sourceFile.end).line + 1,
|
|
79
|
+
startColumn: 0,
|
|
80
|
+
endColumn: 0,
|
|
81
|
+
parameters: [],
|
|
82
|
+
isMethod: false,
|
|
83
|
+
isStatic: false,
|
|
84
|
+
isExported: false,
|
|
85
|
+
isConstructor: false,
|
|
86
|
+
isAsync: false,
|
|
87
|
+
decorators: [],
|
|
88
|
+
bodyStartLine: 1,
|
|
89
|
+
bodyEndLine: sourceFile.getLineAndCharacterOfPosition(sourceFile.end).line + 1,
|
|
90
|
+
});
|
|
91
|
+
result.functions.push(moduleFunc);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Visit a node and extract relevant information
|
|
96
|
+
* @param parentFunction - The name of the containing function (for nested functions)
|
|
97
|
+
*/
|
|
98
|
+
visitNode(node, result, currentClass, parentFunction) {
|
|
99
|
+
const source = node.getSourceFile().text;
|
|
100
|
+
// Function declarations
|
|
101
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
102
|
+
const func = this.extractFunctionDeclaration(node, source, currentClass, parentFunction);
|
|
103
|
+
if (func) {
|
|
104
|
+
result.functions.push(func);
|
|
105
|
+
// Extract calls within the function body
|
|
106
|
+
if (node.body) {
|
|
107
|
+
this.extractCallsFromNode(node.body, result, source);
|
|
108
|
+
// Visit nested functions
|
|
109
|
+
this.visitNestedFunctions(node.body, result, source, currentClass, func.name);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return; // Don't recurse into children - we've handled the body
|
|
113
|
+
}
|
|
114
|
+
// Arrow functions and function expressions assigned to variables
|
|
115
|
+
else if (ts.isVariableStatement(node)) {
|
|
116
|
+
let hasFunction = false;
|
|
117
|
+
for (const decl of node.declarationList.declarations) {
|
|
118
|
+
if (decl.initializer && ts.isIdentifier(decl.name)) {
|
|
119
|
+
if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
|
|
120
|
+
hasFunction = true;
|
|
121
|
+
const func = this.extractVariableFunction(node, decl, source, parentFunction);
|
|
122
|
+
if (func) {
|
|
123
|
+
result.functions.push(func);
|
|
124
|
+
// Extract calls within the function body
|
|
125
|
+
this.extractCallsFromNode(decl.initializer, result, source);
|
|
126
|
+
// Visit nested functions
|
|
127
|
+
this.visitNestedFunctions(decl.initializer.body, result, source, currentClass, func.name);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (hasFunction) {
|
|
133
|
+
return; // Don't recurse into children - we've handled the function body
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Class declarations
|
|
137
|
+
else if (ts.isClassDeclaration(node) && node.name) {
|
|
138
|
+
const classInfo = this.extractClassDeclaration(node, source);
|
|
139
|
+
result.classes.push(classInfo);
|
|
140
|
+
// Visit class members with class context
|
|
141
|
+
for (const member of node.members) {
|
|
142
|
+
this.visitNode(member, result, node.name.text, parentFunction);
|
|
143
|
+
}
|
|
144
|
+
return; // Don't recurse into children again
|
|
145
|
+
}
|
|
146
|
+
// Method declarations
|
|
147
|
+
else if (ts.isMethodDeclaration(node) && node.name && currentClass) {
|
|
148
|
+
const func = this.extractMethodDeclaration(node, source, currentClass);
|
|
149
|
+
if (func) {
|
|
150
|
+
result.functions.push(func);
|
|
151
|
+
if (node.body) {
|
|
152
|
+
this.extractCallsFromNode(node.body, result, source);
|
|
153
|
+
// Visit nested functions in methods
|
|
154
|
+
this.visitNestedFunctions(node.body, result, source, currentClass, func.qualifiedName);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return; // Don't recurse into children - we've handled the body
|
|
158
|
+
}
|
|
159
|
+
// Constructor
|
|
160
|
+
else if (ts.isConstructorDeclaration(node) && currentClass) {
|
|
161
|
+
const func = this.extractConstructor(node, source, currentClass);
|
|
162
|
+
result.functions.push(func);
|
|
163
|
+
if (node.body) {
|
|
164
|
+
this.extractCallsFromNode(node.body, result, source);
|
|
165
|
+
// Visit nested functions in constructor
|
|
166
|
+
this.visitNestedFunctions(node.body, result, source, currentClass, func.qualifiedName);
|
|
167
|
+
}
|
|
168
|
+
return; // Don't recurse into children - we've handled the body
|
|
169
|
+
}
|
|
170
|
+
// Import declarations
|
|
171
|
+
else if (ts.isImportDeclaration(node)) {
|
|
172
|
+
const importInfo = this.extractImportDeclaration(node, source);
|
|
173
|
+
if (importInfo) {
|
|
174
|
+
result.imports.push(importInfo);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Export declarations
|
|
178
|
+
else if (ts.isExportDeclaration(node)) {
|
|
179
|
+
const exports = this.extractExportDeclaration(node, source);
|
|
180
|
+
result.exports.push(...exports);
|
|
181
|
+
}
|
|
182
|
+
// Exported declarations
|
|
183
|
+
else if (this.hasExportModifier(node)) {
|
|
184
|
+
const exportInfo = this.extractExportedDeclaration(node, source);
|
|
185
|
+
if (exportInfo) {
|
|
186
|
+
result.exports.push(exportInfo);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Recurse into children
|
|
190
|
+
ts.forEachChild(node, (child) => this.visitNode(child, result, currentClass, parentFunction));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Visit nested functions within a function body
|
|
194
|
+
* This extracts arrow functions, function expressions, and named functions defined inside other functions
|
|
195
|
+
* Also handles anonymous callbacks passed to functions like useEffect, useCallback, etc.
|
|
196
|
+
*/
|
|
197
|
+
visitNestedFunctions(body, result, source, currentClass, parentFunctionName) {
|
|
198
|
+
let callbackCounter = 0;
|
|
199
|
+
const visit = (node) => {
|
|
200
|
+
// Named function inside another function
|
|
201
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
202
|
+
const func = this.extractFunctionDeclaration(node, source, currentClass, parentFunctionName);
|
|
203
|
+
if (func) {
|
|
204
|
+
result.functions.push(func);
|
|
205
|
+
if (node.body) {
|
|
206
|
+
this.extractCallsFromNode(node.body, result, source);
|
|
207
|
+
// Recursively visit nested functions
|
|
208
|
+
this.visitNestedFunctions(node.body, result, source, currentClass, func.name);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return; // Don't recurse into this function's children
|
|
212
|
+
}
|
|
213
|
+
// Arrow function or function expression assigned to a variable
|
|
214
|
+
if (ts.isVariableStatement(node)) {
|
|
215
|
+
for (const decl of node.declarationList.declarations) {
|
|
216
|
+
if (decl.initializer && ts.isIdentifier(decl.name)) {
|
|
217
|
+
if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
|
|
218
|
+
const func = this.extractNestedFunction(decl, source, parentFunctionName);
|
|
219
|
+
if (func) {
|
|
220
|
+
result.functions.push(func);
|
|
221
|
+
this.extractCallsFromNode(decl.initializer, result, source);
|
|
222
|
+
// Recursively visit nested functions
|
|
223
|
+
if (ts.isBlock(decl.initializer.body)) {
|
|
224
|
+
this.visitNestedFunctions(decl.initializer.body, result, source, currentClass, func.name);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Call expressions with anonymous callback arguments
|
|
233
|
+
// Handles: useEffect(() => {...}), setTimeout(() => {...}), arr.map((x) => {...}), etc.
|
|
234
|
+
if (ts.isCallExpression(node)) {
|
|
235
|
+
const processedArgs = new Set();
|
|
236
|
+
for (const arg of node.arguments) {
|
|
237
|
+
if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
238
|
+
processedArgs.add(arg);
|
|
239
|
+
// Create a synthetic function for the callback
|
|
240
|
+
const calleeName = this.getCallExpressionName(node);
|
|
241
|
+
callbackCounter++;
|
|
242
|
+
const syntheticName = `$${calleeName}$${callbackCounter}`;
|
|
243
|
+
const syntheticQualifiedName = `${parentFunctionName}.${syntheticName}`;
|
|
244
|
+
const startPos = this.getPosition(arg.getStart(), source);
|
|
245
|
+
const endPos = this.getPosition(arg.getEnd(), source);
|
|
246
|
+
const modifiers = ts.canHaveModifiers(arg) ? ts.getModifiers(arg) : undefined;
|
|
247
|
+
const syntheticFunc = this.createFunction({
|
|
248
|
+
name: syntheticName,
|
|
249
|
+
qualifiedName: syntheticQualifiedName,
|
|
250
|
+
startLine: startPos.row + 1,
|
|
251
|
+
endLine: endPos.row + 1,
|
|
252
|
+
startColumn: startPos.column,
|
|
253
|
+
endColumn: endPos.column,
|
|
254
|
+
parameters: this.extractParameters(arg.parameters),
|
|
255
|
+
returnType: arg.type?.getText(),
|
|
256
|
+
isMethod: false,
|
|
257
|
+
isStatic: false,
|
|
258
|
+
isExported: false,
|
|
259
|
+
isConstructor: false,
|
|
260
|
+
isAsync: modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false,
|
|
261
|
+
decorators: [],
|
|
262
|
+
bodyStartLine: this.getPosition(arg.body.getStart(), source).row + 1,
|
|
263
|
+
bodyEndLine: this.getPosition(arg.body.getEnd(), source).row + 1,
|
|
264
|
+
});
|
|
265
|
+
result.functions.push(syntheticFunc);
|
|
266
|
+
// Extract calls from the callback body
|
|
267
|
+
this.extractCallsFromNode(arg.body, result, source);
|
|
268
|
+
// Recursively visit nested functions in the callback
|
|
269
|
+
if (ts.isBlock(arg.body)) {
|
|
270
|
+
this.visitNestedFunctions(arg.body, result, source, currentClass, syntheticQualifiedName);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Visit children but skip the callback arguments we already processed
|
|
275
|
+
ts.forEachChild(node, (child) => {
|
|
276
|
+
if (!processedArgs.has(child)) {
|
|
277
|
+
visit(child);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
ts.forEachChild(node, visit);
|
|
283
|
+
};
|
|
284
|
+
ts.forEachChild(body, visit);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get the name of a call expression for synthetic function naming
|
|
288
|
+
*/
|
|
289
|
+
getCallExpressionName(node) {
|
|
290
|
+
const expr = node.expression;
|
|
291
|
+
if (ts.isIdentifier(expr)) {
|
|
292
|
+
return expr.text;
|
|
293
|
+
}
|
|
294
|
+
else if (ts.isPropertyAccessExpression(expr)) {
|
|
295
|
+
return expr.name.text;
|
|
296
|
+
}
|
|
297
|
+
return 'callback';
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Extract a nested function (arrow function or function expression inside another function)
|
|
301
|
+
*/
|
|
302
|
+
extractNestedFunction(decl, source, parentFunctionName) {
|
|
303
|
+
if (!ts.isIdentifier(decl.name))
|
|
304
|
+
return null;
|
|
305
|
+
const func = decl.initializer;
|
|
306
|
+
const startPos = this.getPosition(decl.getStart(), source);
|
|
307
|
+
const endPos = this.getPosition(decl.getEnd(), source);
|
|
308
|
+
const modifiers = ts.canHaveModifiers(func) ? ts.getModifiers(func) : undefined;
|
|
309
|
+
const name = decl.name.text;
|
|
310
|
+
return this.createFunction({
|
|
311
|
+
name,
|
|
312
|
+
qualifiedName: `${parentFunctionName}.${name}`,
|
|
313
|
+
startLine: startPos.row + 1,
|
|
314
|
+
endLine: endPos.row + 1,
|
|
315
|
+
startColumn: startPos.column,
|
|
316
|
+
endColumn: endPos.column,
|
|
317
|
+
parameters: this.extractParameters(func.parameters),
|
|
318
|
+
returnType: func.type?.getText(),
|
|
319
|
+
isMethod: false,
|
|
320
|
+
isStatic: false,
|
|
321
|
+
isExported: false, // Nested functions are never exported
|
|
322
|
+
isConstructor: false,
|
|
323
|
+
isAsync: modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false,
|
|
324
|
+
decorators: [],
|
|
325
|
+
bodyStartLine: this.getPosition(func.body.getStart(), source).row + 1,
|
|
326
|
+
bodyEndLine: this.getPosition(func.body.getEnd(), source).row + 1,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Extract calls from a node (function body, etc.)
|
|
331
|
+
*/
|
|
332
|
+
extractCallsFromNode(node, result, source) {
|
|
333
|
+
this.extractCallsFromNodeWithCallback(node, result.calls, source);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Extract calls from a node into a specific array
|
|
337
|
+
* Also detects callback patterns where functions are passed as arguments
|
|
338
|
+
*/
|
|
339
|
+
extractCallsFromNodeWithCallback(node, calls, source) {
|
|
340
|
+
const visit = (n) => {
|
|
341
|
+
// Call expressions: foo(), obj.method(), new Foo()
|
|
342
|
+
if (ts.isCallExpression(n)) {
|
|
343
|
+
const call = this.extractCallExpression(n, source);
|
|
344
|
+
if (call) {
|
|
345
|
+
calls.push(call);
|
|
346
|
+
}
|
|
347
|
+
// Check for callback patterns - functions passed as arguments
|
|
348
|
+
this.extractCallbackReferences(n, calls, source);
|
|
349
|
+
}
|
|
350
|
+
// New expressions: new Foo()
|
|
351
|
+
else if (ts.isNewExpression(n)) {
|
|
352
|
+
const call = this.extractNewExpression(n, source);
|
|
353
|
+
if (call) {
|
|
354
|
+
calls.push(call);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// JSX elements: <Component /> or <Component>...</Component>
|
|
358
|
+
// These are effectively function calls to the component
|
|
359
|
+
else if (ts.isJsxSelfClosingElement(n)) {
|
|
360
|
+
const call = this.extractJsxElement(n.tagName, n, source);
|
|
361
|
+
if (call) {
|
|
362
|
+
calls.push(call);
|
|
363
|
+
}
|
|
364
|
+
// Extract function references from JSX attributes (onClick={handleClick}, etc.)
|
|
365
|
+
this.extractJsxAttributeReferences(n.attributes, calls, source);
|
|
366
|
+
}
|
|
367
|
+
else if (ts.isJsxOpeningElement(n)) {
|
|
368
|
+
const call = this.extractJsxElement(n.tagName, n, source);
|
|
369
|
+
if (call) {
|
|
370
|
+
calls.push(call);
|
|
371
|
+
}
|
|
372
|
+
// Extract function references from JSX attributes (onClick={handleClick}, etc.)
|
|
373
|
+
this.extractJsxAttributeReferences(n.attributes, calls, source);
|
|
374
|
+
}
|
|
375
|
+
ts.forEachChild(n, visit);
|
|
376
|
+
};
|
|
377
|
+
visit(node);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Extract function references from JSX attributes
|
|
381
|
+
* Handles patterns like: onClick={handleClick}, onSubmit={formHandler}, ref={myRef}
|
|
382
|
+
*/
|
|
383
|
+
extractJsxAttributeReferences(attributes, calls, source) {
|
|
384
|
+
for (const attr of attributes.properties) {
|
|
385
|
+
if (ts.isJsxAttribute(attr) && attr.initializer) {
|
|
386
|
+
// Check if it's an expression container: onClick={...}
|
|
387
|
+
if (ts.isJsxExpression(attr.initializer) && attr.initializer.expression) {
|
|
388
|
+
const expr = attr.initializer.expression;
|
|
389
|
+
// Direct function reference: onClick={handleClick}
|
|
390
|
+
if (ts.isIdentifier(expr)) {
|
|
391
|
+
const pos = this.getPosition(expr.getStart(), source);
|
|
392
|
+
calls.push(this.createCall({
|
|
393
|
+
calleeName: expr.text,
|
|
394
|
+
fullExpression: expr.text,
|
|
395
|
+
line: pos.row + 1,
|
|
396
|
+
column: pos.column,
|
|
397
|
+
argumentCount: 0,
|
|
398
|
+
isMethodCall: false,
|
|
399
|
+
isConstructorCall: false,
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
// Property access: onClick={this.handleClick} or onClick={handlers.click}
|
|
403
|
+
else if (ts.isPropertyAccessExpression(expr)) {
|
|
404
|
+
const pos = this.getPosition(expr.getStart(), source);
|
|
405
|
+
calls.push(this.createCall({
|
|
406
|
+
calleeName: expr.name.text,
|
|
407
|
+
receiver: expr.expression.getText(),
|
|
408
|
+
fullExpression: expr.getText(),
|
|
409
|
+
line: pos.row + 1,
|
|
410
|
+
column: pos.column,
|
|
411
|
+
argumentCount: 0,
|
|
412
|
+
isMethodCall: true,
|
|
413
|
+
isConstructorCall: false,
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Extract callback references from function call arguments
|
|
422
|
+
* Handles patterns like: setTimeout(myFunc, 1000), arr.map(processItem), etc.
|
|
423
|
+
*/
|
|
424
|
+
extractCallbackReferences(callExpr, calls, source) {
|
|
425
|
+
for (const arg of callExpr.arguments) {
|
|
426
|
+
// Direct function reference: setTimeout(myFunc, 1000)
|
|
427
|
+
if (ts.isIdentifier(arg)) {
|
|
428
|
+
const pos = this.getPosition(arg.getStart(), source);
|
|
429
|
+
calls.push(this.createCall({
|
|
430
|
+
calleeName: arg.text,
|
|
431
|
+
fullExpression: arg.text,
|
|
432
|
+
line: pos.row + 1,
|
|
433
|
+
column: pos.column,
|
|
434
|
+
argumentCount: 0,
|
|
435
|
+
isMethodCall: false,
|
|
436
|
+
isConstructorCall: false,
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
439
|
+
// Property access: obj.method passed as callback
|
|
440
|
+
else if (ts.isPropertyAccessExpression(arg)) {
|
|
441
|
+
const pos = this.getPosition(arg.getStart(), source);
|
|
442
|
+
calls.push(this.createCall({
|
|
443
|
+
calleeName: arg.name.text,
|
|
444
|
+
receiver: arg.expression.getText(),
|
|
445
|
+
fullExpression: arg.getText(),
|
|
446
|
+
line: pos.row + 1,
|
|
447
|
+
column: pos.column,
|
|
448
|
+
argumentCount: 0,
|
|
449
|
+
isMethodCall: true,
|
|
450
|
+
isConstructorCall: false,
|
|
451
|
+
}));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Extract a call expression
|
|
457
|
+
*/
|
|
458
|
+
extractCallExpression(node, source) {
|
|
459
|
+
const pos = this.getPosition(node.getStart(), source);
|
|
460
|
+
let calleeName;
|
|
461
|
+
let receiver;
|
|
462
|
+
let fullExpression;
|
|
463
|
+
const expr = node.expression;
|
|
464
|
+
// Method call: obj.method() or obj.prop.method()
|
|
465
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
466
|
+
calleeName = expr.name.text;
|
|
467
|
+
receiver = expr.expression.getText();
|
|
468
|
+
fullExpression = expr.getText();
|
|
469
|
+
}
|
|
470
|
+
// Element access: obj['method']()
|
|
471
|
+
else if (ts.isElementAccessExpression(expr)) {
|
|
472
|
+
const arg = expr.argumentExpression;
|
|
473
|
+
calleeName = ts.isStringLiteral(arg) ? arg.text : arg.getText();
|
|
474
|
+
receiver = expr.expression.getText();
|
|
475
|
+
fullExpression = expr.getText();
|
|
476
|
+
}
|
|
477
|
+
// Direct call: foo()
|
|
478
|
+
else if (ts.isIdentifier(expr)) {
|
|
479
|
+
calleeName = expr.text;
|
|
480
|
+
fullExpression = expr.text;
|
|
481
|
+
}
|
|
482
|
+
// Other (call expression result, etc.)
|
|
483
|
+
else {
|
|
484
|
+
calleeName = expr.getText();
|
|
485
|
+
fullExpression = expr.getText();
|
|
486
|
+
}
|
|
487
|
+
return this.createCall({
|
|
488
|
+
calleeName,
|
|
489
|
+
receiver,
|
|
490
|
+
fullExpression,
|
|
491
|
+
line: pos.row + 1,
|
|
492
|
+
column: pos.column,
|
|
493
|
+
argumentCount: node.arguments.length,
|
|
494
|
+
isMethodCall: !!receiver,
|
|
495
|
+
isConstructorCall: false,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Extract a new expression
|
|
500
|
+
*/
|
|
501
|
+
extractNewExpression(node, source) {
|
|
502
|
+
const pos = this.getPosition(node.getStart(), source);
|
|
503
|
+
let calleeName;
|
|
504
|
+
let receiver;
|
|
505
|
+
const expr = node.expression;
|
|
506
|
+
if (ts.isIdentifier(expr)) {
|
|
507
|
+
calleeName = expr.text;
|
|
508
|
+
}
|
|
509
|
+
else if (ts.isPropertyAccessExpression(expr)) {
|
|
510
|
+
calleeName = expr.name.text;
|
|
511
|
+
receiver = expr.expression.getText();
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
calleeName = expr.getText();
|
|
515
|
+
}
|
|
516
|
+
return this.createCall({
|
|
517
|
+
calleeName,
|
|
518
|
+
receiver,
|
|
519
|
+
fullExpression: node.getText(),
|
|
520
|
+
line: pos.row + 1,
|
|
521
|
+
column: pos.column,
|
|
522
|
+
argumentCount: node.arguments?.length ?? 0,
|
|
523
|
+
isMethodCall: false,
|
|
524
|
+
isConstructorCall: true,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Extract a JSX element as a function call
|
|
529
|
+
* <Component prop={value} /> is effectively Component({ prop: value })
|
|
530
|
+
*/
|
|
531
|
+
extractJsxElement(tagName, node, source) {
|
|
532
|
+
const pos = this.getPosition(node.getStart(), source);
|
|
533
|
+
let calleeName;
|
|
534
|
+
let receiver;
|
|
535
|
+
// <Component /> - identifier
|
|
536
|
+
if (ts.isIdentifier(tagName)) {
|
|
537
|
+
calleeName = tagName.text;
|
|
538
|
+
// Skip intrinsic HTML elements (lowercase) - only track component calls
|
|
539
|
+
// React components must start with uppercase
|
|
540
|
+
if (!calleeName || calleeName.charAt(0) === calleeName.charAt(0).toLowerCase()) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// <Namespace.Component /> - property access
|
|
545
|
+
else if (ts.isPropertyAccessExpression(tagName)) {
|
|
546
|
+
calleeName = tagName.name.text;
|
|
547
|
+
receiver = tagName.expression.getText();
|
|
548
|
+
}
|
|
549
|
+
// <this.component /> or other
|
|
550
|
+
else {
|
|
551
|
+
calleeName = tagName.getText();
|
|
552
|
+
}
|
|
553
|
+
return this.createCall({
|
|
554
|
+
calleeName,
|
|
555
|
+
receiver,
|
|
556
|
+
fullExpression: `<${tagName.getText()} />`,
|
|
557
|
+
line: pos.row + 1,
|
|
558
|
+
column: pos.column,
|
|
559
|
+
argumentCount: 1, // JSX props are like a single object argument
|
|
560
|
+
isMethodCall: !!receiver,
|
|
561
|
+
isConstructorCall: false,
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Extract a function declaration
|
|
566
|
+
*/
|
|
567
|
+
extractFunctionDeclaration(node, source, currentClass, parentFunction) {
|
|
568
|
+
if (!node.name)
|
|
569
|
+
return null;
|
|
570
|
+
const startPos = this.getPosition(node.getStart(), source);
|
|
571
|
+
const endPos = this.getPosition(node.getEnd(), source);
|
|
572
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
573
|
+
const name = node.name.text;
|
|
574
|
+
// Build qualified name including parent function for nested functions
|
|
575
|
+
let qualifiedName = name;
|
|
576
|
+
if (parentFunction) {
|
|
577
|
+
qualifiedName = `${parentFunction}.${name}`;
|
|
578
|
+
}
|
|
579
|
+
else if (currentClass) {
|
|
580
|
+
qualifiedName = `${currentClass}.${name}`;
|
|
581
|
+
}
|
|
582
|
+
return this.createFunction({
|
|
583
|
+
name,
|
|
584
|
+
qualifiedName,
|
|
585
|
+
startLine: startPos.row + 1,
|
|
586
|
+
endLine: endPos.row + 1,
|
|
587
|
+
startColumn: startPos.column,
|
|
588
|
+
endColumn: endPos.column,
|
|
589
|
+
parameters: this.extractParameters(node.parameters),
|
|
590
|
+
returnType: node.type?.getText(),
|
|
591
|
+
isMethod: false,
|
|
592
|
+
isStatic: false,
|
|
593
|
+
isExported: parentFunction ? false : this.hasExportModifier(node), // Nested functions can't be exported
|
|
594
|
+
isConstructor: false,
|
|
595
|
+
isAsync: modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false,
|
|
596
|
+
className: currentClass ?? undefined,
|
|
597
|
+
decorators: this.extractDecorators(node),
|
|
598
|
+
bodyStartLine: node.body ? this.getPosition(node.body.getStart(), source).row + 1 : startPos.row + 1,
|
|
599
|
+
bodyEndLine: node.body ? this.getPosition(node.body.getEnd(), source).row + 1 : endPos.row + 1,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Extract a variable function (arrow function or function expression)
|
|
604
|
+
*/
|
|
605
|
+
extractVariableFunction(statement, decl, source, parentFunction) {
|
|
606
|
+
if (!ts.isIdentifier(decl.name))
|
|
607
|
+
return null;
|
|
608
|
+
const func = decl.initializer;
|
|
609
|
+
const startPos = this.getPosition(statement.getStart(), source);
|
|
610
|
+
const endPos = this.getPosition(statement.getEnd(), source);
|
|
611
|
+
const modifiers = ts.canHaveModifiers(func) ? ts.getModifiers(func) : undefined;
|
|
612
|
+
const name = decl.name.text;
|
|
613
|
+
// Build qualified name including parent function for nested functions
|
|
614
|
+
const qualifiedName = parentFunction ? `${parentFunction}.${name}` : name;
|
|
615
|
+
return this.createFunction({
|
|
616
|
+
name,
|
|
617
|
+
qualifiedName,
|
|
618
|
+
startLine: startPos.row + 1,
|
|
619
|
+
endLine: endPos.row + 1,
|
|
620
|
+
startColumn: startPos.column,
|
|
621
|
+
endColumn: endPos.column,
|
|
622
|
+
parameters: this.extractParameters(func.parameters),
|
|
623
|
+
returnType: func.type?.getText(),
|
|
624
|
+
isMethod: false,
|
|
625
|
+
isStatic: false,
|
|
626
|
+
isExported: parentFunction ? false : this.hasExportModifier(statement), // Nested functions can't be exported
|
|
627
|
+
isConstructor: false,
|
|
628
|
+
isAsync: modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false,
|
|
629
|
+
decorators: [],
|
|
630
|
+
bodyStartLine: this.getPosition(func.body.getStart(), source).row + 1,
|
|
631
|
+
bodyEndLine: this.getPosition(func.body.getEnd(), source).row + 1,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Extract a method declaration
|
|
636
|
+
*/
|
|
637
|
+
extractMethodDeclaration(node, source, className) {
|
|
638
|
+
const name = ts.isIdentifier(node.name) ? node.name.text : node.name.getText();
|
|
639
|
+
const startPos = this.getPosition(node.getStart(), source);
|
|
640
|
+
const endPos = this.getPosition(node.getEnd(), source);
|
|
641
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
642
|
+
return this.createFunction({
|
|
643
|
+
name,
|
|
644
|
+
startLine: startPos.row + 1,
|
|
645
|
+
endLine: endPos.row + 1,
|
|
646
|
+
startColumn: startPos.column,
|
|
647
|
+
endColumn: endPos.column,
|
|
648
|
+
parameters: this.extractParameters(node.parameters),
|
|
649
|
+
returnType: node.type?.getText(),
|
|
650
|
+
isMethod: true,
|
|
651
|
+
isStatic: modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false,
|
|
652
|
+
isExported: false, // Methods inherit class export status
|
|
653
|
+
isConstructor: false,
|
|
654
|
+
isAsync: modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false,
|
|
655
|
+
className,
|
|
656
|
+
decorators: this.extractDecorators(node),
|
|
657
|
+
bodyStartLine: node.body ? this.getPosition(node.body.getStart(), source).row + 1 : startPos.row + 1,
|
|
658
|
+
bodyEndLine: node.body ? this.getPosition(node.body.getEnd(), source).row + 1 : endPos.row + 1,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Extract a constructor
|
|
663
|
+
*/
|
|
664
|
+
extractConstructor(node, source, className) {
|
|
665
|
+
const startPos = this.getPosition(node.getStart(), source);
|
|
666
|
+
const endPos = this.getPosition(node.getEnd(), source);
|
|
667
|
+
return this.createFunction({
|
|
668
|
+
name: 'constructor',
|
|
669
|
+
startLine: startPos.row + 1,
|
|
670
|
+
endLine: endPos.row + 1,
|
|
671
|
+
startColumn: startPos.column,
|
|
672
|
+
endColumn: endPos.column,
|
|
673
|
+
parameters: this.extractParameters(node.parameters),
|
|
674
|
+
isMethod: true,
|
|
675
|
+
isStatic: false,
|
|
676
|
+
isExported: false,
|
|
677
|
+
isConstructor: true,
|
|
678
|
+
isAsync: false,
|
|
679
|
+
className,
|
|
680
|
+
decorators: [],
|
|
681
|
+
bodyStartLine: node.body ? this.getPosition(node.body.getStart(), source).row + 1 : startPos.row + 1,
|
|
682
|
+
bodyEndLine: node.body ? this.getPosition(node.body.getEnd(), source).row + 1 : endPos.row + 1,
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Extract a class declaration
|
|
687
|
+
*/
|
|
688
|
+
extractClassDeclaration(node, source) {
|
|
689
|
+
const name = node.name?.text ?? 'anonymous';
|
|
690
|
+
const startPos = this.getPosition(node.getStart(), source);
|
|
691
|
+
const endPos = this.getPosition(node.getEnd(), source);
|
|
692
|
+
const baseClasses = [];
|
|
693
|
+
if (node.heritageClauses) {
|
|
694
|
+
for (const clause of node.heritageClauses) {
|
|
695
|
+
if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
|
|
696
|
+
for (const type of clause.types) {
|
|
697
|
+
if (ts.isIdentifier(type.expression)) {
|
|
698
|
+
baseClasses.push(type.expression.text);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const methods = [];
|
|
705
|
+
for (const member of node.members) {
|
|
706
|
+
if (ts.isMethodDeclaration(member) && member.name) {
|
|
707
|
+
methods.push(ts.isIdentifier(member.name) ? member.name.text : member.name.getText());
|
|
708
|
+
}
|
|
709
|
+
else if (ts.isConstructorDeclaration(member)) {
|
|
710
|
+
methods.push('constructor');
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
return this.createClass({
|
|
714
|
+
name,
|
|
715
|
+
startLine: startPos.row + 1,
|
|
716
|
+
endLine: endPos.row + 1,
|
|
717
|
+
baseClasses,
|
|
718
|
+
methods,
|
|
719
|
+
isExported: this.hasExportModifier(node),
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Extract an import declaration
|
|
724
|
+
*/
|
|
725
|
+
extractImportDeclaration(node, source) {
|
|
726
|
+
const moduleSpecifier = node.moduleSpecifier.text;
|
|
727
|
+
const pos = this.getPosition(node.getStart(), source);
|
|
728
|
+
const isTypeOnly = node.importClause?.isTypeOnly ?? false;
|
|
729
|
+
const names = [];
|
|
730
|
+
const importClause = node.importClause;
|
|
731
|
+
if (importClause) {
|
|
732
|
+
// Default import
|
|
733
|
+
if (importClause.name) {
|
|
734
|
+
names.push({
|
|
735
|
+
imported: 'default',
|
|
736
|
+
local: importClause.name.text,
|
|
737
|
+
isDefault: true,
|
|
738
|
+
isNamespace: false,
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
// Named or namespace imports
|
|
742
|
+
const namedBindings = importClause.namedBindings;
|
|
743
|
+
if (namedBindings) {
|
|
744
|
+
if (ts.isNamespaceImport(namedBindings)) {
|
|
745
|
+
names.push({
|
|
746
|
+
imported: '*',
|
|
747
|
+
local: namedBindings.name.text,
|
|
748
|
+
isDefault: false,
|
|
749
|
+
isNamespace: true,
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
else if (ts.isNamedImports(namedBindings)) {
|
|
753
|
+
for (const element of namedBindings.elements) {
|
|
754
|
+
names.push({
|
|
755
|
+
imported: element.propertyName?.text ?? element.name.text,
|
|
756
|
+
local: element.name.text,
|
|
757
|
+
isDefault: false,
|
|
758
|
+
isNamespace: false,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return this.createImport({
|
|
765
|
+
source: moduleSpecifier,
|
|
766
|
+
names,
|
|
767
|
+
line: pos.row + 1,
|
|
768
|
+
isTypeOnly,
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Extract export declarations
|
|
773
|
+
*/
|
|
774
|
+
extractExportDeclaration(node, source) {
|
|
775
|
+
const pos = this.getPosition(node.getStart(), source);
|
|
776
|
+
const moduleSpecifier = node.moduleSpecifier ? node.moduleSpecifier.text : undefined;
|
|
777
|
+
const exports = [];
|
|
778
|
+
if (!node.exportClause) {
|
|
779
|
+
// export * from './foo'
|
|
780
|
+
exports.push(this.createExport({
|
|
781
|
+
name: '*',
|
|
782
|
+
isReExport: true,
|
|
783
|
+
source: moduleSpecifier,
|
|
784
|
+
line: pos.row + 1,
|
|
785
|
+
}));
|
|
786
|
+
}
|
|
787
|
+
else if (ts.isNamedExports(node.exportClause)) {
|
|
788
|
+
for (const element of node.exportClause.elements) {
|
|
789
|
+
exports.push(this.createExport({
|
|
790
|
+
name: element.name.text,
|
|
791
|
+
isReExport: !!moduleSpecifier,
|
|
792
|
+
source: moduleSpecifier,
|
|
793
|
+
line: pos.row + 1,
|
|
794
|
+
}));
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
return exports;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Extract exported declaration
|
|
801
|
+
*/
|
|
802
|
+
extractExportedDeclaration(node, source) {
|
|
803
|
+
const pos = this.getPosition(node.getStart(), source);
|
|
804
|
+
let name = null;
|
|
805
|
+
const isDefault = this.hasDefaultModifier(node);
|
|
806
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
807
|
+
name = node.name.text;
|
|
808
|
+
}
|
|
809
|
+
else if (ts.isClassDeclaration(node) && node.name) {
|
|
810
|
+
name = node.name.text;
|
|
811
|
+
}
|
|
812
|
+
else if (ts.isVariableStatement(node)) {
|
|
813
|
+
const firstDecl = node.declarationList.declarations[0];
|
|
814
|
+
if (firstDecl && ts.isIdentifier(firstDecl.name)) {
|
|
815
|
+
name = firstDecl.name.text;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
if (!name)
|
|
819
|
+
return null;
|
|
820
|
+
return this.createExport({
|
|
821
|
+
name,
|
|
822
|
+
isDefault,
|
|
823
|
+
line: pos.row + 1,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Extract parameters from a parameter list
|
|
828
|
+
*/
|
|
829
|
+
extractParameters(params) {
|
|
830
|
+
return params.map((param) => {
|
|
831
|
+
const name = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();
|
|
832
|
+
return this.parseParameter(name, param.type?.getText(), param.initializer !== undefined, param.dotDotDotToken !== undefined);
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Extract decorators from a node
|
|
837
|
+
*/
|
|
838
|
+
extractDecorators(node) {
|
|
839
|
+
const decorators = [];
|
|
840
|
+
const modifiers = ts.canHaveDecorators(node) ? ts.getDecorators(node) : undefined;
|
|
841
|
+
if (modifiers) {
|
|
842
|
+
for (const decorator of modifiers) {
|
|
843
|
+
decorators.push(decorator.expression.getText());
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return decorators;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Check if node has export modifier
|
|
850
|
+
*/
|
|
851
|
+
hasExportModifier(node) {
|
|
852
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
853
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Check if node has default modifier
|
|
857
|
+
*/
|
|
858
|
+
hasDefaultModifier(node) {
|
|
859
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
|
|
860
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) ?? false;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Get script kind from file path
|
|
864
|
+
*/
|
|
865
|
+
getScriptKind(filePath) {
|
|
866
|
+
const ext = filePath.toLowerCase().split('.').pop();
|
|
867
|
+
switch (ext) {
|
|
868
|
+
case 'tsx': return ts.ScriptKind.TSX;
|
|
869
|
+
case 'jsx': return ts.ScriptKind.JSX;
|
|
870
|
+
case 'js':
|
|
871
|
+
case 'mjs':
|
|
872
|
+
case 'cjs': return ts.ScriptKind.JS;
|
|
873
|
+
default: return ts.ScriptKind.TS;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Get language from file path
|
|
878
|
+
*/
|
|
879
|
+
getLanguageFromPath(filePath) {
|
|
880
|
+
const ext = filePath.toLowerCase().split('.').pop();
|
|
881
|
+
if (ext === 'js' || ext === 'jsx' || ext === 'mjs' || ext === 'cjs') {
|
|
882
|
+
return 'javascript';
|
|
883
|
+
}
|
|
884
|
+
return 'typescript';
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* Convert offset to position
|
|
888
|
+
*/
|
|
889
|
+
getPosition(offset, source) {
|
|
890
|
+
let row = 0;
|
|
891
|
+
let column = 0;
|
|
892
|
+
for (let i = 0; i < offset && i < source.length; i++) {
|
|
893
|
+
if (source[i] === '\n') {
|
|
894
|
+
row++;
|
|
895
|
+
column = 0;
|
|
896
|
+
}
|
|
897
|
+
else {
|
|
898
|
+
column++;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return { row, column };
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
//# sourceMappingURL=typescript-extractor.js.map
|