Merge origin/main into delphi-support

Integrate main branch changes (WASM grammar architecture, centralized
resolution caches, SQLite adapter) with delphi-support branch. Pascal
grammar is now built as WASM and shipped in src/extraction/wasm/ for
consistency with the WASM-based grammar loading approach.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Olaf Monien
2026-02-17 20:23:54 +01:00
co-authored by Claude Opus 4.6
33 changed files with 1266 additions and 958 deletions
+6 -2
View File
@@ -4,16 +4,20 @@
* Tests for the tree-sitter extraction system.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { CodeGraph } from '../src';
import { extractFromSource, scanDirectory, shouldIncludeFile } from '../src/extraction';
import { detectLanguage, isLanguageSupported, getSupportedLanguages } from '../src/extraction/grammars';
import { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars } from '../src/extraction/grammars';
import { normalizePath } from '../src/utils';
import { DEFAULT_CONFIG } from '../src/types';
beforeAll(async () => {
await initGrammars();
});
// Create a temporary directory for each test
function createTempDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-test-'));
+1 -1
View File
@@ -317,7 +317,7 @@ describe('Database Connection', () => {
const version = db.getSchemaVersion();
expect(version).not.toBeNull();
expect(version?.version).toBe(1);
expect(version?.version).toBe(2);
db.close();
});
+25 -23
View File
@@ -13,7 +13,7 @@
* - CLI uninit command
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { describe, it, expect, beforeAll, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
@@ -24,8 +24,13 @@ import {
getSupportedLanguages,
clearParserCache,
getUnavailableGrammarErrors,
initGrammars,
} from '../src/extraction/grammars';
beforeAll(async () => {
await initGrammars();
});
// Create a temporary directory for each test
function createTempDir(): string {
return fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-pr19-test-'));
@@ -320,8 +325,9 @@ describe('Database Layer Improvements', () => {
const { DatabaseConnection } = await import('../src/db');
const { QueryBuilder } = await import('../src/db/queries');
const db = DatabaseConnection.initialize(testDir);
const queries = new QueryBuilder(db.getDatabase());
const dbPath = path.join(testDir, 'codegraph.db');
const db = DatabaseConnection.initialize(dbPath);
const queries = new QueryBuilder(db.getDb());
// Insert a node first (needed as foreign key)
queries.insertNode({
@@ -375,8 +381,9 @@ describe('Database Layer Improvements', () => {
const { DatabaseConnection } = await import('../src/db');
const { QueryBuilder } = await import('../src/db/queries');
const db = DatabaseConnection.initialize(testDir);
const queries = new QueryBuilder(db.getDatabase());
const dbPath = path.join(testDir, 'codegraph.db');
const db = DatabaseConnection.initialize(dbPath);
const queries = new QueryBuilder(db.getDb());
// Insert some nodes
for (let i = 0; i < 3; i++) {
@@ -405,8 +412,9 @@ describe('Database Layer Improvements', () => {
it.skipIf(!HAS_SQLITE)('should set performance pragmas on initialization', async () => {
const { DatabaseConnection } = await import('../src/db');
const db = DatabaseConnection.initialize(testDir);
const rawDb = db.getDatabase();
const dbPath = path.join(testDir, 'codegraph.db');
const db = DatabaseConnection.initialize(dbPath);
const rawDb = db.getDb();
// Check pragmas were set
const synchronous = rawDb.pragma('synchronous', { simple: true });
@@ -428,8 +436,9 @@ describe('Database Layer Improvements', () => {
const { DatabaseConnection } = await import('../src/db');
const { QueryBuilder } = await import('../src/db/queries');
const db = DatabaseConnection.initialize(testDir);
const queries = new QueryBuilder(db.getDatabase());
const dbPath = path.join(testDir, 'codegraph.db');
const db = DatabaseConnection.initialize(dbPath);
const queries = new QueryBuilder(db.getDb());
// Should not throw on empty array
expect(() => queries.insertUnresolvedRefsBatch([])).not.toThrow();
@@ -665,28 +674,21 @@ describe('CLI uninit', () => {
// Tree-sitter Version Pinning
// =============================================================================
describe('Tree-sitter Version Pinning', () => {
it('should have exact versions (no caret) in package.json', () => {
describe('Tree-sitter WASM Setup', () => {
it('should use web-tree-sitter and tree-sitter-wasms in dependencies', () => {
const pkgPath = path.join(__dirname, '..', 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
const treeSitterDeps = Object.entries(pkg.dependencies as Record<string, string>)
.filter(([name]) => name.startsWith('tree-sitter') || name.includes('tree-sitter'));
for (const [name, version] of treeSitterDeps) {
// Skip github: references
if (version.startsWith('github:')) continue;
expect(version, `${name} should not use caret range`).not.toMatch(/^\^/);
}
expect(pkg.dependencies['web-tree-sitter']).toBeDefined();
expect(pkg.dependencies['tree-sitter-wasms']).toBeDefined();
});
it('should have tree-sitter override pinned', () => {
it('should not have native tree-sitter in dependencies', () => {
const pkgPath = path.join(__dirname, '..', 'package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
expect(pkg.overrides).toBeDefined();
expect(pkg.overrides['tree-sitter']).toBeDefined();
expect(pkg.overrides['tree-sitter']).not.toMatch(/^\^/);
expect(pkg.dependencies['tree-sitter']).toBeUndefined();
expect(pkg.overrides).toBeUndefined();
});
});
+109
View File
@@ -10,6 +10,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { execFileSync } from 'child_process';
import CodeGraph from '../src/index';
describe('Sync Module', () => {
@@ -150,4 +151,112 @@ describe('Sync Module', () => {
});
});
});
describe('Git-based sync', () => {
let testDir: string;
let cg: CodeGraph;
function git(...args: string[]) {
execFileSync('git', args, { cwd: testDir, stdio: 'pipe' });
}
beforeEach(async () => {
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-git-sync-'));
// Initialize a git repo with an initial commit
git('init');
git('config', 'user.email', 'test@test.com');
git('config', 'user.name', 'Test');
const srcDir = path.join(testDir, 'src');
fs.mkdirSync(srcDir);
fs.writeFileSync(
path.join(srcDir, 'index.ts'),
`export function hello() { return 'world'; }`
);
git('add', '-A');
git('commit', '-m', 'initial');
// Initialize CodeGraph and index
cg = CodeGraph.initSync(testDir, {
config: {
include: ['**/*.ts'],
exclude: [],
},
});
await cg.indexAll();
});
afterEach(() => {
if (cg) {
cg.destroy();
}
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true, force: true });
}
});
it('should detect modified files via git', async () => {
fs.writeFileSync(
path.join(testDir, 'src', 'index.ts'),
`export function hello() { return 'modified'; }`
);
const result = await cg.sync();
expect(result.filesModified).toBe(1);
expect(result.changedFilePaths).toContain('src/index.ts');
});
it('should detect new untracked files via git', async () => {
fs.writeFileSync(
path.join(testDir, 'src', 'new.ts'),
`export function newFunc() { return 42; }`
);
const result = await cg.sync();
expect(result.filesAdded).toBe(1);
expect(result.changedFilePaths).toContain('src/new.ts');
// Verify the function was indexed
const nodes = cg.searchNodes('newFunc');
expect(nodes.length).toBeGreaterThan(0);
});
it('should detect deleted files via git', async () => {
fs.unlinkSync(path.join(testDir, 'src', 'index.ts'));
const result = await cg.sync();
expect(result.filesRemoved).toBe(1);
// Verify function is gone
const nodes = cg.searchNodes('hello');
expect(nodes.length).toBe(0);
});
it('should skip files not matching config', async () => {
// Create a .js file which doesn't match **/*.ts
fs.writeFileSync(
path.join(testDir, 'src', 'ignored.js'),
`function ignored() {}`
);
const result = await cg.sync();
expect(result.filesAdded).toBe(0);
expect(result.filesModified).toBe(0);
});
it('should report no changes on clean working tree', async () => {
const result = await cg.sync();
expect(result.filesAdded).toBe(0);
expect(result.filesModified).toBe(0);
expect(result.filesRemoved).toBe(0);
expect(result.changedFilePaths).toBeUndefined();
});
});
});