@optave/codegraph 3.12.0 → 3.13.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 +71 -35
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +2 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +1 -0
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +272 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/triage.js +1 -1
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/shared/options.d.ts +2 -1
- package/dist/cli/shared/options.d.ts.map +1 -1
- package/dist/cli/shared/options.js +11 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/db/migrations.js +1 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/domain/graph/builder/call-resolver.d.ts +12 -8
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
- package/dist/domain/graph/builder/call-resolver.js +93 -38
- package/dist/domain/graph/builder/call-resolver.js.map +1 -1
- package/dist/domain/graph/builder/cha.d.ts +9 -1
- package/dist/domain/graph/builder/cha.d.ts.map +1 -1
- package/dist/domain/graph/builder/cha.js +17 -2
- package/dist/domain/graph/builder/cha.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +8 -0
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +22 -3
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +1 -1
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +37 -2
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts +0 -2
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +88 -318
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +4 -0
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.js +341 -82
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/parser.d.ts +4 -5
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +46 -15
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +10 -2
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
- package/dist/domain/wasm-worker-pool.js +2 -0
- package/dist/domain/wasm-worker-pool.js.map +1 -1
- package/dist/domain/wasm-worker-protocol.d.ts +1 -0
- package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
- package/dist/extractors/cpp.d.ts.map +1 -1
- package/dist/extractors/cpp.js +42 -1
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.d.ts.map +1 -1
- package/dist/extractors/cuda.js +42 -1
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/helpers.d.ts +11 -0
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +8 -7
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +137 -6
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/features/structure-query.d.ts +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +6 -6
- package/dist/features/structure-query.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/config.d.ts +77 -4
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +395 -21
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +27 -0
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +59 -1
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/presentation/structure.d.ts +1 -1
- package/dist/presentation/structure.d.ts.map +1 -1
- package/dist/presentation/structure.js +2 -2
- package/dist/presentation/structure.js.map +1 -1
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +7 -8
- package/src/cli/commands/audit.ts +2 -1
- package/src/cli/commands/batch.ts +1 -0
- package/src/cli/commands/build.ts +6 -1
- package/src/cli/commands/config.ts +353 -0
- package/src/cli/commands/triage.ts +1 -1
- package/src/cli/index.ts +10 -0
- package/src/cli/shared/options.ts +11 -1
- package/src/cli/types.ts +2 -0
- package/src/db/migrations.ts +1 -1
- package/src/domain/graph/builder/call-resolver.ts +99 -41
- package/src/domain/graph/builder/cha.ts +18 -1
- package/src/domain/graph/builder/helpers.ts +24 -4
- package/src/domain/graph/builder/incremental.ts +1 -0
- package/src/domain/graph/builder/pipeline.ts +49 -2
- package/src/domain/graph/builder/stages/build-edges.ts +130 -399
- package/src/domain/graph/builder/stages/detect-changes.ts +1 -1
- package/src/domain/graph/builder/stages/finalize.ts +4 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +396 -92
- package/src/domain/graph/builder/stages/resolve-imports.ts +1 -1
- package/src/domain/parser.ts +45 -14
- package/src/domain/wasm-worker-entry.ts +10 -2
- package/src/domain/wasm-worker-pool.ts +1 -0
- package/src/domain/wasm-worker-protocol.ts +1 -0
- package/src/extractors/cpp.ts +44 -1
- package/src/extractors/cuda.ts +44 -1
- package/src/extractors/helpers.ts +43 -0
- package/src/extractors/java.ts +8 -7
- package/src/extractors/javascript.ts +127 -6
- package/src/features/structure-query.ts +7 -7
- package/src/index.ts +5 -1
- package/src/infrastructure/config.ts +481 -22
- package/src/infrastructure/registry.ts +82 -1
- package/src/presentation/structure.ts +3 -3
- package/src/types.ts +41 -0
- package/grammars/tree-sitter-erlang.wasm +0 -0
|
@@ -135,7 +135,7 @@ async function reparseBarrelFiles(
|
|
|
135
135
|
// which only runs on the original (changed + reverse-dep) fileSymbols. Barrel
|
|
136
136
|
// candidates are merged here *after* insertNodes, so wiping those kinds
|
|
137
137
|
// would permanently drop them (mirrors the Rust orchestrator's Stage 6b
|
|
138
|
-
// delete in
|
|
138
|
+
// delete in domain/graph/builder/pipeline.rs).
|
|
139
139
|
const deleteOutgoingEdges = db.prepare(
|
|
140
140
|
`DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)
|
|
141
141
|
AND kind NOT IN ('contains', 'parameter_of')`,
|
package/src/domain/parser.ts
CHANGED
|
@@ -158,6 +158,7 @@ const COMMON_QUERY_PATTERNS: string[] = [
|
|
|
158
158
|
'(variable_declarator name: (identifier) @varfn_name value: (generator_function) @varfn_value)',
|
|
159
159
|
'(method_definition name: (property_identifier) @meth_name) @meth_node',
|
|
160
160
|
'(method_definition name: (private_property_identifier) @meth_name) @meth_node',
|
|
161
|
+
'(method_definition name: (computed_property_name) @meth_name) @meth_node',
|
|
161
162
|
'(import_statement source: (string) @imp_source) @imp_node',
|
|
162
163
|
'(export_statement) @exp_node',
|
|
163
164
|
'(call_expression function: (identifier) @callfn_name) @callfn_node',
|
|
@@ -221,7 +222,9 @@ async function doLoadLanguage(entry: LanguageRegistryEntry): Promise<void> {
|
|
|
221
222
|
file: entry.grammarFile,
|
|
222
223
|
cause: e as Error,
|
|
223
224
|
});
|
|
224
|
-
|
|
225
|
+
const isEnoent = (e as NodeJS.ErrnoException).code === 'ENOENT';
|
|
226
|
+
const log = isEnoent ? debug : warn;
|
|
227
|
+
log(
|
|
225
228
|
`${entry.id} parser failed to initialize: ${(e as Error).message}. ${entry.id} files will be skipped.`,
|
|
226
229
|
);
|
|
227
230
|
_cachedParsers!.set(entry.id, null);
|
|
@@ -465,7 +468,7 @@ export function getInstalledWasmExtensions(): Set<string> {
|
|
|
465
468
|
* Lowercase file extensions covered by the native Rust addon.
|
|
466
469
|
*
|
|
467
470
|
* Mirrors `LanguageKind::from_extension` in
|
|
468
|
-
* `crates/codegraph-core/src/
|
|
471
|
+
* `crates/codegraph-core/src/domain/parser.rs`. Used to classify why the
|
|
469
472
|
* native orchestrator dropped a file: extensions outside this set are a
|
|
470
473
|
* legitimate parser limit (no Rust extractor exists), while extensions inside
|
|
471
474
|
* it indicate a real native bug (parse/read/extract failure).
|
|
@@ -1181,12 +1184,25 @@ async function parseFilesWasm(
|
|
|
1181
1184
|
/**
|
|
1182
1185
|
* Files at or below this count use the inline parse path (no worker spawn).
|
|
1183
1186
|
*
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1187
|
+
* The worker pool exists for crash safety (#965): exotic (non-required) WASM
|
|
1188
|
+
* grammars can trigger uncatchable V8 fatal errors that would kill the main
|
|
1189
|
+
* process. Running them in a worker means only the worker dies; the pool
|
|
1190
|
+
* detects the exit, skips the file, respawns, and continues.
|
|
1191
|
+
*
|
|
1192
|
+
* JS/TS/TSX are required-tier grammars — they have never triggered the V8
|
|
1193
|
+
* fatal crash class and are safe to run inline. The primary hot caller
|
|
1194
|
+
* (this/super dispatch post-pass) exclusively handles JS/TS/TSX files and
|
|
1195
|
+
* measured ~55–64ms/file through the pool vs ~8–10ms/file inline (#1435);
|
|
1196
|
+
* IPC overhead scales linearly with file count, not amortised.
|
|
1197
|
+
*
|
|
1198
|
+
* The threshold is set high enough to keep typical this-dispatch batches
|
|
1199
|
+
* (≤ 18 files on the codegraph corpus) on the inline path, while still
|
|
1200
|
+
* routing truly large exotic-language drops (rare; typical HCL case is 4
|
|
1201
|
+
* files) through the pool for crash isolation. Exotic-language drops are
|
|
1202
|
+
* almost always well under this limit anyway, so they benefit from the
|
|
1203
|
+
* inline fast path too without meaningful crash risk increase.
|
|
1188
1204
|
*/
|
|
1189
|
-
const INLINE_BACKFILL_THRESHOLD =
|
|
1205
|
+
const INLINE_BACKFILL_THRESHOLD = 32;
|
|
1190
1206
|
|
|
1191
1207
|
/**
|
|
1192
1208
|
* Inline WASM parse (no worker) for small file batches.
|
|
@@ -1198,11 +1214,16 @@ const INLINE_BACKFILL_THRESHOLD = 16;
|
|
|
1198
1214
|
*
|
|
1199
1215
|
* Returns symbols with `_tree` set so `runAnalyses` can run AST/CFG/dataflow
|
|
1200
1216
|
* visitors via the unified walker (mirrors how WASM-engine results behaved
|
|
1201
|
-
* before the worker pool was introduced)
|
|
1217
|
+
* before the worker pool was introduced), unless `symbolsOnly` is true — in
|
|
1218
|
+
* that case `_tree` is not set, skipping all analysis visitor walks. Use
|
|
1219
|
+
* `symbolsOnly` when only definitions/calls/typeMap are needed (e.g. the
|
|
1220
|
+
* this/super dispatch post-pass) to avoid the analysis overhead on the inline
|
|
1221
|
+
* path, matching the optimization already applied to the worker-pool path.
|
|
1202
1222
|
*/
|
|
1203
1223
|
async function parseFilesWasmInline(
|
|
1204
1224
|
filePaths: string[],
|
|
1205
1225
|
rootDir: string,
|
|
1226
|
+
symbolsOnly = false,
|
|
1206
1227
|
): Promise<Map<string, ExtractorOutput>> {
|
|
1207
1228
|
const result = new Map<string, ExtractorOutput>();
|
|
1208
1229
|
if (filePaths.length === 0) return result;
|
|
@@ -1220,7 +1241,18 @@ async function parseFilesWasmInline(
|
|
|
1220
1241
|
if (!extracted) continue;
|
|
1221
1242
|
const relPath = path.relative(rootDir, filePath).split(path.sep).join('/');
|
|
1222
1243
|
const symbols = extracted.symbols as ExtractorOutput & { _tree?: unknown; _langId?: string };
|
|
1223
|
-
|
|
1244
|
+
// When symbolsOnly=true, skip setting _tree so runAnalyses does not run
|
|
1245
|
+
// AST/complexity/CFG/dataflow visitor walks — only definitions/calls/typeMap
|
|
1246
|
+
// are needed by callers like the this/super dispatch post-pass.
|
|
1247
|
+
if (!symbolsOnly) {
|
|
1248
|
+
symbols._tree = extracted.tree;
|
|
1249
|
+
} else if (typeof (extracted.tree as any)?.delete === 'function') {
|
|
1250
|
+
// Free the WASM-backed tree immediately — web-tree-sitter trees are backed
|
|
1251
|
+
// by WASM linear memory and require explicit disposal. When symbolsOnly is
|
|
1252
|
+
// true the tree is never stored anywhere, so we must delete it here to
|
|
1253
|
+
// avoid leaking WASM heap on every incremental rebuild.
|
|
1254
|
+
(extracted.tree as any).delete();
|
|
1255
|
+
}
|
|
1224
1256
|
symbols._langId = extracted.langId;
|
|
1225
1257
|
result.set(relPath, symbols);
|
|
1226
1258
|
}
|
|
@@ -1230,14 +1262,13 @@ async function parseFilesWasmInline(
|
|
|
1230
1262
|
/**
|
|
1231
1263
|
* Backfill helper: small batches use the inline (main-thread) path; larger
|
|
1232
1264
|
* batches keep the worker-pool isolation against tree-sitter WASM crashes
|
|
1233
|
-
* (#965).
|
|
1234
|
-
* files in one or two languages).
|
|
1265
|
+
* (#965). See INLINE_BACKFILL_THRESHOLD for threshold rationale.
|
|
1235
1266
|
*
|
|
1236
1267
|
* `opts.symbolsOnly` skips the AST/complexity/CFG/dataflow visitors in the
|
|
1237
1268
|
* worker (and their result serialization across the thread boundary) for
|
|
1238
1269
|
* callers that only consume definitions/calls/typeMap — the native
|
|
1239
|
-
* orchestrator's
|
|
1240
|
-
*
|
|
1270
|
+
* orchestrator's this-dispatch post-pass. Callers that ingest the files into
|
|
1271
|
+
* the DB (dropped-language backfill) must keep
|
|
1241
1272
|
* the default full analysis.
|
|
1242
1273
|
*/
|
|
1243
1274
|
export async function parseFilesWasmForBackfill(
|
|
@@ -1246,7 +1277,7 @@ export async function parseFilesWasmForBackfill(
|
|
|
1246
1277
|
opts: { symbolsOnly?: boolean } = {},
|
|
1247
1278
|
): Promise<Map<string, ExtractorOutput>> {
|
|
1248
1279
|
if (filePaths.length <= INLINE_BACKFILL_THRESHOLD) {
|
|
1249
|
-
return parseFilesWasmInline(filePaths, rootDir);
|
|
1280
|
+
return parseFilesWasmInline(filePaths, rootDir, opts.symbolsOnly);
|
|
1250
1281
|
}
|
|
1251
1282
|
return parseFilesWasm(filePaths, rootDir, opts.symbolsOnly ? EXTRACT_ONLY : FULL_ANALYSIS);
|
|
1252
1283
|
}
|
|
@@ -115,6 +115,7 @@ const COMMON_QUERY_PATTERNS: string[] = [
|
|
|
115
115
|
'(variable_declarator name: (identifier) @varfn_name value: (generator_function) @varfn_value)',
|
|
116
116
|
'(method_definition name: (property_identifier) @meth_name) @meth_node',
|
|
117
117
|
'(method_definition name: (private_property_identifier) @meth_name) @meth_node',
|
|
118
|
+
'(method_definition name: (computed_property_name) @meth_name) @meth_node',
|
|
118
119
|
'(import_statement source: (string) @imp_source) @imp_node',
|
|
119
120
|
'(export_statement) @exp_node',
|
|
120
121
|
'(call_expression function: (identifier) @callfn_name) @callfn_node',
|
|
@@ -125,11 +126,17 @@ const COMMON_QUERY_PATTERNS: string[] = [
|
|
|
125
126
|
'(expression_statement (assignment_expression left: (member_expression) @assign_left right: (_) @assign_right)) @assign_node',
|
|
126
127
|
];
|
|
127
128
|
|
|
128
|
-
const
|
|
129
|
+
const JS_CLASS_PATTERNS: string[] = [
|
|
130
|
+
'(class_declaration name: (identifier) @cls_name) @cls_node',
|
|
131
|
+
// class expressions: `return class Foo extends Bar { ... }` or `const X = class Foo { ... }`
|
|
132
|
+
'(class name: (identifier) @cls_name) @cls_node',
|
|
133
|
+
];
|
|
129
134
|
|
|
130
135
|
const TS_EXTRA_PATTERNS: string[] = [
|
|
131
136
|
'(class_declaration name: (type_identifier) @cls_name) @cls_node',
|
|
132
137
|
'(abstract_class_declaration name: (type_identifier) @cls_name) @cls_node',
|
|
138
|
+
// class expressions: `return class Foo extends Bar { ... }`
|
|
139
|
+
'(class name: (type_identifier) @cls_name) @cls_node',
|
|
133
140
|
'(interface_declaration name: (type_identifier) @iface_name) @iface_node',
|
|
134
141
|
'(type_alias_declaration name: (type_identifier) @type_name) @type_node',
|
|
135
142
|
];
|
|
@@ -433,7 +440,7 @@ async function loadLanguageLazy(entry: LanguageRegistryEntry): Promise<Parser |
|
|
|
433
440
|
const isTS = entry.id === 'typescript' || entry.id === 'tsx';
|
|
434
441
|
const patterns = isTS
|
|
435
442
|
? [...COMMON_QUERY_PATTERNS, ...TS_EXTRA_PATTERNS]
|
|
436
|
-
: [...COMMON_QUERY_PATTERNS,
|
|
443
|
+
: [...COMMON_QUERY_PATTERNS, ...JS_CLASS_PATTERNS];
|
|
437
444
|
_queries.set(entry.id, new Query(lang, patterns.join('\n')));
|
|
438
445
|
}
|
|
439
446
|
return parser;
|
|
@@ -818,6 +825,7 @@ function serializeExtractorOutput(
|
|
|
818
825
|
...(symbols.objectPropBindings?.length
|
|
819
826
|
? { objectPropBindings: symbols.objectPropBindings }
|
|
820
827
|
: {}),
|
|
828
|
+
...(symbols.thisCallBindings?.length ? { thisCallBindings: symbols.thisCallBindings } : {}),
|
|
821
829
|
...(symbols.newExpressions?.length ? { newExpressions: symbols.newExpressions } : {}),
|
|
822
830
|
...(symbols.definePropertyReceivers?.size
|
|
823
831
|
? { definePropertyReceivers: Array.from(symbols.definePropertyReceivers.entries()) }
|
|
@@ -115,6 +115,7 @@ function deserializeResult(ser: SerializedExtractorOutput | null): ExtractorOutp
|
|
|
115
115
|
if (ser.objectRestParamBindings?.length)
|
|
116
116
|
out.objectRestParamBindings = ser.objectRestParamBindings;
|
|
117
117
|
if (ser.objectPropBindings?.length) out.objectPropBindings = ser.objectPropBindings;
|
|
118
|
+
if (ser.thisCallBindings?.length) out.thisCallBindings = ser.thisCallBindings;
|
|
118
119
|
if (ser.newExpressions?.length) out.newExpressions = ser.newExpressions;
|
|
119
120
|
if (ser.definePropertyReceivers?.length) {
|
|
120
121
|
const m = new Map<string, string>();
|
|
@@ -71,6 +71,7 @@ export interface SerializedExtractorOutput {
|
|
|
71
71
|
objectRestParamBindings?: import('../types.js').ObjectRestParamBinding[];
|
|
72
72
|
objectPropBindings?: import('../types.js').ObjectPropBinding[];
|
|
73
73
|
paramBindings?: import('../types.js').ParamBinding[];
|
|
74
|
+
thisCallBindings?: import('../types.js').ThisCallBinding[];
|
|
74
75
|
newExpressions?: readonly string[];
|
|
75
76
|
/** Serialized definePropertyReceivers map (funcName → receiverVarName) as tuple array. */
|
|
76
77
|
definePropertyReceivers?: Array<[string, string]>;
|
package/src/extractors/cpp.ts
CHANGED
|
@@ -5,7 +5,13 @@ import type {
|
|
|
5
5
|
TreeSitterNode,
|
|
6
6
|
TreeSitterTree,
|
|
7
7
|
} from '../types.js';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
extractModifierVisibility,
|
|
10
|
+
findChild,
|
|
11
|
+
isCPrimitiveType,
|
|
12
|
+
nodeEndLine,
|
|
13
|
+
setTypeMapEntry,
|
|
14
|
+
} from './helpers.js';
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Extract symbols from C++ files.
|
|
@@ -50,6 +56,9 @@ function walkCppNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
50
56
|
case 'call_expression':
|
|
51
57
|
handleCppCallExpression(node, ctx);
|
|
52
58
|
break;
|
|
59
|
+
case 'declaration':
|
|
60
|
+
handleCppDeclaration(node, ctx);
|
|
61
|
+
break;
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -204,6 +213,40 @@ function handleCppInclude(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
204
213
|
});
|
|
205
214
|
}
|
|
206
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Seed typeMap for declaration-typed locals: `UserService svc;` and
|
|
218
|
+
* `UserService svc = makeService();` both yield typeMap["svc"] = "UserService"
|
|
219
|
+
* at confidence 0.9. Mirrors `match_c_family_type_map` ("declaration" branch)
|
|
220
|
+
* in the native Rust C++ extractor.
|
|
221
|
+
*/
|
|
222
|
+
function handleCppDeclaration(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
223
|
+
const typeNode = node.childForFieldName('type');
|
|
224
|
+
if (!typeNode) return;
|
|
225
|
+
const typeName = typeNode.text;
|
|
226
|
+
// Skip primitive types — they are never class/struct receivers
|
|
227
|
+
if (isCPrimitiveType(typeName)) return;
|
|
228
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
229
|
+
const child = node.child(i);
|
|
230
|
+
if (!child) continue;
|
|
231
|
+
const kind = child.type;
|
|
232
|
+
let nameNode: TreeSitterNode | null = null;
|
|
233
|
+
if (kind === 'init_declarator') {
|
|
234
|
+
nameNode = child.childForFieldName('declarator') ?? null;
|
|
235
|
+
} else if (kind === 'identifier') {
|
|
236
|
+
nameNode = child;
|
|
237
|
+
}
|
|
238
|
+
// Note: pointer_declarator / reference_declarator children (e.g. `UserService *svc;`)
|
|
239
|
+
// are intentionally skipped here — they are also skipped by the native Rust
|
|
240
|
+
// match_c_family_type_map helper, which only handles 'init_declarator' and
|
|
241
|
+
// 'identifier' children. Both engines have the same scope for this case.
|
|
242
|
+
if (!nameNode) continue;
|
|
243
|
+
const varName = unwrapCppDeclaratorName(nameNode);
|
|
244
|
+
if (varName) {
|
|
245
|
+
setTypeMapEntry(ctx.typeMap, varName, typeName, 0.9);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
207
250
|
function handleCppCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
208
251
|
const funcNode = node.childForFieldName('function');
|
|
209
252
|
if (!funcNode) return;
|
package/src/extractors/cuda.ts
CHANGED
|
@@ -5,7 +5,13 @@ import type {
|
|
|
5
5
|
TreeSitterNode,
|
|
6
6
|
TreeSitterTree,
|
|
7
7
|
} from '../types.js';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
extractModifierVisibility,
|
|
10
|
+
findChild,
|
|
11
|
+
isCPrimitiveType,
|
|
12
|
+
nodeEndLine,
|
|
13
|
+
setTypeMapEntry,
|
|
14
|
+
} from './helpers.js';
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Extract symbols from CUDA files.
|
|
@@ -63,6 +69,9 @@ function walkCudaNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
63
69
|
case 'call_expression':
|
|
64
70
|
handleCudaCallExpression(node, ctx);
|
|
65
71
|
break;
|
|
72
|
+
case 'declaration':
|
|
73
|
+
handleCudaDeclaration(node, ctx);
|
|
74
|
+
break;
|
|
66
75
|
}
|
|
67
76
|
|
|
68
77
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -204,6 +213,40 @@ function handleCudaInclude(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
204
213
|
});
|
|
205
214
|
}
|
|
206
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Seed typeMap for declaration-typed locals: `UserService svc;` and
|
|
218
|
+
* `UserService svc = make();` both yield typeMap["svc"] = "UserService"
|
|
219
|
+
* at confidence 0.9. Mirrors `match_c_family_type_map` ("declaration" branch)
|
|
220
|
+
* in the native Rust CUDA extractor.
|
|
221
|
+
*/
|
|
222
|
+
function handleCudaDeclaration(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
223
|
+
const typeNode = node.childForFieldName('type');
|
|
224
|
+
if (!typeNode) return;
|
|
225
|
+
const typeName = typeNode.text;
|
|
226
|
+
// Skip primitive types — they are never class/struct receivers
|
|
227
|
+
if (isCPrimitiveType(typeName)) return;
|
|
228
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
229
|
+
const child = node.child(i);
|
|
230
|
+
if (!child) continue;
|
|
231
|
+
const kind = child.type;
|
|
232
|
+
let nameNode: TreeSitterNode | null = null;
|
|
233
|
+
if (kind === 'init_declarator') {
|
|
234
|
+
nameNode = child.childForFieldName('declarator') ?? null;
|
|
235
|
+
} else if (kind === 'identifier') {
|
|
236
|
+
nameNode = child;
|
|
237
|
+
}
|
|
238
|
+
// Note: pointer_declarator / reference_declarator children (e.g. `UserService *svc;`)
|
|
239
|
+
// are intentionally skipped here — they are also skipped by the native Rust
|
|
240
|
+
// match_c_family_type_map helper, which only handles 'init_declarator' and
|
|
241
|
+
// 'identifier' children. Both engines have the same scope for this case.
|
|
242
|
+
if (!nameNode) continue;
|
|
243
|
+
const varName = extractCudaFieldName(nameNode);
|
|
244
|
+
if (varName) {
|
|
245
|
+
setTypeMapEntry(ctx.typeMap, varName, typeName, 0.9);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
207
250
|
function handleCudaCallExpression(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
208
251
|
const funcNode = node.childForFieldName('function');
|
|
209
252
|
if (!funcNode) return;
|
|
@@ -305,6 +305,49 @@ export function pushImport(
|
|
|
305
305
|
ctx.imports.push(entry);
|
|
306
306
|
}
|
|
307
307
|
|
|
308
|
+
// ── C-family primitive types ───────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Primitive C/C++/CUDA types that are never class/struct receivers. Seeding
|
|
312
|
+
* these into typeMap would produce spurious receiver edges (e.g. `int x` → `int`).
|
|
313
|
+
* Shared between the C++ and CUDA extractors to prevent divergence.
|
|
314
|
+
*/
|
|
315
|
+
export const C_PRIMITIVE_TYPES: ReadonlySet<string> = new Set([
|
|
316
|
+
'int',
|
|
317
|
+
'long',
|
|
318
|
+
'short',
|
|
319
|
+
'unsigned',
|
|
320
|
+
'signed',
|
|
321
|
+
'float',
|
|
322
|
+
'double',
|
|
323
|
+
'char',
|
|
324
|
+
'bool',
|
|
325
|
+
'void',
|
|
326
|
+
'wchar_t',
|
|
327
|
+
'auto',
|
|
328
|
+
'size_t',
|
|
329
|
+
'uint8_t',
|
|
330
|
+
'uint16_t',
|
|
331
|
+
'uint32_t',
|
|
332
|
+
'uint64_t',
|
|
333
|
+
'int8_t',
|
|
334
|
+
'int16_t',
|
|
335
|
+
'int32_t',
|
|
336
|
+
'int64_t',
|
|
337
|
+
'ptrdiff_t',
|
|
338
|
+
'intptr_t',
|
|
339
|
+
'uintptr_t',
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Return true when `typeName` is a primitive C/C++/CUDA type.
|
|
344
|
+
* Strips leading qualifiers (`const int` → `int`) before checking.
|
|
345
|
+
*/
|
|
346
|
+
export function isCPrimitiveType(typeName: string): boolean {
|
|
347
|
+
const base = typeName.split(/\s+/).pop() ?? typeName;
|
|
348
|
+
return C_PRIMITIVE_TYPES.has(base) || C_PRIMITIVE_TYPES.has(typeName);
|
|
349
|
+
}
|
|
350
|
+
|
|
308
351
|
// ── Parameter extraction ───────────────────────────────────────────────────
|
|
309
352
|
|
|
310
353
|
/**
|
package/src/extractors/java.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
nodeStartLine,
|
|
17
17
|
pushCall,
|
|
18
18
|
pushImport,
|
|
19
|
+
setTypeMapEntry,
|
|
19
20
|
} from './helpers.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -273,13 +274,13 @@ function handleJavaLocalVarDecl(node: TreeSitterNode, ctx: ExtractorOutput): voi
|
|
|
273
274
|
const child = node.child(i);
|
|
274
275
|
if (child?.type === 'variable_declarator') {
|
|
275
276
|
const nameNode = child.childForFieldName('name');
|
|
276
|
-
// Use
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
277
|
+
// Use setTypeMapEntry (first-wins on tie) to match Rust extractor semantics.
|
|
278
|
+
// The typeMap is flat per-file without method scoping, so a local variable
|
|
279
|
+
// in one method (e.g. `InMemoryUserRepository repo` in `createDefault()`) must
|
|
280
|
+
// not override a parameter binding set by an earlier method
|
|
281
|
+
// (e.g. `UserRepository repo` constructor param). First-wins preserves the
|
|
282
|
+
// interface/abstract type annotation that drives correct CHA dispatch.
|
|
283
|
+
if (nameNode && ctx.typeMap) setTypeMapEntry(ctx.typeMap, nameNode.text, typeName, 0.9);
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
}
|
|
@@ -169,7 +169,19 @@ function handleClassCapture(
|
|
|
169
169
|
|
|
170
170
|
/** Handle method_definition capture. */
|
|
171
171
|
function handleMethodCapture(c: Record<string, TreeSitterNode>, definitions: Definition[]): void {
|
|
172
|
-
const
|
|
172
|
+
const methNameNode = c.meth_name!;
|
|
173
|
+
let methName: string;
|
|
174
|
+
if (methNameNode.type === 'computed_property_name') {
|
|
175
|
+
// Extract the inner string literal from `['methodName']` or `["methodName"]`.
|
|
176
|
+
// Non-string computed keys (e.g. `[Symbol.iterator]`) cannot be resolved at
|
|
177
|
+
// dot-notation call sites, so skip them entirely.
|
|
178
|
+
const inner = methNameNode.child(1); // child(0)='[', child(1)=string, child(2)=']'
|
|
179
|
+
if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment')) return;
|
|
180
|
+
methName = inner.text.replace(/^['"]|['"]$/g, '');
|
|
181
|
+
if (!methName) return;
|
|
182
|
+
} else {
|
|
183
|
+
methName = methNameNode.text;
|
|
184
|
+
}
|
|
173
185
|
const parentClass = findParentClass(c.meth_node!);
|
|
174
186
|
const fullName = parentClass ? `${parentClass}.${methName}` : methName;
|
|
175
187
|
const methChildren = extractParameters(c.meth_node!);
|
|
@@ -507,6 +519,15 @@ function extractDestructuredBindingsWalk(node: TreeSitterNode, definitions: Defi
|
|
|
507
519
|
nodeEndLine(declNode),
|
|
508
520
|
definitions,
|
|
509
521
|
);
|
|
522
|
+
} else if (nameN && nameN.type === 'array_pattern') {
|
|
523
|
+
// `const [x, y] = ...` — emit a single constant node whose name is the
|
|
524
|
+
// full array pattern text (e.g. `[x, y]`), matching native engine behaviour.
|
|
525
|
+
definitions.push({
|
|
526
|
+
name: nameN.text,
|
|
527
|
+
kind: 'constant',
|
|
528
|
+
line: nodeStartLine(declNode),
|
|
529
|
+
endLine: nodeEndLine(declNode),
|
|
530
|
+
});
|
|
510
531
|
}
|
|
511
532
|
}
|
|
512
533
|
}
|
|
@@ -816,8 +837,20 @@ function handleClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
816
837
|
function handleMethodDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
817
838
|
const nameNode = node.childForFieldName('name');
|
|
818
839
|
if (nameNode) {
|
|
840
|
+
let methName: string;
|
|
841
|
+
if (nameNode.type === 'computed_property_name') {
|
|
842
|
+
// Extract the inner string literal from `['methodName']` or `["methodName"]`.
|
|
843
|
+
// Non-string computed keys (e.g. `[Symbol.iterator]`) cannot be resolved at
|
|
844
|
+
// dot-notation call sites, so skip them entirely.
|
|
845
|
+
const inner = nameNode.child(1); // child(0)='[', child(1)=string, child(2)=']'
|
|
846
|
+
if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment')) return;
|
|
847
|
+
methName = inner.text.replace(/^['"]|['"]$/g, '');
|
|
848
|
+
if (!methName) return;
|
|
849
|
+
} else {
|
|
850
|
+
methName = nameNode.text;
|
|
851
|
+
}
|
|
819
852
|
const parentClass = findParentClass(node);
|
|
820
|
-
const fullName = parentClass ? `${parentClass}.${
|
|
853
|
+
const fullName = parentClass ? `${parentClass}.${methName}` : methName;
|
|
821
854
|
const methChildren = extractParameters(node);
|
|
822
855
|
const methVis = extractVisibility(node);
|
|
823
856
|
ctx.definitions.push({
|
|
@@ -1017,6 +1050,16 @@ function handleVariableDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
1017
1050
|
nodeEndLine(node),
|
|
1018
1051
|
ctx.definitions,
|
|
1019
1052
|
);
|
|
1053
|
+
} else if (isConst && nameN.type === 'array_pattern' && !hasFunctionScopeAncestor(node)) {
|
|
1054
|
+
// Array destructuring: `const [x, y] = ...` — emit a single constant node
|
|
1055
|
+
// whose name is the full array pattern text (e.g. `[x, y]`), matching
|
|
1056
|
+
// native engine behaviour. Scope guard mirrors the object_pattern branch above.
|
|
1057
|
+
ctx.definitions.push({
|
|
1058
|
+
name: nameN.text,
|
|
1059
|
+
kind: 'constant',
|
|
1060
|
+
line: nodeStartLine(node),
|
|
1061
|
+
endLine: nodeEndLine(node),
|
|
1062
|
+
});
|
|
1020
1063
|
}
|
|
1021
1064
|
}
|
|
1022
1065
|
}
|
|
@@ -1065,8 +1108,19 @@ function extractObjectLiteralFunctions(
|
|
|
1065
1108
|
} else if (child.type === 'method_definition') {
|
|
1066
1109
|
const nameNode = child.childForFieldName('name');
|
|
1067
1110
|
if (nameNode) {
|
|
1111
|
+
let methodName: string;
|
|
1112
|
+
if (nameNode.type === 'computed_property_name') {
|
|
1113
|
+
// Strip brackets+quotes from `['methodName']` to get a resolvable name.
|
|
1114
|
+
// Skip non-string computed keys (e.g. [Symbol.iterator]).
|
|
1115
|
+
const inner = nameNode.child(1);
|
|
1116
|
+
if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment')) continue;
|
|
1117
|
+
methodName = inner.text.replace(/^['"]|['"]$/g, '');
|
|
1118
|
+
if (!methodName) continue;
|
|
1119
|
+
} else {
|
|
1120
|
+
methodName = nameNode.text;
|
|
1121
|
+
}
|
|
1068
1122
|
definitions.push({
|
|
1069
|
-
name: `${varName}.${
|
|
1123
|
+
name: `${varName}.${methodName}`,
|
|
1070
1124
|
kind: 'function',
|
|
1071
1125
|
line: nodeStartLine(child),
|
|
1072
1126
|
endLine: nodeEndLine(child),
|
|
@@ -1813,9 +1867,23 @@ function runContextCollectorWalk(rootNode: TreeSitterNode, out: ContextCollector
|
|
|
1813
1867
|
// Qualify with the enclosing class name so the PTS key matches
|
|
1814
1868
|
// callerName from findCaller (which uses def.name = 'ClassName.method').
|
|
1815
1869
|
const enclosingClass = classStack.length > 0 ? classStack[classStack.length - 1] : null;
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1870
|
+
let rawName: string;
|
|
1871
|
+
if (nameNode.type === 'computed_property_name') {
|
|
1872
|
+
const inner = nameNode.child(1);
|
|
1873
|
+
if (!inner || (inner.type !== 'string' && inner.type !== 'string_fragment')) {
|
|
1874
|
+
// Non-string computed key — skip adding to funcStack (no resolvable name).
|
|
1875
|
+
rawName = '';
|
|
1876
|
+
} else {
|
|
1877
|
+
rawName = inner.text.replace(/^['"]|['"]$/g, '');
|
|
1878
|
+
}
|
|
1879
|
+
} else {
|
|
1880
|
+
rawName = nameNode.text;
|
|
1881
|
+
}
|
|
1882
|
+
if (rawName) {
|
|
1883
|
+
const qualifiedName = enclosingClass ? `${enclosingClass}.${rawName}` : rawName;
|
|
1884
|
+
funcStack.push(qualifiedName);
|
|
1885
|
+
pushedFunc = true;
|
|
1886
|
+
}
|
|
1819
1887
|
}
|
|
1820
1888
|
} else if (t === 'variable_declarator') {
|
|
1821
1889
|
// `const process = (arr) => { ... }` — arrow/expression functions assigned
|
|
@@ -1866,6 +1934,8 @@ function runContextCollectorWalk(rootNode: TreeSitterNode, out: ContextCollector
|
|
|
1866
1934
|
collectCollectionWrapBinding(node, out.fnRefBindings);
|
|
1867
1935
|
} else if (t === 'required_parameter' || t === 'optional_parameter') {
|
|
1868
1936
|
handleParamTypeMap(node, out.typeMap);
|
|
1937
|
+
} else if (t === 'public_field_definition' || t === 'field_definition') {
|
|
1938
|
+
handleFieldDefTypeMap(node, out.typeMap, typeMapClass);
|
|
1869
1939
|
} else if (t === 'assignment_expression') {
|
|
1870
1940
|
handlePropWriteTypeMap(node, out.typeMap, typeMapClass);
|
|
1871
1941
|
} else if (t === 'call_expression') {
|
|
@@ -2090,6 +2160,55 @@ function handleParamTypeMap(node: TreeSitterNode, typeMap: Map<string, TypeMapEn
|
|
|
2090
2160
|
}
|
|
2091
2161
|
}
|
|
2092
2162
|
|
|
2163
|
+
/**
|
|
2164
|
+
* Extract type info from a class field declaration: `private repo: Repository<User>`.
|
|
2165
|
+
*
|
|
2166
|
+
* Seeds a class-scoped key `ClassName.field` (confidence 0.9) as the primary entry
|
|
2167
|
+
* so that two classes with identically-named fields don't overwrite each other's
|
|
2168
|
+
* typeMap entry (issue #1458). The resolver's `CallerClass.X` fallback (call-resolver.ts
|
|
2169
|
+
* line 110) looks up exactly this key.
|
|
2170
|
+
*
|
|
2171
|
+
* Bare `field` and `this.field` keys are kept at lower confidence (0.6) as fallbacks
|
|
2172
|
+
* for single-class files where the resolver may not have a callerClass context.
|
|
2173
|
+
*
|
|
2174
|
+
* Mirrors the field_definition branch of match_js_type_map in
|
|
2175
|
+
* crates/codegraph-core/src/extractors/javascript.rs.
|
|
2176
|
+
*/
|
|
2177
|
+
function handleFieldDefTypeMap(
|
|
2178
|
+
node: TreeSitterNode,
|
|
2179
|
+
typeMap: Map<string, TypeMapEntry>,
|
|
2180
|
+
currentClass: string | null,
|
|
2181
|
+
): void {
|
|
2182
|
+
const nameNode =
|
|
2183
|
+
node.childForFieldName('name') ||
|
|
2184
|
+
node.childForFieldName('property') ||
|
|
2185
|
+
findChild(node, 'property_identifier');
|
|
2186
|
+
if (!nameNode) return;
|
|
2187
|
+
const kind = nameNode.type;
|
|
2188
|
+
if (
|
|
2189
|
+
kind !== 'property_identifier' &&
|
|
2190
|
+
kind !== 'identifier' &&
|
|
2191
|
+
kind !== 'private_property_identifier'
|
|
2192
|
+
)
|
|
2193
|
+
return;
|
|
2194
|
+
const typeAnno = findChild(node, 'type_annotation');
|
|
2195
|
+
if (!typeAnno) return;
|
|
2196
|
+
const typeName = extractSimpleTypeName(typeAnno);
|
|
2197
|
+
if (!typeName) return;
|
|
2198
|
+
if (currentClass) {
|
|
2199
|
+
// Primary: class-scoped key prevents cross-class collision (issue #1458).
|
|
2200
|
+
setTypeMapEntry(typeMap, `${currentClass}.${nameNode.text}`, typeName, 0.9);
|
|
2201
|
+
// Fallback: bare keys at lower confidence for single-class files or when
|
|
2202
|
+
// the resolver does not have a callerClass in scope.
|
|
2203
|
+
setTypeMapEntry(typeMap, nameNode.text, typeName, 0.6);
|
|
2204
|
+
setTypeMapEntry(typeMap, `this.${nameNode.text}`, typeName, 0.6);
|
|
2205
|
+
} else {
|
|
2206
|
+
// No enclosing class declaration (e.g. class expression) — use bare keys only.
|
|
2207
|
+
setTypeMapEntry(typeMap, nameNode.text, typeName, 0.9);
|
|
2208
|
+
setTypeMapEntry(typeMap, `this.${nameNode.text}`, typeName, 0.9);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2093
2212
|
/**
|
|
2094
2213
|
* Phase 8.3d: seed the pts map from object property writes.
|
|
2095
2214
|
*
|
|
@@ -3308,11 +3427,13 @@ function emitPrototypeMethod(
|
|
|
3308
3427
|
): void {
|
|
3309
3428
|
const fullName = `${className}.${methodName}`;
|
|
3310
3429
|
if (rhs.type === 'function_expression' || rhs.type === 'arrow_function') {
|
|
3430
|
+
const params = extractParameters(rhs);
|
|
3311
3431
|
definitions.push({
|
|
3312
3432
|
name: fullName,
|
|
3313
3433
|
kind: 'method',
|
|
3314
3434
|
line: nodeStartLine(rhs),
|
|
3315
3435
|
endLine: nodeEndLine(rhs),
|
|
3436
|
+
children: params.length > 0 ? params : undefined,
|
|
3316
3437
|
});
|
|
3317
3438
|
} else if (rhs.type === 'identifier' && !BUILTIN_GLOBALS.has(rhs.text)) {
|
|
3318
3439
|
// Prototype alias: `A.prototype.t = f` → typeMap['A.t'] = { type: 'f' }
|
|
@@ -324,7 +324,7 @@ export function hotspotsData(
|
|
|
324
324
|
metric: string;
|
|
325
325
|
level: string;
|
|
326
326
|
limit: number;
|
|
327
|
-
|
|
327
|
+
items: unknown[];
|
|
328
328
|
} {
|
|
329
329
|
const { db, nativeDb, close } = openReadonlyWithNative(customDbPath);
|
|
330
330
|
try {
|
|
@@ -337,19 +337,19 @@ export function hotspotsData(
|
|
|
337
337
|
// ── Native fast path: single query instead of 4 eagerly prepared ──
|
|
338
338
|
if (nativeDb?.getHotspots) {
|
|
339
339
|
const rows = nativeDb.getHotspots(kind, metric, noTests, limit);
|
|
340
|
-
const
|
|
341
|
-
const base = { metric, level, limit,
|
|
342
|
-
return paginateResult(base, '
|
|
340
|
+
const items = rows.map(mapNativeHotspotRow);
|
|
341
|
+
const base = { metric, level, limit, items };
|
|
342
|
+
return paginateResult(base, 'items', { limit: opts.limit, offset: opts.offset });
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
// ── JS fallback ───────────────────────────────────────────────────
|
|
346
346
|
const testFilter = testFilterSQL('n.name', noTests && kind === 'file');
|
|
347
347
|
const stmt = db.prepare(buildHotspotQuery(metric, testFilter));
|
|
348
348
|
const rows = stmt.all(kind, limit) as HotspotRow[];
|
|
349
|
-
const
|
|
349
|
+
const items = rows.map(mapJsHotspotRow);
|
|
350
350
|
|
|
351
|
-
const base = { metric, level, limit,
|
|
352
|
-
return paginateResult(base, '
|
|
351
|
+
const base = { metric, level, limit, items };
|
|
352
|
+
return paginateResult(base, 'items', { limit: opts.limit, offset: opts.offset });
|
|
353
353
|
} finally {
|
|
354
354
|
close();
|
|
355
355
|
}
|
package/src/index.ts
CHANGED
|
@@ -52,7 +52,11 @@ export { ownersData } from './features/owners.js';
|
|
|
52
52
|
export { sequenceData } from './features/sequence.js';
|
|
53
53
|
export { hotspotsData, moduleBoundariesData, structureData } from './features/structure.js';
|
|
54
54
|
export { triageData } from './features/triage.js';
|
|
55
|
-
export {
|
|
55
|
+
export {
|
|
56
|
+
loadConfig,
|
|
57
|
+
loadConfigWithProvenance,
|
|
58
|
+
resolveUserConfigPath,
|
|
59
|
+
} from './infrastructure/config.js';
|
|
56
60
|
export type { ArrayCompatSet } from './shared/constants.js';
|
|
57
61
|
export { EXTENSIONS, IGNORE_DIRS } from './shared/constants.js';
|
|
58
62
|
export {
|