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
158 changes: 158 additions & 0 deletions __tests__/extraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3722,3 +3722,161 @@ class Svc {
expect(decoratedNode?.name).toBe('method');
});
});

describe('Julia Extraction', () => {
describe('Language detection', () => {
it('should detect Julia files', () => {
expect(detectLanguage('main.jl')).toBe('julia');
expect(detectLanguage('src/utils.jl')).toBe('julia');
});

it('should report Julia as supported', () => {
expect(isLanguageSupported('julia')).toBe(true);
expect(getSupportedLanguages()).toContain('julia');
});
});

describe('Function extraction', () => {
it('should extract top-level function definitions', () => {
const code = `
function greet(name::String)
println("Hello, \$name!")
end

function add(a::Int, b::Int)::Int
return a + b
end
`;
const result = extractFromSource('utils.jl', code);
const fns = result.nodes.filter((n) => n.kind === 'function');
expect(fns.find((f) => f.name === 'greet')).toBeDefined();
expect(fns.find((f) => f.name === 'add')).toBeDefined();
});

it('should extract function signature', () => {
const code = `
function process(x::Int, y::Float64)::String
return string(x + y)
end
`;
const result = extractFromSource('process.jl', code);
const fn = result.nodes.find((n) => n.kind === 'function' && n.name === 'process');
expect(fn).toBeDefined();
expect(fn?.signature).toContain('x::Int');
});

it('should extract zero-argument functions', () => {
const code = `
function hello()
println("Hello!")
end
`;
const result = extractFromSource('hello.jl', code);
const fn = result.nodes.find((n) => n.kind === 'function' && n.name === 'hello');
expect(fn).toBeDefined();
});

it('should extract macro definitions', () => {
const code = `
macro mytime(expr)
return :(@elapsed \$expr)
end
`;
const result = extractFromSource('macros.jl', code);
const macro = result.nodes.find((n) => n.kind === 'function' && n.name === 'mytime');
expect(macro).toBeDefined();
});
});

describe('Struct extraction', () => {
it('should extract struct definitions', () => {
const code = `
struct Point
x::Float64
y::Float64
end

mutable struct Counter
value::Int
end
`;
const result = extractFromSource('types.jl', code);
const structs = result.nodes.filter((n) => n.kind === 'struct');
expect(structs.find((s) => s.name === 'Point')).toBeDefined();
expect(structs.find((s) => s.name === 'Counter')).toBeDefined();
});

it('should extract parametric struct definitions', () => {
const code = `
struct Vector2D{T<:Number}
x::T
y::T
end
`;
const result = extractFromSource('vector.jl', code);
const struct_ = result.nodes.find((n) => n.kind === 'struct');
expect(struct_).toBeDefined();
// Name may include type parameters (tree-sitter includes full type_head text)
expect(struct_?.name).toContain('Vector2D');
});
});

describe('Abstract type extraction', () => {
it('should extract abstract type definitions', () => {
const code = `
abstract type Animal end
abstract type Shape end
`;
const result = extractFromSource('abstract.jl', code);
const abstracts = result.nodes.filter((n) => n.kind === 'interface');
expect(abstracts.find((a) => a.name === 'Animal')).toBeDefined();
expect(abstracts.find((a) => a.name === 'Shape')).toBeDefined();
});
});

describe('Module extraction', () => {
it('should extract module definitions', () => {
const code = `
module MyModule
export greet

function greet(name::String)
println("Hello, \$name!")
end
end
`;
const result = extractFromSource('mymodule.jl', code);
const fns = result.nodes.filter((n) => n.kind === 'function');
expect(fns.find((f) => f.name === 'greet')).toBeDefined();
});
});

describe('Import extraction', () => {
it('should extract import statements', () => {
const code = `
import LinearAlgebra
import Base.Math: sin, cos
using Statistics
using DataFrames: DataFrame, groupby
`;
const result = extractFromSource('imports.jl', code);
const imports = result.nodes.filter((n) => n.kind === 'import');
expect(imports.length).toBeGreaterThan(0);
});
});

describe('Call extraction', () => {
it('should extract function calls', () => {
const code = `
function main()
x = sqrt(2.0)
println(x)
y = sin(x) + cos(x)
end
`;
const result = extractFromSource('main.jl', code);
const calls = result.unresolvedReferences.filter((r) => r.referenceKind === 'calls');
expect(calls.length).toBeGreaterThan(0);
});
});
});
5 changes: 4 additions & 1 deletion src/extraction/grammars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const WASM_GRAMMAR_FILES: Record<GrammarLanguage, string> = {
dart: 'tree-sitter-dart.wasm',
pascal: 'tree-sitter-pascal.wasm',
scala: 'tree-sitter-scala.wasm',
julia: 'tree-sitter-julia.wasm',
};

/**
Expand Down Expand Up @@ -78,6 +79,7 @@ export const EXTENSION_MAP: Record<string, Language> = {
'.fmx': 'pascal',
'.scala': 'scala',
'.sc': 'scala',
'.jl': 'julia',
};

/**
Expand Down Expand Up @@ -126,7 +128,7 @@ export async function loadGrammarsForLanguages(languages: Language[]): Promise<v
const wasmFile = WASM_GRAMMAR_FILES[lang];
try {
// Pascal and Scala ship their own WASMs (not in tree-sitter-wasms)
const wasmPath = (lang === 'pascal' || lang === 'scala')
const wasmPath = (lang === 'pascal' || lang === 'scala' || lang === 'julia')
? path.join(__dirname, 'wasm', wasmFile)
: require.resolve(`tree-sitter-wasms/out/${wasmFile}`);
const language = await WasmLanguage.load(wasmPath);
Expand Down Expand Up @@ -291,6 +293,7 @@ export function getLanguageDisplayName(language: Language): string {
liquid: 'Liquid',
pascal: 'Pascal / Delphi',
scala: 'Scala',
julia: 'Julia',
unknown: 'Unknown',
};
return names[language] || language;
Expand Down
2 changes: 2 additions & 0 deletions src/extraction/languages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { kotlinExtractor } from './kotlin';
import { dartExtractor } from './dart';
import { pascalExtractor } from './pascal';
import { scalaExtractor } from './scala';
import { juliaExtractor } from './julia';

export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
typescript: typescriptExtractor,
Expand All @@ -43,4 +44,5 @@ export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
dart: dartExtractor,
pascal: pascalExtractor,
scala: scalaExtractor,
julia: juliaExtractor,
};
Loading