@optave/codegraph 3.1.3 → 3.1.5
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 +38 -84
- package/package.json +13 -8
- package/src/ast-analysis/engine.js +32 -12
- package/src/ast-analysis/shared.js +6 -5
- package/src/cli/commands/ast.js +22 -0
- package/src/cli/commands/audit.js +45 -0
- package/src/cli/commands/batch.js +68 -0
- package/src/cli/commands/branch-compare.js +21 -0
- package/src/cli/commands/build.js +26 -0
- package/src/cli/commands/cfg.js +26 -0
- package/src/cli/commands/check.js +74 -0
- package/src/cli/commands/children.js +28 -0
- package/src/cli/commands/co-change.js +67 -0
- package/src/cli/commands/communities.js +19 -0
- package/src/cli/commands/complexity.js +46 -0
- package/src/cli/commands/context.js +30 -0
- package/src/cli/commands/cycles.js +32 -0
- package/src/cli/commands/dataflow.js +28 -0
- package/src/cli/commands/deps.js +12 -0
- package/src/cli/commands/diff-impact.js +26 -0
- package/src/cli/commands/embed.js +30 -0
- package/src/cli/commands/export.js +78 -0
- package/src/cli/commands/exports.js +14 -0
- package/src/cli/commands/flow.js +32 -0
- package/src/cli/commands/fn-impact.js +26 -0
- package/src/cli/commands/impact.js +12 -0
- package/src/cli/commands/info.js +76 -0
- package/src/cli/commands/map.js +19 -0
- package/src/cli/commands/mcp.js +18 -0
- package/src/cli/commands/models.js +19 -0
- package/src/cli/commands/owners.js +25 -0
- package/src/cli/commands/path.js +36 -0
- package/src/cli/commands/plot.js +89 -0
- package/src/cli/commands/query.js +45 -0
- package/src/cli/commands/registry.js +100 -0
- package/src/cli/commands/roles.js +30 -0
- package/src/cli/commands/search.js +42 -0
- package/src/cli/commands/sequence.js +28 -0
- package/src/cli/commands/snapshot.js +66 -0
- package/src/cli/commands/stats.js +15 -0
- package/src/cli/commands/structure.js +33 -0
- package/src/cli/commands/triage.js +78 -0
- package/src/cli/commands/watch.js +12 -0
- package/src/cli/commands/where.js +20 -0
- package/src/cli/index.js +124 -0
- package/src/cli/shared/open-graph.js +13 -0
- package/src/cli/shared/options.js +59 -0
- package/src/cli/shared/output.js +1 -0
- package/src/cli.js +11 -1522
- package/src/db/connection.js +130 -7
- package/src/{db.js → db/index.js} +17 -5
- package/src/db/migrations.js +42 -1
- package/src/db/query-builder.js +20 -12
- package/src/db/repository/base.js +201 -0
- package/src/db/repository/graph-read.js +7 -4
- package/src/db/repository/in-memory-repository.js +575 -0
- package/src/db/repository/index.js +5 -1
- package/src/db/repository/nodes.js +60 -6
- package/src/db/repository/sqlite-repository.js +219 -0
- package/src/domain/analysis/context.js +408 -0
- package/src/domain/analysis/dependencies.js +341 -0
- package/src/domain/analysis/exports.js +134 -0
- package/src/domain/analysis/impact.js +466 -0
- package/src/domain/analysis/module-map.js +322 -0
- package/src/domain/analysis/roles.js +45 -0
- package/src/domain/analysis/symbol-lookup.js +238 -0
- package/src/domain/graph/builder/context.js +85 -0
- package/src/domain/graph/builder/helpers.js +218 -0
- package/src/domain/graph/builder/incremental.js +178 -0
- package/src/domain/graph/builder/pipeline.js +130 -0
- package/src/domain/graph/builder/stages/build-edges.js +297 -0
- package/src/domain/graph/builder/stages/build-structure.js +113 -0
- package/src/domain/graph/builder/stages/collect-files.js +44 -0
- package/src/domain/graph/builder/stages/detect-changes.js +413 -0
- package/src/domain/graph/builder/stages/finalize.js +139 -0
- package/src/domain/graph/builder/stages/insert-nodes.js +195 -0
- package/src/domain/graph/builder/stages/parse-files.js +28 -0
- package/src/domain/graph/builder/stages/resolve-imports.js +143 -0
- package/src/domain/graph/builder/stages/run-analyses.js +44 -0
- package/src/domain/graph/builder.js +11 -0
- package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
- package/src/domain/graph/cycles.js +82 -0
- package/src/{journal.js → domain/graph/journal.js} +1 -1
- package/src/{resolve.js → domain/graph/resolve.js} +3 -3
- package/src/{watcher.js → domain/graph/watcher.js} +10 -150
- package/src/{parser.js → domain/parser.js} +5 -5
- package/src/domain/queries.js +48 -0
- package/src/domain/search/generator.js +163 -0
- package/src/domain/search/index.js +13 -0
- package/src/domain/search/models.js +218 -0
- package/src/domain/search/search/cli-formatter.js +151 -0
- package/src/domain/search/search/filters.js +46 -0
- package/src/domain/search/search/hybrid.js +121 -0
- package/src/domain/search/search/keyword.js +68 -0
- package/src/domain/search/search/prepare.js +66 -0
- package/src/domain/search/search/semantic.js +145 -0
- package/src/domain/search/stores/fts5.js +27 -0
- package/src/domain/search/stores/sqlite-blob.js +24 -0
- package/src/domain/search/strategies/source.js +14 -0
- package/src/domain/search/strategies/structured.js +43 -0
- package/src/domain/search/strategies/text-utils.js +43 -0
- package/src/extractors/csharp.js +10 -2
- package/src/extractors/go.js +3 -1
- package/src/extractors/helpers.js +71 -0
- package/src/extractors/java.js +9 -2
- package/src/extractors/javascript.js +39 -2
- package/src/extractors/php.js +3 -1
- package/src/extractors/python.js +14 -3
- package/src/extractors/rust.js +3 -1
- package/src/{ast.js → features/ast.js} +8 -8
- package/src/{audit.js → features/audit.js} +16 -44
- package/src/{batch.js → features/batch.js} +6 -5
- package/src/{boundaries.js → features/boundaries.js} +2 -2
- package/src/{branch-compare.js → features/branch-compare.js} +3 -3
- package/src/{cfg.js → features/cfg.js} +11 -12
- package/src/{check.js → features/check.js} +13 -30
- package/src/{cochange.js → features/cochange.js} +5 -5
- package/src/{communities.js → features/communities.js} +18 -90
- package/src/{complexity.js → features/complexity.js} +13 -13
- package/src/{dataflow.js → features/dataflow.js} +12 -13
- package/src/features/export.js +378 -0
- package/src/{flow.js → features/flow.js} +4 -4
- package/src/features/graph-enrichment.js +327 -0
- package/src/{manifesto.js → features/manifesto.js} +6 -6
- package/src/{owners.js → features/owners.js} +2 -2
- package/src/{sequence.js → features/sequence.js} +16 -52
- package/src/{snapshot.js → features/snapshot.js} +8 -7
- package/src/{structure.js → features/structure.js} +20 -45
- package/src/{triage.js → features/triage.js} +27 -79
- package/src/graph/algorithms/bfs.js +49 -0
- package/src/graph/algorithms/centrality.js +16 -0
- package/src/graph/algorithms/index.js +5 -0
- package/src/graph/algorithms/louvain.js +26 -0
- package/src/graph/algorithms/shortest-path.js +41 -0
- package/src/graph/algorithms/tarjan.js +49 -0
- package/src/graph/builders/dependency.js +110 -0
- package/src/graph/builders/index.js +3 -0
- package/src/graph/builders/structure.js +40 -0
- package/src/graph/builders/temporal.js +33 -0
- package/src/graph/classifiers/index.js +2 -0
- package/src/graph/classifiers/risk.js +85 -0
- package/src/graph/classifiers/roles.js +64 -0
- package/src/graph/index.js +13 -0
- package/src/graph/model.js +230 -0
- package/src/index.cjs +16 -0
- package/src/index.js +42 -219
- package/src/{native.js → infrastructure/native.js} +3 -1
- package/src/infrastructure/result-formatter.js +2 -21
- package/src/mcp/index.js +2 -0
- package/src/mcp/middleware.js +26 -0
- package/src/mcp/server.js +128 -0
- package/src/{mcp.js → mcp/tool-registry.js} +6 -675
- package/src/mcp/tools/ast-query.js +14 -0
- package/src/mcp/tools/audit.js +21 -0
- package/src/mcp/tools/batch-query.js +11 -0
- package/src/mcp/tools/branch-compare.js +12 -0
- package/src/mcp/tools/cfg.js +21 -0
- package/src/mcp/tools/check.js +43 -0
- package/src/mcp/tools/co-changes.js +20 -0
- package/src/mcp/tools/code-owners.js +12 -0
- package/src/mcp/tools/communities.js +15 -0
- package/src/mcp/tools/complexity.js +18 -0
- package/src/mcp/tools/context.js +17 -0
- package/src/mcp/tools/dataflow.js +26 -0
- package/src/mcp/tools/diff-impact.js +24 -0
- package/src/mcp/tools/execution-flow.js +26 -0
- package/src/mcp/tools/export-graph.js +57 -0
- package/src/mcp/tools/file-deps.js +12 -0
- package/src/mcp/tools/file-exports.js +13 -0
- package/src/mcp/tools/find-cycles.js +15 -0
- package/src/mcp/tools/fn-impact.js +15 -0
- package/src/mcp/tools/impact-analysis.js +12 -0
- package/src/mcp/tools/index.js +71 -0
- package/src/mcp/tools/list-functions.js +14 -0
- package/src/mcp/tools/list-repos.js +11 -0
- package/src/mcp/tools/module-map.js +6 -0
- package/src/mcp/tools/node-roles.js +14 -0
- package/src/mcp/tools/path.js +12 -0
- package/src/mcp/tools/query.js +30 -0
- package/src/mcp/tools/semantic-search.js +65 -0
- package/src/mcp/tools/sequence.js +17 -0
- package/src/mcp/tools/structure.js +15 -0
- package/src/mcp/tools/symbol-children.js +14 -0
- package/src/mcp/tools/triage.js +35 -0
- package/src/mcp/tools/where.js +13 -0
- package/src/{commands → presentation}/audit.js +2 -2
- package/src/{commands → presentation}/batch.js +1 -1
- package/src/{commands → presentation}/branch-compare.js +2 -2
- package/src/{commands → presentation}/cfg.js +1 -1
- package/src/{commands → presentation}/check.js +6 -6
- package/src/presentation/colors.js +44 -0
- package/src/{commands → presentation}/communities.js +1 -1
- package/src/{commands → presentation}/complexity.js +1 -1
- package/src/{commands → presentation}/dataflow.js +1 -1
- package/src/presentation/export.js +444 -0
- package/src/{commands → presentation}/flow.js +2 -2
- package/src/{commands → presentation}/manifesto.js +4 -4
- package/src/{commands → presentation}/owners.js +1 -1
- package/src/presentation/queries-cli/exports.js +46 -0
- package/src/presentation/queries-cli/impact.js +198 -0
- package/src/presentation/queries-cli/index.js +5 -0
- package/src/presentation/queries-cli/inspect.js +334 -0
- package/src/presentation/queries-cli/overview.js +197 -0
- package/src/presentation/queries-cli/path.js +58 -0
- package/src/presentation/queries-cli.js +27 -0
- package/src/{commands → presentation}/query.js +1 -1
- package/src/presentation/result-formatter.js +144 -0
- package/src/presentation/sequence-renderer.js +43 -0
- package/src/{commands → presentation}/sequence.js +2 -2
- package/src/{commands → presentation}/structure.js +2 -2
- package/src/presentation/table.js +47 -0
- package/src/{commands → presentation}/triage.js +1 -1
- package/src/{viewer.js → presentation/viewer.js} +68 -382
- package/src/{constants.js → shared/constants.js} +1 -1
- package/src/shared/errors.js +78 -0
- package/src/shared/file-utils.js +153 -0
- package/src/shared/generators.js +125 -0
- package/src/shared/hierarchy.js +27 -0
- package/src/shared/normalize.js +59 -0
- package/src/builder.js +0 -1486
- package/src/cycles.js +0 -137
- package/src/embedder.js +0 -1097
- package/src/export.js +0 -681
- package/src/queries-cli.js +0 -866
- package/src/queries.js +0 -2289
- /package/src/{config.js → infrastructure/config.js} +0 -0
- /package/src/{logger.js → infrastructure/logger.js} +0 -0
- /package/src/{registry.js → infrastructure/registry.js} +0 -0
- /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
- /package/src/{commands → presentation}/cochange.js +0 -0
- /package/src/{kinds.js → shared/kinds.js} +0 -0
- /package/src/{paginate.js → shared/paginate.js} +0 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { LANGUAGE_REGISTRY } from '../domain/parser.js';
|
|
4
|
+
import { debug } from '../infrastructure/logger.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a file path relative to repoRoot, rejecting traversal outside the repo.
|
|
8
|
+
* Returns null if the resolved path escapes repoRoot.
|
|
9
|
+
*/
|
|
10
|
+
export function safePath(repoRoot, file) {
|
|
11
|
+
const resolved = path.resolve(repoRoot, file);
|
|
12
|
+
if (!resolved.startsWith(repoRoot + path.sep) && resolved !== repoRoot) return null;
|
|
13
|
+
return resolved;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function readSourceRange(repoRoot, file, startLine, endLine) {
|
|
17
|
+
try {
|
|
18
|
+
const absPath = safePath(repoRoot, file);
|
|
19
|
+
if (!absPath) return null;
|
|
20
|
+
const content = fs.readFileSync(absPath, 'utf-8');
|
|
21
|
+
const lines = content.split('\n');
|
|
22
|
+
const start = Math.max(0, (startLine || 1) - 1);
|
|
23
|
+
const end = Math.min(lines.length, endLine || startLine + 50);
|
|
24
|
+
return lines.slice(start, end).join('\n');
|
|
25
|
+
} catch (e) {
|
|
26
|
+
debug(`readSourceRange failed for ${file}: ${e.message}`);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function extractSummary(fileLines, line) {
|
|
32
|
+
if (!fileLines || !line || line <= 1) return null;
|
|
33
|
+
const idx = line - 2; // line above the definition (0-indexed)
|
|
34
|
+
// Scan up to 10 lines above for JSDoc or comment
|
|
35
|
+
let jsdocEnd = -1;
|
|
36
|
+
for (let i = idx; i >= Math.max(0, idx - 10); i--) {
|
|
37
|
+
const trimmed = fileLines[i].trim();
|
|
38
|
+
if (trimmed.endsWith('*/')) {
|
|
39
|
+
jsdocEnd = i;
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#')) {
|
|
43
|
+
// Single-line comment immediately above
|
|
44
|
+
const text = trimmed
|
|
45
|
+
.replace(/^\/\/\s*/, '')
|
|
46
|
+
.replace(/^#\s*/, '')
|
|
47
|
+
.trim();
|
|
48
|
+
return text.length > 100 ? `${text.slice(0, 100)}...` : text;
|
|
49
|
+
}
|
|
50
|
+
if (trimmed !== '' && !trimmed.startsWith('*') && !trimmed.startsWith('/*')) break;
|
|
51
|
+
}
|
|
52
|
+
if (jsdocEnd >= 0) {
|
|
53
|
+
// Find opening /**
|
|
54
|
+
for (let i = jsdocEnd; i >= Math.max(0, jsdocEnd - 20); i--) {
|
|
55
|
+
if (fileLines[i].trim().startsWith('/**')) {
|
|
56
|
+
// Extract first non-tag, non-empty line
|
|
57
|
+
for (let j = i + 1; j <= jsdocEnd; j++) {
|
|
58
|
+
const docLine = fileLines[j]
|
|
59
|
+
.trim()
|
|
60
|
+
.replace(/^\*\s?/, '')
|
|
61
|
+
.trim();
|
|
62
|
+
if (docLine && !docLine.startsWith('@') && docLine !== '/' && docLine !== '*/') {
|
|
63
|
+
return docLine.length > 100 ? `${docLine.slice(0, 100)}...` : docLine;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function extractSignature(fileLines, line) {
|
|
74
|
+
if (!fileLines || !line) return null;
|
|
75
|
+
const idx = line - 1;
|
|
76
|
+
// Gather up to 5 lines to handle multi-line params
|
|
77
|
+
const chunk = fileLines.slice(idx, Math.min(fileLines.length, idx + 5)).join('\n');
|
|
78
|
+
|
|
79
|
+
// JS/TS: function name(params) or (params) => or async function
|
|
80
|
+
let m = chunk.match(
|
|
81
|
+
/(?:export\s+)?(?:async\s+)?function\s*\*?\s*\w*\s*\(([^)]*)\)\s*(?::\s*([^\n{]+))?/,
|
|
82
|
+
);
|
|
83
|
+
if (m) {
|
|
84
|
+
return {
|
|
85
|
+
params: m[1].trim() || null,
|
|
86
|
+
returnType: m[2] ? m[2].trim().replace(/\s*\{$/, '') : null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Arrow: const name = (params) => or (params):ReturnType =>
|
|
90
|
+
m = chunk.match(/=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*([^=>\n{]+))?\s*=>/);
|
|
91
|
+
if (m) {
|
|
92
|
+
return {
|
|
93
|
+
params: m[1].trim() || null,
|
|
94
|
+
returnType: m[2] ? m[2].trim() : null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Python: def name(params) -> return:
|
|
98
|
+
m = chunk.match(/def\s+\w+\s*\(([^)]*)\)\s*(?:->\s*([^:\n]+))?/);
|
|
99
|
+
if (m) {
|
|
100
|
+
return {
|
|
101
|
+
params: m[1].trim() || null,
|
|
102
|
+
returnType: m[2] ? m[2].trim() : null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Go: func (recv) name(params) (returns)
|
|
106
|
+
m = chunk.match(/func\s+(?:\([^)]*\)\s+)?\w+\s*\(([^)]*)\)\s*(?:\(([^)]+)\)|(\w[^\n{]*))?/);
|
|
107
|
+
if (m) {
|
|
108
|
+
return {
|
|
109
|
+
params: m[1].trim() || null,
|
|
110
|
+
returnType: (m[2] || m[3] || '').trim() || null,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// Rust: fn name(params) -> ReturnType
|
|
114
|
+
m = chunk.match(/fn\s+\w+\s*\(([^)]*)\)\s*(?:->\s*([^\n{]+))?/);
|
|
115
|
+
if (m) {
|
|
116
|
+
return {
|
|
117
|
+
params: m[1].trim() || null,
|
|
118
|
+
returnType: m[2] ? m[2].trim() : null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createFileLinesReader(repoRoot) {
|
|
125
|
+
const cache = new Map();
|
|
126
|
+
return function getFileLines(file) {
|
|
127
|
+
if (cache.has(file)) return cache.get(file);
|
|
128
|
+
try {
|
|
129
|
+
const absPath = safePath(repoRoot, file);
|
|
130
|
+
if (!absPath) {
|
|
131
|
+
cache.set(file, null);
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const lines = fs.readFileSync(absPath, 'utf-8').split('\n');
|
|
135
|
+
cache.set(file, lines);
|
|
136
|
+
return lines;
|
|
137
|
+
} catch (e) {
|
|
138
|
+
debug(`getFileLines failed for ${file}: ${e.message}`);
|
|
139
|
+
cache.set(file, null);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function isFileLikeTarget(target) {
|
|
146
|
+
if (target.includes('/') || target.includes('\\')) return true;
|
|
147
|
+
const ext = path.extname(target).toLowerCase();
|
|
148
|
+
if (!ext) return false;
|
|
149
|
+
for (const entry of LANGUAGE_REGISTRY) {
|
|
150
|
+
if (entry.extensions.includes(ext)) return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { iterateFunctionNodes, openReadonlyOrFail } from '../db/index.js';
|
|
2
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
3
|
+
import { ALL_SYMBOL_KINDS } from './kinds.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generator: stream functions one-by-one using .iterate() for memory efficiency.
|
|
7
|
+
* @param {string} [customDbPath]
|
|
8
|
+
* @param {object} [opts]
|
|
9
|
+
* @param {boolean} [opts.noTests]
|
|
10
|
+
* @param {string} [opts.file]
|
|
11
|
+
* @param {string} [opts.pattern]
|
|
12
|
+
* @yields {{ name: string, kind: string, file: string, line: number, role: string|null }}
|
|
13
|
+
*/
|
|
14
|
+
export function* iterListFunctions(customDbPath, opts = {}) {
|
|
15
|
+
const db = openReadonlyOrFail(customDbPath);
|
|
16
|
+
try {
|
|
17
|
+
const noTests = opts.noTests || false;
|
|
18
|
+
|
|
19
|
+
for (const row of iterateFunctionNodes(db, { file: opts.file, pattern: opts.pattern })) {
|
|
20
|
+
if (noTests && isTestFile(row.file)) continue;
|
|
21
|
+
yield {
|
|
22
|
+
name: row.name,
|
|
23
|
+
kind: row.kind,
|
|
24
|
+
file: row.file,
|
|
25
|
+
line: row.line,
|
|
26
|
+
endLine: row.end_line ?? null,
|
|
27
|
+
role: row.role ?? null,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
} finally {
|
|
31
|
+
db.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generator: stream role-classified symbols one-by-one.
|
|
37
|
+
* @param {string} [customDbPath]
|
|
38
|
+
* @param {object} [opts]
|
|
39
|
+
* @param {boolean} [opts.noTests]
|
|
40
|
+
* @param {string} [opts.role]
|
|
41
|
+
* @param {string} [opts.file]
|
|
42
|
+
* @yields {{ name: string, kind: string, file: string, line: number, endLine: number|null, role: string }}
|
|
43
|
+
*/
|
|
44
|
+
export function* iterRoles(customDbPath, opts = {}) {
|
|
45
|
+
const db = openReadonlyOrFail(customDbPath);
|
|
46
|
+
try {
|
|
47
|
+
const noTests = opts.noTests || false;
|
|
48
|
+
const conditions = ['role IS NOT NULL'];
|
|
49
|
+
const params = [];
|
|
50
|
+
|
|
51
|
+
if (opts.role) {
|
|
52
|
+
conditions.push('role = ?');
|
|
53
|
+
params.push(opts.role);
|
|
54
|
+
}
|
|
55
|
+
if (opts.file) {
|
|
56
|
+
conditions.push('file LIKE ?');
|
|
57
|
+
params.push(`%${opts.file}%`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const stmt = db.prepare(
|
|
61
|
+
`SELECT name, kind, file, line, end_line, role FROM nodes WHERE ${conditions.join(' AND ')} ORDER BY role, file, line`,
|
|
62
|
+
);
|
|
63
|
+
for (const row of stmt.iterate(...params)) {
|
|
64
|
+
if (noTests && isTestFile(row.file)) continue;
|
|
65
|
+
yield {
|
|
66
|
+
name: row.name,
|
|
67
|
+
kind: row.kind,
|
|
68
|
+
file: row.file,
|
|
69
|
+
line: row.line,
|
|
70
|
+
endLine: row.end_line ?? null,
|
|
71
|
+
role: row.role ?? null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
} finally {
|
|
75
|
+
db.close();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generator: stream symbol lookup results one-by-one.
|
|
81
|
+
* @param {string} target - Symbol name to search for (partial match)
|
|
82
|
+
* @param {string} [customDbPath]
|
|
83
|
+
* @param {object} [opts]
|
|
84
|
+
* @param {boolean} [opts.noTests]
|
|
85
|
+
* @yields {{ name: string, kind: string, file: string, line: number, role: string|null, exported: boolean, uses: object[] }}
|
|
86
|
+
*/
|
|
87
|
+
export function* iterWhere(target, customDbPath, opts = {}) {
|
|
88
|
+
const db = openReadonlyOrFail(customDbPath);
|
|
89
|
+
try {
|
|
90
|
+
const noTests = opts.noTests || false;
|
|
91
|
+
const placeholders = ALL_SYMBOL_KINDS.map(() => '?').join(', ');
|
|
92
|
+
const stmt = db.prepare(
|
|
93
|
+
`SELECT * FROM nodes WHERE name LIKE ? AND kind IN (${placeholders}) ORDER BY file, line`,
|
|
94
|
+
);
|
|
95
|
+
const crossFileCallersStmt = db.prepare(
|
|
96
|
+
`SELECT COUNT(*) as cnt FROM edges e JOIN nodes n ON e.source_id = n.id
|
|
97
|
+
WHERE e.target_id = ? AND e.kind = 'calls' AND n.file != ?`,
|
|
98
|
+
);
|
|
99
|
+
const usesStmt = db.prepare(
|
|
100
|
+
`SELECT n.name, n.file, n.line FROM edges e JOIN nodes n ON e.source_id = n.id
|
|
101
|
+
WHERE e.target_id = ? AND e.kind = 'calls'`,
|
|
102
|
+
);
|
|
103
|
+
for (const node of stmt.iterate(`%${target}%`, ...ALL_SYMBOL_KINDS)) {
|
|
104
|
+
if (noTests && isTestFile(node.file)) continue;
|
|
105
|
+
|
|
106
|
+
const crossFileCallers = crossFileCallersStmt.get(node.id, node.file);
|
|
107
|
+
const exported = crossFileCallers.cnt > 0;
|
|
108
|
+
|
|
109
|
+
let uses = usesStmt.all(node.id);
|
|
110
|
+
if (noTests) uses = uses.filter((u) => !isTestFile(u.file));
|
|
111
|
+
|
|
112
|
+
yield {
|
|
113
|
+
name: node.name,
|
|
114
|
+
kind: node.kind,
|
|
115
|
+
file: node.file,
|
|
116
|
+
line: node.line,
|
|
117
|
+
role: node.role || null,
|
|
118
|
+
exported,
|
|
119
|
+
uses: uses.map((u) => ({ name: u.name, file: u.file, line: u.line })),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
} finally {
|
|
123
|
+
db.close();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getClassHierarchy } from '../db/index.js';
|
|
2
|
+
|
|
3
|
+
export function resolveMethodViaHierarchy(db, methodName) {
|
|
4
|
+
const methods = db
|
|
5
|
+
.prepare(`SELECT * FROM nodes WHERE kind = 'method' AND name LIKE ?`)
|
|
6
|
+
.all(`%.${methodName}`);
|
|
7
|
+
|
|
8
|
+
const results = [...methods];
|
|
9
|
+
for (const m of methods) {
|
|
10
|
+
const className = m.name.split('.')[0];
|
|
11
|
+
const classNode = db
|
|
12
|
+
.prepare(`SELECT * FROM nodes WHERE name = ? AND kind = 'class' AND file = ?`)
|
|
13
|
+
.get(className, m.file);
|
|
14
|
+
if (!classNode) continue;
|
|
15
|
+
|
|
16
|
+
const ancestors = getClassHierarchy(db, classNode.id);
|
|
17
|
+
for (const ancestorId of ancestors) {
|
|
18
|
+
const ancestor = db.prepare('SELECT name FROM nodes WHERE id = ?').get(ancestorId);
|
|
19
|
+
if (!ancestor) continue;
|
|
20
|
+
const parentMethods = db
|
|
21
|
+
.prepare(`SELECT * FROM nodes WHERE name = ? AND kind = 'method'`)
|
|
22
|
+
.all(`${ancestor.name}.${methodName}`);
|
|
23
|
+
results.push(...parentMethods);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function getFileHash(db, file) {
|
|
2
|
+
const row = db.prepare('SELECT hash FROM file_hashes WHERE file = ?').get(file);
|
|
3
|
+
return row ? row.hash : null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function kindIcon(kind) {
|
|
7
|
+
switch (kind) {
|
|
8
|
+
case 'function':
|
|
9
|
+
return 'f';
|
|
10
|
+
case 'class':
|
|
11
|
+
return '*';
|
|
12
|
+
case 'method':
|
|
13
|
+
return 'o';
|
|
14
|
+
case 'file':
|
|
15
|
+
return '#';
|
|
16
|
+
case 'interface':
|
|
17
|
+
return 'I';
|
|
18
|
+
case 'type':
|
|
19
|
+
return 'T';
|
|
20
|
+
case 'parameter':
|
|
21
|
+
return 'p';
|
|
22
|
+
case 'property':
|
|
23
|
+
return '.';
|
|
24
|
+
case 'constant':
|
|
25
|
+
return 'C';
|
|
26
|
+
default:
|
|
27
|
+
return '-';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Normalize a raw DB/query row into the stable 7-field symbol shape.
|
|
33
|
+
* @param {object} row - Raw row (from SELECT * or explicit columns)
|
|
34
|
+
* @param {object} [db] - Open DB handle; when null, fileHash will be null
|
|
35
|
+
* @param {Map} [hashCache] - Optional per-file cache to avoid repeated getFileHash calls
|
|
36
|
+
* @returns {{ name: string, kind: string, file: string, line: number, endLine: number|null, role: string|null, fileHash: string|null }}
|
|
37
|
+
*/
|
|
38
|
+
export function normalizeSymbol(row, db, hashCache) {
|
|
39
|
+
let fileHash = null;
|
|
40
|
+
if (db) {
|
|
41
|
+
if (hashCache) {
|
|
42
|
+
if (!hashCache.has(row.file)) {
|
|
43
|
+
hashCache.set(row.file, getFileHash(db, row.file));
|
|
44
|
+
}
|
|
45
|
+
fileHash = hashCache.get(row.file);
|
|
46
|
+
} else {
|
|
47
|
+
fileHash = getFileHash(db, row.file);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
name: row.name,
|
|
52
|
+
kind: row.kind,
|
|
53
|
+
file: row.file,
|
|
54
|
+
line: row.line,
|
|
55
|
+
endLine: row.end_line ?? row.endLine ?? null,
|
|
56
|
+
role: row.role ?? null,
|
|
57
|
+
fileHash,
|
|
58
|
+
};
|
|
59
|
+
}
|