@optave/codegraph 3.8.1 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -7
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +43 -0
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +50 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/cli/commands/branch-compare.d.ts.map +1 -1
- package/dist/cli/commands/branch-compare.js +4 -0
- package/dist/cli/commands/branch-compare.js.map +1 -1
- package/dist/cli/commands/diff-impact.d.ts.map +1 -1
- package/dist/cli/commands/diff-impact.js +2 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +3 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/db/repository/base.d.ts +6 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +14 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +1 -0
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +23 -0
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +1 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +25 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +12 -8
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +154 -59
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +27 -1
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/parser.d.ts +4 -0
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +128 -61
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +7 -5
- package/dist/domain/search/models.js.map +1 -1
- package/dist/extractors/javascript.js +19 -9
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +51 -0
- package/src/ast-analysis/visitors/complexity-visitor.ts +55 -1
- package/src/cli/commands/branch-compare.ts +4 -0
- package/src/cli/commands/diff-impact.ts +2 -1
- package/src/cli/commands/info.ts +3 -2
- package/src/db/repository/base.ts +14 -0
- package/src/db/repository/native-repository.ts +25 -0
- package/src/db/repository/sqlite-repository.ts +26 -0
- package/src/domain/analysis/dependencies.ts +11 -6
- package/src/domain/graph/builder/pipeline.ts +185 -64
- package/src/domain/graph/builder/stages/build-edges.ts +23 -1
- package/src/domain/parser.ts +129 -63
- package/src/domain/search/models.ts +11 -5
- package/src/extractors/javascript.ts +21 -8
- package/src/types.ts +2 -1
package/src/domain/parser.ts
CHANGED
|
@@ -102,6 +102,12 @@ let _cachedLanguages: Map<string, Language> | null = null;
|
|
|
102
102
|
// Query cache for JS/TS/TSX extractors (populated during createParsers)
|
|
103
103
|
const _queryCache: Map<string, Query> = new Map();
|
|
104
104
|
|
|
105
|
+
// Tracks whether ALL grammars have been loaded (vs. a lazy subset)
|
|
106
|
+
let _allParsersLoaded: boolean = false;
|
|
107
|
+
|
|
108
|
+
// In-flight grammar loads keyed by language id — prevents concurrent duplicate loads
|
|
109
|
+
const _loadingPromises: Map<string, Promise<void>> = new Map();
|
|
110
|
+
|
|
105
111
|
// Extensions that need typeMap backfill (type annotations only exist in TS/TSX)
|
|
106
112
|
const TS_BACKFILL_EXTS = new Set(['.ts', '.tsx']);
|
|
107
113
|
|
|
@@ -150,42 +156,87 @@ const TS_EXTRA_PATTERNS: string[] = [
|
|
|
150
156
|
'(type_alias_declaration name: (type_identifier) @type_name) @type_node',
|
|
151
157
|
];
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Load a single language grammar and cache the parser + language + query.
|
|
161
|
+
* Uses in-flight deduplication so concurrent callers awaiting the same grammar
|
|
162
|
+
* share a single load rather than producing orphaned WASM instances.
|
|
163
|
+
* Assumes Parser.init() has already been called and _cachedParsers/_cachedLanguages exist.
|
|
164
|
+
*/
|
|
165
|
+
async function loadLanguage(entry: LanguageRegistryEntry): Promise<void> {
|
|
166
|
+
if (_cachedParsers!.has(entry.id)) return;
|
|
167
|
+
const inflight = _loadingPromises.get(entry.id);
|
|
168
|
+
if (inflight) return inflight;
|
|
169
|
+
const p = doLoadLanguage(entry).finally(() => _loadingPromises.delete(entry.id));
|
|
170
|
+
_loadingPromises.set(entry.id, p);
|
|
171
|
+
return p;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function doLoadLanguage(entry: LanguageRegistryEntry): Promise<void> {
|
|
175
|
+
try {
|
|
176
|
+
const lang = await Language.load(grammarPath(entry.grammarFile));
|
|
177
|
+
const parser = new Parser();
|
|
178
|
+
parser.setLanguage(lang);
|
|
179
|
+
_cachedParsers!.set(entry.id, parser);
|
|
180
|
+
_cachedLanguages!.set(entry.id, lang);
|
|
181
|
+
if (entry.extractor === extractSymbols && !_queryCache.has(entry.id)) {
|
|
182
|
+
const isTS = entry.id === 'typescript' || entry.id === 'tsx';
|
|
183
|
+
const patterns = isTS
|
|
184
|
+
? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
|
|
185
|
+
: [...COMMON_QUERY_PATTERNS, JS_CLASS_PATTERN];
|
|
186
|
+
_queryCache.set(entry.id, new Query(lang, patterns.join('\n')));
|
|
187
|
+
}
|
|
188
|
+
} catch (e: unknown) {
|
|
189
|
+
if (entry.required) throw e;
|
|
190
|
+
warn(
|
|
191
|
+
`${entry.id} parser failed to initialize: ${(e as Error).message}. ${entry.id} files will be skipped.`,
|
|
192
|
+
);
|
|
193
|
+
_cachedParsers!.set(entry.id, null);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
155
196
|
|
|
197
|
+
async function initParserRuntime(): Promise<void> {
|
|
156
198
|
if (!_initialized) {
|
|
157
199
|
await Parser.init();
|
|
158
200
|
_initialized = true;
|
|
159
201
|
}
|
|
202
|
+
if (!_cachedParsers) _cachedParsers = new Map();
|
|
203
|
+
if (!_cachedLanguages) _cachedLanguages = new Map();
|
|
204
|
+
}
|
|
160
205
|
|
|
161
|
-
|
|
162
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Load only the WASM grammars needed for the given file paths.
|
|
208
|
+
* Grammars already in cache are reused. This avoids the ~500ms cold-start
|
|
209
|
+
* penalty of loading all 23+ grammars when only 1-2 are needed (e.g. incremental rebuilds).
|
|
210
|
+
*/
|
|
211
|
+
async function ensureParsersForFiles(filePaths: string[]): Promise<Map<string, Parser | null>> {
|
|
212
|
+
await initParserRuntime();
|
|
213
|
+
const needed = new Set<LanguageRegistryEntry>();
|
|
214
|
+
for (const fp of filePaths) {
|
|
215
|
+
const ext = path.extname(fp).toLowerCase();
|
|
216
|
+
const entry = _extToLang.get(ext);
|
|
217
|
+
if (entry && !_cachedParsers!.has(entry.id)) needed.add(entry);
|
|
218
|
+
}
|
|
219
|
+
for (const entry of needed) {
|
|
220
|
+
await loadLanguage(entry);
|
|
221
|
+
}
|
|
222
|
+
return _cachedParsers!;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Load ALL WASM grammars. Used by full builds and feature modules (CFG, dataflow, complexity)
|
|
227
|
+
* that may process files of any language.
|
|
228
|
+
*/
|
|
229
|
+
export async function createParsers(): Promise<Map<string, Parser | null>> {
|
|
230
|
+
if (_cachedParsers && _allParsersLoaded) return _cachedParsers;
|
|
231
|
+
|
|
232
|
+
await initParserRuntime();
|
|
163
233
|
for (const entry of LANGUAGE_REGISTRY) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const parser = new Parser();
|
|
167
|
-
parser.setLanguage(lang);
|
|
168
|
-
parsers.set(entry.id, parser);
|
|
169
|
-
languages.set(entry.id, lang);
|
|
170
|
-
// Compile and cache tree-sitter Query for JS/TS/TSX extractors
|
|
171
|
-
if (entry.extractor === extractSymbols && !_queryCache.has(entry.id)) {
|
|
172
|
-
const isTS = entry.id === 'typescript' || entry.id === 'tsx';
|
|
173
|
-
const patterns = isTS
|
|
174
|
-
? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
|
|
175
|
-
: [...COMMON_QUERY_PATTERNS, JS_CLASS_PATTERN];
|
|
176
|
-
_queryCache.set(entry.id, new Query(lang, patterns.join('\n')));
|
|
177
|
-
}
|
|
178
|
-
} catch (e: unknown) {
|
|
179
|
-
if (entry.required) throw e;
|
|
180
|
-
warn(
|
|
181
|
-
`${entry.id} parser failed to initialize: ${(e as Error).message}. ${entry.id} files will be skipped.`,
|
|
182
|
-
);
|
|
183
|
-
parsers.set(entry.id, null);
|
|
234
|
+
if (!_cachedParsers!.has(entry.id)) {
|
|
235
|
+
await loadLanguage(entry);
|
|
184
236
|
}
|
|
185
237
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return parsers;
|
|
238
|
+
_allParsersLoaded = true;
|
|
239
|
+
return _cachedParsers!;
|
|
189
240
|
}
|
|
190
241
|
|
|
191
242
|
/**
|
|
@@ -217,6 +268,8 @@ export function disposeParsers(): void {
|
|
|
217
268
|
_cachedLanguages = null;
|
|
218
269
|
}
|
|
219
270
|
_initialized = false;
|
|
271
|
+
_allParsersLoaded = false;
|
|
272
|
+
_loadingPromises.clear();
|
|
220
273
|
}
|
|
221
274
|
|
|
222
275
|
export function getParser(parsers: Map<string, Parser | null>, filePath: string): Parser | null {
|
|
@@ -235,20 +288,15 @@ export async function ensureWasmTrees(
|
|
|
235
288
|
fileSymbols: Map<string, any>,
|
|
236
289
|
rootDir: string,
|
|
237
290
|
): Promise<void> {
|
|
238
|
-
//
|
|
239
|
-
|
|
291
|
+
// Single pass: collect absolute paths for files that need parsing
|
|
292
|
+
const filePaths: string[] = [];
|
|
240
293
|
for (const [relPath, symbols] of fileSymbols) {
|
|
241
|
-
if (!symbols._tree) {
|
|
242
|
-
|
|
243
|
-
if (_extToLang.has(ext)) {
|
|
244
|
-
needsParse = true;
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
294
|
+
if (!symbols._tree && _extToLang.has(path.extname(relPath).toLowerCase())) {
|
|
295
|
+
filePaths.push(path.join(rootDir, relPath));
|
|
247
296
|
}
|
|
248
297
|
}
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
const parsers = await createParsers();
|
|
298
|
+
if (filePaths.length === 0) return;
|
|
299
|
+
const parsers = await ensureParsersForFiles(filePaths);
|
|
252
300
|
|
|
253
301
|
for (const [relPath, symbols] of fileSymbols) {
|
|
254
302
|
if (symbols._tree) continue;
|
|
@@ -337,17 +385,19 @@ function patchImports(imports: any[]): void {
|
|
|
337
385
|
}
|
|
338
386
|
}
|
|
339
387
|
|
|
340
|
-
/** Normalize native typeMap array to a Map instance.
|
|
388
|
+
/** Normalize native typeMap array to a Map instance.
|
|
389
|
+
* Uses first-wins semantics at equal confidence to match the WASM/JS extractor. */
|
|
341
390
|
function patchTypeMap(r: any): void {
|
|
342
391
|
if (!r.typeMap) {
|
|
343
392
|
r.typeMap = new Map();
|
|
344
393
|
} else if (!(r.typeMap instanceof Map)) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
{ type: e.typeName, confidence: 0.9 }
|
|
349
|
-
|
|
350
|
-
|
|
394
|
+
const map = new Map<string, TypeMapEntry>();
|
|
395
|
+
for (const e of r.typeMap as Array<{ name: string; typeName: string }>) {
|
|
396
|
+
if (!map.has(e.name)) {
|
|
397
|
+
map.set(e.name, { type: e.typeName, confidence: (e as any).confidence ?? 0.9 });
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
r.typeMap = map;
|
|
351
401
|
}
|
|
352
402
|
}
|
|
353
403
|
|
|
@@ -640,10 +690,17 @@ for (const entry of LANGUAGE_REGISTRY) {
|
|
|
640
690
|
export const SUPPORTED_EXTENSIONS: Set<string> = new Set(_extToLang.keys());
|
|
641
691
|
|
|
642
692
|
/**
|
|
643
|
-
* WASM-based typeMap backfill for
|
|
693
|
+
* WASM-based typeMap backfill for TS/TSX files parsed by the native engine.
|
|
694
|
+
* Serves two purposes:
|
|
695
|
+
* 1. Compatibility with older native binaries that don't emit typeMap (< 3.2.0).
|
|
696
|
+
* 2. Workaround for native parser scope-collision bugs — when the same variable
|
|
697
|
+
* name appears at multiple scopes, native type extraction can produce
|
|
698
|
+
* incorrect results. WASM's JS-based extractor handles scope traversal
|
|
699
|
+
* more accurately. TODO: Remove purpose (2) once the Rust extractor handles
|
|
700
|
+
* nested scopes correctly.
|
|
701
|
+
*
|
|
644
702
|
* Uses tree-sitter AST extraction instead of regex to avoid false positives from
|
|
645
703
|
* matches inside comments and string literals.
|
|
646
|
-
* TODO: Remove once all published native binaries include typeMap extraction (>= 3.2.0)
|
|
647
704
|
*/
|
|
648
705
|
async function backfillTypeMap(
|
|
649
706
|
filePath: string,
|
|
@@ -658,7 +715,7 @@ async function backfillTypeMap(
|
|
|
658
715
|
return { typeMap: new Map(), backfilled: false };
|
|
659
716
|
}
|
|
660
717
|
}
|
|
661
|
-
const parsers = await
|
|
718
|
+
const parsers = await ensureParsersForFiles([filePath]);
|
|
662
719
|
const extracted = wasmExtractSymbols(parsers, filePath, code);
|
|
663
720
|
try {
|
|
664
721
|
if (!extracted || extracted.symbols.typeMap.size === 0) {
|
|
@@ -720,23 +777,26 @@ export async function parseFileAuto(
|
|
|
720
777
|
const result = native.parseFile(filePath, source, !!opts.dataflow, opts.ast !== false);
|
|
721
778
|
if (!result) return null;
|
|
722
779
|
const patched = patchNativeResult(result);
|
|
723
|
-
//
|
|
724
|
-
//
|
|
725
|
-
|
|
780
|
+
// Always backfill typeMap for TS/TSX from WASM — native parser's type
|
|
781
|
+
// extraction can produce incorrect scope-collision results. Non-TS files
|
|
782
|
+
// are skipped to stay consistent with the batch path (backfillTypeMapBatch).
|
|
783
|
+
if (TS_BACKFILL_EXTS.has(path.extname(filePath))) {
|
|
726
784
|
const { typeMap, backfilled } = await backfillTypeMap(filePath, source);
|
|
727
|
-
|
|
728
|
-
|
|
785
|
+
if (backfilled) {
|
|
786
|
+
patched.typeMap = typeMap;
|
|
787
|
+
patched._typeMapBackfilled = true;
|
|
788
|
+
}
|
|
729
789
|
}
|
|
730
790
|
return patched;
|
|
731
791
|
}
|
|
732
792
|
|
|
733
793
|
// WASM path
|
|
734
|
-
const parsers = await
|
|
794
|
+
const parsers = await ensureParsersForFiles([filePath]);
|
|
735
795
|
const extracted = wasmExtractSymbols(parsers, filePath, source);
|
|
736
796
|
return extracted ? extracted.symbols : null;
|
|
737
797
|
}
|
|
738
798
|
|
|
739
|
-
/** Backfill typeMap via WASM for files
|
|
799
|
+
/** Backfill typeMap via WASM for TS/TSX files parsed by the native engine. */
|
|
740
800
|
async function backfillTypeMapBatch(
|
|
741
801
|
needsTypeMap: { filePath: string; relPath: string }[],
|
|
742
802
|
result: Map<string, ExtractorOutput>,
|
|
@@ -746,7 +806,7 @@ async function backfillTypeMapBatch(
|
|
|
746
806
|
);
|
|
747
807
|
if (tsFiles.length === 0) return;
|
|
748
808
|
|
|
749
|
-
const parsers = await
|
|
809
|
+
const parsers = await ensureParsersForFiles(tsFiles.map((f) => f.filePath));
|
|
750
810
|
for (const { filePath, relPath } of tsFiles) {
|
|
751
811
|
let extracted: WasmExtractResult | null | undefined;
|
|
752
812
|
try {
|
|
@@ -778,7 +838,7 @@ async function parseFilesWasm(
|
|
|
778
838
|
rootDir: string,
|
|
779
839
|
): Promise<Map<string, ExtractorOutput>> {
|
|
780
840
|
const result = new Map<string, ExtractorOutput>();
|
|
781
|
-
const parsers = await
|
|
841
|
+
const parsers = await ensureParsersForFiles(filePaths);
|
|
782
842
|
for (const filePath of filePaths) {
|
|
783
843
|
let code: string;
|
|
784
844
|
try {
|
|
@@ -819,7 +879,12 @@ export async function parseFilesAuto(
|
|
|
819
879
|
const patched = patchNativeResult(r);
|
|
820
880
|
const relPath = path.relative(rootDir, r.file).split(path.sep).join('/');
|
|
821
881
|
result.set(relPath, patched);
|
|
822
|
-
|
|
882
|
+
// Always backfill TS/TSX type maps from WASM — the native parser's type
|
|
883
|
+
// extraction can produce incorrect results when the same variable name
|
|
884
|
+
// appears at multiple scopes (e.g. `node: TreeSitterNode` in one function
|
|
885
|
+
// vs `node: NodeRow` in another). The WASM JS extractor handles scope
|
|
886
|
+
// traversal order more accurately.
|
|
887
|
+
if (TS_BACKFILL_EXTS.has(path.extname(r.file))) {
|
|
823
888
|
needsTypeMap.push({ filePath: r.file, relPath });
|
|
824
889
|
}
|
|
825
890
|
}
|
|
@@ -877,12 +942,13 @@ export async function parseFileIncremental(
|
|
|
877
942
|
const result = cache.parseFile(filePath, source);
|
|
878
943
|
if (!result) return null;
|
|
879
944
|
const patched = patchNativeResult(result);
|
|
880
|
-
//
|
|
881
|
-
|
|
882
|
-
if (patched.typeMap.size === 0 && TS_BACKFILL_EXTS.has(path.extname(filePath))) {
|
|
945
|
+
// Always backfill typeMap for TS/TSX from WASM (see parseFileAuto comment).
|
|
946
|
+
if (TS_BACKFILL_EXTS.has(path.extname(filePath))) {
|
|
883
947
|
const { typeMap, backfilled } = await backfillTypeMap(filePath, source);
|
|
884
|
-
|
|
885
|
-
|
|
948
|
+
if (backfilled) {
|
|
949
|
+
patched.typeMap = typeMap;
|
|
950
|
+
patched._typeMapBackfilled = true;
|
|
951
|
+
}
|
|
886
952
|
}
|
|
887
953
|
return patched;
|
|
888
954
|
}
|
|
@@ -74,6 +74,7 @@ export const MODELS: Record<string, ModelConfig> = {
|
|
|
74
74
|
export const EMBEDDING_STRATEGIES: readonly string[] = ['structured', 'source'];
|
|
75
75
|
|
|
76
76
|
export const DEFAULT_MODEL: string = 'nomic-v1.5';
|
|
77
|
+
const NPM_BIN = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
77
78
|
const BATCH_SIZE_MAP: Record<string, number> = {
|
|
78
79
|
minilm: 32,
|
|
79
80
|
'jina-small': 16,
|
|
@@ -106,13 +107,15 @@ export function promptInstall(packageName: string): Promise<boolean> {
|
|
|
106
107
|
if (!process.stdin.isTTY) {
|
|
107
108
|
info(`Installing ${packageName} (optional dependency for semantic search)…`);
|
|
108
109
|
try {
|
|
109
|
-
execFileSync(
|
|
110
|
+
execFileSync(NPM_BIN, ['install', '--no-save', packageName], {
|
|
110
111
|
stdio: 'inherit',
|
|
111
112
|
timeout: 300_000,
|
|
112
113
|
});
|
|
113
114
|
return Promise.resolve(true);
|
|
114
115
|
} catch (err) {
|
|
115
|
-
info(
|
|
116
|
+
info(
|
|
117
|
+
`Auto-install of ${packageName} failed (${err instanceof Error ? err.message : String(err)}). Install it manually with:\n npm install ${packageName}`,
|
|
118
|
+
);
|
|
116
119
|
return Promise.resolve(false);
|
|
117
120
|
}
|
|
118
121
|
}
|
|
@@ -125,12 +128,15 @@ export function promptInstall(packageName: string): Promise<boolean> {
|
|
|
125
128
|
rl.close();
|
|
126
129
|
if (answer.trim().toLowerCase() !== 'y') return resolve(false);
|
|
127
130
|
try {
|
|
128
|
-
execFileSync(
|
|
131
|
+
execFileSync(NPM_BIN, ['install', packageName], {
|
|
129
132
|
stdio: 'inherit',
|
|
130
133
|
timeout: 300_000,
|
|
131
134
|
});
|
|
132
135
|
resolve(true);
|
|
133
|
-
} catch {
|
|
136
|
+
} catch (err) {
|
|
137
|
+
info(
|
|
138
|
+
`Install of ${packageName} failed (${err instanceof Error ? err.message : String(err)}). Install it manually with:\n npm install ${packageName}`,
|
|
139
|
+
);
|
|
134
140
|
resolve(false);
|
|
135
141
|
}
|
|
136
142
|
},
|
|
@@ -188,7 +194,7 @@ async function loadModel(modelKey?: string): Promise<{ extractor: unknown; confi
|
|
|
188
194
|
pipeline = transformers.pipeline;
|
|
189
195
|
|
|
190
196
|
info(`Loading embedding model: ${config.name} (${config.dim}d)...`);
|
|
191
|
-
const pipelineOpts = config.quantized ? {
|
|
197
|
+
const pipelineOpts = config.quantized ? { dtype: 'q8' } : {};
|
|
192
198
|
try {
|
|
193
199
|
extractor =
|
|
194
200
|
await // biome-ignore lint/complexity/noBannedTypes: dynamically loaded transformers pipeline is untyped
|
|
@@ -997,21 +997,34 @@ function handleVarDeclaratorTypeMap(
|
|
|
997
997
|
const nameN = node.childForFieldName('name');
|
|
998
998
|
if (!nameN || nameN.type !== 'identifier') return;
|
|
999
999
|
|
|
1000
|
-
// Type annotation: const x: Foo = …
|
|
1001
1000
|
const typeAnno = findChild(node, 'type_annotation');
|
|
1001
|
+
const valueN = node.childForFieldName('value');
|
|
1002
|
+
|
|
1003
|
+
// Constructor on the same declaration wins over annotation: the runtime type is
|
|
1004
|
+
// what matters for call resolution (e.g. `const x: Base = new Derived()` should
|
|
1005
|
+
// resolve `x.render()` to `Derived.render`, not `Base.render`).
|
|
1006
|
+
// When no constructor is present, annotation still takes precedence over factory.
|
|
1007
|
+
if (valueN?.type === 'new_expression') {
|
|
1008
|
+
const ctorType = extractNewExprTypeName(valueN);
|
|
1009
|
+
if (ctorType) {
|
|
1010
|
+
setTypeMapEntry(typeMap, nameN.text, ctorType, 1.0);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// Type annotation: const x: Foo = … → confidence 0.9
|
|
1002
1016
|
if (typeAnno) {
|
|
1003
1017
|
const typeName = extractSimpleTypeName(typeAnno);
|
|
1004
|
-
if (typeName)
|
|
1018
|
+
if (typeName) {
|
|
1019
|
+
setTypeMapEntry(typeMap, nameN.text, typeName, 0.9);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1005
1022
|
}
|
|
1006
1023
|
|
|
1007
|
-
const valueN = node.childForFieldName('value');
|
|
1008
1024
|
if (!valueN) return;
|
|
1009
1025
|
|
|
1010
|
-
// Constructor
|
|
1011
|
-
if (valueN.type === 'new_expression')
|
|
1012
|
-
const ctorType = extractNewExprTypeName(valueN);
|
|
1013
|
-
if (ctorType) setTypeMapEntry(typeMap, nameN.text, ctorType, 1.0);
|
|
1014
|
-
}
|
|
1026
|
+
// Constructor already handled above — only factory path remains.
|
|
1027
|
+
if (valueN.type === 'new_expression') return;
|
|
1015
1028
|
// Factory method: const x = Foo.create() → confidence 0.7
|
|
1016
1029
|
else if (valueN.type === 'call_expression') {
|
|
1017
1030
|
const fn = valueN.childForFieldName('function');
|
package/src/types.ts
CHANGED
|
@@ -302,6 +302,7 @@ export interface Repository {
|
|
|
302
302
|
// ── Edge queries ──────────────────────────────────────────────────
|
|
303
303
|
findCallees(nodeId: number): RelatedNodeRow[];
|
|
304
304
|
findCallers(nodeId: number): RelatedNodeRow[];
|
|
305
|
+
findCallersBatch(nodeIds: number[]): Map<number, RelatedNodeRow[]>;
|
|
305
306
|
findDistinctCallers(nodeId: number): RelatedNodeRow[];
|
|
306
307
|
findAllOutgoingEdges(nodeId: number): AdjacentEdgeRow[];
|
|
307
308
|
findAllIncomingEdges(nodeId: number): AdjacentEdgeRow[];
|
|
@@ -408,7 +409,7 @@ export interface DefinitionComplexity {
|
|
|
408
409
|
cognitive: number;
|
|
409
410
|
cyclomatic: number;
|
|
410
411
|
maxNesting: number;
|
|
411
|
-
halstead?: HalsteadMetrics;
|
|
412
|
+
halstead?: HalsteadDerivedMetrics | HalsteadMetrics;
|
|
412
413
|
loc?: LOCMetrics;
|
|
413
414
|
maintainabilityIndex?: number;
|
|
414
415
|
}
|