@optave/codegraph 3.9.0 → 3.9.2
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 +12 -13
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +78 -48
- package/dist/ast-analysis/engine.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 +15 -18
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +5 -17
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/structure.d.ts.map +1 -1
- package/dist/cli/commands/structure.js +18 -1
- package/dist/cli/commands/structure.js.map +1 -1
- package/dist/db/connection.d.ts +3 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +24 -6
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/repository/base.d.ts +35 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +8 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/index.d.ts +1 -0
- package/dist/db/repository/index.d.ts.map +1 -1
- package/dist/db/repository/index.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 +46 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +5 -15
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts +6 -33
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +18 -16
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.js +2 -2
- 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 +3 -13
- package/dist/domain/analysis/implementations.js.map +1 -1
- package/dist/domain/graph/builder/context.d.ts +4 -0
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +4 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +18 -0
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/native-db-proxy.d.ts +24 -0
- package/dist/domain/graph/builder/native-db-proxy.d.ts.map +1 -0
- package/dist/domain/graph/builder/native-db-proxy.js +87 -0
- package/dist/domain/graph/builder/native-db-proxy.js.map +1 -0
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +410 -349
- 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 +44 -4
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +2 -2
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +6 -28
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +1 -1
- 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 +16 -12
- 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 +21 -26
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +99 -95
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +7 -2
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/queries.d.ts +1 -1
- package/dist/domain/queries.d.ts.map +1 -1
- package/dist/domain/queries.js +1 -1
- package/dist/domain/queries.js.map +1 -1
- package/dist/extractors/go.js +53 -35
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/javascript.js +66 -27
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +3 -2
- package/dist/features/audit.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +3 -5
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +2 -1
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +78 -58
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +109 -118
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +2 -1
- package/dist/features/flow.js.map +1 -1
- package/dist/features/manifesto.d.ts.map +1 -1
- package/dist/features/manifesto.js +15 -1
- package/dist/features/manifesto.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +147 -97
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/louvain.d.ts.map +1 -1
- package/dist/graph/algorithms/louvain.js +4 -2
- package/dist/graph/algorithms/louvain.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts +2 -0
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +13 -5
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -0
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -0
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/presentation/batch.d.ts.map +1 -1
- package/dist/presentation/batch.js +1 -0
- package/dist/presentation/batch.js.map +1 -1
- package/dist/presentation/communities.d.ts.map +1 -1
- package/dist/presentation/communities.js +38 -34
- package/dist/presentation/communities.js.map +1 -1
- package/dist/presentation/manifesto.d.ts.map +1 -1
- package/dist/presentation/manifesto.js +31 -33
- package/dist/presentation/manifesto.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +47 -46
- package/dist/presentation/queries-cli/inspect.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 +1 -1
- package/dist/presentation/structure.js.map +1 -1
- package/dist/shared/file-utils.d.ts.map +1 -1
- package/dist/shared/file-utils.js +94 -72
- package/dist/shared/file-utils.js.map +1 -1
- package/dist/shared/normalize.d.ts +12 -0
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +4 -0
- package/dist/shared/normalize.js.map +1 -1
- package/dist/types.d.ts +82 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/ast-analysis/engine.ts +99 -55
- package/src/ast-analysis/visitors/ast-store-visitor.ts +19 -21
- package/src/cli/commands/batch.ts +5 -26
- package/src/cli/commands/structure.ts +21 -1
- package/src/db/connection.ts +26 -7
- package/src/db/index.ts +2 -0
- package/src/db/repository/base.ts +43 -0
- package/src/db/repository/index.ts +1 -0
- package/src/db/repository/native-repository.ts +67 -1
- package/src/domain/analysis/context.ts +5 -15
- package/src/domain/analysis/dependencies.ts +19 -16
- package/src/domain/analysis/fn-impact.ts +2 -2
- package/src/domain/analysis/implementations.ts +3 -13
- package/src/domain/graph/builder/context.ts +4 -0
- package/src/domain/graph/builder/incremental.ts +21 -0
- package/src/domain/graph/builder/native-db-proxy.ts +98 -0
- package/src/domain/graph/builder/pipeline.ts +514 -416
- package/src/domain/graph/builder/stages/build-edges.ts +45 -3
- package/src/domain/graph/builder/stages/build-structure.ts +2 -2
- package/src/domain/graph/builder/stages/detect-changes.ts +11 -33
- package/src/domain/graph/builder/stages/finalize.ts +1 -1
- package/src/domain/graph/builder/stages/insert-nodes.ts +17 -14
- package/src/domain/graph/builder/stages/resolve-imports.ts +22 -23
- package/src/domain/graph/watcher.ts +118 -98
- package/src/domain/parser.ts +8 -2
- package/src/domain/queries.ts +1 -1
- package/src/extractors/go.ts +57 -32
- package/src/extractors/javascript.ts +67 -27
- package/src/features/audit.ts +3 -2
- package/src/features/boundaries.ts +3 -5
- package/src/features/branch-compare.ts +2 -3
- package/src/features/complexity.ts +94 -58
- package/src/features/dataflow.ts +153 -132
- package/src/features/flow.ts +2 -1
- package/src/features/manifesto.ts +15 -1
- package/src/features/structure.ts +167 -95
- package/src/graph/algorithms/louvain.ts +5 -2
- package/src/graph/classifiers/roles.ts +14 -5
- package/src/infrastructure/config.ts +1 -0
- package/src/presentation/batch.ts +1 -0
- package/src/presentation/communities.ts +44 -39
- package/src/presentation/manifesto.ts +35 -38
- package/src/presentation/queries-cli/inspect.ts +48 -46
- package/src/presentation/structure.ts +2 -2
- package/src/shared/file-utils.ts +116 -77
- package/src/shared/normalize.ts +10 -0
- package/src/types.ts +86 -0
|
@@ -182,6 +182,39 @@ interface InterfacesData {
|
|
|
182
182
|
results: InterfacesResult[];
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
function renderWhereSymbolResults(results: WhereSymbolResult[]): void {
|
|
186
|
+
for (const r of results) {
|
|
187
|
+
const roleTag = r.role ? ` [${r.role}]` : '';
|
|
188
|
+
const tag = r.exported ? ' (exported)' : '';
|
|
189
|
+
console.log(`\n${kindIcon(r.kind)} ${r.name}${roleTag} ${r.file}:${r.line}${tag}`);
|
|
190
|
+
if (r.uses.length > 0) {
|
|
191
|
+
const useStrs = r.uses.map((u) => `${u.file}:${u.line}`);
|
|
192
|
+
console.log(` Used in: ${useStrs.join(', ')}`);
|
|
193
|
+
} else {
|
|
194
|
+
console.log(' No uses found');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function renderWhereFileResults(results: WhereFileResult[]): void {
|
|
200
|
+
for (const r of results) {
|
|
201
|
+
console.log(`\n# ${r.file}`);
|
|
202
|
+
if (r.symbols.length > 0) {
|
|
203
|
+
const symStrs = r.symbols.map((s) => `${s.name}:${s.line}`);
|
|
204
|
+
console.log(` Symbols: ${symStrs.join(', ')}`);
|
|
205
|
+
}
|
|
206
|
+
if (r.imports.length > 0) {
|
|
207
|
+
console.log(` Imports: ${r.imports.join(', ')}`);
|
|
208
|
+
}
|
|
209
|
+
if (r.importedBy.length > 0) {
|
|
210
|
+
console.log(` Imported by: ${r.importedBy.join(', ')}`);
|
|
211
|
+
}
|
|
212
|
+
if (r.exported.length > 0) {
|
|
213
|
+
console.log(` Exported: ${r.exported.join(', ')}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
185
218
|
export function where(target: string, customDbPath: string, opts: OutputOpts = {}): void {
|
|
186
219
|
const data = whereData(target, customDbPath, opts as Record<string, unknown>) as WhereData;
|
|
187
220
|
if (outputResult(data as unknown as Record<string, unknown>, 'results', opts)) return;
|
|
@@ -196,34 +229,9 @@ export function where(target: string, customDbPath: string, opts: OutputOpts = {
|
|
|
196
229
|
}
|
|
197
230
|
|
|
198
231
|
if (data.mode === 'symbol') {
|
|
199
|
-
|
|
200
|
-
const roleTag = r.role ? ` [${r.role}]` : '';
|
|
201
|
-
const tag = r.exported ? ' (exported)' : '';
|
|
202
|
-
console.log(`\n${kindIcon(r.kind)} ${r.name}${roleTag} ${r.file}:${r.line}${tag}`);
|
|
203
|
-
if (r.uses.length > 0) {
|
|
204
|
-
const useStrs = r.uses.map((u) => `${u.file}:${u.line}`);
|
|
205
|
-
console.log(` Used in: ${useStrs.join(', ')}`);
|
|
206
|
-
} else {
|
|
207
|
-
console.log(' No uses found');
|
|
208
|
-
}
|
|
209
|
-
}
|
|
232
|
+
renderWhereSymbolResults(data.results as WhereSymbolResult[]);
|
|
210
233
|
} else {
|
|
211
|
-
|
|
212
|
-
console.log(`\n# ${r.file}`);
|
|
213
|
-
if (r.symbols.length > 0) {
|
|
214
|
-
const symStrs = r.symbols.map((s) => `${s.name}:${s.line}`);
|
|
215
|
-
console.log(` Symbols: ${symStrs.join(', ')}`);
|
|
216
|
-
}
|
|
217
|
-
if (r.imports.length > 0) {
|
|
218
|
-
console.log(` Imports: ${r.imports.join(', ')}`);
|
|
219
|
-
}
|
|
220
|
-
if (r.importedBy.length > 0) {
|
|
221
|
-
console.log(` Imported by: ${r.importedBy.join(', ')}`);
|
|
222
|
-
}
|
|
223
|
-
if (r.exported.length > 0) {
|
|
224
|
-
console.log(` Exported: ${r.exported.join(', ')}`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
234
|
+
renderWhereFileResults(data.results as WhereFileResult[]);
|
|
227
235
|
}
|
|
228
236
|
console.log();
|
|
229
237
|
}
|
|
@@ -402,6 +410,17 @@ function renderContextResult(r: ContextResult): void {
|
|
|
402
410
|
}
|
|
403
411
|
}
|
|
404
412
|
|
|
413
|
+
function renderExplainSymbolList(label: string, symbols: ExplainSymbol[]): void {
|
|
414
|
+
if (symbols.length === 0) return;
|
|
415
|
+
console.log(`\n## ${label}`);
|
|
416
|
+
for (const s of symbols) {
|
|
417
|
+
const sig = s.signature?.params != null ? `(${s.signature.params})` : '';
|
|
418
|
+
const roleTag = s.role ? ` [${s.role}]` : '';
|
|
419
|
+
const summary = s.summary ? ` -- ${s.summary}` : '';
|
|
420
|
+
console.log(` ${kindIcon(s.kind)} ${s.name}${sig}${roleTag} :${s.line}${summary}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
405
424
|
function renderFileExplain(r: FileExplainResult): void {
|
|
406
425
|
const publicCount = r.publicApi.length;
|
|
407
426
|
const internalCount = r.internal.length;
|
|
@@ -418,25 +437,8 @@ function renderFileExplain(r: FileExplainResult): void {
|
|
|
418
437
|
console.log(` Imported by: ${r.importedBy.map((i) => i.file).join(', ')}`);
|
|
419
438
|
}
|
|
420
439
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
for (const s of r.publicApi) {
|
|
424
|
-
const sig = s.signature?.params != null ? `(${s.signature.params})` : '';
|
|
425
|
-
const roleTag = s.role ? ` [${s.role}]` : '';
|
|
426
|
-
const summary = s.summary ? ` -- ${s.summary}` : '';
|
|
427
|
-
console.log(` ${kindIcon(s.kind)} ${s.name}${sig}${roleTag} :${s.line}${summary}`);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (r.internal.length > 0) {
|
|
432
|
-
console.log(`\n## Internal`);
|
|
433
|
-
for (const s of r.internal) {
|
|
434
|
-
const sig = s.signature?.params != null ? `(${s.signature.params})` : '';
|
|
435
|
-
const roleTag = s.role ? ` [${s.role}]` : '';
|
|
436
|
-
const summary = s.summary ? ` -- ${s.summary}` : '';
|
|
437
|
-
console.log(` ${kindIcon(s.kind)} ${s.name}${sig}${roleTag} :${s.line}${summary}`);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
+
renderExplainSymbolList('Exported', r.publicApi);
|
|
441
|
+
renderExplainSymbolList('Internal', r.internal);
|
|
440
442
|
|
|
441
443
|
if (r.dataFlow.length > 0) {
|
|
442
444
|
console.log(`\n## Data Flow`);
|
|
@@ -75,7 +75,7 @@ export function formatHotspots(data: HotspotsResult): string {
|
|
|
75
75
|
|
|
76
76
|
interface ModuleBoundaryEntry {
|
|
77
77
|
directory: string;
|
|
78
|
-
cohesion: number;
|
|
78
|
+
cohesion: number | null;
|
|
79
79
|
fileCount: number;
|
|
80
80
|
symbolCount: number;
|
|
81
81
|
fanIn: number;
|
|
@@ -95,7 +95,7 @@ export function formatModuleBoundaries(data: ModuleBoundariesResult): string {
|
|
|
95
95
|
const lines = [`\nModule boundaries (cohesion >= ${data.threshold}, ${data.count} modules):\n`];
|
|
96
96
|
for (const m of data.modules) {
|
|
97
97
|
lines.push(
|
|
98
|
-
` ${m.directory}/ cohesion=${m.cohesion.toFixed(2)} (${m.fileCount} files, ${m.symbolCount} symbols)`,
|
|
98
|
+
` ${m.directory}/ cohesion=${m.cohesion !== null ? m.cohesion.toFixed(2) : 'n/a'} (${m.fileCount} files, ${m.symbolCount} symbols)`,
|
|
99
99
|
);
|
|
100
100
|
lines.push(` Incoming: ${m.fanIn} edges Outgoing: ${m.fanOut} edges`);
|
|
101
101
|
if (m.files.length > 0) {
|
package/src/shared/file-utils.ts
CHANGED
|
@@ -45,56 +45,97 @@ interface ExtractSummaryOpts {
|
|
|
45
45
|
summaryMaxChars?: number;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
/** Truncate text to maxChars, appending "..." if truncated. */
|
|
49
|
+
function truncate(text: string, maxChars: number): string {
|
|
50
|
+
return text.length > maxChars ? `${text.slice(0, maxChars)}...` : text;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Try to extract a single-line comment (// or #) above the definition. */
|
|
54
|
+
function extractSingleLineComment(
|
|
55
|
+
fileLines: string[],
|
|
56
|
+
idx: number,
|
|
57
|
+
scanLines: number,
|
|
58
|
+
maxChars: number,
|
|
52
59
|
): string | null {
|
|
53
|
-
|
|
54
|
-
const idx = line - 2; // line above the definition (0-indexed)
|
|
55
|
-
const jsdocEndScanLines = opts.jsdocEndScanLines ?? 10;
|
|
56
|
-
const jsdocOpenScanLines = opts.jsdocOpenScanLines ?? 20;
|
|
57
|
-
const summaryMaxChars = opts.summaryMaxChars ?? 100;
|
|
58
|
-
// Scan up for JSDoc or comment
|
|
59
|
-
let jsdocEnd = -1;
|
|
60
|
-
for (let i = idx; i >= Math.max(0, idx - jsdocEndScanLines); i--) {
|
|
60
|
+
for (let i = idx; i >= Math.max(0, idx - scanLines); i--) {
|
|
61
61
|
const trimmed = fileLines[i]!.trim();
|
|
62
|
-
if (trimmed.endsWith('*/'))
|
|
63
|
-
jsdocEnd = i;
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
62
|
+
if (trimmed.endsWith('*/')) return null; // hit a block comment — defer to JSDoc extractor
|
|
66
63
|
if (trimmed.startsWith('//') || trimmed.startsWith('#')) {
|
|
67
|
-
// Single-line comment immediately above
|
|
68
64
|
const text = trimmed
|
|
69
65
|
.replace(/^\/\/\s*/, '')
|
|
70
66
|
.replace(/^#\s*/, '')
|
|
71
67
|
.trim();
|
|
72
|
-
return text
|
|
68
|
+
return truncate(text, maxChars);
|
|
73
69
|
}
|
|
74
|
-
if (trimmed !== '' && !trimmed.startsWith('*') && !trimmed.startsWith('/*'))
|
|
70
|
+
if (trimmed !== '' && !trimmed.startsWith('*') && !trimmed.startsWith('/*')) return null;
|
|
75
71
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Find the line index where a block comment (*/) ends, scanning upward from idx. */
|
|
76
|
+
function findJsdocEndLine(fileLines: string[], idx: number, scanLines: number): number {
|
|
77
|
+
for (let i = idx; i >= Math.max(0, idx - scanLines); i--) {
|
|
78
|
+
const trimmed = fileLines[i]!.trim();
|
|
79
|
+
if (trimmed.endsWith('*/')) return i;
|
|
80
|
+
if (
|
|
81
|
+
trimmed !== '' &&
|
|
82
|
+
!trimmed.startsWith('*') &&
|
|
83
|
+
!trimmed.startsWith('/*') &&
|
|
84
|
+
!trimmed.startsWith('//') &&
|
|
85
|
+
!trimmed.startsWith('#')
|
|
86
|
+
) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return -1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Extract the first description line from a JSDoc block ending at jsdocEnd. */
|
|
94
|
+
function extractJsdocDescription(
|
|
95
|
+
fileLines: string[],
|
|
96
|
+
jsdocEnd: number,
|
|
97
|
+
openScanLines: number,
|
|
98
|
+
maxChars: number,
|
|
99
|
+
): string | null {
|
|
100
|
+
for (let i = jsdocEnd; i >= Math.max(0, jsdocEnd - openScanLines); i--) {
|
|
101
|
+
if (!fileLines[i]!.trim().startsWith('/**')) continue;
|
|
102
|
+
for (let j = i + 1; j <= jsdocEnd; j++) {
|
|
103
|
+
const docLine = fileLines[j]!.trim()
|
|
104
|
+
.replace(/^\*\s?/, '')
|
|
105
|
+
.trim();
|
|
106
|
+
if (docLine && !docLine.startsWith('@') && docLine !== '/' && docLine !== '*/') {
|
|
107
|
+
return truncate(docLine, maxChars);
|
|
92
108
|
}
|
|
93
109
|
}
|
|
110
|
+
break;
|
|
94
111
|
}
|
|
95
112
|
return null;
|
|
96
113
|
}
|
|
97
114
|
|
|
115
|
+
export function extractSummary(
|
|
116
|
+
fileLines: string[] | null,
|
|
117
|
+
line: number | undefined,
|
|
118
|
+
opts: ExtractSummaryOpts = {},
|
|
119
|
+
): string | null {
|
|
120
|
+
if (!fileLines || !line || line <= 1) return null;
|
|
121
|
+
const idx = line - 2; // line above the definition (0-indexed)
|
|
122
|
+
const jsdocEndScanLines = opts.jsdocEndScanLines ?? 10;
|
|
123
|
+
const jsdocOpenScanLines = opts.jsdocOpenScanLines ?? 20;
|
|
124
|
+
const summaryMaxChars = opts.summaryMaxChars ?? 100;
|
|
125
|
+
|
|
126
|
+
// Try single-line comment first
|
|
127
|
+
const singleLine = extractSingleLineComment(fileLines, idx, jsdocEndScanLines, summaryMaxChars);
|
|
128
|
+
if (singleLine) return singleLine;
|
|
129
|
+
|
|
130
|
+
// Try JSDoc block comment
|
|
131
|
+
const jsdocEnd = findJsdocEndLine(fileLines, idx, jsdocEndScanLines);
|
|
132
|
+
if (jsdocEnd >= 0) {
|
|
133
|
+
return extractJsdocDescription(fileLines, jsdocEnd, jsdocOpenScanLines, summaryMaxChars);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
98
139
|
interface ExtractSignatureOpts {
|
|
99
140
|
signatureGatherLines?: number;
|
|
100
141
|
}
|
|
@@ -104,6 +145,38 @@ export interface Signature {
|
|
|
104
145
|
returnType: string | null;
|
|
105
146
|
}
|
|
106
147
|
|
|
148
|
+
/** Per-language signature patterns. Each entry has a regex and an extractor for return type. */
|
|
149
|
+
const SIGNATURE_PATTERNS: Array<{
|
|
150
|
+
regex: RegExp;
|
|
151
|
+
returnType: (m: RegExpMatchArray) => string | null;
|
|
152
|
+
}> = [
|
|
153
|
+
// JS/TS: function name(params) or async function
|
|
154
|
+
{
|
|
155
|
+
regex: /(?:export\s+)?(?:async\s+)?function\s*\*?\s*\w*\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/,
|
|
156
|
+
returnType: (m) => (m[2] ? m[2].trim().replace(/\s*\{$/, '') : null),
|
|
157
|
+
},
|
|
158
|
+
// Arrow: const name = (params) => or (params):ReturnType =>
|
|
159
|
+
{
|
|
160
|
+
regex: /=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*([^=>\n{]+))?\s*=>/,
|
|
161
|
+
returnType: (m) => (m[2] ? m[2].trim() : null),
|
|
162
|
+
},
|
|
163
|
+
// Python: def name(params) -> return:
|
|
164
|
+
{
|
|
165
|
+
regex: /def\s+\w+\s*\(([^)]*)\)\s*(?:->\s*([^:\n]+))?/,
|
|
166
|
+
returnType: (m) => (m[2] ? m[2].trim() : null),
|
|
167
|
+
},
|
|
168
|
+
// Go: func (recv) name(params) (returns)
|
|
169
|
+
{
|
|
170
|
+
regex: /func\s+(?:\([^)]*\)\s+)?\w+\s*\(([^)]*)\)\s*(?:\(([^)]+)\)|(\w[^\n{]*))?/,
|
|
171
|
+
returnType: (m) => (m[2] || m[3] || '').trim() || null,
|
|
172
|
+
},
|
|
173
|
+
// Rust: fn name(params) -> ReturnType
|
|
174
|
+
{
|
|
175
|
+
regex: /fn\s+\w+\s*\(([^)]*)\)\s*(?:->\s*([^\n{]+))?/,
|
|
176
|
+
returnType: (m) => (m[2] ? m[2].trim() : null),
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
|
|
107
180
|
export function extractSignature(
|
|
108
181
|
fileLines: string[] | null,
|
|
109
182
|
line: number | undefined,
|
|
@@ -112,52 +185,18 @@ export function extractSignature(
|
|
|
112
185
|
if (!fileLines || !line) return null;
|
|
113
186
|
const idx = line - 1;
|
|
114
187
|
const signatureGatherLines = opts.signatureGatherLines ?? 5;
|
|
115
|
-
// Gather lines to handle multi-line params
|
|
116
188
|
const chunk = fileLines
|
|
117
189
|
.slice(idx, Math.min(fileLines.length, idx + signatureGatherLines))
|
|
118
190
|
.join('\n');
|
|
119
191
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
// Arrow: const name = (params) => or (params):ReturnType =>
|
|
131
|
-
m = chunk.match(/=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*([^=>\n{]+))?\s*=>/);
|
|
132
|
-
if (m) {
|
|
133
|
-
return {
|
|
134
|
-
params: m[1]!.trim() || null,
|
|
135
|
-
returnType: m[2] ? m[2].trim() : null,
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
// Python: def name(params) -> return:
|
|
139
|
-
m = chunk.match(/def\s+\w+\s*\(([^)]*)\)\s*(?:->\s*([^:\n]+))?/);
|
|
140
|
-
if (m) {
|
|
141
|
-
return {
|
|
142
|
-
params: m[1]!.trim() || null,
|
|
143
|
-
returnType: m[2] ? m[2].trim() : null,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
// Go: func (recv) name(params) (returns)
|
|
147
|
-
m = chunk.match(/func\s+(?:\([^)]*\)\s+)?\w+\s*\(([^)]*)\)\s*(?:\(([^)]+)\)|(\w[^\n{]*))?/);
|
|
148
|
-
if (m) {
|
|
149
|
-
return {
|
|
150
|
-
params: m[1]!.trim() || null,
|
|
151
|
-
returnType: (m[2] || m[3] || '').trim() || null,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
// Rust: fn name(params) -> ReturnType
|
|
155
|
-
m = chunk.match(/fn\s+\w+\s*\(([^)]*)\)\s*(?:->\s*([^\n{]+))?/);
|
|
156
|
-
if (m) {
|
|
157
|
-
return {
|
|
158
|
-
params: m[1]!.trim() || null,
|
|
159
|
-
returnType: m[2] ? m[2].trim() : null,
|
|
160
|
-
};
|
|
192
|
+
for (const pattern of SIGNATURE_PATTERNS) {
|
|
193
|
+
const m = chunk.match(pattern.regex);
|
|
194
|
+
if (m) {
|
|
195
|
+
return {
|
|
196
|
+
params: m[1]!.trim() || null,
|
|
197
|
+
returnType: pattern.returnType(m),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
161
200
|
}
|
|
162
201
|
return null;
|
|
163
202
|
}
|
package/src/shared/normalize.ts
CHANGED
|
@@ -19,6 +19,16 @@ export function getFileHash(db: DbHandle, file: string): string | null {
|
|
|
19
19
|
return row ? row.hash : null;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/** Pick the 4-field symbol reference from any row that carries at least {name, kind, file, line}. */
|
|
23
|
+
export function toSymbolRef(row: { name: string; kind: string; file: string; line: number }): {
|
|
24
|
+
name: string;
|
|
25
|
+
kind: string;
|
|
26
|
+
file: string;
|
|
27
|
+
line: number;
|
|
28
|
+
} {
|
|
29
|
+
return { name: row.name, kind: row.kind, file: row.file, line: row.line };
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
export function kindIcon(kind: string): string {
|
|
23
33
|
switch (kind) {
|
|
24
34
|
case 'function':
|
package/src/types.ts
CHANGED
|
@@ -334,6 +334,35 @@ export interface Repository {
|
|
|
334
334
|
getFileHash(file: string): string | null;
|
|
335
335
|
hasImplementsEdges(): boolean;
|
|
336
336
|
hasCoChangesTable(): boolean;
|
|
337
|
+
|
|
338
|
+
// ── Composite queries ──────────────────────────────────────────────
|
|
339
|
+
fnDeps(
|
|
340
|
+
name: string,
|
|
341
|
+
opts?: { depth?: number; noTests?: boolean; file?: string; kind?: string },
|
|
342
|
+
): {
|
|
343
|
+
name: string;
|
|
344
|
+
results: Array<{
|
|
345
|
+
name: string;
|
|
346
|
+
kind: string;
|
|
347
|
+
file: string;
|
|
348
|
+
line: number | null;
|
|
349
|
+
endLine: number | null;
|
|
350
|
+
role: string | null;
|
|
351
|
+
fileHash: string | null;
|
|
352
|
+
callees: Array<{ name: string; kind: string; file: string; line: number | null }>;
|
|
353
|
+
callers: Array<{
|
|
354
|
+
name: string;
|
|
355
|
+
kind: string;
|
|
356
|
+
file: string;
|
|
357
|
+
line: number | null;
|
|
358
|
+
viaHierarchy?: string;
|
|
359
|
+
}>;
|
|
360
|
+
transitiveCallers: Record<
|
|
361
|
+
number,
|
|
362
|
+
Array<{ name: string; kind: string; file: string; line: number | null }>
|
|
363
|
+
>;
|
|
364
|
+
}>;
|
|
365
|
+
} | null;
|
|
337
366
|
}
|
|
338
367
|
|
|
339
368
|
/**
|
|
@@ -666,6 +695,12 @@ export interface AnalysisTiming {
|
|
|
666
695
|
complexityMs: number;
|
|
667
696
|
cfgMs: number;
|
|
668
697
|
dataflowMs: number;
|
|
698
|
+
/**
|
|
699
|
+
* Diagnostic: total wall-clock time for the unified walk loop (includes
|
|
700
|
+
* setupVisitors overhead). Walk time is already distributed equally into
|
|
701
|
+
* the per-phase timers above, so this overlaps — it is not an additive
|
|
702
|
+
* bucket. Useful for cross-checking that Σ phase timers ≈ this value.
|
|
703
|
+
*/
|
|
669
704
|
_unifiedWalkMs?: number;
|
|
670
705
|
}
|
|
671
706
|
|
|
@@ -1050,6 +1085,7 @@ export interface CodegraphConfig {
|
|
|
1050
1085
|
incremental: boolean;
|
|
1051
1086
|
dbPath: string;
|
|
1052
1087
|
driftThreshold: number;
|
|
1088
|
+
smallFilesThreshold: number;
|
|
1053
1089
|
};
|
|
1054
1090
|
|
|
1055
1091
|
query: {
|
|
@@ -1878,6 +1914,7 @@ export interface NativeAddon {
|
|
|
1878
1914
|
fileNodeIds: unknown[],
|
|
1879
1915
|
barrelFiles: string[],
|
|
1880
1916
|
rootDir: string,
|
|
1917
|
+
symbolNodes?: Array<{ name: string; file: string; nodeId: number }>,
|
|
1881
1918
|
): unknown[];
|
|
1882
1919
|
engineVersion(): string;
|
|
1883
1920
|
analyzeComplexity(
|
|
@@ -2049,6 +2086,46 @@ export interface NativeComplexityMetrics {
|
|
|
2049
2086
|
halsteadVolume: number | null;
|
|
2050
2087
|
}
|
|
2051
2088
|
|
|
2089
|
+
// ── Native composite query types (fnDeps) ──────────────────────────────
|
|
2090
|
+
|
|
2091
|
+
export interface NativeFnDepsNode {
|
|
2092
|
+
name: string;
|
|
2093
|
+
kind: string;
|
|
2094
|
+
file: string;
|
|
2095
|
+
line: number | null;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
export interface NativeFnDepsCallerNode {
|
|
2099
|
+
name: string;
|
|
2100
|
+
kind: string;
|
|
2101
|
+
file: string;
|
|
2102
|
+
line: number | null;
|
|
2103
|
+
viaHierarchy: string | null;
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
export interface NativeFnDepsTransitiveGroup {
|
|
2107
|
+
depth: number;
|
|
2108
|
+
callers: NativeFnDepsNode[];
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
export interface NativeFnDepsEntry {
|
|
2112
|
+
name: string;
|
|
2113
|
+
kind: string;
|
|
2114
|
+
file: string;
|
|
2115
|
+
line: number | null;
|
|
2116
|
+
endLine: number | null;
|
|
2117
|
+
role: string | null;
|
|
2118
|
+
fileHash: string | null;
|
|
2119
|
+
callees: NativeFnDepsNode[];
|
|
2120
|
+
callers: NativeFnDepsCallerNode[];
|
|
2121
|
+
transitiveCallers: NativeFnDepsTransitiveGroup[];
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
export interface NativeFnDepsResult {
|
|
2125
|
+
name: string;
|
|
2126
|
+
results: NativeFnDepsEntry[];
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2052
2129
|
/** Native rusqlite database wrapper instance (Phase 6.13 + 6.14 + 6.15). */
|
|
2053
2130
|
export interface NativeDatabase {
|
|
2054
2131
|
// ── Lifecycle (6.13) ────────────────────────────────────────────────
|
|
@@ -2133,6 +2210,15 @@ export interface NativeDatabase {
|
|
|
2133
2210
|
getComplexityForNode(nodeId: number): NativeComplexityMetrics | null;
|
|
2134
2211
|
getFileHash(file: string): string | null;
|
|
2135
2212
|
|
|
2213
|
+
// ── Composite queries ──────────────────────────────────────────────
|
|
2214
|
+
fnDeps(
|
|
2215
|
+
name: string,
|
|
2216
|
+
depth: number | null | undefined,
|
|
2217
|
+
noTests: boolean | null | undefined,
|
|
2218
|
+
file: string | null | undefined,
|
|
2219
|
+
kind: string | null | undefined,
|
|
2220
|
+
): NativeFnDepsResult;
|
|
2221
|
+
|
|
2136
2222
|
// ── Build pipeline writes (6.15) ───────────────────────────────────
|
|
2137
2223
|
bulkInsertNodes(
|
|
2138
2224
|
batches: Array<{
|