gitnexus 1.6.6-rc.27 → 1.6.6-rc.29

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.
Files changed (32) hide show
  1. package/dist/core/ingestion/languages/kotlin/arity-metadata.d.ts +7 -0
  2. package/dist/core/ingestion/languages/kotlin/arity-metadata.js +20 -0
  3. package/dist/core/ingestion/languages/kotlin/arity.d.ts +2 -0
  4. package/dist/core/ingestion/languages/kotlin/arity.js +15 -0
  5. package/dist/core/ingestion/languages/kotlin/cache-stats.d.ts +7 -0
  6. package/dist/core/ingestion/languages/kotlin/cache-stats.js +15 -0
  7. package/dist/core/ingestion/languages/kotlin/captures.d.ts +2 -0
  8. package/dist/core/ingestion/languages/kotlin/captures.js +376 -0
  9. package/dist/core/ingestion/languages/kotlin/import-decomposer.d.ts +3 -0
  10. package/dist/core/ingestion/languages/kotlin/import-decomposer.js +37 -0
  11. package/dist/core/ingestion/languages/kotlin/import-target.d.ts +6 -0
  12. package/dist/core/ingestion/languages/kotlin/import-target.js +60 -0
  13. package/dist/core/ingestion/languages/kotlin/index.d.ts +8 -0
  14. package/dist/core/ingestion/languages/kotlin/index.js +8 -0
  15. package/dist/core/ingestion/languages/kotlin/interpret.d.ts +4 -0
  16. package/dist/core/ingestion/languages/kotlin/interpret.js +70 -0
  17. package/dist/core/ingestion/languages/kotlin/merge-bindings.d.ts +2 -0
  18. package/dist/core/ingestion/languages/kotlin/merge-bindings.js +25 -0
  19. package/dist/core/ingestion/languages/kotlin/owners.d.ts +2 -0
  20. package/dist/core/ingestion/languages/kotlin/owners.js +50 -0
  21. package/dist/core/ingestion/languages/kotlin/query.d.ts +3 -0
  22. package/dist/core/ingestion/languages/kotlin/query.js +112 -0
  23. package/dist/core/ingestion/languages/kotlin/receiver-binding.d.ts +3 -0
  24. package/dist/core/ingestion/languages/kotlin/receiver-binding.js +100 -0
  25. package/dist/core/ingestion/languages/kotlin/scope-resolver.d.ts +17 -0
  26. package/dist/core/ingestion/languages/kotlin/scope-resolver.js +37 -0
  27. package/dist/core/ingestion/languages/kotlin/simple-hooks.d.ts +4 -0
  28. package/dist/core/ingestion/languages/kotlin/simple-hooks.js +19 -0
  29. package/dist/core/ingestion/languages/kotlin.js +10 -0
  30. package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
  31. package/dist/server/api.js +7 -14
  32. package/package.json +1 -1
@@ -0,0 +1,7 @@
1
+ import type { SyntaxNode } from '../../utils/ast-helpers.js';
2
+ export interface KotlinArityMetadata {
3
+ readonly parameterCount: number | undefined;
4
+ readonly requiredParameterCount: number | undefined;
5
+ readonly parameterTypes: readonly string[] | undefined;
6
+ }
7
+ export declare function computeKotlinArityMetadata(fnNode: SyntaxNode): KotlinArityMetadata;
@@ -0,0 +1,20 @@
1
+ import { kotlinMethodConfig } from '../../method-extractors/configs/jvm.js';
2
+ export function computeKotlinArityMetadata(fnNode) {
3
+ const params = kotlinMethodConfig.extractParameters?.(fnNode) ?? [];
4
+ let hasVararg = false;
5
+ const parameterTypes = [];
6
+ for (const param of params) {
7
+ if (param.isVariadic)
8
+ hasVararg = true;
9
+ if (param.type !== null)
10
+ parameterTypes.push(param.type);
11
+ }
12
+ if (hasVararg)
13
+ parameterTypes.push('vararg');
14
+ const required = params.filter((p) => !p.isOptional && !p.isVariadic).length;
15
+ return {
16
+ parameterCount: hasVararg ? undefined : params.length,
17
+ requiredParameterCount: required,
18
+ parameterTypes: parameterTypes.length > 0 ? parameterTypes : undefined,
19
+ };
20
+ }
@@ -0,0 +1,2 @@
1
+ import type { Callsite, SymbolDefinition } from '../../../../_shared/index.js';
2
+ export declare function kotlinArityCompatibility(def: SymbolDefinition, callsite: Callsite): 'compatible' | 'unknown' | 'incompatible';
@@ -0,0 +1,15 @@
1
+ export function kotlinArityCompatibility(def, callsite) {
2
+ const min = def.requiredParameterCount;
3
+ const max = def.parameterCount;
4
+ if (min === undefined && max === undefined)
5
+ return 'unknown';
6
+ const argCount = callsite.arity;
7
+ if (!Number.isFinite(argCount) || argCount < 0)
8
+ return 'unknown';
9
+ const hasVararg = def.parameterTypes?.some((t) => t === 'vararg') ?? false;
10
+ if (min !== undefined && argCount < min)
11
+ return 'incompatible';
12
+ if (max !== undefined && argCount > max && !hasVararg)
13
+ return 'incompatible';
14
+ return 'compatible';
15
+ }
@@ -0,0 +1,7 @@
1
+ export declare function recordKotlinCacheHit(): void;
2
+ export declare function recordKotlinCacheMiss(): void;
3
+ export declare function getKotlinCaptureCacheStats(): {
4
+ readonly hits: number;
5
+ readonly misses: number;
6
+ };
7
+ export declare function resetKotlinCaptureCacheStats(): void;
@@ -0,0 +1,15 @@
1
+ let hits = 0;
2
+ let misses = 0;
3
+ export function recordKotlinCacheHit() {
4
+ hits += 1;
5
+ }
6
+ export function recordKotlinCacheMiss() {
7
+ misses += 1;
8
+ }
9
+ export function getKotlinCaptureCacheStats() {
10
+ return { hits, misses };
11
+ }
12
+ export function resetKotlinCaptureCacheStats() {
13
+ hits = 0;
14
+ misses = 0;
15
+ }
@@ -0,0 +1,2 @@
1
+ import type { CaptureMatch } from '../../../../_shared/index.js';
2
+ export declare function emitKotlinScopeCaptures(sourceText: string, _filePath: string, cachedTree?: unknown): readonly CaptureMatch[];
@@ -0,0 +1,376 @@
1
+ import { findNodeAtRange, nodeToCapture, syntheticCapture, } from '../../utils/ast-helpers.js';
2
+ import { getTreeSitterBufferSize } from '../../constants.js';
3
+ import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
4
+ import { computeKotlinArityMetadata } from './arity-metadata.js';
5
+ import { splitKotlinImportHeader } from './import-decomposer.js';
6
+ import { recordKotlinCacheHit, recordKotlinCacheMiss } from './cache-stats.js';
7
+ import { normalizeKotlinType } from './interpret.js';
8
+ import { synthesizeKotlinReceiverBinding } from './receiver-binding.js';
9
+ import { getKotlinParser, getKotlinScopeQuery } from './query.js';
10
+ const FUNCTION_DECL_TAGS = ['@declaration.function'];
11
+ export function emitKotlinScopeCaptures(sourceText, _filePath, cachedTree) {
12
+ let tree = cachedTree;
13
+ if (tree === undefined) {
14
+ tree = parseSourceSafe(getKotlinParser(), sourceText, undefined, {
15
+ bufferSize: getTreeSitterBufferSize(sourceText),
16
+ });
17
+ recordKotlinCacheMiss();
18
+ }
19
+ else {
20
+ recordKotlinCacheHit();
21
+ }
22
+ const out = [];
23
+ const returnTypes = collectKotlinReturnTypeTexts(tree.rootNode);
24
+ out.push(...synthesizeKotlinLocalAssignmentBindings(tree.rootNode, returnTypes));
25
+ out.push(...synthesizeKotlinLoopBindings(tree.rootNode, returnTypes));
26
+ for (const match of getKotlinScopeQuery().matches(tree.rootNode)) {
27
+ const grouped = {};
28
+ for (const capture of match.captures) {
29
+ const tag = '@' + capture.name;
30
+ grouped[tag] = nodeToCapture(tag, capture.node);
31
+ }
32
+ if (Object.keys(grouped).length === 0)
33
+ continue;
34
+ if (grouped['@import.statement'] !== undefined) {
35
+ const importNode = findNodeAtRange(tree.rootNode, grouped['@import.statement'].range, 'import_header');
36
+ if (importNode !== null) {
37
+ const decomposed = splitKotlinImportHeader(importNode);
38
+ if (decomposed !== null) {
39
+ out.push(decomposed);
40
+ continue;
41
+ }
42
+ }
43
+ }
44
+ if (grouped['@reference.call.free'] !== undefined &&
45
+ grouped['@reference.receiver'] !== undefined) {
46
+ continue;
47
+ }
48
+ if (grouped['@reference.read.member'] !== undefined) {
49
+ const anchor = grouped['@reference.read.member'];
50
+ const navNode = findNodeAtRange(tree.rootNode, anchor.range, 'navigation_expression');
51
+ if (navNode === null || !shouldEmitReadMember(navNode))
52
+ continue;
53
+ }
54
+ if (grouped['@scope.function'] !== undefined) {
55
+ out.push(grouped);
56
+ const fnNode = findNodeAtRange(tree.rootNode, grouped['@scope.function'].range, 'function_declaration');
57
+ if (fnNode !== null) {
58
+ out.push(...synthesizeKotlinReceiverBinding(fnNode));
59
+ }
60
+ continue;
61
+ }
62
+ const declTag = FUNCTION_DECL_TAGS.find((tag) => grouped[tag] !== undefined);
63
+ if (declTag !== undefined) {
64
+ const fnNode = findNodeAtRange(tree.rootNode, grouped[declTag].range, 'function_declaration');
65
+ if (fnNode !== null) {
66
+ const arity = computeKotlinArityMetadata(fnNode);
67
+ if (arity.parameterCount !== undefined) {
68
+ grouped['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', fnNode, String(arity.parameterCount));
69
+ }
70
+ if (arity.requiredParameterCount !== undefined) {
71
+ grouped['@declaration.required-parameter-count'] = syntheticCapture('@declaration.required-parameter-count', fnNode, String(arity.requiredParameterCount));
72
+ }
73
+ if (arity.parameterTypes !== undefined) {
74
+ grouped['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', fnNode, JSON.stringify(arity.parameterTypes));
75
+ }
76
+ }
77
+ }
78
+ const callTag = ['@reference.call.free', '@reference.call.member', '@reference.call.constructor'].find((tag) => grouped[tag] !== undefined);
79
+ if (callTag !== undefined && grouped['@reference.arity'] === undefined) {
80
+ const callNode = findNodeAtRange(tree.rootNode, grouped[callTag].range, 'call_expression');
81
+ if (callNode !== null) {
82
+ const args = callArguments(callNode);
83
+ grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(args.length));
84
+ grouped['@reference.parameter-types'] = syntheticCapture('@reference.parameter-types', callNode, JSON.stringify(args.map(inferArgType)));
85
+ }
86
+ }
87
+ out.push(grouped);
88
+ const extensionFallback = extensionFreeCallFallback(grouped, tree.rootNode);
89
+ if (extensionFallback !== null)
90
+ out.push(extensionFallback);
91
+ }
92
+ return out;
93
+ }
94
+ function synthesizeKotlinLoopBindings(rootNode, returnTypes) {
95
+ const out = [];
96
+ for (const fnNode of descendantsOfType(rootNode, 'function_declaration')) {
97
+ const localTypes = collectKotlinLocalTypeTexts(fnNode, returnTypes);
98
+ for (const forNode of descendantsOfType(fnNode, 'for_statement')) {
99
+ const variable = forNode.namedChildren.find((child) => child.type === 'variable_declaration');
100
+ const name = variable?.namedChildren.find((child) => child.type === 'simple_identifier');
101
+ if (variable === undefined || name === undefined)
102
+ continue;
103
+ const explicitType = variable.namedChildren.find((child) => isKotlinTypeNode(child));
104
+ const iterable = forNode.namedChildren.find((child) => child.id !== variable.id && child.type !== 'control_structure_body');
105
+ const rawType = explicitType?.text ??
106
+ (iterable === undefined
107
+ ? null
108
+ : inferKotlinIterableElementType(iterable, localTypes, returnTypes));
109
+ if (rawType === null || rawType.trim() === '')
110
+ continue;
111
+ const anchor = forNode.namedChildren.find((child) => child.type === 'control_structure_body') ?? forNode;
112
+ out.push({
113
+ '@type-binding.annotation': nodeToCapture('@type-binding.annotation', anchor),
114
+ '@type-binding.name': syntheticCapture('@type-binding.name', name, name.text),
115
+ '@type-binding.type': syntheticCapture('@type-binding.type', explicitType ?? iterable ?? name, normalizeKotlinType(rawType)),
116
+ });
117
+ }
118
+ }
119
+ return out;
120
+ }
121
+ function synthesizeKotlinLocalAssignmentBindings(rootNode, returnTypes) {
122
+ const out = [];
123
+ for (const fnNode of descendantsOfType(rootNode, 'function_declaration')) {
124
+ const localTypes = new Map();
125
+ for (const prop of descendantsOfType(fnNode, 'property_declaration')) {
126
+ const inferred = inferKotlinPropertyType(prop, localTypes, returnTypes);
127
+ if (inferred === null)
128
+ continue;
129
+ localTypes.set(inferred.name.text, inferred.rawType);
130
+ if (inferred.synthetic) {
131
+ out.push({
132
+ '@type-binding.annotation': nodeToCapture('@type-binding.annotation', prop),
133
+ '@type-binding.name': syntheticCapture('@type-binding.name', inferred.name, inferred.name.text),
134
+ '@type-binding.type': syntheticCapture('@type-binding.type', inferred.source, normalizeKotlinType(inferred.rawType)),
135
+ });
136
+ }
137
+ }
138
+ }
139
+ return out;
140
+ }
141
+ function collectKotlinLocalTypeTexts(fnNode, returnTypes) {
142
+ const out = new Map();
143
+ for (const node of descendants(fnNode)) {
144
+ if (node.type === 'parameter') {
145
+ const name = descendantsOfType(node, 'simple_identifier')[0];
146
+ const type = node.namedChildren.find((child) => isKotlinTypeNode(child));
147
+ if (name !== undefined && type !== undefined)
148
+ out.set(name.text, type.text);
149
+ continue;
150
+ }
151
+ if (node.type === 'property_declaration') {
152
+ const inferred = inferKotlinPropertyType(node, out, returnTypes);
153
+ if (inferred !== null)
154
+ out.set(inferred.name.text, inferred.rawType);
155
+ }
156
+ }
157
+ return out;
158
+ }
159
+ function collectKotlinReturnTypeTexts(rootNode) {
160
+ const out = new Map();
161
+ for (const fnNode of descendantsOfType(rootNode, 'function_declaration')) {
162
+ const name = fnNode.namedChildren.find((child) => child.type === 'simple_identifier');
163
+ const paramsIndex = fnNode.namedChildren.findIndex((child) => child.type === 'function_value_parameters');
164
+ const type = paramsIndex < 0
165
+ ? undefined
166
+ : fnNode.namedChildren.slice(paramsIndex + 1).find((child) => isKotlinTypeNode(child));
167
+ if (name !== undefined && type !== undefined)
168
+ out.set(name.text, type.text);
169
+ }
170
+ return out;
171
+ }
172
+ function inferKotlinPropertyType(prop, localTypes, returnTypes) {
173
+ const variable = prop.namedChildren.find((child) => child.type === 'variable_declaration');
174
+ const name = variable?.namedChildren.find((child) => child.type === 'simple_identifier');
175
+ if (variable === undefined || name === undefined)
176
+ return null;
177
+ const explicitType = variable.namedChildren.find((child) => isKotlinTypeNode(child));
178
+ if (explicitType !== undefined) {
179
+ return { name, rawType: explicitType.text, source: explicitType, synthetic: false };
180
+ }
181
+ const value = prop.namedChildren.find((child) => child.id !== variable.id && child.type !== 'binding_pattern_kind');
182
+ if (value?.type === 'simple_identifier') {
183
+ const rawType = localTypes.get(value.text);
184
+ return rawType === undefined ? null : { name, rawType, source: value, synthetic: true };
185
+ }
186
+ if (value?.type === 'call_expression') {
187
+ const callee = value.namedChildren.find((child) => child.type === 'simple_identifier');
188
+ if (callee === undefined)
189
+ return null;
190
+ const rawType = returnTypes.get(callee.text) ?? (isUppercaseName(callee.text) ? callee.text : null);
191
+ if (rawType === null)
192
+ return null;
193
+ return { name, rawType, source: callee, synthetic: true };
194
+ }
195
+ return null;
196
+ }
197
+ function inferKotlinIterableElementType(iterable, localTypes, returnTypes) {
198
+ if (iterable.type === 'simple_identifier') {
199
+ const raw = localTypes.get(iterable.text);
200
+ return raw === undefined ? null : kotlinContainerElementType(raw, 'values');
201
+ }
202
+ if (iterable.type === 'navigation_expression') {
203
+ const receiver = iterable.namedChildren[0];
204
+ const member = iterable.namedChildren
205
+ .find((child) => child.type === 'navigation_suffix')
206
+ ?.namedChildren.find((child) => child.type === 'simple_identifier')?.text;
207
+ if (receiver?.type !== 'simple_identifier')
208
+ return null;
209
+ const raw = localTypes.get(receiver.text);
210
+ return raw === undefined ? null : kotlinContainerElementType(raw, member ?? 'values');
211
+ }
212
+ if (iterable.type === 'call_expression') {
213
+ const callee = iterable.namedChildren.find((child) => child.type === 'simple_identifier');
214
+ if (callee === undefined)
215
+ return null;
216
+ const raw = returnTypes.get(callee.text);
217
+ return raw === undefined ? null : kotlinContainerElementType(raw, 'values');
218
+ }
219
+ return null;
220
+ }
221
+ function isUppercaseName(text) {
222
+ return /^[A-Z]/.test(text);
223
+ }
224
+ function kotlinContainerElementType(rawType, member) {
225
+ const parsed = parseKotlinGeneric(rawType);
226
+ if (parsed === null)
227
+ return normalizeKotlinType(rawType);
228
+ const base = parsed.base.split('.').pop() ?? parsed.base;
229
+ if (isKotlinMapType(base)) {
230
+ if (member === 'keys')
231
+ return parsed.args[0] ?? null;
232
+ return parsed.args[1] ?? null;
233
+ }
234
+ if (isKotlinIterableType(base))
235
+ return parsed.args[0] ?? null;
236
+ return normalizeKotlinType(rawType);
237
+ }
238
+ function parseKotlinGeneric(text) {
239
+ const trimmed = text.trim().replace(/\?$/, '');
240
+ const open = trimmed.indexOf('<');
241
+ const close = trimmed.lastIndexOf('>');
242
+ if (open < 0 || close < open)
243
+ return null;
244
+ return {
245
+ base: trimmed.slice(0, open).trim(),
246
+ args: splitTopLevelKotlinArgs(trimmed.slice(open + 1, close)),
247
+ };
248
+ }
249
+ function splitTopLevelKotlinArgs(text) {
250
+ const out = [];
251
+ let depth = 0;
252
+ let start = 0;
253
+ for (let i = 0; i < text.length; i++) {
254
+ const ch = text[i];
255
+ if (ch === '<')
256
+ depth++;
257
+ else if (ch === '>')
258
+ depth--;
259
+ else if (ch === ',' && depth === 0) {
260
+ out.push(text.slice(start, i).trim());
261
+ start = i + 1;
262
+ }
263
+ }
264
+ out.push(text.slice(start).trim());
265
+ return out.filter((arg) => arg.length > 0);
266
+ }
267
+ function isKotlinMapType(base) {
268
+ return ['Map', 'MutableMap', 'HashMap', 'LinkedHashMap'].includes(base);
269
+ }
270
+ function isKotlinIterableType(base) {
271
+ return [
272
+ 'List',
273
+ 'MutableList',
274
+ 'ArrayList',
275
+ 'Set',
276
+ 'MutableSet',
277
+ 'Collection',
278
+ 'Iterable',
279
+ 'Sequence',
280
+ 'Array',
281
+ ].includes(base);
282
+ }
283
+ function isKotlinTypeNode(node) {
284
+ return (node.type === 'user_type' || node.type === 'nullable_type' || node.type === 'function_type');
285
+ }
286
+ function descendantsOfType(node, type) {
287
+ return descendants(node).filter((child) => child.type === type);
288
+ }
289
+ function descendants(node) {
290
+ const out = [];
291
+ for (let i = 0; i < node.namedChildCount; i++) {
292
+ const child = node.namedChild(i);
293
+ if (child === null)
294
+ continue;
295
+ out.push(child, ...descendants(child));
296
+ }
297
+ return out;
298
+ }
299
+ function shouldEmitReadMember(navNode) {
300
+ const parent = navNode.parent;
301
+ if (parent === null)
302
+ return true;
303
+ if (parent.type === 'call_expression')
304
+ return false;
305
+ if (parent.type === 'directly_assignable_expression')
306
+ return false;
307
+ return true;
308
+ }
309
+ function callArguments(callNode) {
310
+ const suffix = callNode.namedChildren.find((child) => child.type === 'call_suffix');
311
+ if (suffix === undefined)
312
+ return [];
313
+ const valueArgs = suffix?.namedChildren.find((child) => child.type === 'value_arguments');
314
+ const args = valueArgs?.namedChildren.filter((child) => child.type === 'value_argument') ?? [];
315
+ const trailingLambdas = suffix.namedChildren.filter((child) => child.type === 'annotated_lambda');
316
+ return [...args, ...trailingLambdas];
317
+ }
318
+ function inferArgType(argNode) {
319
+ const value = argNode.namedChild(0) ?? argNode;
320
+ switch (value.type) {
321
+ case 'integer_literal':
322
+ case 'long_literal':
323
+ return 'Int';
324
+ case 'real_literal':
325
+ return 'Double';
326
+ case 'string_literal':
327
+ case 'line_string_literal':
328
+ case 'multi_line_string_literal':
329
+ return 'String';
330
+ case 'character_literal':
331
+ return 'Char';
332
+ case 'boolean_literal':
333
+ return 'Boolean';
334
+ case 'call_expression': {
335
+ const first = value.namedChild(0);
336
+ return first?.type === 'simple_identifier' ? first.text : '';
337
+ }
338
+ default:
339
+ return '';
340
+ }
341
+ }
342
+ function extensionFreeCallFallback(grouped, rootNode) {
343
+ const member = grouped['@reference.call.member'];
344
+ const receiver = grouped['@reference.receiver'];
345
+ const name = grouped['@reference.name'];
346
+ if (member === undefined || receiver === undefined || name === undefined)
347
+ return null;
348
+ const callNode = findNodeAtRange(rootNode, member.range, 'call_expression');
349
+ if (callNode === null)
350
+ return null;
351
+ const receiverNode = findNodeAtRange(rootNode, receiver.range);
352
+ if (receiverNode === null || !isLiteralReceiver(receiverNode))
353
+ return null;
354
+ const out = {
355
+ '@reference.call.free': syntheticCapture('@reference.call.free', callNode, callNode.text),
356
+ '@reference.name': syntheticCapture('@reference.name', callNode, name.text),
357
+ };
358
+ if (grouped['@reference.arity'] !== undefined)
359
+ out['@reference.arity'] = grouped['@reference.arity'];
360
+ if (grouped['@reference.parameter-types'] !== undefined) {
361
+ out['@reference.parameter-types'] = grouped['@reference.parameter-types'];
362
+ }
363
+ return out;
364
+ }
365
+ function isLiteralReceiver(node) {
366
+ return [
367
+ 'integer_literal',
368
+ 'long_literal',
369
+ 'real_literal',
370
+ 'string_literal',
371
+ 'line_string_literal',
372
+ 'multi_line_string_literal',
373
+ 'character_literal',
374
+ 'boolean_literal',
375
+ ].includes(node.type);
376
+ }
@@ -0,0 +1,3 @@
1
+ import type { CaptureMatch } from '../../../../_shared/index.js';
2
+ import { type SyntaxNode } from '../../utils/ast-helpers.js';
3
+ export declare function splitKotlinImportHeader(importNode: SyntaxNode): CaptureMatch | null;
@@ -0,0 +1,37 @@
1
+ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
2
+ export function splitKotlinImportHeader(importNode) {
3
+ if (importNode.type !== 'import_header')
4
+ return null;
5
+ const spec = parseKotlinImport(importNode);
6
+ if (spec === null)
7
+ return null;
8
+ const out = {
9
+ '@import.statement': nodeToCapture('@import.statement', importNode),
10
+ '@import.kind': syntheticCapture('@import.kind', spec.atNode, spec.kind),
11
+ '@import.source': syntheticCapture('@import.source', spec.atNode, spec.source),
12
+ '@import.name': syntheticCapture('@import.name', spec.atNode, spec.name),
13
+ };
14
+ if (spec.alias !== undefined) {
15
+ out['@import.alias'] = syntheticCapture('@import.alias', spec.atNode, spec.alias);
16
+ }
17
+ return out;
18
+ }
19
+ function parseKotlinImport(node) {
20
+ const identifier = node.namedChildren.find((child) => child.type === 'identifier');
21
+ if (identifier === undefined)
22
+ return null;
23
+ const source = identifier.text.trim();
24
+ if (source.length === 0)
25
+ return null;
26
+ const hasWildcard = node.namedChildren.some((child) => child.type === 'wildcard_import');
27
+ if (hasWildcard) {
28
+ return { kind: 'wildcard', source, name: '*', atNode: node };
29
+ }
30
+ const aliasNode = node.namedChildren.find((child) => child.type === 'import_alias');
31
+ const alias = aliasNode?.namedChildren.find((child) => child.type === 'type_identifier')?.text;
32
+ const importedName = source.split('.').pop() ?? source;
33
+ if (alias !== undefined && alias.length > 0) {
34
+ return { kind: 'alias', source, name: importedName, alias, atNode: node };
35
+ }
36
+ return { kind: 'named', source, name: importedName, atNode: node };
37
+ }
@@ -0,0 +1,6 @@
1
+ import type { ParsedImport, WorkspaceIndex } from '../../../../_shared/index.js';
2
+ export interface KotlinResolveContext {
3
+ readonly fromFile: string;
4
+ readonly allFilePaths: ReadonlySet<string>;
5
+ }
6
+ export declare function resolveKotlinImportTarget(parsedImport: ParsedImport, workspaceIndex: WorkspaceIndex): string | null;
@@ -0,0 +1,60 @@
1
+ export function resolveKotlinImportTarget(parsedImport, workspaceIndex) {
2
+ const ctx = workspaceIndex;
3
+ if (ctx === undefined ||
4
+ typeof ctx.fromFile !== 'string' ||
5
+ !(ctx.allFilePaths instanceof Set)) {
6
+ return null;
7
+ }
8
+ if (parsedImport.kind === 'dynamic-unresolved')
9
+ return null;
10
+ if (parsedImport.targetRaw === null || parsedImport.targetRaw === '')
11
+ return null;
12
+ const target = parsedImport.targetRaw.endsWith('.*')
13
+ ? parsedImport.targetRaw.slice(0, -2)
14
+ : parsedImport.targetRaw;
15
+ const pathLike = target.replace(/\./g, '/');
16
+ return (findKotlinFile(ctx.allFilePaths, pathLike) ??
17
+ findKotlinFile(ctx.allFilePaths, pathLike.split('/').slice(0, -1).join('/')) ??
18
+ findByProgressivePrefixStrip(ctx.allFilePaths, pathLike));
19
+ }
20
+ function findKotlinFile(allFilePaths, pathLike) {
21
+ if (pathLike === '')
22
+ return null;
23
+ const extensions = ['.kt', '.kts'];
24
+ const suffix = `/${pathLike}`;
25
+ const dirPrefix = `${pathLike}/`;
26
+ const suffixDirPrefix = `/${dirPrefix}`;
27
+ let suffixFile = null;
28
+ let directoryChild = null;
29
+ for (const raw of allFilePaths) {
30
+ const file = raw.replace(/\\/g, '/');
31
+ if (!extensions.some((ext) => file.endsWith(ext)))
32
+ continue;
33
+ for (const ext of extensions) {
34
+ if (file === `${pathLike}${ext}`)
35
+ return raw;
36
+ if (suffixFile === null && file.endsWith(`${suffix}${ext}`))
37
+ suffixFile = raw;
38
+ }
39
+ if (directoryChild === null) {
40
+ const atRoot = file.startsWith(dirPrefix);
41
+ const atNested = file.includes(suffixDirPrefix);
42
+ if (atRoot || atNested) {
43
+ const idx = atRoot ? 0 : file.indexOf(suffixDirPrefix) + 1;
44
+ const after = file.slice(idx + dirPrefix.length);
45
+ if (after.length > 0 && !after.includes('/'))
46
+ directoryChild = raw;
47
+ }
48
+ }
49
+ }
50
+ return suffixFile ?? directoryChild;
51
+ }
52
+ function findByProgressivePrefixStrip(allFilePaths, pathLike) {
53
+ const segments = pathLike.split('/').filter(Boolean);
54
+ for (let skip = 1; skip < segments.length; skip++) {
55
+ const found = findKotlinFile(allFilePaths, segments.slice(skip).join('/'));
56
+ if (found !== null)
57
+ return found;
58
+ }
59
+ return null;
60
+ }
@@ -0,0 +1,8 @@
1
+ export { emitKotlinScopeCaptures } from './captures.js';
2
+ export { getKotlinCaptureCacheStats, resetKotlinCaptureCacheStats } from './cache-stats.js';
3
+ export { interpretKotlinImport, interpretKotlinTypeBinding } from './interpret.js';
4
+ export { kotlinArityCompatibility } from './arity.js';
5
+ export { resolveKotlinImportTarget, type KotlinResolveContext } from './import-target.js';
6
+ export { kotlinMergeBindings } from './merge-bindings.js';
7
+ export { populateKotlinOwners } from './owners.js';
8
+ export { kotlinBindingScopeFor, kotlinImportOwningScope, kotlinReceiverBinding, } from './simple-hooks.js';
@@ -0,0 +1,8 @@
1
+ export { emitKotlinScopeCaptures } from './captures.js';
2
+ export { getKotlinCaptureCacheStats, resetKotlinCaptureCacheStats } from './cache-stats.js';
3
+ export { interpretKotlinImport, interpretKotlinTypeBinding } from './interpret.js';
4
+ export { kotlinArityCompatibility } from './arity.js';
5
+ export { resolveKotlinImportTarget } from './import-target.js';
6
+ export { kotlinMergeBindings } from './merge-bindings.js';
7
+ export { populateKotlinOwners } from './owners.js';
8
+ export { kotlinBindingScopeFor, kotlinImportOwningScope, kotlinReceiverBinding, } from './simple-hooks.js';
@@ -0,0 +1,4 @@
1
+ import type { CaptureMatch, ParsedImport, ParsedTypeBinding } from '../../../../_shared/index.js';
2
+ export declare function interpretKotlinImport(captures: CaptureMatch): ParsedImport | null;
3
+ export declare function interpretKotlinTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null;
4
+ export declare function normalizeKotlinType(text: string): string;
@@ -0,0 +1,70 @@
1
+ export function interpretKotlinImport(captures) {
2
+ const kind = captures['@import.kind']?.text;
3
+ const source = captures['@import.source']?.text;
4
+ const name = captures['@import.name']?.text;
5
+ if (kind === undefined || source === undefined)
6
+ return null;
7
+ switch (kind) {
8
+ case 'named':
9
+ return {
10
+ kind: 'named',
11
+ localName: name ?? source.split('.').pop() ?? source,
12
+ importedName: name ?? source.split('.').pop() ?? source,
13
+ targetRaw: source,
14
+ };
15
+ case 'alias': {
16
+ const alias = captures['@import.alias']?.text;
17
+ if (alias === undefined || name === undefined)
18
+ return null;
19
+ return {
20
+ kind: 'alias',
21
+ localName: alias,
22
+ importedName: name,
23
+ alias,
24
+ targetRaw: source,
25
+ };
26
+ }
27
+ case 'wildcard':
28
+ return { kind: 'wildcard', targetRaw: source.endsWith('.*') ? source : `${source}.*` };
29
+ default:
30
+ return null;
31
+ }
32
+ }
33
+ export function interpretKotlinTypeBinding(captures) {
34
+ const nameCap = captures['@type-binding.name'];
35
+ const typeCap = captures['@type-binding.type'];
36
+ if (nameCap === undefined || typeCap === undefined)
37
+ return null;
38
+ let source = 'annotation';
39
+ if (captures['@type-binding.self'] !== undefined)
40
+ source = 'self';
41
+ else if (captures['@type-binding.parameter'] !== undefined)
42
+ source = 'parameter-annotation';
43
+ else if (captures['@type-binding.return'] !== undefined)
44
+ source = 'return-annotation';
45
+ else if (captures['@type-binding.constructor'] !== undefined)
46
+ source = 'constructor-inferred';
47
+ return {
48
+ boundName: nameCap.text,
49
+ rawTypeName: normalizeKotlinType(typeCap.text),
50
+ source,
51
+ };
52
+ }
53
+ export function normalizeKotlinType(text) {
54
+ let out = text.trim();
55
+ while (out.endsWith('?'))
56
+ out = out.slice(0, -1).trim();
57
+ const lastDot = out.lastIndexOf('.');
58
+ if (lastDot >= 0)
59
+ out = out.slice(lastDot + 1);
60
+ const collection = out.match(/^(?:List|MutableList|ArrayList|Set|MutableSet|Collection|Iterable|Sequence|Array)<([^,<>]+)>$/);
61
+ if (collection !== null)
62
+ return normalizeKotlinType(collection[1]);
63
+ const map = out.match(/^(?:Map|MutableMap|HashMap|LinkedHashMap)<[^,<>]+,\s*([^,<>]+)>$/);
64
+ if (map !== null)
65
+ return normalizeKotlinType(map[1]);
66
+ const erased = out.match(/^([A-Za-z_][A-Za-z0-9_]*)<.+>$/s);
67
+ if (erased !== null)
68
+ return erased[1];
69
+ return out;
70
+ }
@@ -0,0 +1,2 @@
1
+ import type { BindingRef } from '../../../../_shared/index.js';
2
+ export declare function kotlinMergeBindings(bindings: readonly BindingRef[]): readonly BindingRef[];
@@ -0,0 +1,25 @@
1
+ function tierOf(binding) {
2
+ switch (binding.origin) {
3
+ case 'local':
4
+ return 0;
5
+ case 'import':
6
+ case 'namespace':
7
+ case 'reexport':
8
+ return 1;
9
+ case 'wildcard':
10
+ return 2;
11
+ default:
12
+ return 3;
13
+ }
14
+ }
15
+ export function kotlinMergeBindings(bindings) {
16
+ if (bindings.length === 0)
17
+ return bindings;
18
+ const best = Math.min(...bindings.map(tierOf));
19
+ const seen = new Map();
20
+ for (const binding of bindings) {
21
+ if (tierOf(binding) === best)
22
+ seen.set(binding.def.nodeId, binding);
23
+ }
24
+ return [...seen.values()];
25
+ }
@@ -0,0 +1,2 @@
1
+ import type { ParsedFile } from '../../../../_shared/index.js';
2
+ export declare function populateKotlinOwners(parsed: ParsedFile): void;
@@ -0,0 +1,50 @@
1
+ import { isClassLike, populateClassOwnedMembers } from '../../scope-resolution/scope/walkers.js';
2
+ export function populateKotlinOwners(parsed) {
3
+ populateClassOwnedMembers(parsed);
4
+ populateCompanionMembersOnEnclosingClass(parsed);
5
+ }
6
+ function populateCompanionMembersOnEnclosingClass(parsed) {
7
+ const scopesById = new Map();
8
+ for (const scope of parsed.scopes)
9
+ scopesById.set(scope.id, scope);
10
+ for (const scope of parsed.scopes) {
11
+ if (scope.kind !== 'Function' || scope.parent === null)
12
+ continue;
13
+ const parent = scopesById.get(scope.parent);
14
+ if (parent === undefined || parent.kind !== 'Class')
15
+ continue;
16
+ if (parent.ownedDefs.some((def) => isClassLike(def.type)))
17
+ continue;
18
+ const enclosing = findEnclosingClassWithDef(parent.parent, scopesById);
19
+ if (enclosing === undefined)
20
+ continue;
21
+ for (const def of scope.ownedDefs) {
22
+ if (def.ownerId !== undefined)
23
+ continue;
24
+ def.ownerId = enclosing.nodeId;
25
+ qualify(def, enclosing);
26
+ }
27
+ }
28
+ }
29
+ function findEnclosingClassWithDef(start, scopesById) {
30
+ let current = start;
31
+ while (current !== null) {
32
+ const scope = scopesById.get(current);
33
+ if (scope === undefined)
34
+ return undefined;
35
+ if (scope.kind === 'Class') {
36
+ const classDef = scope.ownedDefs.find((def) => isClassLike(def.type));
37
+ if (classDef !== undefined)
38
+ return classDef;
39
+ }
40
+ current = scope.parent;
41
+ }
42
+ return undefined;
43
+ }
44
+ function qualify(def, owner) {
45
+ if (def.qualifiedName === undefined || def.qualifiedName.includes('.'))
46
+ return;
47
+ if (owner.qualifiedName === undefined || owner.qualifiedName.length === 0)
48
+ return;
49
+ def.qualifiedName = `${owner.qualifiedName}.${def.qualifiedName}`;
50
+ }
@@ -0,0 +1,3 @@
1
+ import Parser from 'tree-sitter';
2
+ export declare function getKotlinParser(): Parser;
3
+ export declare function getKotlinScopeQuery(): Parser.Query;
@@ -0,0 +1,112 @@
1
+ import Parser from 'tree-sitter';
2
+ import Kotlin from 'tree-sitter-kotlin';
3
+ const KOTLIN_SCOPE_QUERY = `
4
+ ;; Scopes
5
+ (source_file) @scope.module
6
+ (class_declaration) @scope.class
7
+ (object_declaration) @scope.class
8
+ (companion_object) @scope.class
9
+ (function_declaration) @scope.function
10
+
11
+ ;; Declarations — types
12
+ (class_declaration
13
+ "interface"
14
+ (type_identifier) @declaration.name) @declaration.interface
15
+
16
+ (class_declaration
17
+ "class"
18
+ (type_identifier) @declaration.name) @declaration.class
19
+
20
+ (object_declaration
21
+ (type_identifier) @declaration.name) @declaration.class
22
+
23
+ (companion_object
24
+ (type_identifier) @declaration.name) @declaration.class
25
+
26
+ (type_alias
27
+ (type_identifier) @declaration.name) @declaration.type_alias
28
+
29
+ ;; Declarations — functions / methods / properties
30
+ (function_declaration
31
+ (simple_identifier) @declaration.name) @declaration.function
32
+
33
+ (property_declaration
34
+ (variable_declaration
35
+ (simple_identifier) @declaration.name)) @declaration.property
36
+
37
+ (class_parameter
38
+ (binding_pattern_kind)
39
+ (simple_identifier) @declaration.name) @declaration.property
40
+
41
+ ;; Imports
42
+ (import_header) @import.statement
43
+
44
+ ;; Type bindings — parameters
45
+ (parameter
46
+ (simple_identifier) @type-binding.name
47
+ [(user_type) (nullable_type) (function_type)] @type-binding.type) @type-binding.parameter
48
+
49
+ ;; Type bindings — property / local annotations
50
+ (property_declaration
51
+ (variable_declaration
52
+ (simple_identifier) @type-binding.name
53
+ [(user_type) (nullable_type) (function_type)] @type-binding.type)) @type-binding.annotation
54
+
55
+ (class_parameter
56
+ (binding_pattern_kind)
57
+ (simple_identifier) @type-binding.name
58
+ [(user_type) (nullable_type) (function_type)] @type-binding.type) @type-binding.annotation
59
+
60
+ ;; Type bindings — constructor-inferred val user = User(...)
61
+ (property_declaration
62
+ (variable_declaration
63
+ (simple_identifier) @type-binding.name)
64
+ (call_expression
65
+ (simple_identifier) @type-binding.type)) @type-binding.constructor
66
+
67
+ ;; Type bindings — return annotations after function parameters
68
+ (function_declaration
69
+ (simple_identifier) @type-binding.name
70
+ (function_value_parameters)
71
+ [(user_type) (nullable_type) (function_type)] @type-binding.type) @type-binding.return
72
+
73
+ ;; References — direct calls / constructor syntax
74
+ (call_expression
75
+ (simple_identifier) @reference.name) @reference.call.free
76
+
77
+ ;; References — member calls: obj.method()
78
+ (call_expression
79
+ (navigation_expression
80
+ (_) @reference.receiver
81
+ (navigation_suffix
82
+ (simple_identifier) @reference.name))) @reference.call.member
83
+
84
+ ;; References — property writes
85
+ (assignment
86
+ (directly_assignable_expression
87
+ (_) @reference.receiver
88
+ (navigation_suffix
89
+ (simple_identifier) @reference.name))
90
+ (_)) @reference.write.member
91
+
92
+ ;; References — property reads
93
+ (navigation_expression
94
+ (_) @reference.receiver
95
+ (navigation_suffix
96
+ (simple_identifier) @reference.name)) @reference.read.member
97
+ `;
98
+ let parser = null;
99
+ let query = null;
100
+ export function getKotlinParser() {
101
+ if (parser === null) {
102
+ parser = new Parser();
103
+ parser.setLanguage(Kotlin);
104
+ }
105
+ return parser;
106
+ }
107
+ export function getKotlinScopeQuery() {
108
+ if (query === null) {
109
+ query = new Parser.Query(Kotlin, KOTLIN_SCOPE_QUERY);
110
+ }
111
+ return query;
112
+ }
@@ -0,0 +1,3 @@
1
+ import type { CaptureMatch } from '../../../../_shared/index.js';
2
+ import { type SyntaxNode } from '../../utils/ast-helpers.js';
3
+ export declare function synthesizeKotlinReceiverBinding(fnNode: SyntaxNode): CaptureMatch[];
@@ -0,0 +1,100 @@
1
+ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
2
+ import { normalizeKotlinType } from './interpret.js';
3
+ const TYPE_DECL_NODE_TYPES = new Set([
4
+ 'class_declaration',
5
+ 'object_declaration',
6
+ 'companion_object',
7
+ ]);
8
+ export function synthesizeKotlinReceiverBinding(fnNode) {
9
+ if (fnNode.type !== 'function_declaration')
10
+ return [];
11
+ const anchorNode = findFunctionBody(fnNode);
12
+ if (anchorNode === null)
13
+ return [];
14
+ const extensionReceiver = extensionReceiverType(fnNode);
15
+ if (extensionReceiver !== null) {
16
+ return [buildReceiverMatch(anchorNode, 'this', extensionReceiver)];
17
+ }
18
+ const enclosingType = findEnclosingTypeDeclaration(fnNode);
19
+ if (enclosingType === null)
20
+ return [];
21
+ const enclosingName = typeDeclarationName(enclosingType);
22
+ if (enclosingName === null)
23
+ return [];
24
+ const out = [buildReceiverMatch(anchorNode, 'this', enclosingName)];
25
+ const superName = firstSuperclassText(enclosingType);
26
+ if (superName !== null)
27
+ out.push(buildReceiverMatch(anchorNode, 'super', superName));
28
+ return out;
29
+ }
30
+ function findFunctionBody(fnNode) {
31
+ for (let i = 0; i < fnNode.namedChildCount; i++) {
32
+ const child = fnNode.namedChild(i);
33
+ if (child?.type === 'function_body')
34
+ return child;
35
+ }
36
+ return fnNode;
37
+ }
38
+ function extensionReceiverType(fnNode) {
39
+ for (let i = 0; i < fnNode.namedChildCount; i++) {
40
+ const child = fnNode.namedChild(i);
41
+ if (child === null)
42
+ continue;
43
+ if (child.type === 'simple_identifier')
44
+ return null;
45
+ if (child.type === 'user_type' || child.type === 'nullable_type') {
46
+ return normalizeKotlinType(child.text);
47
+ }
48
+ }
49
+ return null;
50
+ }
51
+ function findEnclosingTypeDeclaration(node) {
52
+ let current = node.parent;
53
+ while (current !== null) {
54
+ if (TYPE_DECL_NODE_TYPES.has(current.type))
55
+ return current;
56
+ current = current.parent;
57
+ }
58
+ return null;
59
+ }
60
+ function typeDeclarationName(typeNode) {
61
+ if (typeNode.type === 'companion_object') {
62
+ return (typeNode.namedChildren.find((child) => child.type === 'type_identifier')?.text ??
63
+ enclosingNonCompanionTypeName(typeNode) ??
64
+ 'Companion');
65
+ }
66
+ return typeNode.namedChildren.find((child) => child.type === 'type_identifier')?.text ?? null;
67
+ }
68
+ function enclosingNonCompanionTypeName(node) {
69
+ let current = node.parent;
70
+ while (current !== null) {
71
+ if (current.type === 'class_declaration' || current.type === 'object_declaration') {
72
+ return current.namedChildren.find((child) => child.type === 'type_identifier')?.text ?? null;
73
+ }
74
+ current = current.parent;
75
+ }
76
+ return null;
77
+ }
78
+ function firstSuperclassText(typeNode) {
79
+ if (typeNode.type !== 'class_declaration')
80
+ return null;
81
+ for (const child of typeNode.namedChildren) {
82
+ if (child.type !== 'delegation_specifier')
83
+ continue;
84
+ const ctor = child.namedChildren.find((n) => n.type === 'constructor_invocation');
85
+ const userType = ctor?.namedChildren.find((n) => n.type === 'user_type') ??
86
+ child.namedChildren.find((n) => n.type === 'user_type');
87
+ const name = userType?.namedChildren.find((n) => n.type === 'type_identifier')?.text;
88
+ if (name !== undefined)
89
+ return normalizeKotlinType(name);
90
+ }
91
+ return null;
92
+ }
93
+ function buildReceiverMatch(anchorNode, name, typeText) {
94
+ const out = {
95
+ '@type-binding.self': nodeToCapture('@type-binding.self', anchorNode),
96
+ '@type-binding.name': syntheticCapture('@type-binding.name', anchorNode, name),
97
+ '@type-binding.type': syntheticCapture('@type-binding.type', anchorNode, typeText),
98
+ };
99
+ return out;
100
+ }
@@ -0,0 +1,17 @@
1
+ import type { ScopeResolver } from '../../scope-resolution/contract/scope-resolver.js';
2
+ /**
3
+ * Kotlin scope resolver for RFC #909 Ring 3.
4
+ *
5
+ * Kotlin is intentionally registered but not yet listed in
6
+ * `MIGRATED_LANGUAGES`, matching the Java migration pattern from #1482:
7
+ * the resolver can run in shadow/forced mode, while production default
8
+ * stays on the legacy DAG until registry-primary parity reaches the
9
+ * RFC threshold. Forced mode currently passes 154/175 fixtures (88%),
10
+ * including core import, receiver, companion, default-param, vararg,
11
+ * constructor, local assignment-chain, and collection-iteration fixtures.
12
+ * Remaining gaps are advanced TypeEnv behaviors such as smart casts,
13
+ * cross-file iterable return propagation, method-chain fixpoint cases,
14
+ * overload target-id selection, virtual dispatch, and interface default
15
+ * method dispatch.
16
+ */
17
+ export declare const kotlinScopeResolver: ScopeResolver;
@@ -0,0 +1,37 @@
1
+ import { SupportedLanguages } from '../../../../_shared/index.js';
2
+ import { buildMro, defaultLinearize } from '../../scope-resolution/passes/mro.js';
3
+ import { kotlinProvider } from '../kotlin.js';
4
+ import { kotlinArityCompatibility, kotlinMergeBindings, populateKotlinOwners, resolveKotlinImportTarget, } from './index.js';
5
+ /**
6
+ * Kotlin scope resolver for RFC #909 Ring 3.
7
+ *
8
+ * Kotlin is intentionally registered but not yet listed in
9
+ * `MIGRATED_LANGUAGES`, matching the Java migration pattern from #1482:
10
+ * the resolver can run in shadow/forced mode, while production default
11
+ * stays on the legacy DAG until registry-primary parity reaches the
12
+ * RFC threshold. Forced mode currently passes 154/175 fixtures (88%),
13
+ * including core import, receiver, companion, default-param, vararg,
14
+ * constructor, local assignment-chain, and collection-iteration fixtures.
15
+ * Remaining gaps are advanced TypeEnv behaviors such as smart casts,
16
+ * cross-file iterable return propagation, method-chain fixpoint cases,
17
+ * overload target-id selection, virtual dispatch, and interface default
18
+ * method dispatch.
19
+ */
20
+ export const kotlinScopeResolver = {
21
+ language: SupportedLanguages.Kotlin,
22
+ languageProvider: kotlinProvider,
23
+ importEdgeReason: 'kotlin-scope: import',
24
+ resolveImportTarget: (targetRaw, fromFile, allFilePaths) => {
25
+ const ws = { fromFile, allFilePaths };
26
+ return resolveKotlinImportTarget({ kind: 'named', localName: '_', importedName: '_', targetRaw }, ws);
27
+ },
28
+ mergeBindings: (existing, incoming) => [...kotlinMergeBindings([...existing, ...incoming])],
29
+ arityCompatibility: (callsite, def) => kotlinArityCompatibility(def, callsite),
30
+ buildMro: (graph, parsedFiles, nodeLookup) => buildMro(graph, parsedFiles, nodeLookup, defaultLinearize),
31
+ populateOwners: (parsed) => populateKotlinOwners(parsed),
32
+ isSuperReceiver: (text) => text.trim() === 'super',
33
+ fieldFallbackOnMethodLookup: false,
34
+ propagatesReturnTypesAcrossImports: true,
35
+ collapseMemberCallsByCallerTarget: false,
36
+ hoistTypeBindingsToModule: true,
37
+ };
@@ -0,0 +1,4 @@
1
+ import type { CaptureMatch, ParsedImport, Scope, ScopeId, ScopeTree, TypeRef } from '../../../../_shared/index.js';
2
+ export declare function kotlinBindingScopeFor(decl: CaptureMatch, innermost: Scope, tree: ScopeTree): ScopeId | null;
3
+ export declare function kotlinImportOwningScope(_imp: ParsedImport, _innermost: Scope, _tree: ScopeTree): ScopeId | null;
4
+ export declare function kotlinReceiverBinding(functionScope: Scope): TypeRef | null;
@@ -0,0 +1,19 @@
1
+ export function kotlinBindingScopeFor(decl, innermost, tree) {
2
+ if (decl['@type-binding.return'] === undefined)
3
+ return null;
4
+ let current = innermost;
5
+ while (current !== undefined && current.kind !== 'Module') {
6
+ if (current.parent === null)
7
+ break;
8
+ current = tree.getScope(current.parent);
9
+ }
10
+ return current?.kind === 'Module' ? current.id : null;
11
+ }
12
+ export function kotlinImportOwningScope(_imp, _innermost, _tree) {
13
+ return null;
14
+ }
15
+ export function kotlinReceiverBinding(functionScope) {
16
+ if (functionScope.kind !== 'Function')
17
+ return null;
18
+ return functionScope.typeBindings.get('this') ?? functionScope.typeBindings.get('super') ?? null;
19
+ }
@@ -26,6 +26,7 @@ import { kotlinMethodConfig } from '../method-extractors/configs/jvm.js';
26
26
  import { createVariableExtractor } from '../variable-extractors/generic.js';
27
27
  import { kotlinVariableConfig } from '../variable-extractors/configs/jvm.js';
28
28
  import { createHeritageExtractor } from '../heritage-extractors/generic.js';
29
+ import { emitKotlinScopeCaptures, interpretKotlinImport, interpretKotlinTypeBinding, kotlinArityCompatibility, kotlinBindingScopeFor, kotlinImportOwningScope, kotlinMergeBindings, kotlinReceiverBinding, } from './kotlin/index.js';
29
30
  /** Check if a Kotlin function_declaration capture is inside a class_body (i.e., a method).
30
31
  * Kotlin grammar uses function_declaration for both top-level functions and class methods.
31
32
  * Returns true when the captured definition node has a class_body ancestor. */
@@ -161,4 +162,13 @@ export const kotlinProvider = defineLanguage({
161
162
  return 'Method';
162
163
  return defaultLabel;
163
164
  },
165
+ // ── RFC #909 Ring 3: scope-based resolution hooks ──
166
+ emitScopeCaptures: emitKotlinScopeCaptures,
167
+ interpretImport: interpretKotlinImport,
168
+ interpretTypeBinding: interpretKotlinTypeBinding,
169
+ bindingScopeFor: kotlinBindingScopeFor,
170
+ importOwningScope: kotlinImportOwningScope,
171
+ mergeBindings: (_scope, bindings) => kotlinMergeBindings(bindings),
172
+ receiverBinding: kotlinReceiverBinding,
173
+ arityCompatibility: kotlinArityCompatibility,
164
174
  });
@@ -18,6 +18,7 @@ import { cScopeResolver } from '../../languages/c/scope-resolver.js';
18
18
  import { cppScopeResolver } from '../../languages/cpp/scope-resolver.js';
19
19
  import { phpScopeResolver } from '../../languages/php/scope-resolver.js';
20
20
  import { javascriptScopeResolver } from '../../languages/javascript/scope-resolver.js';
21
+ import { kotlinScopeResolver } from '../../languages/kotlin/scope-resolver.js';
21
22
  /** Map of `SupportedLanguages` → `ScopeResolver`. The phase iterates
22
23
  * this map intersected with `MIGRATED_LANGUAGES` (the per-language
23
24
  * flag set) so adding a resolver here without flipping the flag is
@@ -32,4 +33,5 @@ export const SCOPE_RESOLVERS = new Map([
32
33
  [SupportedLanguages.CPlusPlus, cppScopeResolver],
33
34
  [SupportedLanguages.PHP, phpScopeResolver],
34
35
  [SupportedLanguages.JavaScript, javascriptScopeResolver],
36
+ [SupportedLanguages.Kotlin, kotlinScopeResolver],
35
37
  ]);
@@ -597,6 +597,13 @@ export const createServer = async (port, host = '127.0.0.1') => {
597
597
  // host in those topologies (tracked as a follow-up; not blocking for the
598
598
  // local-bound default).
599
599
  app.set('trust proxy', 'loopback, linklocal, uniquelocal');
600
+ // Chromium Private Network Access (required since Chrome 130+). Must run before
601
+ // cors: the cors middleware ends OPTIONS preflight responses, so this header
602
+ // has to be set on res before cors writes the preflight reply.
603
+ app.use((_req, res, next) => {
604
+ res.setHeader('Access-Control-Allow-Private-Network', 'true');
605
+ next();
606
+ });
600
607
  // CORS: allow localhost, private/LAN networks, and the deployed site.
601
608
  // Non-browser requests (curl, server-to-server) have no origin and are allowed.
602
609
  // Disallowed origins get the response without Access-Control-Allow-Origin,
@@ -608,20 +615,6 @@ export const createServer = async (port, host = '127.0.0.1') => {
608
615
  },
609
616
  }));
610
617
  app.use(express.json({ limit: '10mb' }));
611
- // Support Chromium Private Network Access (required since Chrome 130+).
612
- // Without this header, Chrome/Edge/Brave/Arc block public->loopback requests
613
- // which breaks bridge mode entirely.
614
- app.use((_req, res, next) => {
615
- res.setHeader('Access-Control-Allow-Private-Network', 'true');
616
- next();
617
- });
618
- // Handle PNA preflight: Chromium sends Access-Control-Request-Private-Network
619
- // on OPTIONS requests and expects the allow header in the response.
620
- // Note: the actual Allow-Private-Network header is already set by the global
621
- // middleware above, so we just need to call next() here.
622
- app.options('*', (_req, res, next) => {
623
- next();
624
- });
625
618
  // Initialize MCP backend (multi-repo, shared across all MCP sessions)
626
619
  const backend = new LocalBackend();
627
620
  await backend.init();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.27",
3
+ "version": "1.6.6-rc.29",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",