Skip to content

Commit c7d5535

Browse files
committed
fix(gleam): extract parameters for external functions
Mirror regular `function` behavior so `external_function` definitions expose their parameter list as children in both native and WASM engines. Previously external functions silently dropped parameter children even though the grammar exposes them via the same `parameters` field. Closes #1110
1 parent 2ba882a commit c7d5535

3 files changed

Lines changed: 60 additions & 1 deletion

File tree

crates/codegraph-core/src/extractors/gleam.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ fn handle_external_function(node: &Node, source: &[u8], symbols: &mut FileSymbol
6161
None => return,
6262
};
6363

64+
let params = extract_params(node, source);
65+
6466
symbols.definitions.push(Definition {
6567
name: node_text(&name_node, source).to_string(),
6668
kind: "function".to_string(),
@@ -69,7 +71,7 @@ fn handle_external_function(node: &Node, source: &[u8], symbols: &mut FileSymbol
6971
decorators: None,
7072
complexity: None,
7173
cfg: None,
72-
children: None,
74+
children: opt_children(params),
7375
});
7476
}
7577

@@ -443,4 +445,37 @@ mod tests {
443445
.expect("expected constant");
444446
assert_eq!(c.kind, "variable");
445447
}
448+
449+
#[test]
450+
fn extracts_external_function_with_named_parameters() {
451+
let code = "pub external fn parse(input: String, base: Int) -> Int = \"erlang_mod\" \"parse\"\n";
452+
let s = parse_gleam(code);
453+
let parse_fn = s
454+
.definitions
455+
.iter()
456+
.find(|d| d.name == "parse")
457+
.expect("expected external function `parse`");
458+
assert_eq!(parse_fn.kind, "function");
459+
let children = parse_fn
460+
.children
461+
.as_ref()
462+
.expect("expected external function parameters as children");
463+
let names: Vec<&str> = children.iter().map(|c| c.name.as_str()).collect();
464+
assert!(names.contains(&"input"), "missing `input` param, got {names:?}");
465+
assert!(names.contains(&"base"), "missing `base` param, got {names:?}");
466+
assert!(children.iter().all(|c| c.kind == "parameter"));
467+
}
468+
469+
#[test]
470+
fn external_function_without_param_names_has_no_children() {
471+
// External function with type-only parameters (no names) — should not emit children.
472+
let code = "pub external fn random() -> Int = \"rand\" \"uniform\"\n";
473+
let s = parse_gleam(code);
474+
let random_fn = s
475+
.definitions
476+
.iter()
477+
.find(|d| d.name == "random")
478+
.expect("expected external function `random`");
479+
assert!(random_fn.children.is_none());
480+
}
446481
}

src/extractors/gleam.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,15 @@ function handleExternalFunction(node: TreeSitterNode, ctx: ExtractorOutput): voi
8585
const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
8686
if (!nameNode) return;
8787

88+
const params = extractParams(node);
89+
8890
ctx.definitions.push({
8991
name: nameNode.text,
9092
kind: 'function',
9193
line: node.startPosition.row + 1,
9294
endLine: nodeEndLine(node),
9395
visibility: isPublic(node) ? 'public' : 'private',
96+
children: params.length > 0 ? params : undefined,
9497
});
9598
}
9699

tests/parsers/gleam.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,25 @@ import gleam/string`);
4545
}`);
4646
expect(symbols.calls.length).toBeGreaterThanOrEqual(1);
4747
});
48+
49+
it('extracts external function parameters as children', () => {
50+
const symbols = parseGleam(
51+
`pub external fn parse(input: String, base: Int) -> Int = "erlang_mod" "parse"`,
52+
);
53+
const parseFn = symbols.definitions.find((d) => d.name === 'parse');
54+
expect(parseFn).toBeDefined();
55+
expect(parseFn?.kind).toBe('function');
56+
expect(parseFn?.children).toBeDefined();
57+
const names = parseFn?.children?.map((c) => c.name) ?? [];
58+
expect(names).toContain('input');
59+
expect(names).toContain('base');
60+
expect(parseFn?.children?.every((c) => c.kind === 'parameter')).toBe(true);
61+
});
62+
63+
it('omits children for external functions with type-only parameters', () => {
64+
const symbols = parseGleam(`pub external fn random() -> Int = "rand" "uniform"`);
65+
const randomFn = symbols.definitions.find((d) => d.name === 'random');
66+
expect(randomFn).toBeDefined();
67+
expect(randomFn?.children).toBeUndefined();
68+
});
4869
});

0 commit comments

Comments
 (0)