@nithin554/code-crawler 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +221 -45
- package/dist/config.d.ts +13 -0
- package/dist/config.js +32 -0
- package/dist/config.js.map +1 -0
- package/dist/crawler.d.ts +46 -0
- package/dist/crawler.js +303 -0
- package/dist/crawler.js.map +1 -0
- package/dist/graph/queries.d.ts +111 -0
- package/dist/graph/queries.js +265 -0
- package/dist/graph/queries.js.map +1 -0
- package/dist/graph/schema.d.ts +63 -0
- package/dist/graph/schema.js +54 -0
- package/dist/graph/schema.js.map +1 -0
- package/dist/graph/store.d.ts +66 -0
- package/dist/graph/store.js +251 -0
- package/dist/graph/store.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/language.d.ts +111 -0
- package/dist/parser/language.js +13 -0
- package/dist/parser/language.js.map +1 -0
- package/dist/parser/languages/index.d.ts +5 -0
- package/dist/parser/languages/index.js +31 -0
- package/dist/parser/languages/index.js.map +1 -0
- package/dist/parser/languages/java.d.ts +10 -0
- package/dist/parser/languages/java.js +334 -0
- package/dist/parser/languages/java.js.map +1 -0
- package/dist/parser/treeSitter.d.ts +28 -0
- package/dist/parser/treeSitter.js +114 -0
- package/dist/parser/treeSitter.js.map +1 -0
- package/dist/resolver/javaResolver.d.ts +20 -0
- package/dist/resolver/javaResolver.js +253 -0
- package/dist/resolver/javaResolver.js.map +1 -0
- package/dist/resolver/types.d.ts +45 -0
- package/dist/resolver/types.js +80 -0
- package/dist/resolver/types.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.js +282 -0
- package/dist/server.js.map +1 -0
- package/package.json +44 -7
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* SQLite-backed property graph store.
|
|
6
|
+
*
|
|
7
|
+
* Responsibilities:
|
|
8
|
+
* - Create/own the schema (WAL mode, foreign keys, indexes).
|
|
9
|
+
* - Upsert nodes and edges (idempotent).
|
|
10
|
+
* - Support file-scoped reindexing: removing one file must not disturb
|
|
11
|
+
* symbols referenced from other files.
|
|
12
|
+
* - Provide count/status introspection.
|
|
13
|
+
*
|
|
14
|
+
* The store is synchronous (better-sqlite3), which keeps the MCP tool
|
|
15
|
+
* handlers simple and predictable.
|
|
16
|
+
*/
|
|
17
|
+
const SCHEMA = `
|
|
18
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
19
|
+
id TEXT PRIMARY KEY,
|
|
20
|
+
kind TEXT NOT NULL,
|
|
21
|
+
qualified_name TEXT NOT NULL,
|
|
22
|
+
file_path TEXT,
|
|
23
|
+
properties TEXT NOT NULL DEFAULT '{}',
|
|
24
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
25
|
+
);
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_kind ON nodes (kind);
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_qualified ON nodes (qualified_name);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes (qualified_name);
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_nodes_file ON nodes (file_path);
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS edges (
|
|
32
|
+
source TEXT NOT NULL,
|
|
33
|
+
target TEXT NOT NULL,
|
|
34
|
+
kind TEXT NOT NULL,
|
|
35
|
+
properties TEXT NOT NULL DEFAULT '{}',
|
|
36
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
37
|
+
PRIMARY KEY (source, target, kind),
|
|
38
|
+
FOREIGN KEY (source) REFERENCES nodes (id) ON DELETE CASCADE,
|
|
39
|
+
FOREIGN KEY (target) REFERENCES nodes (id) ON DELETE CASCADE
|
|
40
|
+
);
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON edges (source);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON edges (target);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_edges_kind ON edges (kind);
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS crawl_meta (
|
|
46
|
+
key TEXT PRIMARY KEY,
|
|
47
|
+
value TEXT
|
|
48
|
+
);
|
|
49
|
+
`;
|
|
50
|
+
export class GraphStore {
|
|
51
|
+
db;
|
|
52
|
+
/** Default cap on transitive traversal depth (config override). */
|
|
53
|
+
maxDepth;
|
|
54
|
+
/** Default result cap for list queries (config override). */
|
|
55
|
+
defaultLimit;
|
|
56
|
+
insertNodeStmt;
|
|
57
|
+
insertEdgeStmt;
|
|
58
|
+
deleteFileNodesStmt;
|
|
59
|
+
updateMetaStmt;
|
|
60
|
+
constructor(dbPath, options = {}) {
|
|
61
|
+
this.maxDepth = options.maxDepth ?? 10;
|
|
62
|
+
this.defaultLimit = options.defaultLimit ?? 50;
|
|
63
|
+
const dir = path.dirname(dbPath);
|
|
64
|
+
if (dir && dir !== ".")
|
|
65
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
66
|
+
this.db = new Database(dbPath);
|
|
67
|
+
this.db.pragma("journal_mode = WAL");
|
|
68
|
+
this.db.pragma("foreign_keys = ON");
|
|
69
|
+
this.db.exec(SCHEMA);
|
|
70
|
+
this.insertNodeStmt = this.db.prepare(`
|
|
71
|
+
INSERT INTO nodes (id, kind, qualified_name, file_path, properties)
|
|
72
|
+
VALUES (@id, @kind, @qualifiedName, @filePath, @properties)
|
|
73
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
74
|
+
kind = excluded.kind,
|
|
75
|
+
qualified_name = excluded.qualified_name,
|
|
76
|
+
file_path = excluded.file_path,
|
|
77
|
+
properties = excluded.properties,
|
|
78
|
+
updated_at = unixepoch()
|
|
79
|
+
`);
|
|
80
|
+
this.insertEdgeStmt = this.db.prepare(`
|
|
81
|
+
INSERT INTO edges (source, target, kind, properties)
|
|
82
|
+
VALUES (@source, @target, @kind, @properties)
|
|
83
|
+
ON CONFLICT (source, target, kind) DO UPDATE SET
|
|
84
|
+
properties = excluded.properties,
|
|
85
|
+
created_at = unixepoch()
|
|
86
|
+
`);
|
|
87
|
+
this.deleteFileNodesStmt = this.db.prepare("DELETE FROM nodes WHERE file_path = ?");
|
|
88
|
+
this.updateMetaStmt = this.db.prepare(`INSERT INTO crawl_meta (key, value) VALUES (@key, @value)
|
|
89
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value`);
|
|
90
|
+
}
|
|
91
|
+
close() {
|
|
92
|
+
this.db.close();
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Persist a node. Idempotent: re-indexing an existing symbol just updates it.
|
|
96
|
+
*/
|
|
97
|
+
upsertNode(node) {
|
|
98
|
+
this.insertNodeStmt.run({
|
|
99
|
+
id: node.id,
|
|
100
|
+
kind: node.kind,
|
|
101
|
+
qualifiedName: node.qualifiedName,
|
|
102
|
+
filePath: node.filePath,
|
|
103
|
+
properties: JSON.stringify(node.properties),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Persist multiple nodes in a single transaction for batch ingestion.
|
|
108
|
+
*/
|
|
109
|
+
upsertNodes(nodes) {
|
|
110
|
+
if (nodes.length === 0)
|
|
111
|
+
return;
|
|
112
|
+
const tx = this.db.transaction((items) => {
|
|
113
|
+
for (const n of items)
|
|
114
|
+
this.upsertNode(n);
|
|
115
|
+
});
|
|
116
|
+
tx(nodes);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Persist an edge. Both endpoints must exist (FK enforced).
|
|
120
|
+
*/
|
|
121
|
+
upsertEdge(edge) {
|
|
122
|
+
this.insertEdgeStmt.run({
|
|
123
|
+
source: edge.source,
|
|
124
|
+
target: edge.target,
|
|
125
|
+
kind: edge.kind,
|
|
126
|
+
properties: JSON.stringify(edge.properties),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
upsertEdges(edges) {
|
|
130
|
+
if (edges.length === 0)
|
|
131
|
+
return;
|
|
132
|
+
const tx = this.db.transaction((items) => {
|
|
133
|
+
for (const e of items)
|
|
134
|
+
this.upsertEdge(e);
|
|
135
|
+
});
|
|
136
|
+
tx(edges);
|
|
137
|
+
}
|
|
138
|
+
getNode(id) {
|
|
139
|
+
const row = this.db
|
|
140
|
+
.prepare("SELECT * FROM nodes WHERE id = ?")
|
|
141
|
+
.get(id);
|
|
142
|
+
return row ? rowToNode(row) : null;
|
|
143
|
+
}
|
|
144
|
+
getNodeByQualifiedName(qn) {
|
|
145
|
+
const row = this.db
|
|
146
|
+
.prepare("SELECT * FROM nodes WHERE qualified_name = ? LIMIT 1")
|
|
147
|
+
.get(qn);
|
|
148
|
+
return row ? rowToNode(row) : null;
|
|
149
|
+
}
|
|
150
|
+
/** All nodes originating from one source file (for reindex + stats). */
|
|
151
|
+
getFileNodes(filePath) {
|
|
152
|
+
const rows = this.db
|
|
153
|
+
.prepare("SELECT * FROM nodes WHERE file_path = ?")
|
|
154
|
+
.all(filePath);
|
|
155
|
+
return rows.map(rowToNode);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Remove every node declared in a file, plus their incident edges
|
|
159
|
+
* (cascade). Nodes referenced by the file but declared elsewhere survive.
|
|
160
|
+
*/
|
|
161
|
+
removeFile(filePath) {
|
|
162
|
+
this.deleteFileNodesStmt.run(filePath);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Rebuild the graph for a single file: drop its symbols and edges, then
|
|
166
|
+
* re-ingest. Call inside a caller-supplied transaction when batching.
|
|
167
|
+
*/
|
|
168
|
+
replaceFile(filePath, nodes, edges) {
|
|
169
|
+
const tx = this.db.transaction((fp, ns, es) => {
|
|
170
|
+
// Defer FK checks to commit (cross-file edges may reference nodes from
|
|
171
|
+
// other files that are inserted later in the same batch).
|
|
172
|
+
this.db.pragma("defer_foreign_keys = ON");
|
|
173
|
+
const oldIds = this.db.prepare("SELECT id FROM nodes WHERE file_path = ?").all(fp).map((r) => r.id);
|
|
174
|
+
const newIds = new Set(ns.map((n) => n.id));
|
|
175
|
+
// Drop edges owned by this file (their replacements arrive below).
|
|
176
|
+
const deleteFileEdges = this.db.prepare("DELETE FROM edges WHERE source IN (SELECT id FROM nodes WHERE file_path = ?)");
|
|
177
|
+
deleteFileEdges.run(fp);
|
|
178
|
+
// Sweep nodes that no longer exist in the file. Cascading deletes
|
|
179
|
+
// remove their incident edges (including incoming cross-file CALLS,
|
|
180
|
+
// which correctly become unresolvable again).
|
|
181
|
+
const sweep = this.db.prepare("DELETE FROM nodes WHERE id = ?");
|
|
182
|
+
for (const id of oldIds) {
|
|
183
|
+
if (!newIds.has(id))
|
|
184
|
+
sweep.run(id);
|
|
185
|
+
}
|
|
186
|
+
this.upsertNodes(ns);
|
|
187
|
+
this.upsertEdges(es);
|
|
188
|
+
});
|
|
189
|
+
tx(filePath, nodes, edges);
|
|
190
|
+
}
|
|
191
|
+
/** Ensure every CALLS edge endpoint exists (e.g. phantom unknown symbols). */
|
|
192
|
+
ensureNode(node) {
|
|
193
|
+
if (!this.getNode(node.id))
|
|
194
|
+
this.upsertNode(node);
|
|
195
|
+
}
|
|
196
|
+
setMeta(key, value) {
|
|
197
|
+
this.updateMetaStmt.run({ key, value });
|
|
198
|
+
}
|
|
199
|
+
getMeta(key) {
|
|
200
|
+
const row = this.db
|
|
201
|
+
.prepare("SELECT value FROM crawl_meta WHERE key = ?")
|
|
202
|
+
.get(key);
|
|
203
|
+
return row?.value ?? null;
|
|
204
|
+
}
|
|
205
|
+
stats() {
|
|
206
|
+
const nodeRow = this.db.prepare("SELECT COUNT(*) AS c FROM nodes").get();
|
|
207
|
+
const edgeRow = this.db.prepare("SELECT COUNT(*) AS c FROM edges").get();
|
|
208
|
+
const fileRow = this.db
|
|
209
|
+
.prepare("SELECT COUNT(DISTINCT file_path) AS c FROM nodes WHERE file_path IS NOT NULL")
|
|
210
|
+
.get();
|
|
211
|
+
return {
|
|
212
|
+
nodes: nodeRow.c,
|
|
213
|
+
edges: edgeRow.c,
|
|
214
|
+
files: fileRow.c,
|
|
215
|
+
lastCrawl: this.getMeta("last_crawl_at"),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
clearAll() {
|
|
219
|
+
const tx = this.db.transaction(() => {
|
|
220
|
+
this.db.exec("DELETE FROM edges; DELETE FROM nodes; DELETE FROM crawl_meta;");
|
|
221
|
+
});
|
|
222
|
+
tx();
|
|
223
|
+
}
|
|
224
|
+
/** Run a function inside a transaction. */
|
|
225
|
+
withTransaction(fn) {
|
|
226
|
+
return this.db.transaction(() => {
|
|
227
|
+
// Defer FK enforcement to commit so cross-file edges can be inserted
|
|
228
|
+
// before their target nodes exist (they are guaranteed by commit).
|
|
229
|
+
this.db.pragma("defer_foreign_keys = ON");
|
|
230
|
+
return fn();
|
|
231
|
+
})();
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* SQLite uses 0-based row/col; tree-sitter uses 0-based row but 1-based col.
|
|
235
|
+
* Store and expose tree-sitter-compatible positions (0-based line) and let
|
|
236
|
+
* the presentation layer add +1 where humans expect 1-based lines.
|
|
237
|
+
*/
|
|
238
|
+
raw(sql, ...params) {
|
|
239
|
+
return this.db.prepare(sql).all(...params);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function rowToNode(row) {
|
|
243
|
+
return {
|
|
244
|
+
id: row.id,
|
|
245
|
+
kind: row.kind,
|
|
246
|
+
qualifiedName: row.qualified_name,
|
|
247
|
+
filePath: row.file_path,
|
|
248
|
+
properties: JSON.parse(row.properties),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/graph/store.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { loadConfig } from "./config.js";
|
|
4
|
+
import { GraphStore } from "./graph/store.js";
|
|
5
|
+
import { crawlProject } from "./crawler.js";
|
|
6
|
+
import { createServer } from "./server.js";
|
|
7
|
+
import { searchSymbols } from "./graph/queries.js";
|
|
8
|
+
/**
|
|
9
|
+
* code-crawler CLI:
|
|
10
|
+
* codecrawler serve start the MCP server over stdio (default)
|
|
11
|
+
* codecrawler crawl <path> index a project into the graph database
|
|
12
|
+
* codecrawler status show index summary
|
|
13
|
+
* codecrawler query <symbol> fuzzy-lookup a symbol
|
|
14
|
+
*/
|
|
15
|
+
function usage() {
|
|
16
|
+
console.log(`code-crawler — static call tracing over a codebase property graph
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
codecrawler serve Start the MCP server over stdio (default)
|
|
20
|
+
codecrawler crawl <path> Index a project into the graph database
|
|
21
|
+
codecrawler status Show index summary
|
|
22
|
+
codecrawler query <symbol> Fuzzy-lookup a symbol
|
|
23
|
+
|
|
24
|
+
Environment:
|
|
25
|
+
CODECRAWLER_DB Database path (default ~/.code-crawler/index.db)
|
|
26
|
+
CODECRAWLER_EXCLUDE_DIRS Comma-separated dirs to skip during crawl
|
|
27
|
+
CODECRAWLER_DEFAULT_LIMIT Default result limit for list queries
|
|
28
|
+
CODECRAWLER_MAX_DEPTH Traversal depth cap
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
function makeStore(config) {
|
|
32
|
+
return new GraphStore(config.dbPath, {
|
|
33
|
+
maxDepth: config.maxDepth,
|
|
34
|
+
defaultLimit: config.defaultLimit,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
38
|
+
const config = loadConfig();
|
|
39
|
+
const [cmd, ...rest] = argv;
|
|
40
|
+
switch (cmd) {
|
|
41
|
+
case undefined:
|
|
42
|
+
case "serve": {
|
|
43
|
+
const store = makeStore(config);
|
|
44
|
+
const server = createServer(store, config);
|
|
45
|
+
const transport = new StdioServerTransport();
|
|
46
|
+
console.error(`code-crawler: MCP server ready (db: ${config.dbPath})`);
|
|
47
|
+
await server.connect(transport);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "crawl": {
|
|
51
|
+
const target = rest[0];
|
|
52
|
+
if (!target) {
|
|
53
|
+
console.error("code-crawler: missing <path> argument\n");
|
|
54
|
+
usage();
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
const store = makeStore(config);
|
|
59
|
+
try {
|
|
60
|
+
const result = await crawlProject(store, target, config.excludeDirs);
|
|
61
|
+
console.error(`code-crawler: indexed ${result.filesParsed}/${result.filesScanned} files ` +
|
|
62
|
+
`(${result.nodes} nodes, ${result.edges} edges, ` +
|
|
63
|
+
`${result.resolvedCalls} resolved / ${result.unresolvedCalls} unresolved calls) ` +
|
|
64
|
+
`in ${result.durationMs}ms`);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error(`code-crawler: ${err.message}`);
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
store.close();
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "status": {
|
|
76
|
+
const store = makeStore(config);
|
|
77
|
+
const stats = store.stats();
|
|
78
|
+
console.log(JSON.stringify({
|
|
79
|
+
db: config.dbPath,
|
|
80
|
+
nodes: stats.nodes,
|
|
81
|
+
edges: stats.edges,
|
|
82
|
+
files: stats.files,
|
|
83
|
+
lastCrawl: stats.lastCrawl,
|
|
84
|
+
projectPath: store.getMeta("last_project_path"),
|
|
85
|
+
}, null, 2));
|
|
86
|
+
store.close();
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "query": {
|
|
90
|
+
const symbol = rest[0];
|
|
91
|
+
if (!symbol) {
|
|
92
|
+
console.error("code-crawler: missing <symbol> argument\n");
|
|
93
|
+
usage();
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
const store = makeStore(config);
|
|
98
|
+
const results = searchSymbols(store, symbol, { limit: config.defaultLimit });
|
|
99
|
+
if (results.length === 0) {
|
|
100
|
+
console.log(`code-crawler: no symbols matching "${symbol}"`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log(JSON.stringify(results.map((r) => ({
|
|
104
|
+
qualifiedName: r.qualifiedName,
|
|
105
|
+
kind: r.kind,
|
|
106
|
+
name: r.name,
|
|
107
|
+
file: r.filePath,
|
|
108
|
+
})), null, 2));
|
|
109
|
+
}
|
|
110
|
+
store.close();
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "help":
|
|
114
|
+
case "--help":
|
|
115
|
+
case "-h":
|
|
116
|
+
usage();
|
|
117
|
+
break;
|
|
118
|
+
default:
|
|
119
|
+
console.error(`code-crawler: unknown command "${cmd}"\n`);
|
|
120
|
+
usage();
|
|
121
|
+
process.exitCode = 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const isMain = process.argv[1] &&
|
|
125
|
+
(import.meta.url === `file://${process.argv[1]}` ||
|
|
126
|
+
process.argv[1].endsWith("/dist/index.js"));
|
|
127
|
+
if (isMain) {
|
|
128
|
+
main().catch((err) => {
|
|
129
|
+
console.error(err);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAe,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD;;;;;;GAMG;AAEH,SAAS,KAAK;IACZ,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;CAab,CAAC,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,MAAc;IAC/B,OAAO,IAAI,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE;QACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,YAAY,EAAE,MAAM,CAAC,YAAY;KAClC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,OAAiB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;IAE5B,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,SAAS,CAAC;QACf,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YAChC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,uCAAuC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YACvE,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YAChC,MAAM;QACR,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACvB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,yCAAyC,CAAC,CAAC;gBACzD,KAAK,EAAE,CAAC;gBACR,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;gBACrB,MAAM;YACR,CAAC;YACD,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YAChC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;gBACrE,OAAO,CAAC,KAAK,CACX,yBAAyB,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,YAAY,SAAS;oBACzE,IAAI,MAAM,CAAC,KAAK,WAAW,MAAM,CAAC,KAAK,UAAU;oBACjD,GAAG,MAAM,CAAC,aAAa,eAAe,MAAM,CAAC,eAAe,qBAAqB;oBACjF,MAAM,MAAM,CAAC,UAAU,IAAI,CAC9B,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,iBAAkB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;gBACzD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACvB,CAAC;oBAAS,CAAC;gBACT,KAAK,CAAC,KAAK,EAAE,CAAC;YAChB,CAAC;YACD,MAAM;QACR,CAAC;QACD,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YAChC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;YAC5B,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ;gBACE,EAAE,EAAE,MAAM,CAAC,MAAM;gBACjB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,WAAW,EAAE,KAAK,CAAC,OAAO,CAAC,mBAAmB,CAAC;aAChD,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;YACF,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,MAAM;QACR,CAAC;QACD,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACvB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;gBAC3D,KAAK,EAAE,CAAC;gBACR,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;gBACrB,MAAM;YACR,CAAC;YACD,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YAChC,MAAM,OAAO,GAAG,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;YAC7E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,sCAAsC,MAAM,GAAG,CAAC,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAClB,aAAa,EAAE,CAAC,CAAC,aAAa;oBAC9B,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,CAAC,CAAC,QAAQ;iBACjB,CAAC,CAAC,EACH,IAAI,EACJ,CAAC,CACF,CACF,CAAC;YACJ,CAAC;YACD,KAAK,CAAC,KAAK,EAAE,CAAC;YACd,MAAM;QACR,CAAC;QACD,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;QACd,KAAK,IAAI;YACP,KAAK,EAAE,CAAC;YACR,MAAM;QACR;YACE,OAAO,CAAC,KAAK,CAAC,kCAAkC,GAAG,KAAK,CAAC,CAAC;YAC1D,KAAK,EAAE,CAAC;YACR,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED,MAAM,MAAM,GACV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACf,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAChD,IAAI,MAAM,EAAE,CAAC;IACX,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-agnostic parsing contract.
|
|
3
|
+
*
|
|
4
|
+
* Each supported language implements a `LanguageConfig` that knows how to:
|
|
5
|
+
* - load its tree-sitter grammar (WASM)
|
|
6
|
+
* - extract structural entities (types, methods, fields, imports) and raw
|
|
7
|
+
* call sites from source text
|
|
8
|
+
*
|
|
9
|
+
* The crawler then converts a `ParsedFile` into graph nodes/edges and feeds the
|
|
10
|
+
* raw call sites to the language-specific resolver.
|
|
11
|
+
*/
|
|
12
|
+
export type TypeKind = "class" | "interface" | "enum";
|
|
13
|
+
export interface TypeInfo {
|
|
14
|
+
/** Fully-qualified name, e.g. `com.acme.OrderService`. Nested types use `.`. */
|
|
15
|
+
id: string;
|
|
16
|
+
kind: TypeKind;
|
|
17
|
+
name: string;
|
|
18
|
+
/** Simple or qualified names from the `extends` clause. */
|
|
19
|
+
extendsTypes: string[];
|
|
20
|
+
/** Simple or qualified names from the `implements` clause. */
|
|
21
|
+
implementsTypes: string[];
|
|
22
|
+
/** 0-based row of the declaration start. */
|
|
23
|
+
startLine: number;
|
|
24
|
+
/** 0-based row of the declaration end. */
|
|
25
|
+
endLine: number;
|
|
26
|
+
}
|
|
27
|
+
export interface MethodInfo {
|
|
28
|
+
/** Node id, e.g. `com.acme.OrderService#place` (or `#<init>` for constructors). */
|
|
29
|
+
id: string;
|
|
30
|
+
/** FQN of the enclosing type. */
|
|
31
|
+
typeId: string;
|
|
32
|
+
name: string;
|
|
33
|
+
/** Whether this is a constructor. */
|
|
34
|
+
isConstructor: boolean;
|
|
35
|
+
/** Parameter names and raw type text. */
|
|
36
|
+
params: {
|
|
37
|
+
name: string;
|
|
38
|
+
type: string;
|
|
39
|
+
}[];
|
|
40
|
+
startLine: number;
|
|
41
|
+
endLine: number;
|
|
42
|
+
/** Raw return type text (empty for constructors). */
|
|
43
|
+
returnType: string;
|
|
44
|
+
/** Modifiers extracted from source (public, private, static, ...). */
|
|
45
|
+
modifiers: string[];
|
|
46
|
+
}
|
|
47
|
+
export interface FieldInfo {
|
|
48
|
+
/** Node id, e.g. `com.acme.OrderService.repo`. */
|
|
49
|
+
id: string;
|
|
50
|
+
typeId: string;
|
|
51
|
+
name: string;
|
|
52
|
+
/** Raw type text, e.g. `OrderRepository` or `List<String>`. */
|
|
53
|
+
type: string;
|
|
54
|
+
startLine: number;
|
|
55
|
+
modifiers: string[];
|
|
56
|
+
}
|
|
57
|
+
export interface ImportInfo {
|
|
58
|
+
/** Full import text, e.g. `com.acme.OrderService`. */
|
|
59
|
+
fqn: string;
|
|
60
|
+
/** Short name used in source, e.g. `OrderService`. */
|
|
61
|
+
simpleName: string;
|
|
62
|
+
isStatic: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface CallSite {
|
|
65
|
+
/** 0-based row of the call. */
|
|
66
|
+
line: number;
|
|
67
|
+
/**
|
|
68
|
+
* Receiver expression text: `repo` for `repo.create(...)`, `this.repo` for
|
|
69
|
+
* `this.repo.x()`, `com.acme.Foo` for a qualified static call, or null for
|
|
70
|
+
* a bare `helper()` call.
|
|
71
|
+
*/
|
|
72
|
+
receiver: string | null;
|
|
73
|
+
/** Method or constructor name. */
|
|
74
|
+
name: string;
|
|
75
|
+
/** Raw source text of each argument expression. */
|
|
76
|
+
argumentTexts: string[];
|
|
77
|
+
/** FQN of the enclosing type. */
|
|
78
|
+
enclosingTypeId: string;
|
|
79
|
+
/** Id of the enclosing method (or constructor). */
|
|
80
|
+
enclosingMethodId: string;
|
|
81
|
+
/** True for `new Foo(...)` constructor calls. */
|
|
82
|
+
isConstructorCall: boolean;
|
|
83
|
+
/** True when the receiver looks like a qualified type (e.g. `com.acme.Foo.bar`). */
|
|
84
|
+
qualifiedReceiver: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface ParsedFile {
|
|
87
|
+
filePath: string;
|
|
88
|
+
language: string;
|
|
89
|
+
/** Declared package name, or null when absent. */
|
|
90
|
+
packageName: string | null;
|
|
91
|
+
imports: ImportInfo[];
|
|
92
|
+
types: TypeInfo[];
|
|
93
|
+
methods: MethodInfo[];
|
|
94
|
+
fields: FieldInfo[];
|
|
95
|
+
callSites: CallSite[];
|
|
96
|
+
/**
|
|
97
|
+
* Local variables per method id: `methodId -> (name -> base type text)`.
|
|
98
|
+
* Used by the resolver for receiver-type inference.
|
|
99
|
+
*/
|
|
100
|
+
localsByMethod: Map<string, Map<string, string>>;
|
|
101
|
+
}
|
|
102
|
+
export interface LanguageConfig {
|
|
103
|
+
/** Registry key, e.g. "java". */
|
|
104
|
+
name: string;
|
|
105
|
+
/** File extensions handled by this language (without the dot). */
|
|
106
|
+
extensions: string[];
|
|
107
|
+
/** Load (once) and return the compiled tree-sitter grammar. */
|
|
108
|
+
loadGrammar(): Promise<unknown>;
|
|
109
|
+
/** Extract structural entities + raw call sites from source text. */
|
|
110
|
+
parseFile(source: string, filePath: string): Promise<ParsedFile>;
|
|
111
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language-agnostic parsing contract.
|
|
3
|
+
*
|
|
4
|
+
* Each supported language implements a `LanguageConfig` that knows how to:
|
|
5
|
+
* - load its tree-sitter grammar (WASM)
|
|
6
|
+
* - extract structural entities (types, methods, fields, imports) and raw
|
|
7
|
+
* call sites from source text
|
|
8
|
+
*
|
|
9
|
+
* The crawler then converts a `ParsedFile` into graph nodes/edges and feeds the
|
|
10
|
+
* raw call sites to the language-specific resolver.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=language.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"language.js","sourceRoot":"","sources":["../../src/parser/language.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { LanguageConfig } from "../language.js";
|
|
2
|
+
export declare function getLanguageConfig(name: string): LanguageConfig | null;
|
|
3
|
+
export declare function listLanguages(): string[];
|
|
4
|
+
/** Pick the language for a file path based on its extension. */
|
|
5
|
+
export declare function languageForFile(filePath: string, preferred?: string): LanguageConfig | null;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { javaConfig } from "./java.js";
|
|
2
|
+
/**
|
|
3
|
+
* Registry of supported languages. Adding a language means:
|
|
4
|
+
* 1. implement a LanguageConfig in `./<lang>.ts`
|
|
5
|
+
* 2. register it here
|
|
6
|
+
* 3. implement a resolver keyed by the same name in `src/resolver/`
|
|
7
|
+
*/
|
|
8
|
+
const registry = new Map([
|
|
9
|
+
["java", javaConfig],
|
|
10
|
+
]);
|
|
11
|
+
export function getLanguageConfig(name) {
|
|
12
|
+
return registry.get(name.toLowerCase()) ?? null;
|
|
13
|
+
}
|
|
14
|
+
export function listLanguages() {
|
|
15
|
+
return [...registry.keys()];
|
|
16
|
+
}
|
|
17
|
+
/** Pick the language for a file path based on its extension. */
|
|
18
|
+
export function languageForFile(filePath, preferred) {
|
|
19
|
+
const lower = filePath.toLowerCase();
|
|
20
|
+
if (preferred) {
|
|
21
|
+
const byName = getLanguageConfig(preferred);
|
|
22
|
+
if (byName)
|
|
23
|
+
return byName;
|
|
24
|
+
}
|
|
25
|
+
for (const config of registry.values()) {
|
|
26
|
+
if (config.extensions.some((ext) => lower.endsWith(`.${ext}`)))
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/parser/languages/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC;;;;;GAKG;AAEH,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAyB;IAC/C,CAAC,MAAM,EAAE,UAAU,CAAC;CACrB,CAAC,CAAC;AAEH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,IAAI,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,SAAkB;IAClE,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACrC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;IAC5B,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;YAAE,OAAO,MAAM,CAAC;IAChF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LanguageConfig, ParsedFile } from "../language.js";
|
|
2
|
+
export interface JavaParsedFile extends ParsedFile {
|
|
3
|
+
/**
|
|
4
|
+
* Simple name -> qualified name for types declared in this file and for
|
|
5
|
+
* imported types. Used by the resolver for same-file name resolution.
|
|
6
|
+
*/
|
|
7
|
+
typeByName: Map<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export declare const javaConfig: LanguageConfig;
|
|
10
|
+
export declare function parseFile(source: string, filePath: string): Promise<JavaParsedFile>;
|