circle-ir 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/LICENSE +15 -0
- package/README.md +200 -0
- package/configs/sinks/code_injection.yaml +672 -0
- package/configs/sinks/command.yaml +917 -0
- package/configs/sinks/deserialization.yaml +105 -0
- package/configs/sinks/ldap.yaml +136 -0
- package/configs/sinks/nodejs.json +629 -0
- package/configs/sinks/path.yaml +715 -0
- package/configs/sinks/python.json +501 -0
- package/configs/sinks/rust.json +339 -0
- package/configs/sinks/sql.yaml +233 -0
- package/configs/sinks/ssrf.yaml +160 -0
- package/configs/sinks/xpath.yaml +121 -0
- package/configs/sinks/xss.yaml +727 -0
- package/configs/sources/db_sources.yaml +90 -0
- package/configs/sources/env_sources.yaml +94 -0
- package/configs/sources/express.json +197 -0
- package/configs/sources/file_sources.yaml +164 -0
- package/configs/sources/http_sources.yaml +379 -0
- package/configs/sources/io_sources.yaml +519 -0
- package/configs/sources/network_sources.yaml +99 -0
- package/configs/sources/python.json +230 -0
- package/configs/sources/rust.json +286 -0
- package/configs/sources/spring.yaml +70 -0
- package/dist/analysis/advisory-db.d.ts +86 -0
- package/dist/analysis/advisory-db.js +104 -0
- package/dist/analysis/advisory-db.js.map +1 -0
- package/dist/analysis/cargo-parser.d.ts +42 -0
- package/dist/analysis/cargo-parser.js +102 -0
- package/dist/analysis/cargo-parser.js.map +1 -0
- package/dist/analysis/config-loader.d.ts +37 -0
- package/dist/analysis/config-loader.js +1561 -0
- package/dist/analysis/config-loader.js.map +1 -0
- package/dist/analysis/constant-propagation/ast-utils.d.ts +25 -0
- package/dist/analysis/constant-propagation/ast-utils.js +34 -0
- package/dist/analysis/constant-propagation/ast-utils.js.map +1 -0
- package/dist/analysis/constant-propagation/evaluator.d.ts +32 -0
- package/dist/analysis/constant-propagation/evaluator.js +296 -0
- package/dist/analysis/constant-propagation/evaluator.js.map +1 -0
- package/dist/analysis/constant-propagation/index.d.ts +62 -0
- package/dist/analysis/constant-propagation/index.js +152 -0
- package/dist/analysis/constant-propagation/index.js.map +1 -0
- package/dist/analysis/constant-propagation/patterns.d.ts +8 -0
- package/dist/analysis/constant-propagation/patterns.js +126 -0
- package/dist/analysis/constant-propagation/patterns.js.map +1 -0
- package/dist/analysis/constant-propagation/propagator.d.ts +180 -0
- package/dist/analysis/constant-propagation/propagator.js +1985 -0
- package/dist/analysis/constant-propagation/propagator.js.map +1 -0
- package/dist/analysis/constant-propagation/types.d.ts +63 -0
- package/dist/analysis/constant-propagation/types.js +5 -0
- package/dist/analysis/constant-propagation/types.js.map +1 -0
- package/dist/analysis/constant-propagation.d.ts +9 -0
- package/dist/analysis/constant-propagation.js +18 -0
- package/dist/analysis/constant-propagation.js.map +1 -0
- package/dist/analysis/dependency-scanner.d.ts +79 -0
- package/dist/analysis/dependency-scanner.js +122 -0
- package/dist/analysis/dependency-scanner.js.map +1 -0
- package/dist/analysis/dfg-verifier.d.ts +116 -0
- package/dist/analysis/dfg-verifier.js +399 -0
- package/dist/analysis/dfg-verifier.js.map +1 -0
- package/dist/analysis/findings.d.ts +11 -0
- package/dist/analysis/findings.js +228 -0
- package/dist/analysis/findings.js.map +1 -0
- package/dist/analysis/index.d.ts +16 -0
- package/dist/analysis/index.js +18 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/interprocedural.d.ts +99 -0
- package/dist/analysis/interprocedural.js +526 -0
- package/dist/analysis/interprocedural.js.map +1 -0
- package/dist/analysis/path-finder.d.ts +133 -0
- package/dist/analysis/path-finder.js +354 -0
- package/dist/analysis/path-finder.js.map +1 -0
- package/dist/analysis/rules.d.ts +75 -0
- package/dist/analysis/rules.js +332 -0
- package/dist/analysis/rules.js.map +1 -0
- package/dist/analysis/semver.d.ts +27 -0
- package/dist/analysis/semver.js +127 -0
- package/dist/analysis/semver.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts +15 -0
- package/dist/analysis/taint-matcher.js +634 -0
- package/dist/analysis/taint-matcher.js.map +1 -0
- package/dist/analysis/taint-propagation.d.ts +67 -0
- package/dist/analysis/taint-propagation.js +298 -0
- package/dist/analysis/taint-propagation.js.map +1 -0
- package/dist/analysis/unresolved.d.ts +14 -0
- package/dist/analysis/unresolved.js +202 -0
- package/dist/analysis/unresolved.js.map +1 -0
- package/dist/analyzer.d.ts +43 -0
- package/dist/analyzer.js +1010 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/browser/circle-ir.js +16576 -0
- package/dist/browser.d.ts +38 -0
- package/dist/browser.js +38 -0
- package/dist/browser.js.map +1 -0
- package/dist/core/circle-ir-core.cjs +13626 -0
- package/dist/core/circle-ir-core.d.ts +59 -0
- package/dist/core/circle-ir-core.js +13591 -0
- package/dist/core/extractors/calls.d.ts +13 -0
- package/dist/core/extractors/calls.js +1429 -0
- package/dist/core/extractors/calls.js.map +1 -0
- package/dist/core/extractors/cfg.d.ts +9 -0
- package/dist/core/extractors/cfg.js +519 -0
- package/dist/core/extractors/cfg.js.map +1 -0
- package/dist/core/extractors/dfg.d.ts +12 -0
- package/dist/core/extractors/dfg.js +1081 -0
- package/dist/core/extractors/dfg.js.map +1 -0
- package/dist/core/extractors/exports.d.ts +14 -0
- package/dist/core/extractors/exports.js +80 -0
- package/dist/core/extractors/exports.js.map +1 -0
- package/dist/core/extractors/imports.d.ts +9 -0
- package/dist/core/extractors/imports.js +739 -0
- package/dist/core/extractors/imports.js.map +1 -0
- package/dist/core/extractors/index.d.ts +10 -0
- package/dist/core/extractors/index.js +11 -0
- package/dist/core/extractors/index.js.map +1 -0
- package/dist/core/extractors/meta.d.ts +10 -0
- package/dist/core/extractors/meta.js +109 -0
- package/dist/core/extractors/meta.js.map +1 -0
- package/dist/core/extractors/types.d.ts +10 -0
- package/dist/core/extractors/types.js +1479 -0
- package/dist/core/extractors/types.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/parser.d.ts +84 -0
- package/dist/core/parser.js +250 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core-lib.d.ts +59 -0
- package/dist/core-lib.js +62 -0
- package/dist/core-lib.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.js +14 -0
- package/dist/languages/index.js.map +1 -0
- package/dist/languages/plugins/base.d.ts +44 -0
- package/dist/languages/plugins/base.js +82 -0
- package/dist/languages/plugins/base.js.map +1 -0
- package/dist/languages/plugins/index.d.ts +14 -0
- package/dist/languages/plugins/index.js +25 -0
- package/dist/languages/plugins/index.js.map +1 -0
- package/dist/languages/plugins/java.d.ts +49 -0
- package/dist/languages/plugins/java.js +402 -0
- package/dist/languages/plugins/java.js.map +1 -0
- package/dist/languages/plugins/javascript.d.ts +48 -0
- package/dist/languages/plugins/javascript.js +445 -0
- package/dist/languages/plugins/javascript.js.map +1 -0
- package/dist/languages/plugins/python.d.ts +47 -0
- package/dist/languages/plugins/python.js +480 -0
- package/dist/languages/plugins/python.js.map +1 -0
- package/dist/languages/plugins/rust.d.ts +47 -0
- package/dist/languages/plugins/rust.js +405 -0
- package/dist/languages/plugins/rust.js.map +1 -0
- package/dist/languages/registry.d.ts +30 -0
- package/dist/languages/registry.js +80 -0
- package/dist/languages/registry.js.map +1 -0
- package/dist/languages/types.d.ts +184 -0
- package/dist/languages/types.js +8 -0
- package/dist/languages/types.js.map +1 -0
- package/dist/resolution/cross-file.d.ts +146 -0
- package/dist/resolution/cross-file.js +439 -0
- package/dist/resolution/cross-file.js.map +1 -0
- package/dist/resolution/index.d.ts +12 -0
- package/dist/resolution/index.js +10 -0
- package/dist/resolution/index.js.map +1 -0
- package/dist/resolution/symbol-table.d.ts +136 -0
- package/dist/resolution/symbol-table.js +336 -0
- package/dist/resolution/symbol-table.js.map +1 -0
- package/dist/resolution/type-hierarchy.d.ts +124 -0
- package/dist/resolution/type-hierarchy.js +515 -0
- package/dist/resolution/type-hierarchy.js.map +1 -0
- package/dist/types/config.d.ts +45 -0
- package/dist/types/config.js +5 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/index.d.ts +392 -0
- package/dist/types/index.js +7 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/logger.d.ts +85 -0
- package/dist/utils/logger.js +198 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/wasm/tree-sitter-java.wasm +0 -0
- package/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/dist/wasm/tree-sitter-python.wasm +0 -0
- package/dist/wasm/tree-sitter-rust.wasm +0 -0
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/docs/SPEC.md +1021 -0
- package/examples/browser-example.html +610 -0
- package/examples/node-example.ts +215 -0
- package/package.json +107 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
|
@@ -0,0 +1,739 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import extractor - extracts import declarations
|
|
3
|
+
*/
|
|
4
|
+
import { findNodes, getNodeText } from '../parser.js';
|
|
5
|
+
/**
|
|
6
|
+
* Detect language from tree structure.
|
|
7
|
+
*/
|
|
8
|
+
function detectLanguage(tree) {
|
|
9
|
+
const root = tree.rootNode;
|
|
10
|
+
// Check for JavaScript-specific nodes
|
|
11
|
+
const jsNodeTypes = new Set([
|
|
12
|
+
'arrow_function', 'lexical_declaration', 'function_declaration',
|
|
13
|
+
'export_statement', 'import_statement'
|
|
14
|
+
]);
|
|
15
|
+
// Check for Java-specific nodes
|
|
16
|
+
const javaNodeTypes = new Set([
|
|
17
|
+
'package_declaration', 'import_declaration', 'class_declaration',
|
|
18
|
+
'method_declaration', 'annotation'
|
|
19
|
+
]);
|
|
20
|
+
// Check for Python-specific nodes
|
|
21
|
+
const pythonNodeTypes = new Set([
|
|
22
|
+
'class_definition', 'function_definition', 'decorated_definition',
|
|
23
|
+
'import_from_statement'
|
|
24
|
+
]);
|
|
25
|
+
// Check for Rust-specific nodes
|
|
26
|
+
const rustNodeTypes = new Set([
|
|
27
|
+
'struct_item', 'impl_item', 'function_item', 'use_declaration',
|
|
28
|
+
'mod_item', 'trait_item', 'enum_item'
|
|
29
|
+
]);
|
|
30
|
+
let jsScore = 0;
|
|
31
|
+
let javaScore = 0;
|
|
32
|
+
let pythonScore = 0;
|
|
33
|
+
let rustScore = 0;
|
|
34
|
+
for (let i = 0; i < Math.min(root.childCount, 20); i++) {
|
|
35
|
+
const child = root.child(i);
|
|
36
|
+
if (!child)
|
|
37
|
+
continue;
|
|
38
|
+
if (jsNodeTypes.has(child.type))
|
|
39
|
+
jsScore++;
|
|
40
|
+
if (javaNodeTypes.has(child.type))
|
|
41
|
+
javaScore++;
|
|
42
|
+
if (pythonNodeTypes.has(child.type))
|
|
43
|
+
pythonScore++;
|
|
44
|
+
if (rustNodeTypes.has(child.type))
|
|
45
|
+
rustScore++;
|
|
46
|
+
}
|
|
47
|
+
if (rustScore > jsScore && rustScore > javaScore && rustScore > pythonScore)
|
|
48
|
+
return 'rust';
|
|
49
|
+
if (pythonScore > jsScore && pythonScore > javaScore)
|
|
50
|
+
return 'python';
|
|
51
|
+
return jsScore > javaScore ? 'javascript' : 'java';
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Extract all imports from the tree.
|
|
55
|
+
*/
|
|
56
|
+
export function extractImports(tree, language) {
|
|
57
|
+
const effectiveLanguage = language ?? detectLanguage(tree);
|
|
58
|
+
const isJavaScript = effectiveLanguage === 'javascript' || effectiveLanguage === 'typescript';
|
|
59
|
+
const isPython = effectiveLanguage === 'python';
|
|
60
|
+
const isRust = effectiveLanguage === 'rust';
|
|
61
|
+
if (isRust) {
|
|
62
|
+
return extractRustImports(tree);
|
|
63
|
+
}
|
|
64
|
+
if (isPython) {
|
|
65
|
+
return extractPythonImports(tree);
|
|
66
|
+
}
|
|
67
|
+
if (isJavaScript) {
|
|
68
|
+
return extractJavaScriptImports(tree);
|
|
69
|
+
}
|
|
70
|
+
return extractJavaImports(tree);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract JavaScript/TypeScript imports.
|
|
74
|
+
*/
|
|
75
|
+
function extractJavaScriptImports(tree) {
|
|
76
|
+
const imports = [];
|
|
77
|
+
// Find all ES6 import statements
|
|
78
|
+
const importStatements = findNodes(tree.rootNode, 'import_statement');
|
|
79
|
+
for (const importStmt of importStatements) {
|
|
80
|
+
const importInfos = extractJSImportInfo(importStmt);
|
|
81
|
+
imports.push(...importInfos);
|
|
82
|
+
}
|
|
83
|
+
// Find CommonJS require calls
|
|
84
|
+
const requireCalls = findRequireCalls(tree);
|
|
85
|
+
imports.push(...requireCalls);
|
|
86
|
+
return imports;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Extract Java imports.
|
|
90
|
+
*/
|
|
91
|
+
function extractJavaImports(tree) {
|
|
92
|
+
const imports = [];
|
|
93
|
+
// Find all import declarations
|
|
94
|
+
const importDecls = findNodes(tree.rootNode, 'import_declaration');
|
|
95
|
+
for (const importDecl of importDecls) {
|
|
96
|
+
const importInfo = extractJavaImportInfo(importDecl);
|
|
97
|
+
if (importInfo) {
|
|
98
|
+
imports.push(importInfo);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return imports;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Extract import information from a JavaScript import_statement node.
|
|
105
|
+
*/
|
|
106
|
+
function extractJSImportInfo(node) {
|
|
107
|
+
const imports = [];
|
|
108
|
+
const lineNumber = node.startPosition.row + 1;
|
|
109
|
+
// Get the module source (from 'module')
|
|
110
|
+
const sourceNode = node.childForFieldName('source');
|
|
111
|
+
const fromPackage = sourceNode ? getNodeText(sourceNode).replace(/['"]/g, '') : null;
|
|
112
|
+
// Side-effect import: import 'module'
|
|
113
|
+
if (!node.childForFieldName('import_clause') && !node.childForFieldName('namespace_import')) {
|
|
114
|
+
// Check for named imports directly in the node
|
|
115
|
+
let hasImportClause = false;
|
|
116
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
117
|
+
const child = node.child(i);
|
|
118
|
+
if (child && (child.type === 'import_clause' || child.type === 'namespace_import' ||
|
|
119
|
+
child.type === 'named_imports' || child.type === 'identifier')) {
|
|
120
|
+
hasImportClause = true;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!hasImportClause && fromPackage) {
|
|
125
|
+
imports.push({
|
|
126
|
+
imported_name: '*',
|
|
127
|
+
from_package: fromPackage,
|
|
128
|
+
alias: null,
|
|
129
|
+
is_wildcard: true,
|
|
130
|
+
line_number: lineNumber,
|
|
131
|
+
});
|
|
132
|
+
return imports;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Look through all children for import components
|
|
136
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
137
|
+
const child = node.child(i);
|
|
138
|
+
if (!child)
|
|
139
|
+
continue;
|
|
140
|
+
// Default import: import foo from 'module'
|
|
141
|
+
if (child.type === 'identifier') {
|
|
142
|
+
const name = getNodeText(child);
|
|
143
|
+
// Skip 'import' keyword and module source
|
|
144
|
+
if (name !== 'import' && name !== 'from') {
|
|
145
|
+
imports.push({
|
|
146
|
+
imported_name: 'default',
|
|
147
|
+
from_package: fromPackage,
|
|
148
|
+
alias: name,
|
|
149
|
+
is_wildcard: false,
|
|
150
|
+
line_number: lineNumber,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Namespace import: import * as ns from 'module'
|
|
155
|
+
if (child.type === 'namespace_import') {
|
|
156
|
+
// Try field name first, then look for identifier child
|
|
157
|
+
let aliasNode = child.childForFieldName('alias');
|
|
158
|
+
if (!aliasNode) {
|
|
159
|
+
// In tree-sitter-javascript, the alias is just an identifier child
|
|
160
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
161
|
+
const subChild = child.child(j);
|
|
162
|
+
if (subChild && subChild.type === 'identifier') {
|
|
163
|
+
aliasNode = subChild;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const alias = aliasNode ? getNodeText(aliasNode) : null;
|
|
169
|
+
imports.push({
|
|
170
|
+
imported_name: '*',
|
|
171
|
+
from_package: fromPackage,
|
|
172
|
+
alias,
|
|
173
|
+
is_wildcard: true,
|
|
174
|
+
line_number: lineNumber,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
// Named imports: import { foo, bar as baz } from 'module'
|
|
178
|
+
if (child.type === 'named_imports') {
|
|
179
|
+
for (let j = 0; j < child.namedChildCount; j++) {
|
|
180
|
+
const specifier = child.namedChild(j);
|
|
181
|
+
if (!specifier)
|
|
182
|
+
continue;
|
|
183
|
+
if (specifier.type === 'import_specifier') {
|
|
184
|
+
const nameNode = specifier.childForFieldName('name');
|
|
185
|
+
const aliasNode = specifier.childForFieldName('alias');
|
|
186
|
+
const importedName = nameNode ? getNodeText(nameNode) : null;
|
|
187
|
+
const alias = aliasNode ? getNodeText(aliasNode) : null;
|
|
188
|
+
if (importedName) {
|
|
189
|
+
imports.push({
|
|
190
|
+
imported_name: importedName,
|
|
191
|
+
from_package: fromPackage,
|
|
192
|
+
alias,
|
|
193
|
+
is_wildcard: false,
|
|
194
|
+
line_number: lineNumber,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Import clause might contain default + named imports
|
|
201
|
+
if (child.type === 'import_clause') {
|
|
202
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
203
|
+
const subChild = child.child(j);
|
|
204
|
+
if (!subChild)
|
|
205
|
+
continue;
|
|
206
|
+
if (subChild.type === 'identifier') {
|
|
207
|
+
const name = getNodeText(subChild);
|
|
208
|
+
imports.push({
|
|
209
|
+
imported_name: 'default',
|
|
210
|
+
from_package: fromPackage,
|
|
211
|
+
alias: name,
|
|
212
|
+
is_wildcard: false,
|
|
213
|
+
line_number: lineNumber,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (subChild.type === 'namespace_import') {
|
|
217
|
+
let aliasNode = subChild.childForFieldName('alias');
|
|
218
|
+
if (!aliasNode) {
|
|
219
|
+
for (let k = 0; k < subChild.childCount; k++) {
|
|
220
|
+
const nsChild = subChild.child(k);
|
|
221
|
+
if (nsChild && nsChild.type === 'identifier') {
|
|
222
|
+
aliasNode = nsChild;
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const alias = aliasNode ? getNodeText(aliasNode) : null;
|
|
228
|
+
imports.push({
|
|
229
|
+
imported_name: '*',
|
|
230
|
+
from_package: fromPackage,
|
|
231
|
+
alias,
|
|
232
|
+
is_wildcard: true,
|
|
233
|
+
line_number: lineNumber,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (subChild.type === 'named_imports') {
|
|
237
|
+
for (let k = 0; k < subChild.namedChildCount; k++) {
|
|
238
|
+
const specifier = subChild.namedChild(k);
|
|
239
|
+
if (!specifier || specifier.type !== 'import_specifier')
|
|
240
|
+
continue;
|
|
241
|
+
const nameNode = specifier.childForFieldName('name');
|
|
242
|
+
const aliasNode = specifier.childForFieldName('alias');
|
|
243
|
+
const importedName = nameNode ? getNodeText(nameNode) : null;
|
|
244
|
+
const alias = aliasNode ? getNodeText(aliasNode) : null;
|
|
245
|
+
if (importedName) {
|
|
246
|
+
imports.push({
|
|
247
|
+
imported_name: importedName,
|
|
248
|
+
from_package: fromPackage,
|
|
249
|
+
alias,
|
|
250
|
+
is_wildcard: false,
|
|
251
|
+
line_number: lineNumber,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return imports;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Find CommonJS require calls: const x = require('module')
|
|
263
|
+
*/
|
|
264
|
+
function findRequireCalls(tree) {
|
|
265
|
+
const imports = [];
|
|
266
|
+
// Find all call expressions
|
|
267
|
+
const callExpressions = findNodes(tree.rootNode, 'call_expression');
|
|
268
|
+
for (const call of callExpressions) {
|
|
269
|
+
const funcNode = call.childForFieldName('function');
|
|
270
|
+
if (!funcNode || getNodeText(funcNode) !== 'require')
|
|
271
|
+
continue;
|
|
272
|
+
const argsNode = call.childForFieldName('arguments');
|
|
273
|
+
if (!argsNode)
|
|
274
|
+
continue;
|
|
275
|
+
// Get the module path from first argument
|
|
276
|
+
const firstArg = argsNode.namedChild(0);
|
|
277
|
+
if (!firstArg || (firstArg.type !== 'string' && firstArg.type !== 'template_string'))
|
|
278
|
+
continue;
|
|
279
|
+
const fromPackage = getNodeText(firstArg).replace(/['"]/g, '');
|
|
280
|
+
const lineNumber = call.startPosition.row + 1;
|
|
281
|
+
// Try to find the variable being assigned
|
|
282
|
+
let alias = null;
|
|
283
|
+
const parent = call.parent;
|
|
284
|
+
if (parent?.type === 'variable_declarator') {
|
|
285
|
+
const nameNode = parent.childForFieldName('name');
|
|
286
|
+
if (nameNode) {
|
|
287
|
+
if (nameNode.type === 'identifier') {
|
|
288
|
+
alias = getNodeText(nameNode);
|
|
289
|
+
}
|
|
290
|
+
else if (nameNode.type === 'object_pattern') {
|
|
291
|
+
// Destructuring: const { foo, bar } = require('module')
|
|
292
|
+
for (let i = 0; i < nameNode.namedChildCount; i++) {
|
|
293
|
+
const prop = nameNode.namedChild(i);
|
|
294
|
+
if (!prop)
|
|
295
|
+
continue;
|
|
296
|
+
if (prop.type === 'shorthand_property_identifier_pattern') {
|
|
297
|
+
const name = getNodeText(prop);
|
|
298
|
+
imports.push({
|
|
299
|
+
imported_name: name,
|
|
300
|
+
from_package: fromPackage,
|
|
301
|
+
alias: null,
|
|
302
|
+
is_wildcard: false,
|
|
303
|
+
line_number: lineNumber,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
else if (prop.type === 'pair_pattern') {
|
|
307
|
+
const keyNode = prop.childForFieldName('key');
|
|
308
|
+
const valueNode = prop.childForFieldName('value');
|
|
309
|
+
const importedName = keyNode ? getNodeText(keyNode) : null;
|
|
310
|
+
const propAlias = valueNode && valueNode.type === 'identifier' ? getNodeText(valueNode) : null;
|
|
311
|
+
if (importedName) {
|
|
312
|
+
imports.push({
|
|
313
|
+
imported_name: importedName,
|
|
314
|
+
from_package: fromPackage,
|
|
315
|
+
alias: propAlias,
|
|
316
|
+
is_wildcard: false,
|
|
317
|
+
line_number: lineNumber,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
continue; // Continue to next call expression instead of returning
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
imports.push({
|
|
327
|
+
imported_name: '*',
|
|
328
|
+
from_package: fromPackage,
|
|
329
|
+
alias,
|
|
330
|
+
is_wildcard: true,
|
|
331
|
+
line_number: lineNumber,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
return imports;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Extract import information from a Java import_declaration node.
|
|
338
|
+
*/
|
|
339
|
+
function extractJavaImportInfo(node) {
|
|
340
|
+
// Check for static import
|
|
341
|
+
const isStatic = hasStaticModifier(node);
|
|
342
|
+
// Get the full import path
|
|
343
|
+
const scopedId = findScopedIdentifier(node);
|
|
344
|
+
if (!scopedId)
|
|
345
|
+
return null;
|
|
346
|
+
const fullPath = getNodeText(scopedId);
|
|
347
|
+
// Check for wildcard import
|
|
348
|
+
const isWildcard = fullPath.endsWith('.*') || hasWildcard(node);
|
|
349
|
+
// Parse the import path
|
|
350
|
+
const { importedName, fromPackage } = parseImportPath(fullPath, isWildcard);
|
|
351
|
+
return {
|
|
352
|
+
imported_name: importedName,
|
|
353
|
+
from_package: fromPackage,
|
|
354
|
+
alias: null, // Java doesn't support import aliases
|
|
355
|
+
is_wildcard: isWildcard,
|
|
356
|
+
line_number: node.startPosition.row + 1,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Check if the import has the static modifier.
|
|
361
|
+
*/
|
|
362
|
+
function hasStaticModifier(node) {
|
|
363
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
364
|
+
const child = node.child(i);
|
|
365
|
+
if (child && child.type === 'static') {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Check if the import has a wildcard asterisk.
|
|
373
|
+
*/
|
|
374
|
+
function hasWildcard(node) {
|
|
375
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
376
|
+
const child = node.child(i);
|
|
377
|
+
if (child && child.type === 'asterisk') {
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Find the scoped identifier in an import declaration.
|
|
385
|
+
*/
|
|
386
|
+
function findScopedIdentifier(node) {
|
|
387
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
388
|
+
const child = node.child(i);
|
|
389
|
+
if (!child)
|
|
390
|
+
continue;
|
|
391
|
+
if (child.type === 'scoped_identifier' || child.type === 'identifier') {
|
|
392
|
+
return child;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Parse an import path into imported name and package.
|
|
399
|
+
*/
|
|
400
|
+
function parseImportPath(fullPath, isWildcard) {
|
|
401
|
+
if (isWildcard) {
|
|
402
|
+
// For "java.util.*", package is "java.util", name is "*"
|
|
403
|
+
const cleanPath = fullPath.replace('.*', '').replace('*', '');
|
|
404
|
+
return {
|
|
405
|
+
importedName: '*',
|
|
406
|
+
fromPackage: cleanPath || null,
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
// For "java.util.ArrayList", package is "java.util", name is "ArrayList"
|
|
410
|
+
const lastDot = fullPath.lastIndexOf('.');
|
|
411
|
+
if (lastDot === -1) {
|
|
412
|
+
return {
|
|
413
|
+
importedName: fullPath,
|
|
414
|
+
fromPackage: null,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
return {
|
|
418
|
+
importedName: fullPath.substring(lastDot + 1),
|
|
419
|
+
fromPackage: fullPath.substring(0, lastDot),
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
// =============================================================================
|
|
423
|
+
// Python Import Extraction
|
|
424
|
+
// =============================================================================
|
|
425
|
+
/**
|
|
426
|
+
* Extract Python imports.
|
|
427
|
+
*/
|
|
428
|
+
function extractPythonImports(tree) {
|
|
429
|
+
const imports = [];
|
|
430
|
+
// Find all import statements: import os, sys
|
|
431
|
+
const importStatements = findNodes(tree.rootNode, 'import_statement');
|
|
432
|
+
for (const stmt of importStatements) {
|
|
433
|
+
const importInfos = extractPythonImportStatement(stmt);
|
|
434
|
+
imports.push(...importInfos);
|
|
435
|
+
}
|
|
436
|
+
// Find all from-import statements: from os import path
|
|
437
|
+
const importFromStatements = findNodes(tree.rootNode, 'import_from_statement');
|
|
438
|
+
for (const stmt of importFromStatements) {
|
|
439
|
+
const importInfos = extractPythonFromImportStatement(stmt);
|
|
440
|
+
imports.push(...importInfos);
|
|
441
|
+
}
|
|
442
|
+
return imports;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Extract import information from a Python import_statement node.
|
|
446
|
+
* Handles: import os, import os.path, import os as operating_system
|
|
447
|
+
*/
|
|
448
|
+
function extractPythonImportStatement(node) {
|
|
449
|
+
const imports = [];
|
|
450
|
+
const lineNumber = node.startPosition.row + 1;
|
|
451
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
452
|
+
const child = node.child(i);
|
|
453
|
+
if (!child)
|
|
454
|
+
continue;
|
|
455
|
+
if (child.type === 'dotted_name') {
|
|
456
|
+
const fullName = getNodeText(child);
|
|
457
|
+
const parts = fullName.split('.');
|
|
458
|
+
imports.push({
|
|
459
|
+
imported_name: parts[parts.length - 1],
|
|
460
|
+
from_package: parts.length > 1 ? parts.slice(0, -1).join('.') : null,
|
|
461
|
+
alias: null,
|
|
462
|
+
is_wildcard: false,
|
|
463
|
+
line_number: lineNumber,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
else if (child.type === 'aliased_import') {
|
|
467
|
+
const nameNode = child.childForFieldName('name');
|
|
468
|
+
const aliasNode = child.childForFieldName('alias');
|
|
469
|
+
if (nameNode) {
|
|
470
|
+
const fullName = getNodeText(nameNode);
|
|
471
|
+
const parts = fullName.split('.');
|
|
472
|
+
imports.push({
|
|
473
|
+
imported_name: parts[parts.length - 1],
|
|
474
|
+
from_package: parts.length > 1 ? parts.slice(0, -1).join('.') : null,
|
|
475
|
+
alias: aliasNode ? getNodeText(aliasNode) : null,
|
|
476
|
+
is_wildcard: false,
|
|
477
|
+
line_number: lineNumber,
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return imports;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Extract import information from a Python import_from_statement node.
|
|
486
|
+
* Handles: from os import path, from os import path as p, from os import *
|
|
487
|
+
*/
|
|
488
|
+
function extractPythonFromImportStatement(node) {
|
|
489
|
+
const imports = [];
|
|
490
|
+
const lineNumber = node.startPosition.row + 1;
|
|
491
|
+
// Get the module name
|
|
492
|
+
const moduleNode = node.childForFieldName('module_name');
|
|
493
|
+
let fromPackage = moduleNode ? getNodeText(moduleNode) : null;
|
|
494
|
+
// Handle relative imports: from . import x, from .. import x
|
|
495
|
+
if (!fromPackage) {
|
|
496
|
+
// Check for relative import dots
|
|
497
|
+
let relativeDots = '';
|
|
498
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
499
|
+
const child = node.child(i);
|
|
500
|
+
if (child && child.type === 'relative_import') {
|
|
501
|
+
relativeDots = getNodeText(child);
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
if (relativeDots) {
|
|
506
|
+
fromPackage = relativeDots;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Extract imported names
|
|
510
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
511
|
+
const child = node.child(i);
|
|
512
|
+
if (!child)
|
|
513
|
+
continue;
|
|
514
|
+
// Wildcard import: from os import *
|
|
515
|
+
if (child.type === 'wildcard_import') {
|
|
516
|
+
imports.push({
|
|
517
|
+
imported_name: '*',
|
|
518
|
+
from_package: fromPackage,
|
|
519
|
+
alias: null,
|
|
520
|
+
is_wildcard: true,
|
|
521
|
+
line_number: lineNumber,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
// Simple name: from os import path
|
|
525
|
+
else if (child.type === 'dotted_name' && child !== moduleNode) {
|
|
526
|
+
imports.push({
|
|
527
|
+
imported_name: getNodeText(child),
|
|
528
|
+
from_package: fromPackage,
|
|
529
|
+
alias: null,
|
|
530
|
+
is_wildcard: false,
|
|
531
|
+
line_number: lineNumber,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
// Aliased import: from os import path as p
|
|
535
|
+
else if (child.type === 'aliased_import') {
|
|
536
|
+
const nameNode = child.childForFieldName('name');
|
|
537
|
+
const aliasNode = child.childForFieldName('alias');
|
|
538
|
+
if (nameNode) {
|
|
539
|
+
imports.push({
|
|
540
|
+
imported_name: getNodeText(nameNode),
|
|
541
|
+
from_package: fromPackage,
|
|
542
|
+
alias: aliasNode ? getNodeText(aliasNode) : null,
|
|
543
|
+
is_wildcard: false,
|
|
544
|
+
line_number: lineNumber,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return imports;
|
|
550
|
+
}
|
|
551
|
+
// =============================================================================
|
|
552
|
+
// Rust Import Extraction
|
|
553
|
+
// =============================================================================
|
|
554
|
+
/**
|
|
555
|
+
* Extract Rust use declarations.
|
|
556
|
+
*/
|
|
557
|
+
function extractRustImports(tree) {
|
|
558
|
+
const imports = [];
|
|
559
|
+
// Find all use declarations
|
|
560
|
+
const useDecls = findNodes(tree.rootNode, 'use_declaration');
|
|
561
|
+
for (const useDecl of useDecls) {
|
|
562
|
+
const useImports = extractRustUseDecl(useDecl);
|
|
563
|
+
imports.push(...useImports);
|
|
564
|
+
}
|
|
565
|
+
return imports;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Extract imports from a Rust use declaration.
|
|
569
|
+
* Handles various forms:
|
|
570
|
+
* - use std::io;
|
|
571
|
+
* - use std::collections::HashMap;
|
|
572
|
+
* - use actix_web::{web, App, HttpServer};
|
|
573
|
+
* - use foo::bar::*;
|
|
574
|
+
* - use crate::module::Type as Alias;
|
|
575
|
+
*/
|
|
576
|
+
function extractRustUseDecl(node) {
|
|
577
|
+
const imports = [];
|
|
578
|
+
const lineNumber = node.startPosition.row + 1;
|
|
579
|
+
const text = getNodeText(node);
|
|
580
|
+
// Find the use_list or scoped_identifier or identifier inside
|
|
581
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
582
|
+
const child = node.child(i);
|
|
583
|
+
if (!child)
|
|
584
|
+
continue;
|
|
585
|
+
if (child.type === 'scoped_identifier') {
|
|
586
|
+
// Simple use: use std::io;
|
|
587
|
+
const parts = getNodeText(child).split('::');
|
|
588
|
+
const importedName = parts.pop() || '';
|
|
589
|
+
const fromPackage = parts.join('::');
|
|
590
|
+
imports.push({
|
|
591
|
+
imported_name: importedName,
|
|
592
|
+
from_package: fromPackage || null,
|
|
593
|
+
alias: null,
|
|
594
|
+
is_wildcard: importedName === '*',
|
|
595
|
+
line_number: lineNumber,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
else if (child.type === 'scoped_use_list') {
|
|
599
|
+
// Grouped use: use foo::{Bar, Baz};
|
|
600
|
+
const scopedImports = extractRustScopedUseList(child, lineNumber);
|
|
601
|
+
imports.push(...scopedImports);
|
|
602
|
+
}
|
|
603
|
+
else if (child.type === 'use_as_clause') {
|
|
604
|
+
// Aliased import: use foo::Bar as B;
|
|
605
|
+
const pathNode = child.childForFieldName('path');
|
|
606
|
+
const aliasNode = child.childForFieldName('alias');
|
|
607
|
+
if (pathNode) {
|
|
608
|
+
const parts = getNodeText(pathNode).split('::');
|
|
609
|
+
const importedName = parts.pop() || '';
|
|
610
|
+
const fromPackage = parts.join('::');
|
|
611
|
+
imports.push({
|
|
612
|
+
imported_name: importedName,
|
|
613
|
+
from_package: fromPackage || null,
|
|
614
|
+
alias: aliasNode ? getNodeText(aliasNode) : null,
|
|
615
|
+
is_wildcard: false,
|
|
616
|
+
line_number: lineNumber,
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
else if (child.type === 'use_wildcard') {
|
|
621
|
+
// Wildcard: use foo::*;
|
|
622
|
+
const pathNode = child.childForFieldName('path');
|
|
623
|
+
const fromPackage = pathNode ? getNodeText(pathNode) : null;
|
|
624
|
+
imports.push({
|
|
625
|
+
imported_name: '*',
|
|
626
|
+
from_package: fromPackage,
|
|
627
|
+
alias: null,
|
|
628
|
+
is_wildcard: true,
|
|
629
|
+
line_number: lineNumber,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
else if (child.type === 'identifier') {
|
|
633
|
+
// Single identifier: use std; (rare)
|
|
634
|
+
imports.push({
|
|
635
|
+
imported_name: getNodeText(child),
|
|
636
|
+
from_package: null,
|
|
637
|
+
alias: null,
|
|
638
|
+
is_wildcard: false,
|
|
639
|
+
line_number: lineNumber,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
return imports;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Extract imports from a Rust scoped_use_list.
|
|
647
|
+
* e.g., use actix_web::{web, App, HttpServer};
|
|
648
|
+
*/
|
|
649
|
+
function extractRustScopedUseList(node, lineNumber) {
|
|
650
|
+
const imports = [];
|
|
651
|
+
// Find the base path (before the {})
|
|
652
|
+
let basePath = '';
|
|
653
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
654
|
+
const child = node.child(i);
|
|
655
|
+
if (!child)
|
|
656
|
+
continue;
|
|
657
|
+
if (child.type === 'scoped_identifier' || child.type === 'identifier') {
|
|
658
|
+
basePath = getNodeText(child);
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Find the use_list (inside {})
|
|
663
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
664
|
+
const child = node.child(i);
|
|
665
|
+
if (!child || child.type !== 'use_list')
|
|
666
|
+
continue;
|
|
667
|
+
// Extract each item in the use list
|
|
668
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
669
|
+
const item = child.child(j);
|
|
670
|
+
if (!item)
|
|
671
|
+
continue;
|
|
672
|
+
if (item.type === 'identifier') {
|
|
673
|
+
imports.push({
|
|
674
|
+
imported_name: getNodeText(item),
|
|
675
|
+
from_package: basePath || null,
|
|
676
|
+
alias: null,
|
|
677
|
+
is_wildcard: false,
|
|
678
|
+
line_number: lineNumber,
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
else if (item.type === 'scoped_identifier') {
|
|
682
|
+
// Nested path in use list
|
|
683
|
+
const parts = getNodeText(item).split('::');
|
|
684
|
+
const importedName = parts.pop() || '';
|
|
685
|
+
const nestedPath = parts.join('::');
|
|
686
|
+
const fullPath = basePath ? `${basePath}::${nestedPath}` : nestedPath;
|
|
687
|
+
imports.push({
|
|
688
|
+
imported_name: importedName,
|
|
689
|
+
from_package: fullPath || null,
|
|
690
|
+
alias: null,
|
|
691
|
+
is_wildcard: false,
|
|
692
|
+
line_number: lineNumber,
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
else if (item.type === 'use_as_clause') {
|
|
696
|
+
const pathNode = item.childForFieldName('path');
|
|
697
|
+
const aliasNode = item.childForFieldName('alias');
|
|
698
|
+
if (pathNode) {
|
|
699
|
+
const pathText = getNodeText(pathNode);
|
|
700
|
+
// Handle both simple identifier and scoped path
|
|
701
|
+
if (pathText.includes('::')) {
|
|
702
|
+
const parts = pathText.split('::');
|
|
703
|
+
const importedName = parts.pop() || '';
|
|
704
|
+
const nestedPath = parts.join('::');
|
|
705
|
+
const fullPath = basePath ? `${basePath}::${nestedPath}` : nestedPath;
|
|
706
|
+
imports.push({
|
|
707
|
+
imported_name: importedName,
|
|
708
|
+
from_package: fullPath || null,
|
|
709
|
+
alias: aliasNode ? getNodeText(aliasNode) : null,
|
|
710
|
+
is_wildcard: false,
|
|
711
|
+
line_number: lineNumber,
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
imports.push({
|
|
716
|
+
imported_name: pathText,
|
|
717
|
+
from_package: basePath || null,
|
|
718
|
+
alias: aliasNode ? getNodeText(aliasNode) : null,
|
|
719
|
+
is_wildcard: false,
|
|
720
|
+
line_number: lineNumber,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
else if (item.type === 'self') {
|
|
726
|
+
// use foo::{self}; imports the module itself
|
|
727
|
+
imports.push({
|
|
728
|
+
imported_name: 'self',
|
|
729
|
+
from_package: basePath || null,
|
|
730
|
+
alias: null,
|
|
731
|
+
is_wildcard: false,
|
|
732
|
+
line_number: lineNumber,
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return imports;
|
|
738
|
+
}
|
|
739
|
+
//# sourceMappingURL=imports.js.map
|