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
25 changes: 24 additions & 1 deletion .claude/skills/agent-eval/corpus.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,29 @@
"question": "How do shadcn-svelte components compose and apply their styling?"
}
],
"Slint": [
{
"name": "slint-nodejs-template",
"repo": "https://github.com/slint-ui/slint-nodejs-template",
"size": "Small",
"files": "~6",
"question": "How does the JavaScript entry point load the Slint UI and update the MainWindow state?"
},
{
"name": "SurrealismUI",
"repo": "https://github.com/Surrealism-All/SurrealismUI",
"size": "Medium",
"files": "~3000",
"question": "How does SurrealismUI define and compose a Button component from shared style and theme primitives?"
},
{
"name": "slint",
"repo": "https://github.com/slint-ui/slint",
"size": "Large",
"files": "~4300",
"question": "How does the Slint example gallery UI compose component definitions and callback handlers from .slint files into the viewer runtime?"
}
],
"Lua": [
{
"name": "lualine.nvim",
Expand Down Expand Up @@ -424,4 +447,4 @@
"question": "When a ggplot object is printed, how does the plot actually get built and drawn \u2014 trace the path from print/plot to where geoms render. Name the key functions in order."
}
]
}
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### New Features

- CodeGraph now indexes Slint (`.slint`) UI files, including components, globals, interfaces, structs, enums, properties, callbacks, functions, imports, and call edges. (#648, #916)

## [1.1.6] - 2026-06-30

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ The reliable, universal payoff is **surgical context and speed**: CodeGraph coll
| **Full-Text Search** | Find code by name instantly across your entire codebase, powered by FTS5 |
| **Impact Analysis** | Trace callers, callees, and the full impact radius of any symbol before making changes |
| **Always Fresh** | File watcher uses native OS events (FSEvents/inotify/ReadDirectoryChangesW) with debounced auto-sync — the graph stays current as you code, zero config |
| **20+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Objective-C, Swift, Kotlin, Scala, Dart, Lua, Luau, R, Svelte, Vue, Astro, Liquid, Pascal/Delphi |
| **20+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Objective-C, Swift, Kotlin, Scala, Dart, Lua, Luau, R, Slint, Svelte, Vue, Astro, Liquid, Pascal/Delphi |
| **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 17 frameworks |
| **Mixed iOS / React Native / Expo** | Closes cross-language flows that static parsing misses: Swift ↔ ObjC bridging, React Native legacy bridge + TurboModules + Fabric view components, native → JS event emitters, Expo Modules |
| **100% Local** | No data leaves your machine. No API keys. No external services. SQLite database only |
Expand Down Expand Up @@ -714,6 +714,7 @@ is written):
| Lua | `.lua` | Full support (functions, methods with receivers, local variables, `require` imports, call edges) |
| R | `.R` `.r` | Full support (functions in every assignment form, S4/R5/R6 classes with methods, `library`/`require` imports, `source()` file references, call edges) |
| Luau | `.luau` | Full support (everything in Lua, plus `type`/`export type` aliases, typed signatures, and Roblox instance-path `require`) |
| Slint | `.slint` | Full support (components, globals, interfaces, structs, enums, properties, callbacks, functions, imports, and call edges) |

## Measured cross-file coverage

Expand Down
169 changes: 169 additions & 0 deletions __tests__/slint-extraction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/**
* Slint extraction tests.
*/

import { beforeAll, describe, expect, it } from 'vitest';
import { extractFromSource } from '../src/extraction';
import {
detectLanguage,
getSupportedLanguages,
initGrammars,
isLanguageSupported,
isSourceFile,
loadGrammarsForLanguages,
} from '../src/extraction/grammars';

beforeAll(async () => {
await initGrammars();
await loadGrammarsForLanguages(['slint']);
});

describe('Slint Extraction', () => {
describe('Language detection', () => {
it('should detect Slint files', () => {
expect(detectLanguage('ui/main.slint')).toBe('slint');
expect(isSourceFile('ui/main.slint')).toBe(true);
});

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

it('extracts components, globals, interfaces, structs, enums, members, and imports', () => {
const code = `
import { Button, VerticalBox } from "std-widgets.slint";

export struct Person {
name: string,
age: int,
}

export enum Mode {
Light,
Dark,
}

global AppState {
in-out property <int> counter: 0;
callback increment(int);

public function bump(delta: int) -> int {
counter += delta;
return counter;
}
}

export interface Greeter {
callback greet(string) -> string;
function reset();
}

export component MainWindow inherits Window implements Greeter {
in property <string> title: "Demo";
out property <int> doubled: AppState.counter * 2;
callback accepted(string);

function handle-click(name: string) {
AppState.bump(1);
accepted(name);
}

VerticalBox {
Button {
text: title;
clicked => {
root.handle-click("world");
}
}
}
}
`;
const result = extractFromSource('ui/main.slint', code);
const byName = new Map(result.nodes.map((n) => [`${n.kind}:${n.name}`, n]));

expect(byName.get('import:std-widgets.slint')?.signature).toContain('Button');
expect(byName.get('struct:Person')?.isExported).toBe(true);
expect(byName.get('field:name')?.qualifiedName).toBe('Person::name');
expect(byName.get('enum:Mode')?.isExported).toBe(true);
expect(byName.get('enum_member:Light')?.qualifiedName).toBe('Mode::Light');
expect(byName.get('class:AppState')?.language).toBe('slint');
expect(byName.get('property:counter')?.qualifiedName).toBe('AppState::counter');
expect(byName.get('method:bump')?.signature).toBe('(delta: int) -> int');
expect(byName.get('interface:Greeter')?.isExported).toBe(true);
expect(byName.get('method:greet')?.qualifiedName).toBe('Greeter::greet');
expect(byName.get('component:MainWindow')?.isExported).toBe(true);
expect(byName.get('property:title')?.qualifiedName).toBe('MainWindow::title');
expect(byName.get('method:handle-click')?.qualifiedName).toBe('MainWindow::handle-click');
});

it('records Slint inheritance, implemented interfaces, child components, and calls', () => {
const code = `
interface Greeter {
callback greet(string);
}

export component MainWindow inherits Window implements Greeter {
callback accepted(string);

function handle-click(name: string) {
accepted(name);
}

Button {
clicked => {
root.handle-click("world");
}
}
}
`;
const result = extractFromSource('ui/main.slint', code);
const main = result.nodes.find((n) => n.kind === 'component' && n.name === 'MainWindow');
const handler = result.nodes.find((n) => n.kind === 'method' && n.name === 'handle-click');
expect(main).toBeDefined();
expect(handler).toBeDefined();

expect(result.unresolvedReferences).toEqual(
expect.arrayContaining([
expect.objectContaining({ fromNodeId: main?.id, referenceKind: 'extends', referenceName: 'Window' }),
expect.objectContaining({ fromNodeId: main?.id, referenceKind: 'implements', referenceName: 'Greeter' }),
expect.objectContaining({ fromNodeId: main?.id, referenceKind: 'references', referenceName: 'Button' }),
expect.objectContaining({ fromNodeId: main?.id, referenceKind: 'calls', referenceName: 'handle-click' }),
expect.objectContaining({ fromNodeId: handler?.id, referenceKind: 'calls', referenceName: 'accepted' }),
])
);
});

it('records Slint re-export barrels as import dependencies', () => {
const code = `
export { AboutPage } from "about_page.slint";
export { TableViewPage, TableViewPageAdapter } from "table_view_page.slint";
`;
const result = extractFromSource('ui/pages/pages.slint', code);
const file = result.nodes.find((n) => n.kind === 'file' && n.name === 'pages.slint');
expect(file).toBeDefined();

expect(result.nodes).toEqual(
expect.arrayContaining([
expect.objectContaining({
kind: 'import',
name: 'about_page.slint',
signature: 'export { AboutPage } from "about_page.slint";',
}),
expect.objectContaining({
kind: 'import',
name: 'table_view_page.slint',
signature: 'export { TableViewPage, TableViewPageAdapter } from "table_view_page.slint";',
}),
])
);
expect(result.unresolvedReferences).toEqual(
expect.arrayContaining([
expect.objectContaining({ fromNodeId: file?.id, referenceKind: 'imports', referenceName: 'AboutPage' }),
expect.objectContaining({ fromNodeId: file?.id, referenceKind: 'imports', referenceName: 'TableViewPage' }),
expect.objectContaining({ fromNodeId: file?.id, referenceKind: 'imports', referenceName: 'TableViewPageAdapter' }),
])
);
});
});
1 change: 1 addition & 0 deletions site/src/content/docs/reference/languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@ Language support is automatic from the file extension — there's nothing to con
| Lua | `.lua` | Full support (functions, methods, locals, `require` imports, call edges) |
| R | `.R`, `.r` | Full support (functions, S4/R5/R6 classes with methods, `library`/`require` imports, `source()` file references, call edges) |
| Luau | `.luau` | Full support (Lua, plus typed signatures, `type` aliases, Roblox `require`) |
| Slint | `.slint` | Full support (components, globals, interfaces, structs, enums, properties, callbacks, functions, imports, call edges) |
8 changes: 6 additions & 2 deletions src/extraction/grammars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const WASM_GRAMMAR_FILES: Record<GrammarLanguage, string> = {
r: 'tree-sitter-r.wasm',
luau: 'tree-sitter-luau.wasm',
objc: 'tree-sitter-objc.wasm',
slint: 'tree-sitter-slint.wasm',
};

/**
Expand Down Expand Up @@ -108,6 +109,7 @@ export const EXTENSION_MAP: Record<string, Language> = {
'.luau': 'luau',
'.m': 'objc',
'.mm': 'objc',
'.slint': 'slint',
// XML: file-level tracking; the MyBatis extractor matches `<mapper namespace="...">`
// shape and emits SQL-statement nodes (other XML returns empty).
'.xml': 'xml',
Expand Down Expand Up @@ -220,8 +222,9 @@ export async function loadGrammarsForLanguages(languages: Language[]): Promise<v
// build (ABI 13) has no primary-constructor support and parses
// `class Foo(...)` as an ERROR that swallows the whole class (#237); we
// vendor the upstream ABI-15 tree-sitter-c-sharp 0.23.5 wasm, which parses
// primary constructors natively.
const wasmPath = (lang === 'pascal' || lang === 'scala' || lang === 'lua' || lang === 'luau' || lang === 'csharp' || lang === 'r')
// primary constructors natively. Slint has no tree-sitter-wasms package
// entry, so we vendor the upstream ABI-15 grammar.
const wasmPath = (lang === 'pascal' || lang === 'scala' || lang === 'lua' || lang === 'luau' || lang === 'csharp' || lang === 'r' || lang === 'slint')
? path.join(__dirname, 'wasm', wasmFile)
: require.resolve(`tree-sitter-wasms/out/${wasmFile}`);
const language = await WasmLanguage.load(wasmPath);
Expand Down Expand Up @@ -432,6 +435,7 @@ export function getLanguageDisplayName(language: Language): string {
lua: 'Lua',
luau: 'Luau',
objc: 'Objective-C',
slint: 'Slint',
yaml: 'YAML',
twig: 'Twig',
xml: 'XML',
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 @@ -27,6 +27,7 @@ import { luaExtractor } from './lua';
import { rExtractor } from './r';
import { luauExtractor } from './luau';
import { objcExtractor } from './objc';
import { slintExtractor } from './slint';

export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
typescript: typescriptExtractor,
Expand All @@ -51,4 +52,5 @@ export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
r: rExtractor,
luau: luauExtractor,
objc: objcExtractor,
slint: slintExtractor,
};
Loading