circle-ir 3.4.0 → 3.6.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/dist/analyzer.js +52 -2
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +364 -1
- package/dist/core/circle-ir-core.cjs +85 -0
- package/dist/core/circle-ir-core.js +85 -0
- package/dist/core/extractors/calls.js +119 -0
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/parser.d.ts +1 -1
- package/dist/languages/plugins/bash.d.ts +51 -0
- package/dist/languages/plugins/bash.js +243 -0
- package/dist/languages/plugins/bash.js.map +1 -0
- package/dist/languages/plugins/index.d.ts +1 -0
- package/dist/languages/plugins/index.js +3 -0
- package/dist/languages/plugins/index.js.map +1 -1
- package/dist/languages/types.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/wasm/tree-sitter-bash.wasm +0 -0
- package/package.json +2 -1
- package/wasm/tree-sitter-bash.wasm +0 -0
|
@@ -5470,6 +5470,9 @@ function extractCalls(tree, cache, language) {
|
|
|
5470
5470
|
const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
|
|
5471
5471
|
const isPython = detectedLanguage === "python";
|
|
5472
5472
|
const isRust = detectedLanguage === "rust";
|
|
5473
|
+
if (detectedLanguage === "bash") {
|
|
5474
|
+
return extractBashCalls(tree, cache);
|
|
5475
|
+
}
|
|
5473
5476
|
if (isRust) {
|
|
5474
5477
|
return extractRustCalls(tree, cache);
|
|
5475
5478
|
}
|
|
@@ -6159,6 +6162,88 @@ function inferTypeFromReceiverName(receiver) {
|
|
|
6159
6162
|
const lowerReceiver = receiver.toLowerCase();
|
|
6160
6163
|
return patterns[lowerReceiver] ?? null;
|
|
6161
6164
|
}
|
|
6165
|
+
function extractBashCalls(tree, cache) {
|
|
6166
|
+
const calls = [];
|
|
6167
|
+
const commands = getNodesFromCache(tree.rootNode, "command", cache);
|
|
6168
|
+
for (const cmd of commands) {
|
|
6169
|
+
const callInfo = extractBashCommandInfo(cmd);
|
|
6170
|
+
if (callInfo) {
|
|
6171
|
+
calls.push(callInfo);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
return calls;
|
|
6175
|
+
}
|
|
6176
|
+
function extractBashCommandInfo(node) {
|
|
6177
|
+
const nameNode = node.childForFieldName("name");
|
|
6178
|
+
if (!nameNode) return null;
|
|
6179
|
+
const commandName = getNodeText(nameNode);
|
|
6180
|
+
if (!commandName) return null;
|
|
6181
|
+
const args2 = [];
|
|
6182
|
+
let position = 0;
|
|
6183
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6184
|
+
const child = node.child(i2);
|
|
6185
|
+
if (!child) continue;
|
|
6186
|
+
if (child === nameNode) continue;
|
|
6187
|
+
if (child.type.includes("redirect") || child.type === "heredoc_body" || child.type === "file_descriptor") {
|
|
6188
|
+
continue;
|
|
6189
|
+
}
|
|
6190
|
+
const expression = getNodeText(child);
|
|
6191
|
+
if (!expression.trim()) continue;
|
|
6192
|
+
const variable = extractBashVariableRef(child);
|
|
6193
|
+
args2.push({
|
|
6194
|
+
position: position++,
|
|
6195
|
+
expression,
|
|
6196
|
+
variable,
|
|
6197
|
+
literal: null
|
|
6198
|
+
});
|
|
6199
|
+
}
|
|
6200
|
+
const inMethod = findBashEnclosingFunction(node);
|
|
6201
|
+
return {
|
|
6202
|
+
method_name: commandName,
|
|
6203
|
+
receiver: null,
|
|
6204
|
+
arguments: args2,
|
|
6205
|
+
location: {
|
|
6206
|
+
line: node.startPosition.row + 1,
|
|
6207
|
+
column: node.startPosition.column
|
|
6208
|
+
},
|
|
6209
|
+
in_method: inMethod,
|
|
6210
|
+
resolved: false,
|
|
6211
|
+
resolution: { status: "external_method" }
|
|
6212
|
+
};
|
|
6213
|
+
}
|
|
6214
|
+
function extractBashVariableRef(node) {
|
|
6215
|
+
const type = node.type;
|
|
6216
|
+
if (type === "simple_expansion" || type === "expansion") {
|
|
6217
|
+
return getNodeText(node).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6218
|
+
}
|
|
6219
|
+
if (type === "string" || type === "concatenation") {
|
|
6220
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6221
|
+
const child = node.child(i2);
|
|
6222
|
+
if (!child) continue;
|
|
6223
|
+
if (child.type === "simple_expansion" || child.type === "expansion") {
|
|
6224
|
+
return getNodeText(child).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
if (type === "word") {
|
|
6229
|
+
const text = getNodeText(node);
|
|
6230
|
+
if (text.startsWith("$")) {
|
|
6231
|
+
return text.slice(1).replace(/^\{/, "").replace(/\}$/, "");
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
return null;
|
|
6235
|
+
}
|
|
6236
|
+
function findBashEnclosingFunction(node) {
|
|
6237
|
+
let current = node.parent;
|
|
6238
|
+
while (current) {
|
|
6239
|
+
if (current.type === "function_definition") {
|
|
6240
|
+
const nameNode = current.childForFieldName("name");
|
|
6241
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
6242
|
+
}
|
|
6243
|
+
current = current.parent;
|
|
6244
|
+
}
|
|
6245
|
+
return null;
|
|
6246
|
+
}
|
|
6162
6247
|
function extractPythonCalls(tree, cache) {
|
|
6163
6248
|
const calls = [];
|
|
6164
6249
|
const context = buildPythonResolutionContext(tree, cache);
|
|
@@ -13896,6 +13981,9 @@ function getLanguageRegistry() {
|
|
|
13896
13981
|
function registerLanguage(plugin) {
|
|
13897
13982
|
getLanguageRegistry().register(plugin);
|
|
13898
13983
|
}
|
|
13984
|
+
function getLanguagePlugin(language) {
|
|
13985
|
+
return getLanguageRegistry().get(language);
|
|
13986
|
+
}
|
|
13899
13987
|
|
|
13900
13988
|
// src/languages/plugins/base.ts
|
|
13901
13989
|
var BaseLanguagePlugin = class {
|
|
@@ -16033,12 +16121,241 @@ var RustPlugin = class extends BaseLanguagePlugin {
|
|
|
16033
16121
|
}
|
|
16034
16122
|
};
|
|
16035
16123
|
|
|
16124
|
+
// src/languages/plugins/bash.ts
|
|
16125
|
+
var BashPlugin = class extends BaseLanguagePlugin {
|
|
16126
|
+
id = "bash";
|
|
16127
|
+
name = "Bash/Shell";
|
|
16128
|
+
extensions = [".sh", ".bash", ".zsh", ".ksh"];
|
|
16129
|
+
wasmPath = "tree-sitter-bash.wasm";
|
|
16130
|
+
nodeTypes = {
|
|
16131
|
+
// Type declarations — shell has no OOP types
|
|
16132
|
+
classDeclaration: [],
|
|
16133
|
+
interfaceDeclaration: [],
|
|
16134
|
+
enumDeclaration: [],
|
|
16135
|
+
functionDeclaration: ["function_definition"],
|
|
16136
|
+
methodDeclaration: ["function_definition"],
|
|
16137
|
+
// Expressions — commands are treated as calls
|
|
16138
|
+
methodCall: ["command"],
|
|
16139
|
+
functionCall: ["command"],
|
|
16140
|
+
assignment: ["variable_assignment"],
|
|
16141
|
+
variableDeclaration: ["variable_assignment", "declaration_command"],
|
|
16142
|
+
// Parameters and arguments — positional args are child words
|
|
16143
|
+
parameter: [],
|
|
16144
|
+
argument: [],
|
|
16145
|
+
// Annotations/decorators — none in shell
|
|
16146
|
+
annotation: [],
|
|
16147
|
+
decorator: [],
|
|
16148
|
+
// Imports — shell uses `source` / `.` but no formal import system
|
|
16149
|
+
importStatement: [],
|
|
16150
|
+
// Control flow
|
|
16151
|
+
ifStatement: ["if_statement"],
|
|
16152
|
+
forStatement: ["for_statement", "c_style_for_statement"],
|
|
16153
|
+
whileStatement: ["while_statement"],
|
|
16154
|
+
tryStatement: [],
|
|
16155
|
+
returnStatement: []
|
|
16156
|
+
};
|
|
16157
|
+
/**
|
|
16158
|
+
* Shell scripts don't have a formal framework concept.
|
|
16159
|
+
*/
|
|
16160
|
+
detectFramework(_context) {
|
|
16161
|
+
return void 0;
|
|
16162
|
+
}
|
|
16163
|
+
/**
|
|
16164
|
+
* Bash taint source patterns.
|
|
16165
|
+
* In shell, tainted data enters via `read` (stdin).
|
|
16166
|
+
* curl/wget are excluded as sources (see comment in implementation).
|
|
16167
|
+
*/
|
|
16168
|
+
getBuiltinSources() {
|
|
16169
|
+
return [
|
|
16170
|
+
// read built-in reads user input from stdin.
|
|
16171
|
+
// curl/wget are intentionally excluded: they're also registered as sinks (SSRF),
|
|
16172
|
+
// and without DFG tracking of $() command substitution, including them as sources
|
|
16173
|
+
// would generate false positives for safe curl calls.
|
|
16174
|
+
{
|
|
16175
|
+
method: "read",
|
|
16176
|
+
type: "io_input",
|
|
16177
|
+
severity: "high",
|
|
16178
|
+
confidence: 0.9,
|
|
16179
|
+
returnTainted: true
|
|
16180
|
+
}
|
|
16181
|
+
];
|
|
16182
|
+
}
|
|
16183
|
+
/**
|
|
16184
|
+
* Bash taint sink patterns.
|
|
16185
|
+
* Key sinks: eval (CWE-94), bash/sh -c (CWE-78), DB clients (CWE-89),
|
|
16186
|
+
* file operations (CWE-22), SSRF via curl/wget (CWE-918).
|
|
16187
|
+
*/
|
|
16188
|
+
getBuiltinSinks() {
|
|
16189
|
+
return [
|
|
16190
|
+
// Code / command injection via eval
|
|
16191
|
+
{
|
|
16192
|
+
method: "eval",
|
|
16193
|
+
type: "code_injection",
|
|
16194
|
+
cwe: "CWE-94",
|
|
16195
|
+
severity: "critical",
|
|
16196
|
+
argPositions: [0]
|
|
16197
|
+
},
|
|
16198
|
+
// Command injection: spawning a sub-shell with -c flag
|
|
16199
|
+
{
|
|
16200
|
+
method: "bash",
|
|
16201
|
+
type: "command_injection",
|
|
16202
|
+
cwe: "CWE-78",
|
|
16203
|
+
severity: "critical",
|
|
16204
|
+
argPositions: [1]
|
|
16205
|
+
},
|
|
16206
|
+
{
|
|
16207
|
+
method: "sh",
|
|
16208
|
+
type: "command_injection",
|
|
16209
|
+
cwe: "CWE-78",
|
|
16210
|
+
severity: "critical",
|
|
16211
|
+
argPositions: [1]
|
|
16212
|
+
},
|
|
16213
|
+
{
|
|
16214
|
+
method: "zsh",
|
|
16215
|
+
type: "command_injection",
|
|
16216
|
+
cwe: "CWE-78",
|
|
16217
|
+
severity: "critical",
|
|
16218
|
+
argPositions: [1]
|
|
16219
|
+
},
|
|
16220
|
+
{
|
|
16221
|
+
method: "ksh",
|
|
16222
|
+
type: "command_injection",
|
|
16223
|
+
cwe: "CWE-78",
|
|
16224
|
+
severity: "critical",
|
|
16225
|
+
argPositions: [1]
|
|
16226
|
+
},
|
|
16227
|
+
// SQL injection via DB CLI clients (first arg is query/expression)
|
|
16228
|
+
{
|
|
16229
|
+
method: "mysql",
|
|
16230
|
+
type: "sql_injection",
|
|
16231
|
+
cwe: "CWE-89",
|
|
16232
|
+
severity: "critical",
|
|
16233
|
+
argPositions: [1]
|
|
16234
|
+
},
|
|
16235
|
+
{
|
|
16236
|
+
method: "psql",
|
|
16237
|
+
type: "sql_injection",
|
|
16238
|
+
cwe: "CWE-89",
|
|
16239
|
+
severity: "critical",
|
|
16240
|
+
argPositions: [1]
|
|
16241
|
+
},
|
|
16242
|
+
{
|
|
16243
|
+
method: "sqlite3",
|
|
16244
|
+
type: "sql_injection",
|
|
16245
|
+
cwe: "CWE-89",
|
|
16246
|
+
severity: "critical",
|
|
16247
|
+
argPositions: [1]
|
|
16248
|
+
},
|
|
16249
|
+
// Path traversal via file operations (first arg is path)
|
|
16250
|
+
{
|
|
16251
|
+
method: "cat",
|
|
16252
|
+
type: "path_traversal",
|
|
16253
|
+
cwe: "CWE-22",
|
|
16254
|
+
severity: "high",
|
|
16255
|
+
argPositions: [0]
|
|
16256
|
+
},
|
|
16257
|
+
{
|
|
16258
|
+
method: "rm",
|
|
16259
|
+
type: "path_traversal",
|
|
16260
|
+
cwe: "CWE-22",
|
|
16261
|
+
severity: "high",
|
|
16262
|
+
argPositions: [0]
|
|
16263
|
+
},
|
|
16264
|
+
{
|
|
16265
|
+
method: "cp",
|
|
16266
|
+
type: "path_traversal",
|
|
16267
|
+
cwe: "CWE-22",
|
|
16268
|
+
severity: "high",
|
|
16269
|
+
argPositions: [0]
|
|
16270
|
+
},
|
|
16271
|
+
{
|
|
16272
|
+
method: "mv",
|
|
16273
|
+
type: "path_traversal",
|
|
16274
|
+
cwe: "CWE-22",
|
|
16275
|
+
severity: "high",
|
|
16276
|
+
argPositions: [0]
|
|
16277
|
+
},
|
|
16278
|
+
{
|
|
16279
|
+
method: "chmod",
|
|
16280
|
+
type: "path_traversal",
|
|
16281
|
+
cwe: "CWE-22",
|
|
16282
|
+
severity: "medium",
|
|
16283
|
+
argPositions: [1]
|
|
16284
|
+
},
|
|
16285
|
+
{
|
|
16286
|
+
method: "chown",
|
|
16287
|
+
type: "path_traversal",
|
|
16288
|
+
cwe: "CWE-22",
|
|
16289
|
+
severity: "medium",
|
|
16290
|
+
argPositions: [1]
|
|
16291
|
+
},
|
|
16292
|
+
// SSRF — curl/wget with externally-controlled URL
|
|
16293
|
+
{
|
|
16294
|
+
method: "curl",
|
|
16295
|
+
type: "ssrf",
|
|
16296
|
+
cwe: "CWE-918",
|
|
16297
|
+
severity: "high",
|
|
16298
|
+
argPositions: [0]
|
|
16299
|
+
},
|
|
16300
|
+
{
|
|
16301
|
+
method: "wget",
|
|
16302
|
+
type: "ssrf",
|
|
16303
|
+
cwe: "CWE-918",
|
|
16304
|
+
severity: "high",
|
|
16305
|
+
argPositions: [0]
|
|
16306
|
+
}
|
|
16307
|
+
];
|
|
16308
|
+
}
|
|
16309
|
+
/**
|
|
16310
|
+
* Shell has no OOP receiver types.
|
|
16311
|
+
*/
|
|
16312
|
+
getReceiverType(_node, _context) {
|
|
16313
|
+
return void 0;
|
|
16314
|
+
}
|
|
16315
|
+
/**
|
|
16316
|
+
* Bash string literals: quoted strings and raw ($'...') strings.
|
|
16317
|
+
*/
|
|
16318
|
+
isStringLiteral(node) {
|
|
16319
|
+
return node.type === "string" || node.type === "raw_string" || node.type === "ansi_c_string";
|
|
16320
|
+
}
|
|
16321
|
+
/**
|
|
16322
|
+
* Extract string value from bash string literal, stripping quotes.
|
|
16323
|
+
*/
|
|
16324
|
+
getStringValue(node) {
|
|
16325
|
+
if (!this.isStringLiteral(node)) return void 0;
|
|
16326
|
+
const text = node.text;
|
|
16327
|
+
if (node.type === "raw_string") {
|
|
16328
|
+
return text.slice(1, -1);
|
|
16329
|
+
}
|
|
16330
|
+
if (node.type === "ansi_c_string") {
|
|
16331
|
+
return text.slice(2, -1);
|
|
16332
|
+
}
|
|
16333
|
+
const match = text.match(/^"(.*)"$/s);
|
|
16334
|
+
if (match) return match[1];
|
|
16335
|
+
return text;
|
|
16336
|
+
}
|
|
16337
|
+
// Extraction methods — delegate to base extractors via generic walker
|
|
16338
|
+
extractTypes(_context) {
|
|
16339
|
+
return [];
|
|
16340
|
+
}
|
|
16341
|
+
extractCalls(_context) {
|
|
16342
|
+
return [];
|
|
16343
|
+
}
|
|
16344
|
+
extractImports(_context) {
|
|
16345
|
+
return [];
|
|
16346
|
+
}
|
|
16347
|
+
extractPackage(_context) {
|
|
16348
|
+
return void 0;
|
|
16349
|
+
}
|
|
16350
|
+
};
|
|
16351
|
+
|
|
16036
16352
|
// src/languages/plugins/index.ts
|
|
16037
16353
|
function registerBuiltinPlugins() {
|
|
16038
16354
|
registerLanguage(new JavaPlugin());
|
|
16039
16355
|
registerLanguage(new JavaScriptPlugin());
|
|
16040
16356
|
registerLanguage(new PythonPlugin());
|
|
16041
16357
|
registerLanguage(new RustPlugin());
|
|
16358
|
+
registerLanguage(new BashPlugin());
|
|
16042
16359
|
}
|
|
16043
16360
|
|
|
16044
16361
|
// src/utils/logger.ts
|
|
@@ -16367,6 +16684,18 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
16367
16684
|
"member_expression",
|
|
16368
16685
|
"assignment_expression"
|
|
16369
16686
|
]);
|
|
16687
|
+
} else if (language === "bash") {
|
|
16688
|
+
nodeTypesToCollect = /* @__PURE__ */ new Set([
|
|
16689
|
+
// Bash AST nodes
|
|
16690
|
+
"command",
|
|
16691
|
+
"function_definition",
|
|
16692
|
+
"variable_assignment",
|
|
16693
|
+
"declaration_command",
|
|
16694
|
+
"if_statement",
|
|
16695
|
+
"for_statement",
|
|
16696
|
+
"c_style_for_statement",
|
|
16697
|
+
"while_statement"
|
|
16698
|
+
]);
|
|
16370
16699
|
} else {
|
|
16371
16700
|
nodeTypesToCollect = /* @__PURE__ */ new Set([
|
|
16372
16701
|
// Java AST nodes
|
|
@@ -16397,7 +16726,41 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
16397
16726
|
}
|
|
16398
16727
|
}
|
|
16399
16728
|
}
|
|
16400
|
-
|
|
16729
|
+
let baseConfig = options.taintConfig ?? getDefaultConfig();
|
|
16730
|
+
if (!options.taintConfig) {
|
|
16731
|
+
const plugin = getLanguagePlugin(language);
|
|
16732
|
+
if (plugin) {
|
|
16733
|
+
const pluginSources = plugin.getBuiltinSources();
|
|
16734
|
+
const pluginSinks = plugin.getBuiltinSinks();
|
|
16735
|
+
if (pluginSources.length > 0 || pluginSinks.length > 0) {
|
|
16736
|
+
baseConfig = {
|
|
16737
|
+
...baseConfig,
|
|
16738
|
+
sources: [
|
|
16739
|
+
...baseConfig.sources,
|
|
16740
|
+
...pluginSources.map((s) => ({
|
|
16741
|
+
method: s.method,
|
|
16742
|
+
class: s.class,
|
|
16743
|
+
annotation: s.annotation,
|
|
16744
|
+
type: s.type,
|
|
16745
|
+
severity: s.severity,
|
|
16746
|
+
return_tainted: s.returnTainted ?? false
|
|
16747
|
+
}))
|
|
16748
|
+
],
|
|
16749
|
+
sinks: [
|
|
16750
|
+
...baseConfig.sinks,
|
|
16751
|
+
...pluginSinks.map((s) => ({
|
|
16752
|
+
method: s.method,
|
|
16753
|
+
class: s.class,
|
|
16754
|
+
type: s.type,
|
|
16755
|
+
cwe: s.cwe,
|
|
16756
|
+
severity: s.severity,
|
|
16757
|
+
arg_positions: s.argPositions
|
|
16758
|
+
}))
|
|
16759
|
+
]
|
|
16760
|
+
};
|
|
16761
|
+
}
|
|
16762
|
+
}
|
|
16763
|
+
}
|
|
16401
16764
|
const preliminaryTaint = analyzeTaint(calls, types, baseConfig);
|
|
16402
16765
|
const taintedParameters = [];
|
|
16403
16766
|
for (const source of preliminaryTaint.sources) {
|
|
@@ -5546,6 +5546,9 @@ function extractCalls(tree, cache, language) {
|
|
|
5546
5546
|
const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
|
|
5547
5547
|
const isPython = detectedLanguage === "python";
|
|
5548
5548
|
const isRust = detectedLanguage === "rust";
|
|
5549
|
+
if (detectedLanguage === "bash") {
|
|
5550
|
+
return extractBashCalls(tree, cache);
|
|
5551
|
+
}
|
|
5549
5552
|
if (isRust) {
|
|
5550
5553
|
return extractRustCalls(tree, cache);
|
|
5551
5554
|
}
|
|
@@ -6235,6 +6238,88 @@ function inferTypeFromReceiverName(receiver) {
|
|
|
6235
6238
|
const lowerReceiver = receiver.toLowerCase();
|
|
6236
6239
|
return patterns[lowerReceiver] ?? null;
|
|
6237
6240
|
}
|
|
6241
|
+
function extractBashCalls(tree, cache) {
|
|
6242
|
+
const calls = [];
|
|
6243
|
+
const commands = getNodesFromCache(tree.rootNode, "command", cache);
|
|
6244
|
+
for (const cmd of commands) {
|
|
6245
|
+
const callInfo = extractBashCommandInfo(cmd);
|
|
6246
|
+
if (callInfo) {
|
|
6247
|
+
calls.push(callInfo);
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
return calls;
|
|
6251
|
+
}
|
|
6252
|
+
function extractBashCommandInfo(node) {
|
|
6253
|
+
const nameNode = node.childForFieldName("name");
|
|
6254
|
+
if (!nameNode) return null;
|
|
6255
|
+
const commandName = getNodeText(nameNode);
|
|
6256
|
+
if (!commandName) return null;
|
|
6257
|
+
const args2 = [];
|
|
6258
|
+
let position = 0;
|
|
6259
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6260
|
+
const child = node.child(i2);
|
|
6261
|
+
if (!child) continue;
|
|
6262
|
+
if (child === nameNode) continue;
|
|
6263
|
+
if (child.type.includes("redirect") || child.type === "heredoc_body" || child.type === "file_descriptor") {
|
|
6264
|
+
continue;
|
|
6265
|
+
}
|
|
6266
|
+
const expression = getNodeText(child);
|
|
6267
|
+
if (!expression.trim()) continue;
|
|
6268
|
+
const variable = extractBashVariableRef(child);
|
|
6269
|
+
args2.push({
|
|
6270
|
+
position: position++,
|
|
6271
|
+
expression,
|
|
6272
|
+
variable,
|
|
6273
|
+
literal: null
|
|
6274
|
+
});
|
|
6275
|
+
}
|
|
6276
|
+
const inMethod = findBashEnclosingFunction(node);
|
|
6277
|
+
return {
|
|
6278
|
+
method_name: commandName,
|
|
6279
|
+
receiver: null,
|
|
6280
|
+
arguments: args2,
|
|
6281
|
+
location: {
|
|
6282
|
+
line: node.startPosition.row + 1,
|
|
6283
|
+
column: node.startPosition.column
|
|
6284
|
+
},
|
|
6285
|
+
in_method: inMethod,
|
|
6286
|
+
resolved: false,
|
|
6287
|
+
resolution: { status: "external_method" }
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6290
|
+
function extractBashVariableRef(node) {
|
|
6291
|
+
const type = node.type;
|
|
6292
|
+
if (type === "simple_expansion" || type === "expansion") {
|
|
6293
|
+
return getNodeText(node).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6294
|
+
}
|
|
6295
|
+
if (type === "string" || type === "concatenation") {
|
|
6296
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6297
|
+
const child = node.child(i2);
|
|
6298
|
+
if (!child) continue;
|
|
6299
|
+
if (child.type === "simple_expansion" || child.type === "expansion") {
|
|
6300
|
+
return getNodeText(child).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6301
|
+
}
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
if (type === "word") {
|
|
6305
|
+
const text = getNodeText(node);
|
|
6306
|
+
if (text.startsWith("$")) {
|
|
6307
|
+
return text.slice(1).replace(/^\{/, "").replace(/\}$/, "");
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
return null;
|
|
6311
|
+
}
|
|
6312
|
+
function findBashEnclosingFunction(node) {
|
|
6313
|
+
let current = node.parent;
|
|
6314
|
+
while (current) {
|
|
6315
|
+
if (current.type === "function_definition") {
|
|
6316
|
+
const nameNode = current.childForFieldName("name");
|
|
6317
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
6318
|
+
}
|
|
6319
|
+
current = current.parent;
|
|
6320
|
+
}
|
|
6321
|
+
return null;
|
|
6322
|
+
}
|
|
6238
6323
|
function extractPythonCalls(tree, cache) {
|
|
6239
6324
|
const calls = [];
|
|
6240
6325
|
const context = buildPythonResolutionContext(tree, cache);
|
|
@@ -5481,6 +5481,9 @@ function extractCalls(tree, cache, language) {
|
|
|
5481
5481
|
const isJavaScript = detectedLanguage === "javascript" || detectedLanguage === "typescript";
|
|
5482
5482
|
const isPython = detectedLanguage === "python";
|
|
5483
5483
|
const isRust = detectedLanguage === "rust";
|
|
5484
|
+
if (detectedLanguage === "bash") {
|
|
5485
|
+
return extractBashCalls(tree, cache);
|
|
5486
|
+
}
|
|
5484
5487
|
if (isRust) {
|
|
5485
5488
|
return extractRustCalls(tree, cache);
|
|
5486
5489
|
}
|
|
@@ -6170,6 +6173,88 @@ function inferTypeFromReceiverName(receiver) {
|
|
|
6170
6173
|
const lowerReceiver = receiver.toLowerCase();
|
|
6171
6174
|
return patterns[lowerReceiver] ?? null;
|
|
6172
6175
|
}
|
|
6176
|
+
function extractBashCalls(tree, cache) {
|
|
6177
|
+
const calls = [];
|
|
6178
|
+
const commands = getNodesFromCache(tree.rootNode, "command", cache);
|
|
6179
|
+
for (const cmd of commands) {
|
|
6180
|
+
const callInfo = extractBashCommandInfo(cmd);
|
|
6181
|
+
if (callInfo) {
|
|
6182
|
+
calls.push(callInfo);
|
|
6183
|
+
}
|
|
6184
|
+
}
|
|
6185
|
+
return calls;
|
|
6186
|
+
}
|
|
6187
|
+
function extractBashCommandInfo(node) {
|
|
6188
|
+
const nameNode = node.childForFieldName("name");
|
|
6189
|
+
if (!nameNode) return null;
|
|
6190
|
+
const commandName = getNodeText(nameNode);
|
|
6191
|
+
if (!commandName) return null;
|
|
6192
|
+
const args2 = [];
|
|
6193
|
+
let position = 0;
|
|
6194
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6195
|
+
const child = node.child(i2);
|
|
6196
|
+
if (!child) continue;
|
|
6197
|
+
if (child === nameNode) continue;
|
|
6198
|
+
if (child.type.includes("redirect") || child.type === "heredoc_body" || child.type === "file_descriptor") {
|
|
6199
|
+
continue;
|
|
6200
|
+
}
|
|
6201
|
+
const expression = getNodeText(child);
|
|
6202
|
+
if (!expression.trim()) continue;
|
|
6203
|
+
const variable = extractBashVariableRef(child);
|
|
6204
|
+
args2.push({
|
|
6205
|
+
position: position++,
|
|
6206
|
+
expression,
|
|
6207
|
+
variable,
|
|
6208
|
+
literal: null
|
|
6209
|
+
});
|
|
6210
|
+
}
|
|
6211
|
+
const inMethod = findBashEnclosingFunction(node);
|
|
6212
|
+
return {
|
|
6213
|
+
method_name: commandName,
|
|
6214
|
+
receiver: null,
|
|
6215
|
+
arguments: args2,
|
|
6216
|
+
location: {
|
|
6217
|
+
line: node.startPosition.row + 1,
|
|
6218
|
+
column: node.startPosition.column
|
|
6219
|
+
},
|
|
6220
|
+
in_method: inMethod,
|
|
6221
|
+
resolved: false,
|
|
6222
|
+
resolution: { status: "external_method" }
|
|
6223
|
+
};
|
|
6224
|
+
}
|
|
6225
|
+
function extractBashVariableRef(node) {
|
|
6226
|
+
const type = node.type;
|
|
6227
|
+
if (type === "simple_expansion" || type === "expansion") {
|
|
6228
|
+
return getNodeText(node).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6229
|
+
}
|
|
6230
|
+
if (type === "string" || type === "concatenation") {
|
|
6231
|
+
for (let i2 = 0; i2 < node.childCount; i2++) {
|
|
6232
|
+
const child = node.child(i2);
|
|
6233
|
+
if (!child) continue;
|
|
6234
|
+
if (child.type === "simple_expansion" || child.type === "expansion") {
|
|
6235
|
+
return getNodeText(child).replace(/^\$\{?/, "").replace(/\}$/, "");
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
if (type === "word") {
|
|
6240
|
+
const text = getNodeText(node);
|
|
6241
|
+
if (text.startsWith("$")) {
|
|
6242
|
+
return text.slice(1).replace(/^\{/, "").replace(/\}$/, "");
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
return null;
|
|
6246
|
+
}
|
|
6247
|
+
function findBashEnclosingFunction(node) {
|
|
6248
|
+
let current = node.parent;
|
|
6249
|
+
while (current) {
|
|
6250
|
+
if (current.type === "function_definition") {
|
|
6251
|
+
const nameNode = current.childForFieldName("name");
|
|
6252
|
+
return nameNode ? getNodeText(nameNode) : null;
|
|
6253
|
+
}
|
|
6254
|
+
current = current.parent;
|
|
6255
|
+
}
|
|
6256
|
+
return null;
|
|
6257
|
+
}
|
|
6173
6258
|
function extractPythonCalls(tree, cache) {
|
|
6174
6259
|
const calls = [];
|
|
6175
6260
|
const context = buildPythonResolutionContext(tree, cache);
|