Skip to content

Commit 2656b98

Browse files
authored
feat(types): migrate remaining leaf modules to TypeScript (#566)
* feat(types): migrate remaining leaf modules to TypeScript Migrate 8 leaf modules from JavaScript to TypeScript: - shared/constants, file-utils, generators, hierarchy - infrastructure/config, native, registry, update-check Add full type annotations, export RepoListEntry for declaration emit, add missing mcp defaults (implementations, interfaces), and expand community config type to match DEFAULTS. Impact: 96 functions changed, 218 affected * fix(types): resolve TypeScript compilation errors blocking CI - Type loadNative()/getNative() with NativeAddon interface instead of bare object - Coerce null to undefined for readSourceRange end_line parameter - Add index signature to McpDefaults for Record<string, number> compatibility Impact: 3 functions changed, 3 affected * fix: type loadNative() return as NativeAddon instead of object The object return type caused TS2339 errors in resolve.ts and parser.ts where properties like resolveImport, computeConfidence, resolveImports, and ParseTreeCache were accessed on the returned value. * fix: convert null to undefined for readSourceRange end_line parameter readSourceRange expects number | undefined but c.end_line can be number | null | undefined. Use nullish coalescing to convert. * fix: spread McpDefaults to satisfy Record<string, number> parameter McpDefaults interface lacks an index signature, so it cannot be passed directly to initMcpDefaults which expects Record<string, number>. Spreading creates a plain object with the index signature.
1 parent b78b48b commit 2656b98

11 files changed

Lines changed: 344 additions & 169 deletions

File tree

src/domain/analysis/context.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@ function buildCallees(
4646
const summary = cLines ? extractSummary(cLines, c.line, displayOpts) : null;
4747
let calleeSource: string | null = null;
4848
if (depth >= 1) {
49-
calleeSource = readSourceRange(repoRoot, c.file, c.line, c.end_line, displayOpts);
49+
calleeSource = readSourceRange(
50+
repoRoot,
51+
c.file,
52+
c.line,
53+
c.end_line ?? undefined,
54+
displayOpts,
55+
);
5056
}
5157
return {
5258
name: c.name,
@@ -80,7 +86,13 @@ function buildCallees(
8086
line: c.line,
8187
endLine: c.end_line || null,
8288
summary: cLines ? extractSummary(cLines, c.line, displayOpts) : null,
83-
source: readSourceRange(repoRoot, c.file, c.line, c.end_line, displayOpts),
89+
source: readSourceRange(
90+
repoRoot,
91+
c.file,
92+
c.line,
93+
c.end_line ?? undefined,
94+
displayOpts,
95+
),
8496
});
8597
}
8698
}
Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import { execFileSync } from 'node:child_process';
22
import fs from 'node:fs';
33
import path from 'node:path';
4+
import type { CodegraphConfig } from '../types.js';
45
import { debug, warn } from './logger.js';
56

6-
export const CONFIG_FILES = ['.codegraphrc.json', '.codegraphrc', 'codegraph.config.json'];
7+
export type { CodegraphConfig } from '../types.js';
8+
9+
export const CONFIG_FILES: readonly string[] = [
10+
'.codegraphrc.json',
11+
'.codegraphrc',
12+
'codegraph.config.json',
13+
];
714

815
export const DEFAULTS = {
9-
include: [],
10-
exclude: [],
11-
ignoreDirs: [],
12-
extensions: [],
13-
aliases: {},
16+
include: [] as string[],
17+
exclude: [] as string[],
18+
ignoreDirs: [] as string[],
19+
extensions: [] as string[],
20+
aliases: {} as Record<string, string>,
1421
build: {
1522
incremental: true,
1623
dbPath: '.codegraph/graph.db',
@@ -21,29 +28,35 @@ export const DEFAULTS = {
2128
defaultLimit: 20,
2229
excludeTests: false,
2330
},
24-
embeddings: { model: 'nomic-v1.5', llmProvider: null },
25-
llm: { provider: null, model: null, baseUrl: null, apiKey: null, apiKeyCommand: null },
31+
embeddings: { model: 'nomic-v1.5', llmProvider: null as string | null },
32+
llm: {
33+
provider: null as string | null,
34+
model: null as string | null,
35+
baseUrl: null as string | null,
36+
apiKey: null as string | null,
37+
apiKeyCommand: null as string | null,
38+
},
2639
search: { defaultMinScore: 0.2, rrfK: 60, topK: 15, similarityWarnThreshold: 0.85 },
27-
ci: { failOnCycles: false, impactThreshold: null },
40+
ci: { failOnCycles: false, impactThreshold: null as number | null },
2841
manifesto: {
2942
rules: {
3043
cognitive: { warn: 15 },
3144
cyclomatic: { warn: 10 },
3245
maxNesting: { warn: 4 },
33-
maintainabilityIndex: { warn: 20, fail: null },
34-
importCount: { warn: null, fail: null },
35-
exportCount: { warn: null, fail: null },
36-
lineCount: { warn: null, fail: null },
37-
fanIn: { warn: null, fail: null },
38-
fanOut: { warn: null, fail: null },
39-
noCycles: { warn: null, fail: null },
40-
boundaries: { warn: null, fail: null },
46+
maintainabilityIndex: { warn: 20, fail: null as number | null },
47+
importCount: { warn: null as number | null, fail: null as number | null },
48+
exportCount: { warn: null as number | null, fail: null as number | null },
49+
lineCount: { warn: null as number | null, fail: null as number | null },
50+
fanIn: { warn: null as number | null, fail: null as number | null },
51+
fanOut: { warn: null as number | null, fail: null as number | null },
52+
noCycles: { warn: null as number | null, fail: null as number | null },
53+
boundaries: { warn: null as number | null, fail: null as number | null },
4154
},
42-
boundaries: null,
55+
boundaries: null as unknown,
4356
},
4457
check: {
4558
cycles: true,
46-
blastRadius: null,
59+
blastRadius: null as number | null,
4760
signatures: true,
4861
boundaries: true,
4962
depth: 3,
@@ -94,7 +107,7 @@ export const DEFAULTS = {
94107
'dead-entry': 0.3,
95108
'dead-ffi': 0.05,
96109
'dead-unresolved': 0.15,
97-
},
110+
} as Record<string, number>,
98111
defaultRoleWeight: 0.5,
99112
},
100113
display: {
@@ -129,19 +142,21 @@ export const DEFAULTS = {
129142
structure: 30,
130143
triage: 20,
131144
ast_query: 50,
145+
implementations: 50,
146+
interfaces: 50,
132147
},
133148
},
134-
};
149+
} satisfies CodegraphConfig;
135150

136151
// Per-cwd config cache — avoids re-reading the config file on every query call.
137152
// The config file rarely changes within a single process lifetime.
138-
const _configCache = new Map();
153+
const _configCache = new Map<string, CodegraphConfig>();
139154

140155
/**
141156
* Load project configuration from a .codegraphrc.json or similar file.
142157
* Returns merged config with defaults. Results are cached per cwd.
143158
*/
144-
export function loadConfig(cwd) {
159+
export function loadConfig(cwd?: string): CodegraphConfig {
145160
cwd = cwd || process.cwd();
146161
const cached = _configCache.get(cwd);
147162
if (cached) return structuredClone(cached);
@@ -153,16 +168,18 @@ export function loadConfig(cwd) {
153168
const raw = fs.readFileSync(filePath, 'utf-8');
154169
const config = JSON.parse(raw);
155170
debug(`Loaded config from ${filePath}`);
156-
const merged = mergeConfig(DEFAULTS, config);
171+
const merged = mergeConfig(DEFAULTS as unknown as Record<string, unknown>, config);
157172
if ('excludeTests' in config && !(config.query && 'excludeTests' in config.query)) {
158-
merged.query.excludeTests = Boolean(config.excludeTests);
173+
(merged['query'] as Record<string, unknown>)['excludeTests'] = Boolean(
174+
config.excludeTests,
175+
);
159176
}
160-
delete merged.excludeTests;
161-
const result = resolveSecrets(applyEnvOverrides(merged));
177+
delete merged['excludeTests'];
178+
const result = resolveSecrets(applyEnvOverrides(merged as unknown as CodegraphConfig));
162179
_configCache.set(cwd, structuredClone(result));
163180
return result;
164-
} catch (err) {
165-
debug(`Failed to parse config ${filePath}: ${err.message}`);
181+
} catch (err: unknown) {
182+
debug(`Failed to parse config ${filePath}: ${(err as Error).message}`);
166183
}
167184
}
168185
}
@@ -176,43 +193,44 @@ export function loadConfig(cwd) {
176193
* pick up on-disk config changes, and for test isolation when tests share
177194
* the same cwd.
178195
*/
179-
export function clearConfigCache() {
196+
export function clearConfigCache(): void {
180197
_configCache.clear();
181198
}
182199

183-
const ENV_LLM_MAP = {
200+
const ENV_LLM_MAP: Record<string, string> = {
184201
CODEGRAPH_LLM_PROVIDER: 'provider',
185202
CODEGRAPH_LLM_API_KEY: 'apiKey',
186203
CODEGRAPH_LLM_MODEL: 'model',
187204
};
188205

189-
export function applyEnvOverrides(config) {
206+
export function applyEnvOverrides(config: CodegraphConfig): CodegraphConfig {
190207
for (const [envKey, field] of Object.entries(ENV_LLM_MAP)) {
191-
if (process.env[envKey] !== undefined) {
192-
config.llm[field] = process.env[envKey];
208+
if (process.env[envKey as keyof NodeJS.ProcessEnv] !== undefined) {
209+
(config.llm as Record<string, unknown>)[field] =
210+
process.env[envKey as keyof NodeJS.ProcessEnv];
193211
}
194212
}
195213
return config;
196214
}
197215

198-
export function resolveSecrets(config) {
216+
export function resolveSecrets(config: CodegraphConfig): CodegraphConfig {
199217
const cmd = config.llm.apiKeyCommand;
200218
if (typeof cmd !== 'string' || cmd.trim() === '') return config;
201219

202220
const parts = cmd.trim().split(/\s+/);
203221
const [executable, ...args] = parts;
204222
try {
205-
const result = execFileSync(executable, args, {
223+
const result = execFileSync(executable!, args, {
206224
encoding: 'utf-8',
207225
timeout: 10_000,
208226
maxBuffer: 64 * 1024,
209227
stdio: ['ignore', 'pipe', 'pipe'],
210228
}).trim();
211229
if (result) {
212-
config.llm.apiKey = result;
230+
(config.llm as Record<string, unknown>)['apiKey'] = result;
213231
}
214-
} catch (err) {
215-
warn(`apiKeyCommand failed: ${err.message}`);
232+
} catch (err: unknown) {
233+
warn(`apiKeyCommand failed: ${(err as Error).message}`);
216234
}
217235
return config;
218236
}
@@ -224,7 +242,7 @@ export function resolveSecrets(config) {
224242
* Supports trailing `/*` or `/**` patterns (e.g. "packages/*").
225243
* Does not depend on an external glob library — uses fs.readdirSync.
226244
*/
227-
function expandWorkspaceGlob(pattern, rootDir) {
245+
function expandWorkspaceGlob(pattern: string, rootDir: string): string[] {
228246
// Strip trailing /*, /**, or just *
229247
const clean = pattern.replace(/\/?\*\*?$/, '');
230248
const baseDir = path.resolve(rootDir, clean);
@@ -243,7 +261,7 @@ function expandWorkspaceGlob(pattern, rootDir) {
243261
/**
244262
* Read a package.json and return its name field, or null.
245263
*/
246-
function readPackageName(pkgDir) {
264+
function readPackageName(pkgDir: string): string | null {
247265
try {
248266
const raw = fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8');
249267
const pkg = JSON.parse(raw);
@@ -253,11 +271,16 @@ function readPackageName(pkgDir) {
253271
}
254272
}
255273

274+
interface WorkspaceEntry {
275+
dir: string;
276+
entry: string | null;
277+
}
278+
256279
/**
257280
* Resolve the entry-point source file for a workspace package.
258281
* Checks exports → main → index file fallback.
259282
*/
260-
function resolveWorkspaceEntry(pkgDir) {
283+
function resolveWorkspaceEntry(pkgDir: string): string | null {
261284
try {
262285
const raw = fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8');
263286
const pkg = JSON.parse(raw);
@@ -300,14 +323,10 @@ function resolveWorkspaceEntry(pkgDir) {
300323
* 1. pnpm-workspace.yaml — `packages:` array
301324
* 2. package.json — `workspaces` field (npm/yarn)
302325
* 3. lerna.json — `packages` array
303-
*
304-
* @param {string} rootDir - Project root directory
305-
* @returns {Map<string, { dir: string, entry: string|null }>}
306-
* Map of package name → { absolute dir, resolved entry file }
307326
*/
308-
export function detectWorkspaces(rootDir) {
309-
const workspaces = new Map();
310-
const patterns = [];
327+
export function detectWorkspaces(rootDir: string): Map<string, WorkspaceEntry> {
328+
const workspaces = new Map<string, WorkspaceEntry>();
329+
const patterns: string[] = [];
311330

312331
// 1. pnpm-workspace.yaml
313332
const pnpmPath = path.join(rootDir, 'pnpm-workspace.yaml');
@@ -317,11 +336,11 @@ export function detectWorkspaces(rootDir) {
317336
// Simple YAML parse for `packages:` array — no dependency needed
318337
const packagesMatch = raw.match(/^packages:\s*\n((?:\s+-\s+.+\n?)*)/m);
319338
if (packagesMatch) {
320-
const lines = packagesMatch[1].match(/^\s+-\s+['"]?([^'"#\n]+)['"]?\s*$/gm);
339+
const lines = packagesMatch[1]!.match(/^\s+-\s+['"]?([^'"#\n]+)['"]?\s*$/gm);
321340
if (lines) {
322341
for (const line of lines) {
323342
const m = line.match(/^\s+-\s+['"]?([^'"#\n]+?)['"]?\s*$/);
324-
if (m) patterns.push(m[1].trim());
343+
if (m) patterns.push(m[1]!.trim());
325344
}
326345
}
327346
}
@@ -393,8 +412,11 @@ export function detectWorkspaces(rootDir) {
393412
return workspaces;
394413
}
395414

396-
export function mergeConfig(defaults, overrides) {
397-
const result = { ...defaults };
415+
export function mergeConfig(
416+
defaults: Record<string, unknown>,
417+
overrides: Record<string, unknown>,
418+
): Record<string, unknown> {
419+
const result: Record<string, unknown> = { ...defaults };
398420
for (const [key, value] of Object.entries(overrides)) {
399421
if (
400422
value &&
@@ -404,7 +426,10 @@ export function mergeConfig(defaults, overrides) {
404426
typeof defaults[key] === 'object' &&
405427
!Array.isArray(defaults[key])
406428
) {
407-
result[key] = mergeConfig(defaults[key], value);
429+
result[key] = mergeConfig(
430+
defaults[key] as Record<string, unknown>,
431+
value as Record<string, unknown>,
432+
);
408433
} else {
409434
result[key] = value;
410435
}

0 commit comments

Comments
 (0)