@optave/codegraph 3.8.0 → 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 +13 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +137 -86
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/metrics.d.ts +0 -3
- package/dist/ast-analysis/metrics.d.ts.map +1 -1
- package/dist/ast-analysis/metrics.js +30 -13
- package/dist/ast-analysis/metrics.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +24 -19
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +55 -39
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +91 -70
- package/dist/ast-analysis/visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +81 -39
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
- package/dist/ast-analysis/visitors/dataflow-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/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +16 -2
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +29 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +16 -5
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/base.d.ts +16 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +31 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +7 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +100 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +8 -4
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +4 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +51 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/brief.d.ts.map +1 -1
- package/dist/domain/analysis/brief.js +13 -17
- package/dist/domain/analysis/brief.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +14 -11
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +64 -59
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +2 -7
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +33 -31
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts.map +1 -1
- package/dist/domain/analysis/implementations.js +11 -19
- package/dist/domain/analysis/implementations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +55 -76
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +7 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
- package/dist/domain/analysis/query-helpers.js +15 -1
- package/dist/domain/analysis/query-helpers.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +352 -107
- 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 +49 -18
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +2 -2
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -0
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +114 -22
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/resolve.js +1 -1
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts +2 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +170 -75
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +3 -4
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +141 -89
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.js +1 -1
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +4 -3
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +23 -8
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +29 -18
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -29
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.js +58 -46
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +65 -54
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.js +58 -67
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +58 -60
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/ast.d.ts +16 -14
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +83 -81
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +8 -6
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +69 -72
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +19 -7
- package/dist/features/communities.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +120 -125
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +136 -137
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +84 -79
- package/dist/features/flow.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +69 -65
- package/dist/features/structure-query.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +70 -55
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +288 -266
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +5 -1
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +6 -4
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/suppress.d.ts +25 -0
- package/dist/infrastructure/suppress.d.ts.map +1 -0
- package/dist/infrastructure/suppress.js +43 -0
- package/dist/infrastructure/suppress.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +29 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/dataflow.d.ts.map +1 -1
- package/dist/presentation/dataflow.js +47 -38
- package/dist/presentation/dataflow.js.map +1 -1
- package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
- package/dist/presentation/diff-impact-mermaid.js +60 -51
- package/dist/presentation/diff-impact-mermaid.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +20 -14
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +15 -13
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +101 -79
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +25 -16
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli/path.js +26 -20
- package/dist/presentation/queries-cli/path.js.map +1 -1
- package/dist/presentation/result-formatter.d.ts +10 -0
- package/dist/presentation/result-formatter.d.ts.map +1 -1
- package/dist/presentation/result-formatter.js +16 -1
- package/dist/presentation/result-formatter.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +18 -12
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/errors.d.ts +5 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +5 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/hierarchy.d.ts +8 -2
- package/dist/shared/hierarchy.d.ts.map +1 -1
- package/dist/shared/hierarchy.js +42 -1
- package/dist/shared/hierarchy.js.map +1 -1
- package/dist/shared/normalize.d.ts +6 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +20 -12
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +0 -9
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js +0 -15
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +12 -5
- 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 +176 -104
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/shared.ts +33 -24
- package/src/ast-analysis/visitor-utils.ts +52 -32
- package/src/ast-analysis/visitor.ts +132 -71
- package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
- package/src/ast-analysis/visitors/complexity-visitor.ts +89 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- 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/cli/commands/watch.ts +16 -2
- package/src/db/connection.ts +29 -28
- package/src/db/query-builder.ts +15 -3
- package/src/db/repository/base.ts +34 -0
- package/src/db/repository/native-repository.ts +104 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +55 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +77 -81
- package/src/domain/analysis/fn-impact.ts +36 -43
- package/src/domain/analysis/implementations.ts +11 -17
- package/src/domain/analysis/module-map.ts +58 -92
- package/src/domain/analysis/query-helpers.ts +18 -1
- package/src/domain/graph/builder/pipeline.ts +409 -99
- package/src/domain/graph/builder/stages/build-edges.ts +45 -19
- package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
- package/src/domain/graph/builder/stages/finalize.ts +2 -2
- package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
- package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
- package/src/domain/graph/cycles.ts +110 -23
- package/src/domain/graph/resolve.ts +1 -1
- package/src/domain/graph/watcher.ts +202 -96
- package/src/domain/parser.ts +143 -89
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +26 -7
- package/src/domain/search/search/hybrid.ts +69 -51
- package/src/extractors/go.ts +43 -33
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +66 -54
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/python.ts +31 -25
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +57 -61
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +130 -110
- package/src/features/audit.ts +8 -6
- package/src/features/branch-compare.ts +105 -79
- package/src/features/communities.ts +25 -10
- package/src/features/complexity.ts +171 -134
- package/src/features/dataflow.ts +165 -175
- package/src/features/flow.ts +129 -92
- package/src/features/structure-query.ts +79 -64
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/model.ts +6 -1
- package/src/infrastructure/config.ts +6 -4
- package/src/infrastructure/suppress.ts +47 -0
- package/src/mcp/server.ts +53 -37
- package/src/presentation/dataflow.ts +50 -44
- package/src/presentation/diff-impact-mermaid.ts +104 -62
- package/src/presentation/queries-cli/exports.ts +21 -13
- package/src/presentation/queries-cli/impact.ts +15 -13
- package/src/presentation/queries-cli/inspect.ts +100 -81
- package/src/presentation/queries-cli/overview.ts +26 -16
- package/src/presentation/queries-cli/path.ts +33 -25
- package/src/presentation/result-formatter.ts +19 -1
- package/src/presentation/viewer.ts +42 -14
- package/src/shared/errors.ts +6 -0
- package/src/shared/hierarchy.ts +50 -2
- package/src/shared/normalize.ts +31 -12
- package/src/shared/paginate.ts +0 -17
- package/src/types.ts +26 -5
|
@@ -294,99 +294,107 @@ export function children(name: string, customDbPath: string, opts: OutputOpts =
|
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
-
function
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
console.log(
|
|
297
|
+
function renderSignature(sig: ContextResult['signature']): void {
|
|
298
|
+
if (!sig) return;
|
|
299
|
+
console.log('## Type/Shape Info');
|
|
300
|
+
if (sig.params != null) console.log(` Parameters: (${sig.params})`);
|
|
301
|
+
if (sig.returnType) console.log(` Returns: ${sig.returnType}`);
|
|
302
|
+
console.log();
|
|
303
|
+
}
|
|
301
304
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
function renderComplexity(cx: NonNullable<ContextResult['complexity']>): void {
|
|
306
|
+
const miPart = cx.maintainabilityIndex ? ` | MI: ${cx.maintainabilityIndex}` : '';
|
|
307
|
+
console.log('## Complexity');
|
|
308
|
+
console.log(
|
|
309
|
+
` Cognitive: ${cx.cognitive} | Cyclomatic: ${cx.cyclomatic} | Max Nesting: ${cx.maxNesting}${miPart}`,
|
|
310
|
+
);
|
|
311
|
+
console.log();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function renderSource(source: string, indent = ' '): void {
|
|
315
|
+
console.log('## Source');
|
|
316
|
+
for (const line of source.split('\n')) {
|
|
317
|
+
console.log(`${indent}${line}`);
|
|
307
318
|
}
|
|
319
|
+
console.log();
|
|
320
|
+
}
|
|
308
321
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
322
|
+
function renderCallees(callees: CalleeRef[]): void {
|
|
323
|
+
if (callees.length === 0) return;
|
|
324
|
+
console.log(`## Direct Dependencies (${callees.length})`);
|
|
325
|
+
for (const c of callees) {
|
|
326
|
+
const summary = c.summary ? ` — ${c.summary}` : '';
|
|
327
|
+
console.log(` ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}${summary}`);
|
|
328
|
+
if (c.source) {
|
|
329
|
+
const maxSourceLines = 10;
|
|
330
|
+
for (const line of c.source.split('\n').slice(0, maxSourceLines)) {
|
|
331
|
+
console.log(` | ${line}`);
|
|
332
|
+
}
|
|
313
333
|
}
|
|
314
|
-
console.log();
|
|
315
334
|
}
|
|
335
|
+
console.log();
|
|
336
|
+
}
|
|
316
337
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
);
|
|
324
|
-
console.log();
|
|
338
|
+
function renderCallers(callers: CallerRef[]): void {
|
|
339
|
+
if (callers.length === 0) return;
|
|
340
|
+
console.log(`## Callers (${callers.length})`);
|
|
341
|
+
for (const c of callers) {
|
|
342
|
+
const via = c.viaHierarchy ? ` (via ${c.viaHierarchy})` : '';
|
|
343
|
+
console.log(` ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}${via}`);
|
|
325
344
|
}
|
|
345
|
+
console.log();
|
|
346
|
+
}
|
|
326
347
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
console.log();
|
|
348
|
+
function renderSymbolRefList(label: string, items: SymbolRef[]): void {
|
|
349
|
+
if (items.length === 0) return;
|
|
350
|
+
console.log(`## ${label} (${items.length})`);
|
|
351
|
+
for (const s of items) {
|
|
352
|
+
console.log(` ${kindIcon(s.kind)} ${s.name} ${s.file}:${s.line}`);
|
|
333
353
|
}
|
|
354
|
+
console.log();
|
|
355
|
+
}
|
|
334
356
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
357
|
+
function renderRelatedTests(tests: ContextResult['relatedTests']): void {
|
|
358
|
+
if (tests.length === 0) return;
|
|
359
|
+
console.log('## Related Tests');
|
|
360
|
+
const maxTestSourceLines = 20;
|
|
361
|
+
for (const t of tests) {
|
|
362
|
+
console.log(` ${t.file} — ${t.testCount} tests`);
|
|
363
|
+
for (const tn of t.testNames) {
|
|
364
|
+
console.log(` - ${tn}`);
|
|
365
|
+
}
|
|
366
|
+
if (t.source) {
|
|
367
|
+
console.log(' Source:');
|
|
368
|
+
for (const line of t.source.split('\n').slice(0, maxTestSourceLines)) {
|
|
369
|
+
console.log(` | ${line}`);
|
|
344
370
|
}
|
|
345
371
|
}
|
|
346
|
-
console.log();
|
|
347
372
|
}
|
|
373
|
+
console.log();
|
|
374
|
+
}
|
|
348
375
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
console.log(` ${kindIcon(c.kind)} ${c.name} ${c.file}:${c.line}${via}`);
|
|
354
|
-
}
|
|
355
|
-
console.log();
|
|
356
|
-
}
|
|
376
|
+
function renderContextResult(r: ContextResult): void {
|
|
377
|
+
const lineRange = r.endLine ? `${r.line}-${r.endLine}` : `${r.line}`;
|
|
378
|
+
const roleTag = r.role ? ` [${r.role}]` : '';
|
|
379
|
+
console.log(`\n# ${r.name} (${r.kind})${roleTag} — ${r.file}:${lineRange}\n`);
|
|
357
380
|
|
|
358
|
-
|
|
359
|
-
console.log(`## Implementors (${r.implementors.length})`);
|
|
360
|
-
for (const impl of r.implementors) {
|
|
361
|
-
console.log(` ${kindIcon(impl.kind)} ${impl.name} ${impl.file}:${impl.line}`);
|
|
362
|
-
}
|
|
363
|
-
console.log();
|
|
364
|
-
}
|
|
381
|
+
renderSignature(r.signature);
|
|
365
382
|
|
|
366
|
-
if (r.
|
|
367
|
-
console.log(`##
|
|
368
|
-
for (const
|
|
369
|
-
console.log(` ${kindIcon(
|
|
383
|
+
if (r.children && r.children.length > 0) {
|
|
384
|
+
console.log(`## Children (${r.children.length})`);
|
|
385
|
+
for (const c of r.children) {
|
|
386
|
+
console.log(` ${kindIcon(c.kind)} ${c.name} :${c.line}`);
|
|
370
387
|
}
|
|
371
388
|
console.log();
|
|
372
389
|
}
|
|
373
390
|
|
|
374
|
-
if (r.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (t.source) {
|
|
382
|
-
console.log(' Source:');
|
|
383
|
-
for (const line of t.source.split('\n').slice(0, 20)) {
|
|
384
|
-
console.log(` | ${line}`);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
console.log();
|
|
389
|
-
}
|
|
391
|
+
if (r.complexity) renderComplexity(r.complexity);
|
|
392
|
+
if (r.source) renderSource(r.source);
|
|
393
|
+
renderCallees(r.callees);
|
|
394
|
+
renderCallers(r.callers);
|
|
395
|
+
renderSymbolRefList('Implementors', r.implementors || []);
|
|
396
|
+
renderSymbolRefList('Implements', r.implements || []);
|
|
397
|
+
renderRelatedTests(r.relatedTests);
|
|
390
398
|
|
|
391
399
|
if (r.callees.length === 0 && r.callers.length === 0 && r.relatedTests.length === 0) {
|
|
392
400
|
console.log(' (no call edges or tests found — may be invoked dynamically or via re-exports)');
|
|
@@ -439,7 +447,7 @@ function renderFileExplain(r: FileExplainResult): void {
|
|
|
439
447
|
console.log();
|
|
440
448
|
}
|
|
441
449
|
|
|
442
|
-
function
|
|
450
|
+
function renderExplainHeader(r: FunctionExplainResult, indent: string): void {
|
|
443
451
|
const lineRange = r.endLine ? `${r.line}-${r.endLine}` : `${r.line}`;
|
|
444
452
|
const lineInfo = r.lineCount ? `${r.lineCount} lines` : '';
|
|
445
453
|
const summaryPart = r.summary ? ` | ${r.summary}` : '';
|
|
@@ -454,15 +462,19 @@ function renderFunctionExplain(r: FunctionExplainResult, indent = ''): void {
|
|
|
454
462
|
if (r.signature.params != null) console.log(`${indent} Parameters: (${r.signature.params})`);
|
|
455
463
|
if (r.signature.returnType) console.log(`${indent} Returns: ${r.signature.returnType}`);
|
|
456
464
|
}
|
|
465
|
+
}
|
|
457
466
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
467
|
+
function renderExplainComplexity(
|
|
468
|
+
cx: NonNullable<FunctionExplainResult['complexity']>,
|
|
469
|
+
indent: string,
|
|
470
|
+
): void {
|
|
471
|
+
const miPart = cx.maintainabilityIndex ? ` MI=${cx.maintainabilityIndex}` : '';
|
|
472
|
+
console.log(
|
|
473
|
+
`${indent} Complexity: cognitive=${cx.cognitive} cyclomatic=${cx.cyclomatic} nesting=${cx.maxNesting}${miPart}`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
465
476
|
|
|
477
|
+
function renderExplainEdges(r: FunctionExplainResult, indent: string): void {
|
|
466
478
|
if (r.callees.length > 0) {
|
|
467
479
|
console.log(`\n${indent} Calls (${r.callees.length}):`);
|
|
468
480
|
for (const c of r.callees) {
|
|
@@ -488,8 +500,15 @@ function renderFunctionExplain(r: FunctionExplainResult, indent = ''): void {
|
|
|
488
500
|
if (r.callees.length === 0 && r.callers.length === 0) {
|
|
489
501
|
console.log(`${indent} (no call edges found -- may be invoked dynamically or via re-exports)`);
|
|
490
502
|
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function renderFunctionExplain(r: FunctionExplainResult, indent = ''): void {
|
|
506
|
+
renderExplainHeader(r, indent);
|
|
507
|
+
if (r.complexity) renderExplainComplexity(r.complexity, indent);
|
|
508
|
+
renderExplainEdges(r, indent);
|
|
491
509
|
|
|
492
510
|
if (r.depDetails && r.depDetails.length > 0) {
|
|
511
|
+
const depthLevel = r._depth || 0;
|
|
493
512
|
console.log(`\n${indent} --- Dependencies (depth ${depthLevel + 1}) ---`);
|
|
494
513
|
for (const dep of r.depDetails) {
|
|
495
514
|
renderFunctionExplain(dep, `${indent} `);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { kindIcon, moduleMapData, rolesData, statsData } from '../../domain/queries.js';
|
|
3
|
+
import { debug } from '../../infrastructure/logger.js';
|
|
3
4
|
import { outputResult } from '../../infrastructure/result-formatter.js';
|
|
5
|
+
import { toErrorMessage } from '../../shared/errors.js';
|
|
4
6
|
|
|
5
7
|
interface OutputOpts {
|
|
6
8
|
json?: boolean;
|
|
@@ -236,8 +238,8 @@ export async function stats(customDbPath: string, opts: OutputOpts = {}): Promis
|
|
|
236
238
|
try {
|
|
237
239
|
const { communitySummaryForStats } = await import('../../features/communities.js');
|
|
238
240
|
data.communities = communitySummaryForStats(customDbPath, { noTests: opts.noTests });
|
|
239
|
-
} catch {
|
|
240
|
-
|
|
241
|
+
} catch (e) {
|
|
242
|
+
debug(`stats: community detection failed (optional): ${toErrorMessage(e)}`);
|
|
241
243
|
}
|
|
242
244
|
|
|
243
245
|
if (outputResult(data as unknown as Record<string, unknown>, null, opts)) return;
|
|
@@ -281,6 +283,26 @@ export function moduleMap(customDbPath: string, limit = 20, opts: OutputOpts = {
|
|
|
281
283
|
);
|
|
282
284
|
}
|
|
283
285
|
|
|
286
|
+
function groupByRole(symbols: RoleSymbol[]): Record<string, RoleSymbol[]> {
|
|
287
|
+
const byRole: Record<string, RoleSymbol[]> = {};
|
|
288
|
+
for (const s of symbols) {
|
|
289
|
+
if (!byRole[s.role]) byRole[s.role] = [];
|
|
290
|
+
byRole[s.role]!.push(s);
|
|
291
|
+
}
|
|
292
|
+
return byRole;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function printRoleGroup(role: string, symbols: RoleSymbol[]): void {
|
|
296
|
+
console.log(`## ${role} (${symbols.length})`);
|
|
297
|
+
for (const s of symbols.slice(0, 30)) {
|
|
298
|
+
console.log(` ${kindIcon(s.kind)} ${s.name} ${s.file}:${s.line}`);
|
|
299
|
+
}
|
|
300
|
+
if (symbols.length > 30) {
|
|
301
|
+
console.log(` ... and ${symbols.length - 30} more`);
|
|
302
|
+
}
|
|
303
|
+
console.log();
|
|
304
|
+
}
|
|
305
|
+
|
|
284
306
|
export function roles(customDbPath: string, opts: OutputOpts = {}): void {
|
|
285
307
|
const data = rolesData(customDbPath, opts) as RolesData;
|
|
286
308
|
if (outputResult(data as unknown as Record<string, unknown>, 'symbols', opts)) return;
|
|
@@ -298,20 +320,8 @@ export function roles(customDbPath: string, opts: OutputOpts = {}): void {
|
|
|
298
320
|
.map(([role, count]) => `${role}: ${count}`);
|
|
299
321
|
console.log(` ${summaryParts.join(' ')}\n`);
|
|
300
322
|
|
|
301
|
-
const byRole
|
|
302
|
-
for (const s of data.symbols) {
|
|
303
|
-
if (!byRole[s.role]) byRole[s.role] = [];
|
|
304
|
-
byRole[s.role]!.push(s);
|
|
305
|
-
}
|
|
306
|
-
|
|
323
|
+
const byRole = groupByRole(data.symbols);
|
|
307
324
|
for (const [role, symbols] of Object.entries(byRole)) {
|
|
308
|
-
|
|
309
|
-
for (const s of symbols.slice(0, 30)) {
|
|
310
|
-
console.log(` ${kindIcon(s.kind)} ${s.name} ${s.file}:${s.line}`);
|
|
311
|
-
}
|
|
312
|
-
if (symbols.length > 30) {
|
|
313
|
-
console.log(` ... and ${symbols.length - 30} more`);
|
|
314
|
-
}
|
|
315
|
-
console.log();
|
|
325
|
+
printRoleGroup(role, symbols);
|
|
316
326
|
}
|
|
317
327
|
}
|
|
@@ -127,6 +127,37 @@ interface FilePathDataResult {
|
|
|
127
127
|
alternateCount: number;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
function printFilePathNotFound(from: string, to: string, data: FilePathDataResult): void {
|
|
131
|
+
const dir = data.reverse ? 'reverse ' : '';
|
|
132
|
+
console.log(`No ${dir}file path from "${from}" to "${to}" within ${data.maxDepth} hops.`);
|
|
133
|
+
if (data.fromCandidates.length > 1) {
|
|
134
|
+
console.log(
|
|
135
|
+
`\n "${from}" matched ${data.fromCandidates.length} files — using: ${data.fromCandidates[0]}`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (data.toCandidates.length > 1) {
|
|
139
|
+
console.log(
|
|
140
|
+
` "${to}" matched ${data.toCandidates.length} files — using: ${data.toCandidates[0]}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function printFilePathSteps(data: FilePathDataResult): void {
|
|
146
|
+
for (let i = 0; i < data.path.length; i++) {
|
|
147
|
+
const indent = ' '.repeat(i + 1);
|
|
148
|
+
if (i === 0) {
|
|
149
|
+
console.log(`${indent}${data.path[i]}`);
|
|
150
|
+
} else {
|
|
151
|
+
console.log(`${indent}→ ${data.path[i]}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (data.alternateCount > 0) {
|
|
155
|
+
console.log(
|
|
156
|
+
`\n (${data.alternateCount} alternate shortest ${data.alternateCount === 1 ? 'path' : 'paths'} at same depth)`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
130
161
|
function filePath(from: string, to: string, customDbPath: string, opts: PathOpts = {}): void {
|
|
131
162
|
const data = filePathData(from, to, customDbPath, opts) as FilePathDataResult;
|
|
132
163
|
if (outputResult(data as unknown as Record<string, unknown>, null, opts)) return;
|
|
@@ -137,18 +168,7 @@ function filePath(from: string, to: string, customDbPath: string, opts: PathOpts
|
|
|
137
168
|
}
|
|
138
169
|
|
|
139
170
|
if (!data.found) {
|
|
140
|
-
|
|
141
|
-
console.log(`No ${dir}file path from "${from}" to "${to}" within ${data.maxDepth} hops.`);
|
|
142
|
-
if (data.fromCandidates.length > 1) {
|
|
143
|
-
console.log(
|
|
144
|
-
`\n "${from}" matched ${data.fromCandidates.length} files — using: ${data.fromCandidates[0]}`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
if (data.toCandidates.length > 1) {
|
|
148
|
-
console.log(
|
|
149
|
-
` "${to}" matched ${data.toCandidates.length} files — using: ${data.toCandidates[0]}`,
|
|
150
|
-
);
|
|
151
|
-
}
|
|
171
|
+
printFilePathNotFound(from, to, data);
|
|
152
172
|
return;
|
|
153
173
|
}
|
|
154
174
|
|
|
@@ -162,18 +182,6 @@ function filePath(from: string, to: string, customDbPath: string, opts: PathOpts
|
|
|
162
182
|
console.log(
|
|
163
183
|
`\nFile path from ${from} to ${to} (${data.hops} ${data.hops === 1 ? 'hop' : 'hops'})${dir}:\n`,
|
|
164
184
|
);
|
|
165
|
-
|
|
166
|
-
const indent = ' '.repeat(i + 1);
|
|
167
|
-
if (i === 0) {
|
|
168
|
-
console.log(`${indent}${data.path[i]}`);
|
|
169
|
-
} else {
|
|
170
|
-
console.log(`${indent}→ ${data.path[i]}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (data.alternateCount > 0) {
|
|
174
|
-
console.log(
|
|
175
|
-
`\n (${data.alternateCount} alternate shortest ${data.alternateCount === 1 ? 'path' : 'paths'} at same depth)`,
|
|
176
|
-
);
|
|
177
|
-
}
|
|
185
|
+
printFilePathSteps(data);
|
|
178
186
|
console.log();
|
|
179
187
|
}
|
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { loadConfig } from '../infrastructure/config.js';
|
|
2
|
-
import {
|
|
2
|
+
import type { PaginationMeta } from '../shared/paginate.js';
|
|
3
3
|
import { formatTable, truncEnd } from './table.js';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Print data as newline-delimited JSON (NDJSON).
|
|
7
|
+
*
|
|
8
|
+
* Emits a `_meta` line with pagination info (if present), then one JSON
|
|
9
|
+
* line per item in the named array field.
|
|
10
|
+
*/
|
|
11
|
+
export function printNdjson(
|
|
12
|
+
data: Record<string, unknown> & { _pagination?: PaginationMeta },
|
|
13
|
+
field: string,
|
|
14
|
+
): void {
|
|
15
|
+
if (data._pagination) console.log(JSON.stringify({ _meta: data._pagination }));
|
|
16
|
+
const items = data[field];
|
|
17
|
+
if (Array.isArray(items)) {
|
|
18
|
+
// console.log is intentional: NDJSON serialisation must go to stdout, not the structured logger
|
|
19
|
+
for (const item of items) console.log(JSON.stringify(item));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
5
23
|
function flattenObject(obj: Record<string, unknown>, prefix = ''): Record<string, unknown> {
|
|
6
24
|
const result: Record<string, unknown> = {};
|
|
7
25
|
for (const [key, value] of Object.entries(obj)) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { debug } from '../infrastructure/logger.js';
|
|
4
|
+
import { toErrorMessage } from '../shared/errors.js';
|
|
3
5
|
import { COMMUNITY_COLORS, DEFAULT_NODE_COLORS, DEFAULT_ROLE_COLORS } from './colors.js';
|
|
4
6
|
|
|
5
7
|
// Re-export color constants so existing consumers are unaffected
|
|
@@ -74,8 +76,8 @@ export function loadPlotConfig(dir: string): PlotConfig {
|
|
|
74
76
|
...(raw.riskThresholds || {}),
|
|
75
77
|
},
|
|
76
78
|
};
|
|
77
|
-
} catch {
|
|
78
|
-
|
|
79
|
+
} catch (e) {
|
|
80
|
+
debug(`loadViewerConfig: invalid JSON in config file: ${toErrorMessage(e)}`);
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
}
|
|
@@ -179,19 +181,45 @@ export interface ViewerData {
|
|
|
179
181
|
seedNodeIds: (number | string)[];
|
|
180
182
|
}
|
|
181
183
|
|
|
184
|
+
interface ResolvedPlotConfig {
|
|
185
|
+
layoutOpts: LayoutOptions;
|
|
186
|
+
title: string;
|
|
187
|
+
layoutAlgorithm: string;
|
|
188
|
+
layoutDirection: string;
|
|
189
|
+
physicsEnabled: boolean;
|
|
190
|
+
sizeBy: string;
|
|
191
|
+
clusterBy: string;
|
|
192
|
+
effectiveColorBy: string;
|
|
193
|
+
effectiveRisk: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function resolvePlotConfig(cfg: PlotConfig): ResolvedPlotConfig {
|
|
197
|
+
return {
|
|
198
|
+
layoutOpts: buildLayoutOptions(cfg),
|
|
199
|
+
title: cfg.title || 'Codegraph',
|
|
200
|
+
layoutAlgorithm: cfg.layout?.algorithm || 'hierarchical',
|
|
201
|
+
layoutDirection: cfg.layout?.direction || 'LR',
|
|
202
|
+
physicsEnabled: cfg.physics?.enabled !== false,
|
|
203
|
+
sizeBy: cfg.sizeBy || 'uniform',
|
|
204
|
+
clusterBy: cfg.clusterBy || 'none',
|
|
205
|
+
effectiveColorBy:
|
|
206
|
+
cfg.overlays?.complexity && cfg.colorBy === 'kind' ? 'complexity' : cfg.colorBy || 'kind',
|
|
207
|
+
effectiveRisk: cfg.overlays?.risk || false,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
182
211
|
export function renderPlotHTML(data: ViewerData, cfg: PlotConfig): string {
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const effectiveRisk = cfg.overlays?.risk || false;
|
|
212
|
+
const {
|
|
213
|
+
layoutOpts,
|
|
214
|
+
title,
|
|
215
|
+
layoutAlgorithm,
|
|
216
|
+
layoutDirection,
|
|
217
|
+
physicsEnabled,
|
|
218
|
+
sizeBy,
|
|
219
|
+
clusterBy,
|
|
220
|
+
effectiveColorBy,
|
|
221
|
+
effectiveRisk,
|
|
222
|
+
} = resolvePlotConfig(cfg);
|
|
195
223
|
|
|
196
224
|
return `<!DOCTYPE html>
|
|
197
225
|
<html lang="en">
|
package/src/shared/errors.ts
CHANGED
|
@@ -77,3 +77,9 @@ export class BoundaryError extends CodegraphError {
|
|
|
77
77
|
export function toErrorMessage(e: unknown): string {
|
|
78
78
|
return e instanceof Error ? e.message : String(e);
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Catch-suppression helpers (`suppressError`, `suppressErrorAsync`) live in
|
|
83
|
+
* `infrastructure/suppress.ts` to avoid a shared→infrastructure layer inversion.
|
|
84
|
+
* Import them from there instead of from this module.
|
|
85
|
+
*/
|
package/src/shared/hierarchy.ts
CHANGED
|
@@ -1,10 +1,58 @@
|
|
|
1
1
|
import { getClassHierarchy } from '../db/index.js';
|
|
2
|
-
import type { BetterSqlite3Database, NodeRow } from '../types.js';
|
|
2
|
+
import type { BetterSqlite3Database, NodeRow, Repository } from '../types.js';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Resolve all methods in the class hierarchy that share the given method name.
|
|
6
|
+
*
|
|
7
|
+
* Accepts either a raw BetterSqlite3Database handle (legacy) or a Repository
|
|
8
|
+
* instance (preferred — works with both SqliteRepository and NativeRepository).
|
|
9
|
+
*/
|
|
4
10
|
export function resolveMethodViaHierarchy(
|
|
5
|
-
|
|
11
|
+
dbOrRepo: BetterSqlite3Database | Repository,
|
|
6
12
|
methodName: string,
|
|
7
13
|
): NodeRow[] {
|
|
14
|
+
// Detect Repository vs raw DB by duck-typing on findNodesWithFanIn
|
|
15
|
+
if (
|
|
16
|
+
typeof (dbOrRepo as Repository).findNodesWithFanIn === 'function' &&
|
|
17
|
+
typeof (dbOrRepo as Repository).getClassHierarchy === 'function'
|
|
18
|
+
) {
|
|
19
|
+
return resolveViaRepo(dbOrRepo as Repository, methodName);
|
|
20
|
+
}
|
|
21
|
+
return resolveViaRawDb(dbOrRepo as BetterSqlite3Database, methodName);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Strip fan_in from NodeRowWithFanIn to produce a plain NodeRow. */
|
|
25
|
+
function stripFanIn(rows: Array<{ fan_in: number } & NodeRow>): NodeRow[] {
|
|
26
|
+
return rows.map(({ fan_in: _, ...rest }) => rest);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveViaRepo(repo: Repository, methodName: string): NodeRow[] {
|
|
30
|
+
const methods = stripFanIn(repo.findNodesWithFanIn(`%.${methodName}`, { kinds: ['method'] }));
|
|
31
|
+
|
|
32
|
+
const results: NodeRow[] = [...methods];
|
|
33
|
+
for (const m of methods) {
|
|
34
|
+
const className = m.name.split('.')[0]!;
|
|
35
|
+
const classNodes = repo.findNodesWithFanIn(className, {
|
|
36
|
+
kinds: ['class'],
|
|
37
|
+
file: m.file,
|
|
38
|
+
});
|
|
39
|
+
const classNode = classNodes[0];
|
|
40
|
+
if (!classNode) continue;
|
|
41
|
+
|
|
42
|
+
const ancestors = repo.getClassHierarchy(classNode.id);
|
|
43
|
+
for (const ancestorId of ancestors) {
|
|
44
|
+
const ancestor = repo.findNodeById(ancestorId);
|
|
45
|
+
if (!ancestor) continue;
|
|
46
|
+
const parentMethods = stripFanIn(
|
|
47
|
+
repo.findNodesWithFanIn(`${ancestor.name}.${methodName}`, { kinds: ['method'] }),
|
|
48
|
+
);
|
|
49
|
+
results.push(...parentMethods);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveViaRawDb(db: BetterSqlite3Database, methodName: string): NodeRow[] {
|
|
8
56
|
const methods = db
|
|
9
57
|
.prepare(`SELECT * FROM nodes WHERE kind = 'method' AND name LIKE ?`)
|
|
10
58
|
.all(`%.${methodName}`) as NodeRow[];
|
package/src/shared/normalize.ts
CHANGED
|
@@ -3,6 +3,15 @@ interface DbHandle {
|
|
|
3
3
|
prepare(sql: string): { get(...params: unknown[]): unknown };
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
/** Anything that can look up a file hash — either a raw DB or a Repository. */
|
|
7
|
+
interface HashSource {
|
|
8
|
+
getFileHash(file: string): string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isHashSource(x: unknown): x is HashSource {
|
|
12
|
+
return typeof x === 'object' && x !== null && typeof (x as HashSource).getFileHash === 'function';
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
export function getFileHash(db: DbHandle, file: string): string | null {
|
|
7
16
|
const row = db.prepare('SELECT hash FROM file_hashes WHERE file = ?').get(file) as
|
|
8
17
|
| { hash: string }
|
|
@@ -55,25 +64,35 @@ interface RawSymbolRow {
|
|
|
55
64
|
role?: string | null;
|
|
56
65
|
}
|
|
57
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Resolve a file hash, using the cache when available.
|
|
69
|
+
* Accepts a raw DB handle (with .prepare) or a Repository (with .getFileHash).
|
|
70
|
+
*/
|
|
71
|
+
function resolveFileHash(
|
|
72
|
+
db: DbHandle | HashSource,
|
|
73
|
+
file: string,
|
|
74
|
+
hashCache?: Map<string, string | null>,
|
|
75
|
+
): string | null {
|
|
76
|
+
const lookupHash = isHashSource(db)
|
|
77
|
+
? (f: string) => db.getFileHash(f)
|
|
78
|
+
: (f: string) => getFileHash(db as DbHandle, f);
|
|
79
|
+
if (!hashCache) return lookupHash(file);
|
|
80
|
+
if (!hashCache.has(file)) {
|
|
81
|
+
hashCache.set(file, lookupHash(file));
|
|
82
|
+
}
|
|
83
|
+
return hashCache.get(file) ?? null;
|
|
84
|
+
}
|
|
85
|
+
|
|
58
86
|
/**
|
|
59
87
|
* Normalize a raw DB/query row into the stable 7-field symbol shape.
|
|
88
|
+
* Accepts a raw DB handle (with .prepare), a Repository (with .getFileHash), or null.
|
|
60
89
|
*/
|
|
61
90
|
export function normalizeSymbol(
|
|
62
91
|
row: RawSymbolRow,
|
|
63
|
-
db?: DbHandle | null,
|
|
92
|
+
db?: DbHandle | HashSource | null,
|
|
64
93
|
hashCache?: Map<string, string | null>,
|
|
65
94
|
): NormalizedSymbol {
|
|
66
|
-
|
|
67
|
-
if (db) {
|
|
68
|
-
if (hashCache) {
|
|
69
|
-
if (!hashCache.has(row.file)) {
|
|
70
|
-
hashCache.set(row.file, getFileHash(db, row.file));
|
|
71
|
-
}
|
|
72
|
-
fileHash = hashCache.get(row.file) ?? null;
|
|
73
|
-
} else {
|
|
74
|
-
fileHash = getFileHash(db, row.file);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
95
|
+
const fileHash = db ? resolveFileHash(db, row.file, hashCache) : null;
|
|
77
96
|
return {
|
|
78
97
|
name: row.name,
|
|
79
98
|
kind: row.kind,
|
package/src/shared/paginate.ts
CHANGED
|
@@ -105,20 +105,3 @@ export function paginateResult<T extends Record<string, unknown>>(
|
|
|
105
105
|
const { items, pagination } = paginate(arr, { limit, offset });
|
|
106
106
|
return { ...result, [field]: items, _pagination: pagination };
|
|
107
107
|
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Print data as newline-delimited JSON (NDJSON).
|
|
111
|
-
*
|
|
112
|
-
* Emits a `_meta` line with pagination info (if present), then one JSON
|
|
113
|
-
* line per item in the named array field.
|
|
114
|
-
*/
|
|
115
|
-
export function printNdjson(
|
|
116
|
-
data: Record<string, unknown> & { _pagination?: PaginationMeta },
|
|
117
|
-
field: string,
|
|
118
|
-
): void {
|
|
119
|
-
if (data._pagination) console.log(JSON.stringify({ _meta: data._pagination }));
|
|
120
|
-
const items = data[field];
|
|
121
|
-
if (Array.isArray(items)) {
|
|
122
|
-
for (const item of items) console.log(JSON.stringify(item));
|
|
123
|
-
}
|
|
124
|
-
}
|