@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.
Files changed (66) hide show
  1. package/README.md +11 -7
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +43 -0
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  6. package/dist/ast-analysis/visitors/complexity-visitor.js +50 -1
  7. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  8. package/dist/cli/commands/branch-compare.d.ts.map +1 -1
  9. package/dist/cli/commands/branch-compare.js +4 -0
  10. package/dist/cli/commands/branch-compare.js.map +1 -1
  11. package/dist/cli/commands/diff-impact.d.ts.map +1 -1
  12. package/dist/cli/commands/diff-impact.js +2 -1
  13. package/dist/cli/commands/diff-impact.js.map +1 -1
  14. package/dist/cli/commands/info.d.ts.map +1 -1
  15. package/dist/cli/commands/info.js +3 -2
  16. package/dist/cli/commands/info.js.map +1 -1
  17. package/dist/db/repository/base.d.ts +6 -0
  18. package/dist/db/repository/base.d.ts.map +1 -1
  19. package/dist/db/repository/base.js +14 -0
  20. package/dist/db/repository/base.js.map +1 -1
  21. package/dist/db/repository/native-repository.d.ts +1 -0
  22. package/dist/db/repository/native-repository.d.ts.map +1 -1
  23. package/dist/db/repository/native-repository.js +23 -0
  24. package/dist/db/repository/native-repository.js.map +1 -1
  25. package/dist/db/repository/sqlite-repository.d.ts +1 -0
  26. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  27. package/dist/db/repository/sqlite-repository.js +25 -0
  28. package/dist/db/repository/sqlite-repository.js.map +1 -1
  29. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  30. package/dist/domain/analysis/dependencies.js +12 -8
  31. package/dist/domain/analysis/dependencies.js.map +1 -1
  32. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  33. package/dist/domain/graph/builder/pipeline.js +154 -59
  34. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  35. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  36. package/dist/domain/graph/builder/stages/build-edges.js +27 -1
  37. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  38. package/dist/domain/parser.d.ts +4 -0
  39. package/dist/domain/parser.d.ts.map +1 -1
  40. package/dist/domain/parser.js +128 -61
  41. package/dist/domain/parser.js.map +1 -1
  42. package/dist/domain/search/models.d.ts.map +1 -1
  43. package/dist/domain/search/models.js +7 -5
  44. package/dist/domain/search/models.js.map +1 -1
  45. package/dist/extractors/javascript.js +19 -9
  46. package/dist/extractors/javascript.js.map +1 -1
  47. package/dist/types.d.ts +2 -1
  48. package/dist/types.d.ts.map +1 -1
  49. package/grammars/tree-sitter-erlang.wasm +0 -0
  50. package/grammars/tree-sitter-gleam.wasm +0 -0
  51. package/package.json +9 -9
  52. package/src/ast-analysis/engine.ts +51 -0
  53. package/src/ast-analysis/visitors/complexity-visitor.ts +55 -1
  54. package/src/cli/commands/branch-compare.ts +4 -0
  55. package/src/cli/commands/diff-impact.ts +2 -1
  56. package/src/cli/commands/info.ts +3 -2
  57. package/src/db/repository/base.ts +14 -0
  58. package/src/db/repository/native-repository.ts +25 -0
  59. package/src/db/repository/sqlite-repository.ts +26 -0
  60. package/src/domain/analysis/dependencies.ts +11 -6
  61. package/src/domain/graph/builder/pipeline.ts +185 -64
  62. package/src/domain/graph/builder/stages/build-edges.ts +23 -1
  63. package/src/domain/parser.ts +129 -63
  64. package/src/domain/search/models.ts +11 -5
  65. package/src/extractors/javascript.ts +21 -8
  66. package/src/types.ts +2 -1
@@ -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
- export async function createParsers(): Promise<Map<string, Parser | null>> {
154
- if (_cachedParsers) return _cachedParsers;
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
- const parsers = new Map<string, Parser | null>();
162
- const languages = new Map<string, Language>();
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
- try {
165
- const lang = await Language.load(grammarPath(entry.grammarFile));
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
- _cachedParsers = parsers;
187
- _cachedLanguages = languages;
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
- // Check if any file needs a tree
239
- let needsParse = false;
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
- const ext = path.extname(relPath).toLowerCase();
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 (!needsParse) return;
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
- r.typeMap = new Map(
346
- r.typeMap.map((e: { name: string; typeName: string }) => [
347
- e.name,
348
- { type: e.typeName, confidence: 0.9 } as TypeMapEntry,
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 older native binaries that don't emit typeMap.
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 createParsers();
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
- // Only backfill typeMap for TS/TSX JS files have no type annotations,
724
- // and the native engine already handles `new Expr()` patterns.
725
- if (patched.typeMap.size === 0 && TS_BACKFILL_EXTS.has(path.extname(filePath))) {
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
- patched.typeMap = typeMap;
728
- if (backfilled) patched._typeMapBackfilled = true;
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 createParsers();
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 missing type-map data from native engine. */
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 createParsers();
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 createParsers();
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
- if (patched.typeMap.size === 0) {
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
- // Only backfill typeMap for TS/TSX JS files have no type annotations,
881
- // and the native engine already handles `new Expr()` patterns.
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
- patched.typeMap = typeMap;
885
- if (backfilled) patched._typeMapBackfilled = true;
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('npm', ['install', '--no-save', packageName], {
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(`Auto-install failed: ${err instanceof Error ? err.message : String(err)}`);
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('npm', ['install', packageName], {
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 ? { quantized: true } : {};
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) setTypeMapEntry(typeMap, nameN.text, typeName, 0.9);
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: const x = new Foo() confidence 1.0
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
  }