Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions src/extractors/julia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,14 @@ function handleStructDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
}

function handleAbstractDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
// abstract_definition: `abstract type` type_head `end`
// The identifier is nested inside `type_head` — possibly wrapped in a
// `Name <: Super` binary_expression or a `Name{T,...}` parameterized form.
// Mirror handleStructDef and skip rather than emit a garbled name when no
// base identifier can be located.
const typeHead = findChild(node, 'type_head');
if (!typeHead) return;
const nameNode = findBaseName(typeHead);
if (!nameNode) return;

ctx.definitions.push({
Expand All @@ -298,10 +305,17 @@ function handleMacroDef(
ctx: ExtractorOutput,
currentModule: string | null,
): void {
const nameNode = node.childForFieldName('name') || findChild(node, 'identifier');
// macro_definition: `macro` signature/call_expression body `end`.
// The name lives in the same shape as a function signature — unwrap via
// signatureCall so we don't pick up an identifier from the body (e.g.
// `macro mymac(x) x end` would otherwise resolve to `@x`).
const callSig = signatureCall(node);
const nameNode =
callSig?.child(0) ?? node.childForFieldName('name') ?? findChild(node, 'identifier');
if (!nameNode) return;

const name = currentModule ? `${currentModule}.@${nameNode.text}` : `@${nameNode.text}`;
const base = nameNode.text;
const name = currentModule ? `${currentModule}.@${base}` : `@${base}`;
ctx.definitions.push({
name,
kind: 'function',
Expand Down Expand Up @@ -360,8 +374,15 @@ function handleImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
// Don't record if parent is assignment LHS (that's a function definition)
if (node.parent?.type === 'assignment' && node === node.parent.child(0)) return;
// Don't record if parent is function_definition (that's a signature)
if (node.parent?.type === 'function_definition') return;
// Skip when this call is the signature of a function/macro definition.
// tree-sitter-julia wraps the signature in a `signature` node whose parent
// is `function_definition` or `macro_definition`. Body calls (e.g.
// `println(name)` inside `function greet ... end`) appear as descendants of
// the body, not as direct children of `signature`, so they are unaffected.
if (node.parent?.type === 'signature') {
const grand = node.parent.parent;
if (grand?.type === 'function_definition' || grand?.type === 'macro_definition') return;
}

const funcNode = node.child(0);
if (!funcNode) return;
Expand Down
39 changes: 39 additions & 0 deletions tests/parsers/julia.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,45 @@ end`);
expect(names).not.toContain('Foo.Base.show');
});

it('extracts abstract type', () => {
const symbols = parseJulia(`abstract type AbstractShape end`);
const abs = symbols.definitions.find((d) => d.name === 'AbstractShape');
expect(abs).toBeDefined();
expect(abs).toMatchObject({ kind: 'type' });
});

it('extracts parameterized abstract type base name', () => {
// Parameterized generics with a supertype must record only the base
// identifier — never the raw `Name{T} <: Super{T,1}` text.
const symbols = parseJulia(`abstract type AbstractVector{T} <: AbstractArray{T,1} end`);
const names = symbols.definitions.map((d) => d.name);
expect(names).toContain('AbstractVector');
expect(names.every((n) => !n.includes('{') && !n.includes('<'))).toBe(true);
});

it('extracts macro definitions with correct name', () => {
// `findChild(node, 'identifier')` would resolve to the body's `x` here,
// recording the macro as `@x` instead of `@mymac`.
const symbols = parseJulia(`macro mymac(x)
x
end`);
const names = symbols.definitions.map((d) => d.name);
expect(names).toContain('@mymac');
expect(names).not.toContain('@x');
});

it('does not record function signature as call', () => {
// The signature's `call_expression` lives inside a `signature` node — a
// naive `parent.type === 'function_definition'` guard misses it and
// records `greet` as both a definition and a call.
const symbols = parseJulia(`function greet(name)
println(name)
end`);
const callNames = symbols.calls.map((c) => c.name);
expect(callNames).not.toContain('greet');
expect(callNames).toContain('println');
});

it('selected_import handles qualified module', () => {
// `import Foo.Bar: baz` — module is a scoped_identifier. The import
// must record `Foo.Bar` as the source and `baz` as the imported name,
Expand Down
Loading