@remnic/coding-graph 9.3.759
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 +130 -0
- package/dist/chunk-5I2DBHOQ.js +1042 -0
- package/dist/chunk-5I2DBHOQ.js.map +1 -0
- package/dist/chunk-CPYJACC5.js +1838 -0
- package/dist/chunk-CPYJACC5.js.map +1 -0
- package/dist/chunk-ZVCMIM4T.js +216 -0
- package/dist/chunk-ZVCMIM4T.js.map +1 -0
- package/dist/cypher/query-parser.d.ts +253 -0
- package/dist/cypher/query-parser.js +17 -0
- package/dist/cypher/query-parser.js.map +1 -0
- package/dist/graph-schema.d.ts +84 -0
- package/dist/graph-schema.js +17 -0
- package/dist/graph-schema.js.map +1 -0
- package/dist/graph-store.d.ts +938 -0
- package/dist/graph-store.js +16 -0
- package/dist/graph-store.js.map +1 -0
- package/dist/index.d.ts +1953 -0
- package/dist/index.js +3509 -0
- package/dist/index.js.map +1 -0
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-go.wasm +0 -0
- package/grammars/tree-sitter-java.wasm +0 -0
- package/grammars/tree-sitter-javascript.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-php.wasm +0 -0
- package/grammars/tree-sitter-python.wasm +0 -0
- package/grammars/tree-sitter-ruby.wasm +0 -0
- package/grammars/tree-sitter-rust.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-tsx.wasm +0 -0
- package/grammars/tree-sitter-typescript.wasm +0 -0
- package/package.json +79 -0
- package/src/co-change.test.ts +175 -0
- package/src/co-change.ts +167 -0
- package/src/cypher/query-parser.test.ts +1107 -0
- package/src/cypher/query-parser.ts +1692 -0
- package/src/detect-changes.test.ts +533 -0
- package/src/detect-changes.ts +367 -0
- package/src/engine/emit.ts +556 -0
- package/src/engine/engine.test.ts +1417 -0
- package/src/engine/engine.ts +182 -0
- package/src/engine/extractors.ts +486 -0
- package/src/engine/fixtures.ts +364 -0
- package/src/engine/language-sniff.ts +56 -0
- package/src/engine/parser-backend.ts +206 -0
- package/src/engine/utf16-offsets.ts +68 -0
- package/src/git-invoker.test.ts +116 -0
- package/src/git-invoker.ts +426 -0
- package/src/graph-schema.test.ts +541 -0
- package/src/graph-schema.ts +383 -0
- package/src/graph-store-pr2.test.ts +1879 -0
- package/src/graph-store.test.ts +1420 -0
- package/src/graph-store.ts +3489 -0
- package/src/index-status.test.ts +303 -0
- package/src/index-status.ts +135 -0
- package/src/index.ts +384 -0
- package/src/lsp/byte-position.ts +173 -0
- package/src/lsp/characterization.test.ts +174 -0
- package/src/lsp/client.test.ts +275 -0
- package/src/lsp/client.ts +484 -0
- package/src/lsp/config.ts +219 -0
- package/src/lsp/degradation.ts +86 -0
- package/src/lsp/fixtures/fake-server.mjs +198 -0
- package/src/lsp/framing.test.ts +180 -0
- package/src/lsp/framing.ts +177 -0
- package/src/lsp/resolution.test.ts +497 -0
- package/src/lsp/resolution.ts +483 -0
- package/src/lsp/status.ts +140 -0
- package/src/lsp/types.ts +167 -0
- package/src/reindex.test.ts +1038 -0
- package/src/reindex.ts +908 -0
- package/src/row-types.ts +45 -0
- package/src/semantic/canonical-text.test.ts +150 -0
- package/src/semantic/canonical-text.ts +219 -0
- package/src/semantic/config.ts +235 -0
- package/src/semantic/index.ts +78 -0
- package/src/semantic/minhash.test.ts +197 -0
- package/src/semantic/minhash.ts +261 -0
- package/src/semantic/semantic-query.ts +173 -0
- package/src/semantic/semantic.test.ts +1315 -0
- package/src/semantic/similarity.ts +268 -0
- package/src/semantic/types.ts +145 -0
- package/src/semantic/vectors.ts +235 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Engine factory — constructs a `CodingGraphEngine` backed by the WASM
|
|
3
|
+
* tree-sitter parser + tier-1 extractors.
|
|
4
|
+
*
|
|
5
|
+
* Rule 11 (no module-level state): the backend, query caches, and parser
|
|
6
|
+
* instances all live on the returned engine object. Two `createCodingGraphEngine`
|
|
7
|
+
* calls in the same process have fully isolated state.
|
|
8
|
+
*
|
|
9
|
+
* Rule 30/48: `codingGraph.enabled` defaults `false` in @remnic/core; the
|
|
10
|
+
* loader is never invoked when disabled, so this factory is only reached when
|
|
11
|
+
* the user has explicitly opted in.
|
|
12
|
+
*/
|
|
13
|
+
import {
|
|
14
|
+
CODING_GRAPH_ENGINE_VERSION,
|
|
15
|
+
TIER_1_LANGUAGES,
|
|
16
|
+
type CodingGraphEngine,
|
|
17
|
+
type CodingGraphLanguage,
|
|
18
|
+
type CreateCodingGraphEngineOptions,
|
|
19
|
+
type FileIR,
|
|
20
|
+
type ParseFileInput,
|
|
21
|
+
type ParseResult,
|
|
22
|
+
} from "@remnic/core";
|
|
23
|
+
|
|
24
|
+
import { WasmTreeSitterBackend, type ParserBackend } from "./parser-backend.js";
|
|
25
|
+
import { emitFileIR } from "./emit.js";
|
|
26
|
+
import { sniffLanguage, isTier1Language } from "./language-sniff.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The internal engine object. Implements `CodingGraphEngine` from @remnic/core.
|
|
30
|
+
*/
|
|
31
|
+
class CodingGraphEngineImpl implements CodingGraphEngine {
|
|
32
|
+
readonly engineVersion: string;
|
|
33
|
+
readonly supportedLanguages: readonly CodingGraphLanguage[];
|
|
34
|
+
private readonly backend: ParserBackend;
|
|
35
|
+
private disposed = false;
|
|
36
|
+
/**
|
|
37
|
+
* Serialize parse calls. The backend's single Parser instance is shared
|
|
38
|
+
* across all languages, so concurrent setLanguage/parse calls would race.
|
|
39
|
+
* Each parseFile call awaits the previous before touching the parser.
|
|
40
|
+
*/
|
|
41
|
+
private parseChain: Promise<void> = Promise.resolve();
|
|
42
|
+
|
|
43
|
+
constructor(backend: ParserBackend) {
|
|
44
|
+
this.engineVersion = CODING_GRAPH_ENGINE_VERSION;
|
|
45
|
+
this.supportedLanguages = TIER_1_LANGUAGES;
|
|
46
|
+
this.backend = backend;
|
|
47
|
+
}
|
|
48
|
+
async parseFile(input: ParseFileInput): Promise<ParseResult> {
|
|
49
|
+
if (this.disposed) {
|
|
50
|
+
return {
|
|
51
|
+
ok: false,
|
|
52
|
+
code: "parse_failed",
|
|
53
|
+
path: input.path,
|
|
54
|
+
message: "engine has been disposed",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Serialize: wait for any in-flight parse before touching the shared parser.
|
|
59
|
+
const previous = this.parseChain;
|
|
60
|
+
let release!: () => void;
|
|
61
|
+
this.parseChain = new Promise<void>((resolve) => {
|
|
62
|
+
release = resolve;
|
|
63
|
+
});
|
|
64
|
+
await previous;
|
|
65
|
+
// Re-check disposed: another caller may have disposed the engine
|
|
66
|
+
// while we were waiting for the previous parse to finish.
|
|
67
|
+
if (this.disposed) {
|
|
68
|
+
release();
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
code: "parse_failed",
|
|
72
|
+
path: input.path,
|
|
73
|
+
message: "engine has been disposed",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
return await this.doParseFile(input);
|
|
78
|
+
} finally {
|
|
79
|
+
release();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private async doParseFile(input: ParseFileInput): Promise<ParseResult> {
|
|
84
|
+
|
|
85
|
+
// Resolve the language — explicit override wins, then sniff from path.
|
|
86
|
+
const lang: CodingGraphLanguage | null =
|
|
87
|
+
input.language ?? sniffLanguage(input.path);
|
|
88
|
+
if (!lang || !isTier1Language(lang)) {
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
code: "parse_failed",
|
|
92
|
+
path: input.path,
|
|
93
|
+
message: `unsupported language for path "${input.path}"; ` +
|
|
94
|
+
`supported extensions map to: ${TIER_1_LANGUAGES.join(", ")}`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Ensure the grammar is loaded (rule 51 — error, not silent skip).
|
|
99
|
+
try {
|
|
100
|
+
await this.backend.ensureLanguage(lang);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
code: "parse_failed",
|
|
105
|
+
path: input.path,
|
|
106
|
+
message: `failed to load grammar for ${lang}: ${err instanceof Error ? err.message : String(err)}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// web-tree-sitter parses UTF-16 internally, so node offsets are UTF-16
|
|
111
|
+
// code-unit offsets. We convert to UTF-8 byte offsets after extraction
|
|
112
|
+
// (issue #1659 item 3 — multibyte span accuracy).
|
|
113
|
+
const contentStr = Buffer.from(input.content).toString("utf-8");
|
|
114
|
+
const tree = this.backend.parse(lang, contentStr);
|
|
115
|
+
if (!tree) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
code: "parse_failed",
|
|
119
|
+
path: input.path,
|
|
120
|
+
message: `tree-sitter returned null for ${lang} (grammar may be corrupt)`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const root = tree.rootNode;
|
|
126
|
+
const language = this.backend.getLanguage(lang);
|
|
127
|
+
if (!language) {
|
|
128
|
+
return {
|
|
129
|
+
ok: false,
|
|
130
|
+
code: "parse_failed",
|
|
131
|
+
path: input.path,
|
|
132
|
+
message: `language object unavailable for ${lang}`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const ir: FileIR = emitFileIR(
|
|
137
|
+
input.path,
|
|
138
|
+
lang,
|
|
139
|
+
input.content,
|
|
140
|
+
root,
|
|
141
|
+
language,
|
|
142
|
+
contentStr,
|
|
143
|
+
);
|
|
144
|
+
return { ok: true, ir };
|
|
145
|
+
} catch (err) {
|
|
146
|
+
// Rule 44: extraction/query errors surface as tagged parse_failed,
|
|
147
|
+
// not thrown exceptions that abort batch reindex.
|
|
148
|
+
return {
|
|
149
|
+
ok: false,
|
|
150
|
+
code: "parse_failed",
|
|
151
|
+
path: input.path,
|
|
152
|
+
message: `extraction failed for ${lang}: ${err instanceof Error ? err.message : String(err)}`,
|
|
153
|
+
};
|
|
154
|
+
} finally {
|
|
155
|
+
tree.delete();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async dispose(): Promise<void> {
|
|
160
|
+
if (this.disposed) return;
|
|
161
|
+
this.disposed = true;
|
|
162
|
+
// Wait for any in-flight parse to finish before tearing down the
|
|
163
|
+
// backend. Without this, a parseFile call that already passed the
|
|
164
|
+
// disposed guard can touch freed parser/grammar state.
|
|
165
|
+
await this.parseChain;
|
|
166
|
+
await this.backend.dispose();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Construct a coding-graph engine. PR2 implementation: real WASM tree-sitter
|
|
172
|
+
* parser with tier-1 extractors. No longer throws `not_implemented`.
|
|
173
|
+
*
|
|
174
|
+
* @throws never — load failures surface as `{ ok: false, code: "parse_failed" }`
|
|
175
|
+
* per-file, not as constructor exceptions.
|
|
176
|
+
*/
|
|
177
|
+
export function createCodingGraphEngine(
|
|
178
|
+
_options: CreateCodingGraphEngineOptions = {},
|
|
179
|
+
): CodingGraphEngine {
|
|
180
|
+
const backend = new WasmTreeSitterBackend();
|
|
181
|
+
return new CodingGraphEngineImpl(backend);
|
|
182
|
+
}
|
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-language symbol extractors for the tier-1 languages.
|
|
3
|
+
*
|
|
4
|
+
* Each tier-1 language has a `LanguageExtractor` providing tree-sitter query
|
|
5
|
+
* strings. The `emit.ts` module compiles these against the loaded grammar,
|
|
6
|
+
* runs them against the root node, and walks captures into FileIR.
|
|
7
|
+
*
|
|
8
|
+
* Query capture conventions (fixed across all languages):
|
|
9
|
+
*
|
|
10
|
+
* Definitions — every definition pattern captures:
|
|
11
|
+
* @def.<kind> the definition node (span = [startIndex, endIndex))
|
|
12
|
+
* @name the identifier node whose .text is the symbol name
|
|
13
|
+
* <kind> ∈ function | method | class | interface | enum | type | module
|
|
14
|
+
*
|
|
15
|
+
* Imports — every import pattern captures:
|
|
16
|
+
* @__import.stmt the import-statement node (grouping key)
|
|
17
|
+
* @import.module the module specifier text (cleaned in emit.ts)
|
|
18
|
+
* @import.name zero or more imported-name identifiers
|
|
19
|
+
*
|
|
20
|
+
* Exports — @export.name
|
|
21
|
+
* Call sites — @call.callee
|
|
22
|
+
* Routes — @route.verb + @route.path + @route.handler
|
|
23
|
+
*/
|
|
24
|
+
import type { CodingGraphLanguage } from "@remnic/core";
|
|
25
|
+
|
|
26
|
+
export type DefKind = "function" | "method" | "class" | "interface" | "enum" | "type" | "module";
|
|
27
|
+
|
|
28
|
+
export interface LanguageExtractor {
|
|
29
|
+
readonly definitionsQuery: string;
|
|
30
|
+
readonly importsQuery: string;
|
|
31
|
+
readonly exportsQuery: string;
|
|
32
|
+
readonly callSitesQuery: string;
|
|
33
|
+
readonly routesQuery: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ===========================================================================
|
|
37
|
+
// JS family (JavaScript, TypeScript, TSX) — shared definition + call patterns.
|
|
38
|
+
// ===========================================================================
|
|
39
|
+
|
|
40
|
+
const JS_FAMILY_DEFINITIONS = `
|
|
41
|
+
(function_declaration name: (identifier) @name) @def.function
|
|
42
|
+
(method_definition name: (property_identifier) @name) @def.method
|
|
43
|
+
(class_declaration name: (type_identifier) @name) @def.class
|
|
44
|
+
(interface_declaration name: (type_identifier) @name) @def.interface
|
|
45
|
+
(enum_declaration name: (identifier) @name) @def.enum
|
|
46
|
+
(type_alias_declaration name: (type_identifier) @name) @def.type
|
|
47
|
+
|
|
48
|
+
; const handler = () => {} / const handler = function () {}
|
|
49
|
+
; Indexes arrow-function and function-expression declarations so route
|
|
50
|
+
; handlers and React components defined this way appear as symbols.
|
|
51
|
+
(variable_declarator
|
|
52
|
+
name: (identifier) @name
|
|
53
|
+
value: [(arrow_function) (function_expression)]) @def.function
|
|
54
|
+
`.trim();
|
|
55
|
+
|
|
56
|
+
const JS_IMPORTS = `
|
|
57
|
+
(import_statement
|
|
58
|
+
source: (string (string_fragment) @import.module)) @__import.stmt
|
|
59
|
+
(import_statement
|
|
60
|
+
(import_clause (identifier) @import.name)
|
|
61
|
+
source: (string (string_fragment) @import.module)) @__import.stmt
|
|
62
|
+
(import_statement
|
|
63
|
+
(import_clause (namespace_import (identifier) @import.name))
|
|
64
|
+
source: (string (string_fragment) @import.module)) @__import.stmt
|
|
65
|
+
(import_statement
|
|
66
|
+
(import_clause (named_imports (import_specifier name: (identifier) @import.name)))
|
|
67
|
+
source: (string (string_fragment) @import.module)) @__import.stmt
|
|
68
|
+
|
|
69
|
+
; CommonJS require("...") — capture the module specifier so dependency
|
|
70
|
+
; edges exist for Node/CommonJS codebases, not just ES-module imports.
|
|
71
|
+
(call_expression
|
|
72
|
+
function: (identifier) @__import.require
|
|
73
|
+
arguments: (arguments (string (string_fragment) @import.module))
|
|
74
|
+
(#eq? @__import.require "require")) @__import.stmt
|
|
75
|
+
`.trim();
|
|
76
|
+
|
|
77
|
+
// TS/TSX exports — class/interface names use type_identifier.
|
|
78
|
+
const TS_EXPORTS = `
|
|
79
|
+
(export_statement declaration: (function_declaration name: (identifier) @export.name))
|
|
80
|
+
(export_statement declaration: (class_declaration name: (type_identifier) @export.name))
|
|
81
|
+
(export_statement (lexical_declaration (variable_declarator name: (identifier) @export.name)))
|
|
82
|
+
(export_statement declaration: (enum_declaration name: (identifier) @export.name))
|
|
83
|
+
(export_statement declaration: (interface_declaration name: (type_identifier) @export.name))
|
|
84
|
+
(export_statement (export_clause (export_specifier name: (identifier) @export.name)))
|
|
85
|
+
|
|
86
|
+
; export default App; (bare identifier re-export)
|
|
87
|
+
(export_statement (identifier) @export.name)
|
|
88
|
+
`.trim();
|
|
89
|
+
|
|
90
|
+
// JavaScript exports — class names use identifier (no type_identifier in JS grammar).
|
|
91
|
+
// Includes CommonJS export patterns (module.exports / exports.X) so
|
|
92
|
+
// Node/CommonJS public APIs are not marked unexported.
|
|
93
|
+
const JS_EXPORTS = `
|
|
94
|
+
(export_statement declaration: (function_declaration name: (identifier) @export.name))
|
|
95
|
+
(export_statement declaration: (class_declaration name: (identifier) @export.name))
|
|
96
|
+
(export_statement (lexical_declaration (variable_declarator name: (identifier) @export.name)))
|
|
97
|
+
(export_statement (export_clause (export_specifier name: (identifier) @export.name)))
|
|
98
|
+
|
|
99
|
+
; export default App; (bare identifier re-export)
|
|
100
|
+
(export_statement (identifier) @export.name)
|
|
101
|
+
|
|
102
|
+
; CommonJS: module.exports = { App, createRouter }
|
|
103
|
+
(assignment_expression
|
|
104
|
+
left: (member_expression object: (identifier) @__cjs.mod property: (property_identifier) @__cjs.exp)
|
|
105
|
+
right: (object (shorthand_property_identifier) @export.name)
|
|
106
|
+
(#eq? @__cjs.mod "module") (#eq? @__cjs.exp "exports"))
|
|
107
|
+
; CommonJS: module.exports = { publicName: createRouter }
|
|
108
|
+
; Capture the VALUE identifier (the actual symbol), not the key (public alias).
|
|
109
|
+
(assignment_expression
|
|
110
|
+
left: (member_expression object: (identifier) @__cjs.mod2 property: (property_identifier) @__cjs.exp2)
|
|
111
|
+
right: (object (pair key: (property_identifier) @__cjs.key2 value: (identifier) @export.name))
|
|
112
|
+
(#eq? @__cjs.mod2 "module") (#eq? @__cjs.exp2 "exports"))
|
|
113
|
+
; Fallback: { foo: <non-identifier-value> } — capture the key as the public name
|
|
114
|
+
; when the value is not a named symbol (arrow fn, literal, etc).
|
|
115
|
+
(assignment_expression
|
|
116
|
+
left: (member_expression object: (identifier) @__cjs.mod2b property: (property_identifier) @__cjs.exp2b)
|
|
117
|
+
right: (object (pair key: (property_identifier) @export.name value: (_) @__cjs.nonId))
|
|
118
|
+
(#eq? @__cjs.mod2b "module") (#eq? @__cjs.exp2b "exports")
|
|
119
|
+
(#not-match? @__cjs.nonId "^[A-Za-z_$][A-Za-z0-9_$]*$"))
|
|
120
|
+
; CommonJS: module.exports = App
|
|
121
|
+
(assignment_expression
|
|
122
|
+
left: (member_expression object: (identifier) @__cjs.mod3 property: (property_identifier) @__cjs.exp3)
|
|
123
|
+
right: (identifier) @export.name
|
|
124
|
+
(#eq? @__cjs.mod3 "module") (#eq? @__cjs.exp3 "exports"))
|
|
125
|
+
; CommonJS: exports.handler = handler
|
|
126
|
+
(assignment_expression
|
|
127
|
+
left: (member_expression object: (identifier) @__cjs.exp4 property: (property_identifier) @export.name)
|
|
128
|
+
(#eq? @__cjs.exp4 "exports"))
|
|
129
|
+
`.trim();
|
|
130
|
+
|
|
131
|
+
const JS_CALLS = `
|
|
132
|
+
(call_expression function: (identifier) @call.callee)
|
|
133
|
+
(call_expression function: (member_expression property: (property_identifier) @call.callee))
|
|
134
|
+
`.trim();
|
|
135
|
+
|
|
136
|
+
const JS_ROUTES = `
|
|
137
|
+
; Unified route pattern: captures verb + path + the arguments node.
|
|
138
|
+
; emit.ts extracts the handler from the LAST argument (identifier,
|
|
139
|
+
; arrow_function, or function_expression), correctly handling middleware:
|
|
140
|
+
; app.get("/users", requireAuth, getUsers) → handler=getUsers
|
|
141
|
+
(call_expression
|
|
142
|
+
function: (member_expression
|
|
143
|
+
object: (_) @__route.app
|
|
144
|
+
property: (property_identifier) @route.verb)
|
|
145
|
+
arguments: (arguments
|
|
146
|
+
. (string (string_fragment) @route.path)
|
|
147
|
+
) @route.args
|
|
148
|
+
(#match? @route.verb "^(get|post|put|patch|delete|head|options|all|use)$"))
|
|
149
|
+
`.trim();
|
|
150
|
+
|
|
151
|
+
// ===========================================================================
|
|
152
|
+
// Per-language extractors.
|
|
153
|
+
// ===========================================================================
|
|
154
|
+
|
|
155
|
+
const TYPESCRIPT_EXTRACTOR: LanguageExtractor = {
|
|
156
|
+
definitionsQuery: JS_FAMILY_DEFINITIONS,
|
|
157
|
+
importsQuery: JS_IMPORTS,
|
|
158
|
+
exportsQuery: TS_EXPORTS,
|
|
159
|
+
callSitesQuery: JS_CALLS,
|
|
160
|
+
routesQuery: JS_ROUTES,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const TSX_EXTRACTOR: LanguageExtractor = TYPESCRIPT_EXTRACTOR;
|
|
164
|
+
|
|
165
|
+
const JAVASCRIPT_EXTRACTOR: LanguageExtractor = {
|
|
166
|
+
definitionsQuery: `
|
|
167
|
+
(function_declaration name: (identifier) @name) @def.function
|
|
168
|
+
(method_definition name: (property_identifier) @name) @def.method
|
|
169
|
+
(class_declaration name: (identifier) @name) @def.class
|
|
170
|
+
|
|
171
|
+
; const handler = () => {} / const handler = function () {}
|
|
172
|
+
(variable_declarator
|
|
173
|
+
name: (identifier) @name
|
|
174
|
+
value: [(arrow_function) (function_expression)]) @def.function
|
|
175
|
+
`.trim(),
|
|
176
|
+
importsQuery: JS_IMPORTS,
|
|
177
|
+
exportsQuery: JS_EXPORTS,
|
|
178
|
+
callSitesQuery: JS_CALLS,
|
|
179
|
+
routesQuery: JS_ROUTES,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const PYTHON_EXTRACTOR: LanguageExtractor = {
|
|
183
|
+
definitionsQuery: `
|
|
184
|
+
(function_definition name: (identifier) @name) @def.function
|
|
185
|
+
(class_definition name: (identifier) @name) @def.class
|
|
186
|
+
`.trim(),
|
|
187
|
+
importsQuery: `
|
|
188
|
+
(import_statement (dotted_name) @import.module) @__import.stmt
|
|
189
|
+
(import_from_statement module_name: (dotted_name) @import.module) @__import.stmt
|
|
190
|
+
; Python relative imports: from .models import User / from ..parent import X
|
|
191
|
+
; tree-sitter-python wraps the module inside a relative_import node, so the
|
|
192
|
+
; module_name field is NOT set. Capture the relative_import node itself so
|
|
193
|
+
; the prefix dots are preserved (..parent, not just parent) — different
|
|
194
|
+
; relative levels must not collapse to the same module name
|
|
195
|
+
; (chatgpt-codex-connector #1688 P2: 'Preserve dots in Python relative imports').
|
|
196
|
+
(import_from_statement (relative_import) @import.module) @__import.stmt
|
|
197
|
+
`.trim(),
|
|
198
|
+
exportsQuery: ``,
|
|
199
|
+
callSitesQuery: `
|
|
200
|
+
(call function: (identifier) @call.callee)
|
|
201
|
+
(call function: (attribute attribute: (identifier) @call.callee))
|
|
202
|
+
`.trim(),
|
|
203
|
+
routesQuery: `
|
|
204
|
+
(decorated_definition
|
|
205
|
+
(decorator
|
|
206
|
+
(call function: (attribute attribute: (identifier) @route.verb)
|
|
207
|
+
arguments: (argument_list (string) @route.path)))
|
|
208
|
+
definition: (function_definition name: (identifier) @route.handler)
|
|
209
|
+
(#match? @route.verb "^(get|post|put|patch|delete|route|api_route)$"))
|
|
210
|
+
`.trim(),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const GO_EXTRACTOR: LanguageExtractor = {
|
|
214
|
+
definitionsQuery: `
|
|
215
|
+
(function_declaration name: (identifier) @name) @def.function
|
|
216
|
+
|
|
217
|
+
; Go methods sit outside their receiver struct, so byte-span nesting
|
|
218
|
+
; cannot compute qualified names. Capture the receiver type_identifier
|
|
219
|
+
; so extractSymbols can prefix the method name (Server.Start).
|
|
220
|
+
(method_declaration
|
|
221
|
+
receiver: (parameter_list
|
|
222
|
+
(parameter_declaration type: (type_identifier) @__receiver.type))
|
|
223
|
+
name: (field_identifier) @name) @def.method
|
|
224
|
+
(method_declaration
|
|
225
|
+
receiver: (parameter_list
|
|
226
|
+
(parameter_declaration type: (pointer_type (type_identifier) @__receiver.type)))
|
|
227
|
+
name: (field_identifier) @name) @def.method
|
|
228
|
+
(type_spec name: (type_identifier) @name type: (struct_type)) @def.class
|
|
229
|
+
(type_spec name: (type_identifier) @name type: (interface_type)) @def.interface
|
|
230
|
+
(type_spec name: (type_identifier) @name type: (type_identifier)) @def.type
|
|
231
|
+
`.trim(),
|
|
232
|
+
importsQuery: `
|
|
233
|
+
(import_spec path: (interpreted_string_literal) @import.module) @__import.stmt
|
|
234
|
+
`.trim(),
|
|
235
|
+
exportsQuery: ``,
|
|
236
|
+
callSitesQuery: `
|
|
237
|
+
(call_expression function: (identifier) @call.callee)
|
|
238
|
+
(call_expression function: (selector_expression field: (field_identifier) @call.callee))
|
|
239
|
+
`.trim(),
|
|
240
|
+
routesQuery: ``,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const RUST_EXTRACTOR: LanguageExtractor = {
|
|
244
|
+
definitionsQuery: `
|
|
245
|
+
(function_item name: (identifier) @name) @def.function
|
|
246
|
+
(function_signature_item name: (identifier) @name) @def.function
|
|
247
|
+
(struct_item name: (type_identifier) @name) @def.class
|
|
248
|
+
(enum_item name: (type_identifier) @name) @def.enum
|
|
249
|
+
(trait_item name: (type_identifier) @name) @def.interface
|
|
250
|
+
(type_item name: (type_identifier) @name) @def.type
|
|
251
|
+
(mod_item name: (identifier) @name) @def.module
|
|
252
|
+
|
|
253
|
+
; Rust impl methods — the impl block sits outside the struct's byte span,
|
|
254
|
+
; so byte-span nesting cannot compute the parent struct. Capture the impl
|
|
255
|
+
; type_identifier so extractSymbols can prefix qualified names (Config.new).
|
|
256
|
+
; These also match functions caught by the general patterns above;
|
|
257
|
+
; extractSymbols deduplicates by node identity (startByte+endByte+name).
|
|
258
|
+
(impl_item
|
|
259
|
+
type: (type_identifier) @__receiver.type
|
|
260
|
+
body: (declaration_list
|
|
261
|
+
(function_item name: (identifier) @name) @def.method))
|
|
262
|
+
(impl_item
|
|
263
|
+
type: (type_identifier) @__receiver.type
|
|
264
|
+
body: (declaration_list
|
|
265
|
+
(function_signature_item name: (identifier) @name) @def.method))
|
|
266
|
+
`.trim(),
|
|
267
|
+
importsQuery: `
|
|
268
|
+
(use_declaration (scoped_identifier) @import.module) @__import.stmt
|
|
269
|
+
(use_declaration (scoped_use_list) @import.module) @__import.stmt
|
|
270
|
+
`.trim(),
|
|
271
|
+
exportsQuery: ``,
|
|
272
|
+
callSitesQuery: `
|
|
273
|
+
(call_expression function: (identifier) @call.callee)
|
|
274
|
+
(call_expression function: (field_expression field: (field_identifier) @call.callee))
|
|
275
|
+
(call_expression function: (scoped_identifier) @call.callee)
|
|
276
|
+
`.trim(),
|
|
277
|
+
routesQuery: ``,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const JAVA_EXTRACTOR: LanguageExtractor = {
|
|
281
|
+
definitionsQuery: `
|
|
282
|
+
(class_declaration name: (identifier) @name) @def.class
|
|
283
|
+
(interface_declaration name: (identifier) @name) @def.interface
|
|
284
|
+
(enum_declaration name: (identifier) @name) @def.enum
|
|
285
|
+
(record_declaration name: (identifier) @name) @def.class
|
|
286
|
+
(method_declaration name: (identifier) @name) @def.method
|
|
287
|
+
(constructor_declaration name: (identifier) @name) @def.method
|
|
288
|
+
`.trim(),
|
|
289
|
+
importsQuery: `
|
|
290
|
+
(import_declaration (scoped_identifier) @import.module) @__import.stmt
|
|
291
|
+
`.trim(),
|
|
292
|
+
exportsQuery: ``,
|
|
293
|
+
callSitesQuery: `
|
|
294
|
+
(method_invocation name: (identifier) @call.callee)
|
|
295
|
+
`.trim(),
|
|
296
|
+
routesQuery: ``,
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const C_EXTRACTOR: LanguageExtractor = {
|
|
300
|
+
definitionsQuery: `
|
|
301
|
+
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @def.function
|
|
302
|
+
(type_definition declarator: (type_identifier) @name) @def.type
|
|
303
|
+
`.trim(),
|
|
304
|
+
importsQuery: `
|
|
305
|
+
(preproc_include path: (system_lib_string) @import.module) @__import.stmt
|
|
306
|
+
(preproc_include path: (string_literal) @import.module) @__import.stmt
|
|
307
|
+
`.trim(),
|
|
308
|
+
exportsQuery: ``,
|
|
309
|
+
callSitesQuery: `
|
|
310
|
+
(call_expression function: (identifier) @call.callee)
|
|
311
|
+
`.trim(),
|
|
312
|
+
routesQuery: ``,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const CPP_EXTRACTOR: LanguageExtractor = {
|
|
316
|
+
definitionsQuery: `
|
|
317
|
+
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @def.function
|
|
318
|
+
(function_definition declarator: (function_declarator declarator: (field_identifier) @name)) @def.method
|
|
319
|
+
; C++ out-of-class method definitions: void A::start() {}
|
|
320
|
+
; The qualified_identifier (A::start) encodes the full qualified name.
|
|
321
|
+
(function_definition declarator: (function_declarator declarator: (qualified_identifier) @name)) @def.method
|
|
322
|
+
(class_specifier name: (type_identifier) @name) @def.class
|
|
323
|
+
(struct_specifier name: (type_identifier) @name) @def.class
|
|
324
|
+
(enum_specifier name: (type_identifier) @name) @def.enum
|
|
325
|
+
(namespace_definition name: (namespace_identifier) @name) @def.module
|
|
326
|
+
`.trim(),
|
|
327
|
+
importsQuery: `
|
|
328
|
+
(preproc_include path: (system_lib_string) @import.module) @__import.stmt
|
|
329
|
+
(preproc_include path: (string_literal) @import.module) @__import.stmt
|
|
330
|
+
`.trim(),
|
|
331
|
+
exportsQuery: ``,
|
|
332
|
+
callSitesQuery: `
|
|
333
|
+
(call_expression function: (identifier) @call.callee)
|
|
334
|
+
(call_expression function: (field_expression field: (field_identifier) @call.callee))
|
|
335
|
+
`.trim(),
|
|
336
|
+
routesQuery: ``,
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const CSHARP_EXTRACTOR: LanguageExtractor = {
|
|
340
|
+
definitionsQuery: `
|
|
341
|
+
(class_declaration name: (identifier) @name) @def.class
|
|
342
|
+
(interface_declaration name: (identifier) @name) @def.interface
|
|
343
|
+
(enum_declaration name: (identifier) @name) @def.enum
|
|
344
|
+
(struct_declaration name: (identifier) @name) @def.class
|
|
345
|
+
(method_declaration name: (identifier) @name) @def.method
|
|
346
|
+
`.trim(),
|
|
347
|
+
importsQuery: `
|
|
348
|
+
(using_directive (identifier) @import.module) @__import.stmt
|
|
349
|
+
(using_directive (qualified_name) @import.module) @__import.stmt
|
|
350
|
+
`.trim(),
|
|
351
|
+
exportsQuery: ``,
|
|
352
|
+
callSitesQuery: `
|
|
353
|
+
(invocation_expression function: (identifier) @call.callee)
|
|
354
|
+
(invocation_expression function: (member_access_expression name: (identifier) @call.callee))
|
|
355
|
+
`.trim(),
|
|
356
|
+
routesQuery: ``,
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const RUBY_EXTRACTOR: LanguageExtractor = {
|
|
360
|
+
definitionsQuery: `
|
|
361
|
+
(class name: (constant) @name) @def.class
|
|
362
|
+
(module name: (constant) @name) @def.module
|
|
363
|
+
(method name: (identifier) @name) @def.method
|
|
364
|
+
(singleton_method name: (identifier) @name) @def.method
|
|
365
|
+
`.trim(),
|
|
366
|
+
importsQuery: `
|
|
367
|
+
(call
|
|
368
|
+
method: (identifier) @__import.method
|
|
369
|
+
arguments: (argument_list (string) @import.module)
|
|
370
|
+
(#match? @__import.method "^(require|require_relative|load)$")) @__import.stmt
|
|
371
|
+
`.trim(),
|
|
372
|
+
exportsQuery: ``,
|
|
373
|
+
callSitesQuery: `
|
|
374
|
+
(call method: (identifier) @call.callee)
|
|
375
|
+
`.trim(),
|
|
376
|
+
routesQuery: ``,
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
const PHP_EXTRACTOR: LanguageExtractor = {
|
|
380
|
+
definitionsQuery: `
|
|
381
|
+
(class_declaration name: (name) @name) @def.class
|
|
382
|
+
(interface_declaration name: (name) @name) @def.interface
|
|
383
|
+
(trait_declaration name: (name) @name) @def.class
|
|
384
|
+
(function_definition name: (name) @name) @def.function
|
|
385
|
+
(method_declaration name: (name) @name) @def.method
|
|
386
|
+
`.trim(),
|
|
387
|
+
importsQuery: `
|
|
388
|
+
(namespace_use_declaration (namespace_use_clause (qualified_name) @import.module)) @__import.stmt
|
|
389
|
+
`.trim(),
|
|
390
|
+
exportsQuery: ``,
|
|
391
|
+
callSitesQuery: `
|
|
392
|
+
(function_call_expression function: (name) @call.callee)
|
|
393
|
+
(member_call_expression (name) @call.callee)
|
|
394
|
+
`.trim(),
|
|
395
|
+
routesQuery: ``,
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const KOTLIN_EXTRACTOR: LanguageExtractor = {
|
|
399
|
+
definitionsQuery: `
|
|
400
|
+
(class_declaration (type_identifier) @name) @def.class
|
|
401
|
+
(object_declaration (type_identifier) @name) @def.module
|
|
402
|
+
(function_declaration (simple_identifier) @name) @def.function
|
|
403
|
+
`.trim(),
|
|
404
|
+
importsQuery: `
|
|
405
|
+
; Capture the full identifier node — its .text is the complete import
|
|
406
|
+
; path (e.g. "kotlin.collections"). Do NOT capture nested simple_identifier
|
|
407
|
+
; children, which would emit bogus segment-level modules.
|
|
408
|
+
(import_header (identifier) @import.module) @__import.stmt
|
|
409
|
+
`.trim(),
|
|
410
|
+
exportsQuery: ``,
|
|
411
|
+
callSitesQuery: `
|
|
412
|
+
(call_expression (simple_identifier) @call.callee)
|
|
413
|
+
`.trim(),
|
|
414
|
+
routesQuery: ``,
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const SWIFT_EXTRACTOR: LanguageExtractor = {
|
|
418
|
+
definitionsQuery: `
|
|
419
|
+
(class_declaration name: (type_identifier) @name) @def.class
|
|
420
|
+
(protocol_declaration name: (type_identifier) @name) @def.interface
|
|
421
|
+
(function_declaration name: (simple_identifier) @name) @def.function
|
|
422
|
+
`.trim(),
|
|
423
|
+
importsQuery: `
|
|
424
|
+
(import_declaration (identifier) @import.module) @__import.stmt
|
|
425
|
+
`.trim(),
|
|
426
|
+
exportsQuery: ``,
|
|
427
|
+
callSitesQuery: `
|
|
428
|
+
(call_expression (simple_identifier) @call.callee)
|
|
429
|
+
`.trim(),
|
|
430
|
+
routesQuery: ``,
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const BASH_EXTRACTOR: LanguageExtractor = {
|
|
434
|
+
definitionsQuery: `
|
|
435
|
+
(function_definition name: (word) @name) @def.function
|
|
436
|
+
`.trim(),
|
|
437
|
+
importsQuery: ``,
|
|
438
|
+
exportsQuery: ``,
|
|
439
|
+
callSitesQuery: `
|
|
440
|
+
(command name: (command_name (word) @call.callee))
|
|
441
|
+
`.trim(),
|
|
442
|
+
routesQuery: ``,
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
// ===========================================================================
|
|
446
|
+
// Registry — single source of truth.
|
|
447
|
+
// ===========================================================================
|
|
448
|
+
|
|
449
|
+
export const EXTRACTORS: Record<CodingGraphLanguage, LanguageExtractor> = {
|
|
450
|
+
typescript: TYPESCRIPT_EXTRACTOR,
|
|
451
|
+
tsx: TSX_EXTRACTOR,
|
|
452
|
+
javascript: JAVASCRIPT_EXTRACTOR,
|
|
453
|
+
python: PYTHON_EXTRACTOR,
|
|
454
|
+
go: GO_EXTRACTOR,
|
|
455
|
+
rust: RUST_EXTRACTOR,
|
|
456
|
+
java: JAVA_EXTRACTOR,
|
|
457
|
+
c: C_EXTRACTOR,
|
|
458
|
+
cpp: CPP_EXTRACTOR,
|
|
459
|
+
csharp: CSHARP_EXTRACTOR,
|
|
460
|
+
ruby: RUBY_EXTRACTOR,
|
|
461
|
+
php: PHP_EXTRACTOR,
|
|
462
|
+
kotlin: KOTLIN_EXTRACTOR,
|
|
463
|
+
swift: SWIFT_EXTRACTOR,
|
|
464
|
+
bash: BASH_EXTRACTOR,
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const VALID_KINDS = new Set<string>([
|
|
468
|
+
"function",
|
|
469
|
+
"method",
|
|
470
|
+
"class",
|
|
471
|
+
"interface",
|
|
472
|
+
"enum",
|
|
473
|
+
"type",
|
|
474
|
+
"module",
|
|
475
|
+
]);
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Extract the SymbolIR kind from a `@def.<kind>` capture name.
|
|
479
|
+
* Returns `null` for non-definition captures or invalid kinds.
|
|
480
|
+
*/
|
|
481
|
+
export function kindFromCapture(captureName: string): DefKind | null {
|
|
482
|
+
if (!captureName.startsWith("def.")) return null;
|
|
483
|
+
const kind = captureName.slice(4);
|
|
484
|
+
if (!VALID_KINDS.has(kind)) return null;
|
|
485
|
+
return kind as DefKind;
|
|
486
|
+
}
|