mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-04 02:31:36 +00:00
add test for Julia
This commit is contained in:
parent
56065efaa4
commit
0444e41f69
12 changed files with 410 additions and 33 deletions
|
|
@ -30,6 +30,30 @@ import { juliaVariableConfig } from '../variable-extractors/configs/julia.js';
|
|||
import { createCallExtractor } from '../call-extractors/generic.js';
|
||||
import { juliaCallConfig } from '../call-extractors/configs/julia.js';
|
||||
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
|
||||
import type { SyntaxNode } from '../utils/ast-helpers.js';
|
||||
|
||||
const JULIA_CALL_RESULT = { kind: 'call' } as const;
|
||||
const JULIA_SKIP_RESULT = { kind: 'skip' } as const;
|
||||
|
||||
function routeJuliaCall(calledName: string, callNode: SyntaxNode) {
|
||||
// Skip signature-shape calls (e.g., `function run()` parsed as call_expression in signature).
|
||||
if (callNode.parent?.type === 'signature') return JULIA_SKIP_RESULT;
|
||||
|
||||
if (calledName === 'include') {
|
||||
const args =
|
||||
callNode.childForFieldName('arguments') ??
|
||||
callNode.namedChildren.find((n) => n.type === 'argument_list');
|
||||
const literal = args?.namedChildren.find((n) => n.type === 'string_literal');
|
||||
const content = literal?.namedChildren.find((n) => n.type === 'content');
|
||||
const importPath = content?.text?.trim();
|
||||
if (!importPath || importPath.length > 1024 || /[\x00-\x1f]/.test(importPath)) {
|
||||
return JULIA_SKIP_RESULT;
|
||||
}
|
||||
return { kind: 'import', importPath, isRelative: true } as const;
|
||||
}
|
||||
|
||||
return JULIA_CALL_RESULT;
|
||||
}
|
||||
|
||||
const BUILT_INS: ReadonlySet<string> = new Set([
|
||||
'print',
|
||||
|
|
@ -97,6 +121,7 @@ export const juliaProvider = defineLanguage({
|
|||
typeConfig: juliaTypeConfig,
|
||||
exportChecker: juliaExportChecker,
|
||||
importResolver: createImportResolver(juliaImportConfig),
|
||||
callRouter: routeJuliaCall,
|
||||
importSemantics: 'wildcard-leaf',
|
||||
callExtractor: createCallExtractor(juliaCallConfig),
|
||||
fieldExtractor: createFieldExtractor(juliaFieldConfig),
|
||||
|
|
|
|||
|
|
@ -2,9 +2,24 @@
|
|||
// Verified against tree-sitter-julia grammar
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import type { NodeLabel } from 'gitnexus-shared';
|
||||
import type { MethodExtractionConfig, ParameterInfo } from '../../method-types.js';
|
||||
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
||||
|
||||
function getJuliaParameterList(node: SyntaxNode): SyntaxNode | undefined {
|
||||
const direct = node.childForFieldName('parameters');
|
||||
if (direct) return direct;
|
||||
|
||||
const signature =
|
||||
node.childForFieldName('signature') ?? node.namedChildren.find((n) => n.type === 'signature');
|
||||
if (!signature) return undefined;
|
||||
const headerCall = signature.namedChildren.find((n) => n.type === 'call_expression');
|
||||
if (!headerCall) return undefined;
|
||||
const args = headerCall.childForFieldName('arguments');
|
||||
if (args) return args;
|
||||
return headerCall.namedChildren.find((n) => n.type === 'argument_list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract parameters from a Julia function definition.
|
||||
*
|
||||
|
|
@ -17,7 +32,7 @@ import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
|||
* - Keyword: `; kw=default`
|
||||
*/
|
||||
function extractJuliaParameters(node: SyntaxNode): ParameterInfo[] {
|
||||
const paramList = node.childForFieldName('parameters');
|
||||
const paramList = getJuliaParameterList(node);
|
||||
if (!paramList) return [];
|
||||
|
||||
const params: ParameterInfo[] = [];
|
||||
|
|
@ -28,7 +43,13 @@ function extractJuliaParameters(node: SyntaxNode): ParameterInfo[] {
|
|||
|
||||
switch (param.type) {
|
||||
case 'identifier': {
|
||||
params.push({ name: param.text, type: null, rawType: null, isOptional: false, isVariadic: false });
|
||||
params.push({
|
||||
name: param.text,
|
||||
type: null,
|
||||
rawType: null,
|
||||
isOptional: false,
|
||||
isVariadic: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'typed_parameter': {
|
||||
|
|
@ -62,7 +83,13 @@ function extractJuliaParameters(node: SyntaxNode): ParameterInfo[] {
|
|||
case 'splat_parameter': {
|
||||
const nameNode = param.firstNamedChild;
|
||||
if (nameNode) {
|
||||
params.push({ name: nameNode.text, type: null, rawType: null, isOptional: false, isVariadic: true });
|
||||
params.push({
|
||||
name: nameNode.text,
|
||||
type: null,
|
||||
rawType: null,
|
||||
isOptional: false,
|
||||
isVariadic: true,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -78,18 +105,56 @@ function extractJuliaReturnType(node: SyntaxNode): string | undefined {
|
|||
return retType?.text?.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract function name from a Julia function node during parent-walk.
|
||||
*
|
||||
* Julia function_definition stores the name inside signature → call_expression:
|
||||
* function run(x) ... end
|
||||
* → (function_definition (signature (call_expression (identifier "run") ...)) ...)
|
||||
*
|
||||
* Short-form assignment functions are handled separately (assignment is not in
|
||||
* FUNCTION_NODE_TYPES, so they don't appear in the parent-walk).
|
||||
*/
|
||||
function juliaExtractFunctionName(
|
||||
node: SyntaxNode,
|
||||
): { funcName: string | null; label: NodeLabel } | null {
|
||||
if (node.type !== 'function_definition' && node.type !== 'macro_definition') return null;
|
||||
|
||||
// Navigate: function_definition → signature (first named child) → call_expression → identifier
|
||||
const sig = node.namedChild(0);
|
||||
if (sig?.type !== 'signature') return { funcName: null, label: 'Function' };
|
||||
|
||||
const callExpr = sig.namedChild(0);
|
||||
if (callExpr?.type !== 'call_expression') return { funcName: null, label: 'Function' };
|
||||
|
||||
// The function name is the first child of the call_expression (an identifier)
|
||||
const nameNode = callExpr.child(0);
|
||||
const funcName = nameNode?.type === 'identifier' ? nameNode.text : null;
|
||||
return { funcName, label: 'Function' };
|
||||
}
|
||||
|
||||
export const juliaMethodConfig: MethodExtractionConfig = {
|
||||
language: SupportedLanguages.Julia,
|
||||
// Julia methods are defined outside structs — no struct body method extraction
|
||||
typeDeclarationNodes: ['struct_definition'],
|
||||
methodNodeTypes: ['function_definition', 'short_function_definition'],
|
||||
methodNodeTypes: ['function_definition'],
|
||||
bodyNodeTypes: ['field_declaration_list'],
|
||||
|
||||
extractName(node) {
|
||||
const nameNode = node.childForFieldName('name');
|
||||
return nameNode?.text;
|
||||
// Navigate: function_definition → signature → call_expression → identifier
|
||||
const sig = node.namedChild(0);
|
||||
if (sig?.type === 'signature') {
|
||||
const callExpr = sig.namedChild(0);
|
||||
if (callExpr?.type === 'call_expression') {
|
||||
const nameNode = callExpr.child(0);
|
||||
if (nameNode?.type === 'identifier') return nameNode.text;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
extractFunctionName: juliaExtractFunctionName,
|
||||
|
||||
extractReturnType: extractJuliaReturnType,
|
||||
extractParameters: extractJuliaParameters,
|
||||
|
||||
|
|
|
|||
|
|
@ -1512,59 +1512,81 @@ export const DART_QUERIES = `
|
|||
(type_identifier) @heritage.trait))) @heritage
|
||||
`;
|
||||
|
||||
// Julia queries - works with tree-sitter-julia
|
||||
// Julia queries — verified against tree-sitter-julia grammar.
|
||||
//
|
||||
// Key structural facts (no named fields for struct/abstract type children):
|
||||
// struct Foo <: Bar → (struct_definition (type_head (binary_expression (identifier "Foo") ...)))
|
||||
// struct Simple → (struct_definition (type_head (identifier "Simple")))
|
||||
// abstract type A <: B → (abstract_definition (type_head (binary_expression ...)))
|
||||
// module M → (module_definition name: (identifier "M")) ← has named field
|
||||
// function f(x) → (function_definition (signature (call_expression . (identifier "f") ...)))
|
||||
// f(x) = expr → (assignment . (call_expression . (identifier "f") ...) ...)
|
||||
// macro @m(x) → (macro_definition (signature (call_expression . (identifier "m") ...)))
|
||||
export const JULIA_QUERIES = `
|
||||
; ── Structs (class-like types) ───────────────────────────────────────────────
|
||||
; Simple struct (no supertype): (type_head (identifier))
|
||||
(struct_definition
|
||||
name: (identifier) @name) @definition.class
|
||||
(type_head (identifier) @name)) @definition.class
|
||||
|
||||
; Struct with supertype: (type_head (binary_expression . identifier <: identifier))
|
||||
(struct_definition
|
||||
(type_head (binary_expression . (identifier) @name))) @definition.class
|
||||
|
||||
; ── Abstract types ────────────────────────────────────────────────────────────
|
||||
(abstract_definition
|
||||
name: (identifier) @name) @definition.class
|
||||
(type_head (identifier) @name)) @definition.class
|
||||
|
||||
(abstract_definition
|
||||
(type_head (binary_expression . (identifier) @name))) @definition.class
|
||||
|
||||
; ── Modules ───────────────────────────────────────────────────────────────────
|
||||
(module_definition
|
||||
name: (identifier) @name) @definition.module
|
||||
|
||||
; ── Function definitions ──────────────────────────────────────────────────────
|
||||
; Full form: function f(args...) ... end
|
||||
(function_definition
|
||||
name: (identifier) @name) @definition.function
|
||||
(signature (call_expression . (identifier) @name))) @definition.function
|
||||
|
||||
; ── Short-form function definitions: f(x) = expr ─────────────────────────────
|
||||
(short_function_definition
|
||||
name: (identifier) @name) @definition.function
|
||||
; Short form: f(args...) = expr
|
||||
; The leading . anchors call_expression as the first child (LHS), preventing
|
||||
; false matches on the RHS call expression in the assignment.
|
||||
(assignment .
|
||||
(call_expression . (identifier) @name)) @definition.function
|
||||
|
||||
; ── Macro definitions ─────────────────────────────────────────────────────────
|
||||
(macro_definition
|
||||
name: (identifier) @name) @definition.function
|
||||
(signature (call_expression . (identifier) @name))) @definition.function
|
||||
|
||||
; ── Import statements ─────────────────────────────────────────────────────────
|
||||
(import_statement) @import
|
||||
(import_statement (identifier) @import.source) @import
|
||||
|
||||
(using_statement) @import
|
||||
(using_statement (identifier) @import.source) @import
|
||||
|
||||
; ── Call expressions ──────────────────────────────────────────────────────────
|
||||
(call_expression
|
||||
(identifier) @call.name) @call
|
||||
; Free calls: f(args...)
|
||||
(call_expression . (identifier) @call.name) @call
|
||||
|
||||
; Member calls: obj.method(args...) — (field_expression value: receiver (identifier method))
|
||||
(call_expression
|
||||
(field_expression
|
||||
(identifier) @call.name)) @call
|
||||
(field_expression value: (_) (identifier) @call.name)) @call
|
||||
|
||||
; ── Heritage: struct Foo <: Bar ───────────────────────────────────────────────
|
||||
; ── Heritage: struct/abstract Foo <: Bar ──────────────────────────────────────
|
||||
(struct_definition
|
||||
name: (identifier) @heritage.class
|
||||
supertype: (type_clause
|
||||
(identifier) @heritage.extends)) @heritage
|
||||
(type_head
|
||||
(binary_expression
|
||||
(identifier) @heritage.class
|
||||
(identifier) @heritage.extends))) @heritage
|
||||
|
||||
(abstract_definition
|
||||
name: (identifier) @heritage.class
|
||||
supertype: (type_clause
|
||||
(identifier) @heritage.extends)) @heritage
|
||||
(type_head
|
||||
(binary_expression
|
||||
(identifier) @heritage.class
|
||||
(identifier) @heritage.extends))) @heritage
|
||||
|
||||
; ── Variable assignments at module scope ──────────────────────────────────────
|
||||
(assignment
|
||||
left: (identifier) @name) @definition.variable
|
||||
; ── Variable assignments ──────────────────────────────────────────────────────
|
||||
; Simple assignment: x = expr (LHS is a plain identifier, not a call)
|
||||
(assignment . (identifier) @name) @definition.variable
|
||||
`;
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
|
|
|
|||
|
|
@ -25,14 +25,31 @@ const DECLARATION_NODE_TYPES: ReadonlySet<string> = new Set([
|
|||
'assignment',
|
||||
]);
|
||||
|
||||
function getJuliaParameterList(node: SyntaxNode): SyntaxNode | undefined {
|
||||
const direct = node.childForFieldName('parameters');
|
||||
if (direct) return direct;
|
||||
|
||||
const signature =
|
||||
node.childForFieldName('signature') ?? node.namedChildren.find((n) => n.type === 'signature');
|
||||
if (!signature) return undefined;
|
||||
const headerCall = signature.namedChildren.find((n) => n.type === 'call_expression');
|
||||
if (!headerCall) return undefined;
|
||||
const args = headerCall.childForFieldName('arguments');
|
||||
if (args) return args;
|
||||
return headerCall.namedChildren.find((n) => n.type === 'argument_list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract type annotations from function parameter lists into env.
|
||||
* Julia: `function foo(x::Int, y::String)` → env.set("x", "Int")
|
||||
*/
|
||||
const extractDeclaration: TypeBindingExtractor = (node: SyntaxNode, env: Map<string, string>): void => {
|
||||
const extractDeclaration: TypeBindingExtractor = (
|
||||
node: SyntaxNode,
|
||||
env: Map<string, string>,
|
||||
): void => {
|
||||
if (node.type !== 'function_definition' && node.type !== 'short_function_definition') return;
|
||||
|
||||
const paramList = node.childForFieldName('parameters');
|
||||
const paramList = getJuliaParameterList(node);
|
||||
if (!paramList) return;
|
||||
|
||||
for (let i = 0; i < paramList.namedChildCount; i++) {
|
||||
|
|
@ -51,7 +68,10 @@ const extractDeclaration: TypeBindingExtractor = (node: SyntaxNode, env: Map<str
|
|||
}
|
||||
};
|
||||
|
||||
const extractParameter: ParameterExtractor = (_node: SyntaxNode, _env: Map<string, string>): void => {
|
||||
const extractParameter: ParameterExtractor = (
|
||||
_node: SyntaxNode,
|
||||
_env: Map<string, string>,
|
||||
): void => {
|
||||
// Parameter types are handled by extractDeclaration above
|
||||
};
|
||||
|
||||
|
|
|
|||
7
gitnexus/test/fixtures/lang-resolution/julia-calls/app.jl
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/julia-calls/app.jl
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
function write_audit(x)
|
||||
return x
|
||||
end
|
||||
|
||||
function run()
|
||||
write_audit(42)
|
||||
end
|
||||
7
gitnexus/test/fixtures/lang-resolution/julia-export-visibility/app.jl
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/julia-export-visibility/app.jl
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
function _internal()
|
||||
return 1
|
||||
end
|
||||
|
||||
function public_api()
|
||||
return _internal()
|
||||
end
|
||||
15
gitnexus/test/fixtures/lang-resolution/julia-heritage/types.jl
vendored
Normal file
15
gitnexus/test/fixtures/lang-resolution/julia-heritage/types.jl
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
abstract type AbstractAnimal end
|
||||
|
||||
abstract type Animal <: AbstractAnimal end
|
||||
|
||||
struct Dog <: Animal
|
||||
name::String
|
||||
end
|
||||
|
||||
struct Cat <: Animal
|
||||
name::String
|
||||
end
|
||||
|
||||
function speak(a::Animal)
|
||||
println("...")
|
||||
end
|
||||
5
gitnexus/test/fixtures/lang-resolution/julia-include-import/app.jl
vendored
Normal file
5
gitnexus/test/fixtures/lang-resolution/julia-include-import/app.jl
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
include("utils.jl")
|
||||
|
||||
function run()
|
||||
write_audit(42)
|
||||
end
|
||||
3
gitnexus/test/fixtures/lang-resolution/julia-include-import/utils.jl
vendored
Normal file
3
gitnexus/test/fixtures/lang-resolution/julia-include-import/utils.jl
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
function write_audit(x)
|
||||
return x
|
||||
end
|
||||
7
gitnexus/test/fixtures/lang-resolution/julia-typed-params/app.jl
vendored
Normal file
7
gitnexus/test/fixtures/lang-resolution/julia-typed-params/app.jl
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
struct User
|
||||
id::Int
|
||||
end
|
||||
|
||||
function process(user::User, limit::Int)
|
||||
return limit
|
||||
end
|
||||
26
gitnexus/test/integration/resolvers/julia-debug.test.ts
Normal file
26
gitnexus/test/integration/resolvers/julia-debug.test.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { describe, it, beforeAll } from 'vitest';
|
||||
import path from 'path';
|
||||
import {
|
||||
FIXTURES,
|
||||
getRelationships,
|
||||
getNodesByLabel,
|
||||
runPipelineFromRepo,
|
||||
type PipelineResult,
|
||||
} from './helpers.js';
|
||||
|
||||
describe('Julia debug', () => {
|
||||
let result: PipelineResult;
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'julia-calls'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('shows all edges', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
console.log(
|
||||
'CALLS:',
|
||||
calls.map((c) => `${c.source} → ${c.target}`),
|
||||
);
|
||||
const fns = getNodesByLabel(result, 'Function');
|
||||
console.log('Functions:', fns);
|
||||
});
|
||||
});
|
||||
175
gitnexus/test/integration/resolvers/julia.test.ts
Normal file
175
gitnexus/test/integration/resolvers/julia.test.ts
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
* Julia: function definitions, struct/abstract type detection, heritage (<:),
|
||||
* and function call resolution.
|
||||
*/
|
||||
import { describe, it, expect, beforeAll } from 'vitest';
|
||||
import path from 'path';
|
||||
import {
|
||||
FIXTURES,
|
||||
getRelationships,
|
||||
getNodesByLabel,
|
||||
getNodesByLabelFull,
|
||||
edgeSet,
|
||||
runPipelineFromRepo,
|
||||
type PipelineResult,
|
||||
} from './helpers.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Function definitions and call resolution (single file)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Julia function detection and call resolution', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'julia-calls'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('detects both functions', () => {
|
||||
const fns = getNodesByLabel(result, 'Function');
|
||||
expect(fns).toContain('write_audit');
|
||||
expect(fns).toContain('run');
|
||||
});
|
||||
|
||||
it('emits CALLS edge: run → write_audit', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const edge = calls.find((c) => c.source === 'run' && c.target === 'write_audit');
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
|
||||
it('does not emit signature self-call edges', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
expect(calls.find((c) => c.source === 'run' && c.target === 'run')).toBeUndefined();
|
||||
expect(
|
||||
calls.find((c) => c.source === 'write_audit' && c.target === 'write_audit'),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not emit spurious CALLS edges from built-in calls', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const builtinCalls = calls.filter((c) => c.target === 'println');
|
||||
expect(builtinCalls.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Struct and abstract type detection + heritage (<:)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Julia struct and abstract type detection with heritage', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'julia-heritage'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('detects all class-like types (structs and abstract types)', () => {
|
||||
const classes = getNodesByLabel(result, 'Class');
|
||||
expect(classes).toContain('AbstractAnimal');
|
||||
expect(classes).toContain('Animal');
|
||||
expect(classes).toContain('Dog');
|
||||
expect(classes).toContain('Cat');
|
||||
});
|
||||
|
||||
it('detects the speak function', () => {
|
||||
const fns = getNodesByLabel(result, 'Function');
|
||||
expect(fns).toContain('speak');
|
||||
});
|
||||
|
||||
it('emits EXTENDS edge: Animal <: AbstractAnimal', () => {
|
||||
const extends_ = getRelationships(result, 'EXTENDS');
|
||||
const edge = extends_.find((e) => e.source === 'Animal' && e.target === 'AbstractAnimal');
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
|
||||
it('emits EXTENDS edge: Dog <: Animal', () => {
|
||||
const extends_ = getRelationships(result, 'EXTENDS');
|
||||
const edge = extends_.find((e) => e.source === 'Dog' && e.target === 'Animal');
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
|
||||
it('emits EXTENDS edge: Cat <: Animal', () => {
|
||||
const extends_ = getRelationships(result, 'EXTENDS');
|
||||
const edge = extends_.find((e) => e.source === 'Cat' && e.target === 'Animal');
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
|
||||
it('emits exactly 3 EXTENDS edges', () => {
|
||||
const extends_ = getRelationships(result, 'EXTENDS');
|
||||
expect(edgeSet(extends_).sort()).toEqual([
|
||||
'Animal → AbstractAnimal',
|
||||
'Cat → Animal',
|
||||
'Dog → Animal',
|
||||
]);
|
||||
});
|
||||
|
||||
it('all heritage edges point to real graph nodes', () => {
|
||||
for (const edge of getRelationships(result, 'EXTENDS')) {
|
||||
const target = result.graph.getNode(edge.rel.targetId);
|
||||
expect(target).toBeDefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Julia include() import routing', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'julia-include-import'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('emits IMPORTS edge: app.jl → utils.jl from include()', () => {
|
||||
const imports = getRelationships(result, 'IMPORTS');
|
||||
const includeEdge = imports.find(
|
||||
(e) => e.sourceFilePath === 'app.jl' && e.targetFilePath === 'utils.jl',
|
||||
);
|
||||
expect(includeEdge).toBeDefined();
|
||||
});
|
||||
|
||||
it('resolves run → write_audit across include target', () => {
|
||||
const calls = getRelationships(result, 'CALLS');
|
||||
const edge = calls.find(
|
||||
(c) => c.source === 'run' && c.target === 'write_audit' && c.targetFilePath === 'utils.jl',
|
||||
);
|
||||
expect(edge).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Julia typed parameter extraction', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'julia-typed-params'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('extracts parameter metadata for process(user::User, limit::Int)', () => {
|
||||
const fns = getNodesByLabelFull(result, 'Function');
|
||||
const process = fns.find((n) => n.name === 'process');
|
||||
expect(process).toBeDefined();
|
||||
expect(process?.properties.parameterCount).toBe(2);
|
||||
expect(process?.properties.requiredParameterCount).toBe(2);
|
||||
expect(process?.properties.parameterTypes).toEqual(['User', 'Int']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Julia export visibility in top-level scripts', () => {
|
||||
let result: PipelineResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runPipelineFromRepo(path.join(FIXTURES, 'julia-export-visibility'), () => {});
|
||||
}, 60000);
|
||||
|
||||
it('marks underscore-prefixed function as internal', () => {
|
||||
const fns = getNodesByLabelFull(result, 'Function');
|
||||
const internal = fns.find((n) => n.name === '_internal');
|
||||
expect(internal).toBeDefined();
|
||||
expect(internal?.properties.isExported).toBe(false);
|
||||
});
|
||||
|
||||
it('marks non-underscore function as exported', () => {
|
||||
const fns = getNodesByLabelFull(result, 'Function');
|
||||
const api = fns.find((n) => n.name === 'public_api');
|
||||
expect(api).toBeDefined();
|
||||
expect(api?.properties.isExported).toBe(true);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue