Skip to content

Commit b78b48b

Browse files
authored
feat(types): migrate core modules to TypeScript (Phase 5.4) (#554)
* feat(types): add SQLite, StmtCache, and NativeAddon foundation types Add §21 (SqliteStatement, BetterSqlite3Database, StmtCache) and §22 (NativeAddon, NativeParseTreeCache) to types.ts. These interfaces capture the exact better-sqlite3 and napi-rs surfaces used by the repository and native modules, enabling type-safe migration without @types/better-sqlite3. Impact: 24 functions changed, 0 affected * feat(types): convert db/repository layer from JS to TypeScript Rename all 14 src/db/repository/*.js files to .ts and add type annotations throughout the repository module (base, nodes, edges, embeddings, complexity, cfg, cochange, dataflow, graph-read, build-stmts, cached-stmt, in-memory-repository, sqlite-repository, index barrel). Add Node.js ESM loader hook (scripts/ts-resolve-hooks.js) that falls back from .js to .ts imports when the .js file no longer exists on disk, enabling gradual migration where .js consumers still use .js import specifiers. Update vitest.config.js with a Vite resolve plugin and NODE_OPTIONS registration so require() calls and child processes in tests resolve correctly. * feat(types): migrate core modules to TypeScript (Phase 5.4) Migrate 22 core source files from .js to .ts with full type annotations: - extractors/ (11 files): all language extractors typed with ExtractorOutput - domain/parser.ts: LANGUAGE_REGISTRY typed, parser functions annotated - domain/graph/resolve.ts: import resolution with BareSpecifier, PathAliases - domain/analysis/ (9 files): query-layer analysis modules typed All 2034 tests pass, zero type errors, biome clean. Impact: 2 functions changed, 0 affected * fix: add .js → .ts fallback to dynamic import verifier (#554) The verify-imports script checks exact .js paths but doesn't account for the resolver hook fallback to .ts during incremental migration. Impact: 1 functions changed, 0 affected * fix: add load hook, remove unused imports, fix Node 20 compat (#554) - Remove unused imports (existsSync, pathToFileURL) from ts-resolve-hooks.js - Add load hook for .ts files with fallback for Node < 22.6 - Make --experimental-strip-types conditional on Node >= 22.6 in vitest config Impact: 1 functions changed, 6 affected * fix: add load hook, preserve NODE_OPTIONS, fix Node 20 compat (#554) - ts-resolve-hooks.js load hook throws ERR_TS_UNSUPPORTED on Node < 22.6 - vitest.config.js preserves existing NODE_OPTIONS and uses --strip-types on Node >= 23 - skip child-process tests on Node < 22.6 (cli, batch CLI, CJS wrapper) * fix: replace CJS require() with ESM import in formatCycles tests (#554) require() bypasses Vite's .js→.ts resolver plugin, causing ERR_MODULE_NOT_FOUND for renamed .ts files on all Node versions. Use the top-level ESM import instead. * fix: add dedup guards, Node 20.6 guard, simplify type assertion (#554) - vitest.config.js: dedup --strip-types and --import flags - ts-resolve-loader.js: guard module.register() for Node >= 20.6 - in-memory-repository.ts: simplify complex type assertion to `as string` - Fix TS errors from main merge (models.ts, helpers.ts, pipeline.ts, watcher.ts) * fix: cast kind to SymbolKind instead of string in includes() (#554) The `as string` cast widened the type beyond what `includes()` accepts on `SymbolKind[]`. Cast to `SymbolKind` instead. * fix: resolve readonly array includes TS errors from main merge EVERY_SYMBOL_KIND and CORE_SYMBOL_KINDS are readonly-typed after kinds.js was converted to kinds.ts in #553. Array.prototype.includes on a readonly T[] rejects a wider argument type — cast to readonly string[] at call sites where the argument is string/AnyNodeKind. Also spread CORE_SYMBOL_KINDS where a mutable string[] is expected. * fix: guard --import flag for Node >= 20.6 and add implements IRepository to base class - vitest.config.js: only inject --import <loader> into NODE_OPTIONS when Node >= 20.6, matching the same boundary enforced inside ts-resolve-loader.js - src/db/repository/base.ts: add implements IRepository so TypeScript statically enforces that the class stays in sync with the interface in types.ts * fix: correct NativeAddon.resolveImports signature and remove unused disposeParsers parameter - types.ts: fix resolveImport and resolveImports signatures to match actual native call sites in resolve.ts (remove spurious extensions param, add knownFiles param, fix return type) - domain/parser.ts: remove unused _parsers parameter from disposeParsers — no caller ever passes it, and the function always operates on module-level caches - domain/analysis/dependencies.ts: move biome-ignore to correct line (suppression had no effect on wrong line) Impact: 4 functions changed, 9 affected * fix: correct NativeAddon.parseFiles signature and relax transaction type parseFiles: fix parameter order/types to match actual call site in parser.ts (string[], rootDir, dataflow, ast) instead of ({filePath,source}[], dataflow, ast). transaction: relax parameter types to be compatible with better-sqlite3's generic Transaction<F>, allowing direct cast without intermediate unknown. * fix: document double-cast in purgeFilesFromGraph Add comment explaining why as unknown as BetterSqlite3Database is needed: better-sqlite3 types don't declare open/name properties that the project interface requires. * fix: restore typed db in watcher.ts instead of widening to any Replace db: any with proper BetterSqlite3.Database type annotation. Use typedDb alias for functions expecting the project's BetterSqlite3Database interface, avoiding untyped code paths.
1 parent 19f9aab commit b78b48b

47 files changed

Lines changed: 2156 additions & 1960 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

scripts/ts-resolve-hooks.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* ESM resolve/load hooks for .js → .ts fallback during gradual migration.
3+
*
4+
* - resolve: when a .js specifier resolves to a path that doesn't exist,
5+
* check if a .ts version exists and redirect to it.
6+
* - load: for .ts files, delegate to Node's native loader (works on
7+
* Node >= 22.6 with --experimental-strip-types). On older Node versions,
8+
* throws a clear error instead of returning unparseable TypeScript source.
9+
*/
10+
11+
import { fileURLToPath } from 'node:url';
12+
13+
export async function resolve(specifier, context, nextResolve) {
14+
try {
15+
return await nextResolve(specifier, context);
16+
} catch (err) {
17+
// Only intercept ERR_MODULE_NOT_FOUND for .js specifiers
18+
if (err.code === 'ERR_MODULE_NOT_FOUND' && specifier.endsWith('.js')) {
19+
const tsSpecifier = specifier.replace(/\.js$/, '.ts');
20+
try {
21+
return await nextResolve(tsSpecifier, context);
22+
} catch {
23+
// .ts also not found — throw the original error
24+
}
25+
}
26+
throw err;
27+
}
28+
}
29+
30+
export async function load(url, context, nextLoad) {
31+
if (!url.endsWith('.ts')) return nextLoad(url, context);
32+
33+
// On Node >= 22.6 with --experimental-strip-types, Node handles .ts natively
34+
try {
35+
return await nextLoad(url, context);
36+
} catch (err) {
37+
if (err.code !== 'ERR_UNKNOWN_FILE_EXTENSION') throw err;
38+
}
39+
40+
// Node < 22.6 cannot strip TypeScript syntax. Throw a clear error instead
41+
// of returning raw TS source that would produce a confusing SyntaxError.
42+
const filePath = fileURLToPath(url);
43+
throw Object.assign(
44+
new Error(
45+
`Cannot load TypeScript file ${filePath} on Node ${process.versions.node}. ` +
46+
`TypeScript type stripping requires Node >= 22.6 with --experimental-strip-types.`,
47+
),
48+
{ code: 'ERR_TS_UNSUPPORTED' },
49+
);
50+
}

scripts/ts-resolve-loader.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Node.js ESM loader hook for the JS → TS gradual migration.
3+
*
4+
* When a .js import specifier can't be found on disk, this loader tries the
5+
* corresponding .ts file. This lets plain .js files import from already-
6+
* migrated .ts modules without changing their import specifiers.
7+
*
8+
* Usage: node --import ./scripts/ts-resolve-loader.js ...
9+
* (or via NODE_OPTIONS / vitest poolOptions.execArgv)
10+
*/
11+
12+
// module.register() requires Node >= 20.6.0
13+
const [_major, _minor] = process.versions.node.split('.').map(Number);
14+
if (_major > 20 || (_major === 20 && _minor >= 6)) {
15+
const { register } = await import('node:module');
16+
const hooksURL = new URL('./ts-resolve-hooks.js', import.meta.url);
17+
register(hooksURL.href, { parentURL: import.meta.url });
18+
}

scripts/verify-imports.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ function resolveSpecifier(specifier, fromFile) {
111111
// Exact file exists
112112
if (existsSync(target) && statSync(target).isFile()) return null;
113113

114+
// .js → .ts fallback (mirrors the ESM resolver hook for incremental TS migration)
115+
if (specifier.endsWith('.js')) {
116+
const tsTarget = target.replace(/\.js$/, '.ts');
117+
if (existsSync(tsTarget) && statSync(tsTarget).isFile()) return null;
118+
}
119+
114120
// Try implicit extensions (.js, .ts, .mjs, .cjs)
115121
for (const ext of ['.js', '.ts', '.mjs', '.cjs']) {
116122
if (!extname(target) && existsSync(target + ext)) return null;

src/db/repository/base.js

Lines changed: 0 additions & 201 deletions
This file was deleted.

0 commit comments

Comments
 (0)