gitnexus 1.6.5-rc.13 → 1.6.5-rc.14
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/dist/core/ingestion/languages/java/arity-metadata.d.ts +18 -0
- package/dist/core/ingestion/languages/java/arity-metadata.js +40 -0
- package/dist/core/ingestion/languages/java/arity.d.ts +10 -0
- package/dist/core/ingestion/languages/java/arity.js +24 -0
- package/dist/core/ingestion/languages/java/cache-stats.d.ts +15 -0
- package/dist/core/ingestion/languages/java/cache-stats.js +26 -0
- package/dist/core/ingestion/languages/java/captures.d.ts +17 -0
- package/dist/core/ingestion/languages/java/captures.js +187 -0
- package/dist/core/ingestion/languages/java/import-decomposer.d.ts +18 -0
- package/dist/core/ingestion/languages/java/import-decomposer.js +85 -0
- package/dist/core/ingestion/languages/java/import-target.d.ts +17 -0
- package/dist/core/ingestion/languages/java/import-target.js +100 -0
- package/dist/core/ingestion/languages/java/index.d.ts +29 -0
- package/dist/core/ingestion/languages/java/index.js +29 -0
- package/dist/core/ingestion/languages/java/interpret.d.ts +13 -0
- package/dist/core/ingestion/languages/java/interpret.js +131 -0
- package/dist/core/ingestion/languages/java/merge-bindings.d.ts +12 -0
- package/dist/core/ingestion/languages/java/merge-bindings.js +40 -0
- package/dist/core/ingestion/languages/java/query.d.ts +30 -0
- package/dist/core/ingestion/languages/java/query.js +192 -0
- package/dist/core/ingestion/languages/java/receiver-binding.d.ts +11 -0
- package/dist/core/ingestion/languages/java/receiver-binding.js +95 -0
- package/dist/core/ingestion/languages/java/scope-resolver.d.ts +50 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +74 -0
- package/dist/core/ingestion/languages/java/simple-hooks.d.ts +13 -0
- package/dist/core/ingestion/languages/java/simple-hooks.js +34 -0
- package/dist/core/ingestion/languages/java.js +11 -0
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract Java arity metadata from a method-like tree-sitter node —
|
|
3
|
+
* `method_declaration` or `constructor_declaration`.
|
|
4
|
+
*
|
|
5
|
+
* Reuses `javaMethodConfig.extractParameters` so scope-extracted defs
|
|
6
|
+
* carry the same arity semantics as the legacy parse-worker path:
|
|
7
|
+
* - varargs (`...`) collapses `parameterCount` to `undefined`
|
|
8
|
+
* - `parameterTypes` collects declared type names; a literal
|
|
9
|
+
* `'varargs'` marker is appended for variadic methods so
|
|
10
|
+
* `javaArityCompatibility` can detect them.
|
|
11
|
+
*/
|
|
12
|
+
import type { SyntaxNode } from '../../utils/ast-helpers.js';
|
|
13
|
+
export interface JavaArityMetadata {
|
|
14
|
+
readonly parameterCount: number | undefined;
|
|
15
|
+
readonly requiredParameterCount: number | undefined;
|
|
16
|
+
readonly parameterTypes: readonly string[] | undefined;
|
|
17
|
+
}
|
|
18
|
+
export declare function computeJavaArityMetadata(fnNode: SyntaxNode): JavaArityMetadata;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract Java arity metadata from a method-like tree-sitter node —
|
|
3
|
+
* `method_declaration` or `constructor_declaration`.
|
|
4
|
+
*
|
|
5
|
+
* Reuses `javaMethodConfig.extractParameters` so scope-extracted defs
|
|
6
|
+
* carry the same arity semantics as the legacy parse-worker path:
|
|
7
|
+
* - varargs (`...`) collapses `parameterCount` to `undefined`
|
|
8
|
+
* - `parameterTypes` collects declared type names; a literal
|
|
9
|
+
* `'varargs'` marker is appended for variadic methods so
|
|
10
|
+
* `javaArityCompatibility` can detect them.
|
|
11
|
+
*/
|
|
12
|
+
import { javaMethodConfig } from '../../method-extractors/configs/jvm.js';
|
|
13
|
+
export function computeJavaArityMetadata(fnNode) {
|
|
14
|
+
const params = javaMethodConfig.extractParameters?.(fnNode) ?? [];
|
|
15
|
+
let hasVariadic = false;
|
|
16
|
+
const types = [];
|
|
17
|
+
for (const p of params) {
|
|
18
|
+
if (p.isVariadic)
|
|
19
|
+
hasVariadic = true;
|
|
20
|
+
if (p.type !== null)
|
|
21
|
+
types.push(p.type);
|
|
22
|
+
}
|
|
23
|
+
if (hasVariadic)
|
|
24
|
+
types.push('varargs');
|
|
25
|
+
const total = params.length;
|
|
26
|
+
// For varargs methods, `parameterCount` (max) is unknown — any number of
|
|
27
|
+
// trailing arguments is valid. But the fixed-prefix parameters (everything
|
|
28
|
+
// before the variadic `...` param) are still required, so we preserve that
|
|
29
|
+
// count in `requiredParameterCount` so `javaArityCompatibility` can reject
|
|
30
|
+
// calls that undersupply the fixed prefix (e.g. `f(int x, String... args)`
|
|
31
|
+
// called with 0 args).
|
|
32
|
+
const fixedCount = params.filter((p) => !p.isVariadic).length;
|
|
33
|
+
const parameterCount = hasVariadic ? undefined : total;
|
|
34
|
+
const requiredParameterCount = hasVariadic ? fixedCount : total;
|
|
35
|
+
return {
|
|
36
|
+
parameterCount,
|
|
37
|
+
requiredParameterCount,
|
|
38
|
+
parameterTypes: types.length > 0 ? types : undefined,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Java arity check, accommodating varargs (`...`).
|
|
3
|
+
*
|
|
4
|
+
* Verdicts:
|
|
5
|
+
* - `'compatible'` — argCount matches parameterCount, OR varargs present.
|
|
6
|
+
* - `'incompatible'` — argCount mismatches with no varargs.
|
|
7
|
+
* - `'unknown'` — metadata absent / incomplete.
|
|
8
|
+
*/
|
|
9
|
+
import type { Callsite, SymbolDefinition } from '../../../../_shared/index.js';
|
|
10
|
+
export declare function javaArityCompatibility(def: SymbolDefinition, callsite: Callsite): 'compatible' | 'unknown' | 'incompatible';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Java arity check, accommodating varargs (`...`).
|
|
3
|
+
*
|
|
4
|
+
* Verdicts:
|
|
5
|
+
* - `'compatible'` — argCount matches parameterCount, OR varargs present.
|
|
6
|
+
* - `'incompatible'` — argCount mismatches with no varargs.
|
|
7
|
+
* - `'unknown'` — metadata absent / incomplete.
|
|
8
|
+
*/
|
|
9
|
+
export function javaArityCompatibility(def, callsite) {
|
|
10
|
+
const max = def.parameterCount;
|
|
11
|
+
const min = def.requiredParameterCount;
|
|
12
|
+
if (max === undefined && min === undefined)
|
|
13
|
+
return 'unknown';
|
|
14
|
+
const argCount = callsite.arity;
|
|
15
|
+
if (!Number.isFinite(argCount) || argCount < 0)
|
|
16
|
+
return 'unknown';
|
|
17
|
+
const hasVarArgs = def.parameterTypes !== undefined &&
|
|
18
|
+
def.parameterTypes.some((t) => t === 'varargs' || t.includes('...'));
|
|
19
|
+
if (min !== undefined && argCount < min)
|
|
20
|
+
return 'incompatible';
|
|
21
|
+
if (max !== undefined && argCount > max && !hasVarArgs)
|
|
22
|
+
return 'incompatible';
|
|
23
|
+
return 'compatible';
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode counters for the cross-phase scope-captures parse cache
|
|
3
|
+
* (Java mirror of `languages/csharp/cache-stats.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Gated by `PROF_SCOPE_RESOLUTION=1`. Production builds fold every
|
|
6
|
+
* increment into dead code via the module-level `PROF` constant, so
|
|
7
|
+
* the hot path in `captures.ts` stays branch-free.
|
|
8
|
+
*/
|
|
9
|
+
export declare function recordCacheHit(): void;
|
|
10
|
+
export declare function recordCacheMiss(): void;
|
|
11
|
+
export declare function getJavaCaptureCacheStats(): {
|
|
12
|
+
hits: number;
|
|
13
|
+
misses: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function resetJavaCaptureCacheStats(): void;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev-mode counters for the cross-phase scope-captures parse cache
|
|
3
|
+
* (Java mirror of `languages/csharp/cache-stats.ts`).
|
|
4
|
+
*
|
|
5
|
+
* Gated by `PROF_SCOPE_RESOLUTION=1`. Production builds fold every
|
|
6
|
+
* increment into dead code via the module-level `PROF` constant, so
|
|
7
|
+
* the hot path in `captures.ts` stays branch-free.
|
|
8
|
+
*/
|
|
9
|
+
const PROF = process.env.PROF_SCOPE_RESOLUTION === '1';
|
|
10
|
+
let CACHE_HITS = 0;
|
|
11
|
+
let CACHE_MISSES = 0;
|
|
12
|
+
export function recordCacheHit() {
|
|
13
|
+
if (PROF)
|
|
14
|
+
CACHE_HITS++;
|
|
15
|
+
}
|
|
16
|
+
export function recordCacheMiss() {
|
|
17
|
+
if (PROF)
|
|
18
|
+
CACHE_MISSES++;
|
|
19
|
+
}
|
|
20
|
+
export function getJavaCaptureCacheStats() {
|
|
21
|
+
return { hits: CACHE_HITS, misses: CACHE_MISSES };
|
|
22
|
+
}
|
|
23
|
+
export function resetJavaCaptureCacheStats() {
|
|
24
|
+
CACHE_HITS = 0;
|
|
25
|
+
CACHE_MISSES = 0;
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `emitScopeCaptures` for Java.
|
|
3
|
+
*
|
|
4
|
+
* Drives the Java scope query against tree-sitter-java and groups raw
|
|
5
|
+
* matches into `CaptureMatch[]` for the central extractor. Layers:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Decomposed import declarations** — each `import_declaration`
|
|
8
|
+
* is re-emitted with `@import.kind/source/name` markers.
|
|
9
|
+
* 2. **Receiver binding synthesis** — `this`/`super` type-bindings
|
|
10
|
+
* on instance methods.
|
|
11
|
+
* 3. **Arity metadata** on method/constructor declarations.
|
|
12
|
+
* 4. **Reference arity** on call sites.
|
|
13
|
+
*
|
|
14
|
+
* Pure given the input source text. No I/O, no globals consulted.
|
|
15
|
+
*/
|
|
16
|
+
import type { CaptureMatch } from '../../../../_shared/index.js';
|
|
17
|
+
export declare function emitJavaScopeCaptures(sourceText: string, _filePath: string, cachedTree?: unknown): readonly CaptureMatch[];
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `emitScopeCaptures` for Java.
|
|
3
|
+
*
|
|
4
|
+
* Drives the Java scope query against tree-sitter-java and groups raw
|
|
5
|
+
* matches into `CaptureMatch[]` for the central extractor. Layers:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Decomposed import declarations** — each `import_declaration`
|
|
8
|
+
* is re-emitted with `@import.kind/source/name` markers.
|
|
9
|
+
* 2. **Receiver binding synthesis** — `this`/`super` type-bindings
|
|
10
|
+
* on instance methods.
|
|
11
|
+
* 3. **Arity metadata** on method/constructor declarations.
|
|
12
|
+
* 4. **Reference arity** on call sites.
|
|
13
|
+
*
|
|
14
|
+
* Pure given the input source text. No I/O, no globals consulted.
|
|
15
|
+
*/
|
|
16
|
+
import { findNodeAtRange, nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
17
|
+
import { splitImportDeclaration } from './import-decomposer.js';
|
|
18
|
+
import { computeJavaArityMetadata } from './arity-metadata.js';
|
|
19
|
+
import { synthesizeJavaReceiverBinding } from './receiver-binding.js';
|
|
20
|
+
import { getJavaParser, getJavaScopeQuery } from './query.js';
|
|
21
|
+
import { recordCacheHit, recordCacheMiss } from './cache-stats.js';
|
|
22
|
+
import { getTreeSitterBufferSize } from '../../constants.js';
|
|
23
|
+
import { parseSourceSafe } from '../../../tree-sitter/safe-parse.js';
|
|
24
|
+
/** Declaration anchors that carry function-like arity metadata. */
|
|
25
|
+
const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.constructor'];
|
|
26
|
+
/** tree-sitter-java node types that the method extractor accepts. */
|
|
27
|
+
const FUNCTION_NODE_TYPES = ['method_declaration', 'constructor_declaration'];
|
|
28
|
+
/** Suppress read.member emissions when the field_access is already
|
|
29
|
+
* covered by a method_invocation (object of a call) or an
|
|
30
|
+
* assignment_expression (write target). */
|
|
31
|
+
function shouldEmitReadMember(memberNode) {
|
|
32
|
+
const parent = memberNode.parent;
|
|
33
|
+
if (parent === null)
|
|
34
|
+
return true;
|
|
35
|
+
switch (parent.type) {
|
|
36
|
+
case 'method_invocation':
|
|
37
|
+
// Don't emit read.member when the field_access is the object of a method_invocation
|
|
38
|
+
// (the method call already handles this relationship)
|
|
39
|
+
return parent.childForFieldName('object')?.id !== memberNode.id;
|
|
40
|
+
case 'assignment_expression':
|
|
41
|
+
return parent.childForFieldName('left')?.id !== memberNode.id;
|
|
42
|
+
default:
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export function emitJavaScopeCaptures(sourceText, _filePath, cachedTree) {
|
|
47
|
+
let tree = cachedTree;
|
|
48
|
+
if (tree === undefined) {
|
|
49
|
+
tree = parseSourceSafe(getJavaParser(), sourceText, undefined, {
|
|
50
|
+
bufferSize: getTreeSitterBufferSize(sourceText),
|
|
51
|
+
});
|
|
52
|
+
recordCacheMiss();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
recordCacheHit();
|
|
56
|
+
}
|
|
57
|
+
const rawMatches = getJavaScopeQuery().matches(tree.rootNode);
|
|
58
|
+
const out = [];
|
|
59
|
+
for (const m of rawMatches) {
|
|
60
|
+
const grouped = {};
|
|
61
|
+
for (const c of m.captures) {
|
|
62
|
+
const tag = '@' + c.name;
|
|
63
|
+
grouped[tag] = nodeToCapture(tag, c.node);
|
|
64
|
+
}
|
|
65
|
+
if (Object.keys(grouped).length === 0)
|
|
66
|
+
continue;
|
|
67
|
+
// Decompose each `import_declaration`.
|
|
68
|
+
if (grouped['@import.statement'] !== undefined) {
|
|
69
|
+
const stmtCapture = grouped['@import.statement'];
|
|
70
|
+
const stmtNode = findNodeAtRange(tree.rootNode, stmtCapture.range, 'import_declaration');
|
|
71
|
+
if (stmtNode !== null) {
|
|
72
|
+
const decomposed = splitImportDeclaration(stmtNode);
|
|
73
|
+
if (decomposed !== null) {
|
|
74
|
+
out.push(decomposed);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
out.push(grouped);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// Skip free-call matches that are actually member calls. The query
|
|
82
|
+
// matches ALL method_invocations as @reference.call.free (without
|
|
83
|
+
// negation) because tree-sitter-java's query engine drops !object
|
|
84
|
+
// patterns when a positive object: pattern exists for the same node
|
|
85
|
+
// type. Filter here: if the match has @reference.call.free but also
|
|
86
|
+
// has @reference.receiver, it's a member call — skip the free match
|
|
87
|
+
// (the separate @reference.call.member match covers it).
|
|
88
|
+
if (grouped['@reference.call.free'] !== undefined &&
|
|
89
|
+
grouped['@reference.receiver'] !== undefined) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// Filter read.member when it's a child of method_invocation or assignment.
|
|
93
|
+
if (grouped['@reference.read.member'] !== undefined) {
|
|
94
|
+
const anchor = grouped['@reference.read.member'];
|
|
95
|
+
const memberNode = findNodeAtRange(tree.rootNode, anchor.range, 'field_access');
|
|
96
|
+
if (memberNode === null || !shouldEmitReadMember(memberNode)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Synthesize `this` / `super` receiver type-bindings on every
|
|
101
|
+
// instance method-like.
|
|
102
|
+
if (grouped['@scope.function'] !== undefined) {
|
|
103
|
+
out.push(grouped);
|
|
104
|
+
const anchor = grouped['@scope.function'];
|
|
105
|
+
const fnNode = findFunctionNode(tree.rootNode, anchor.range);
|
|
106
|
+
if (fnNode !== null) {
|
|
107
|
+
for (const synth of synthesizeJavaReceiverBinding(fnNode)) {
|
|
108
|
+
out.push(synth);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Synthesize arity metadata on function-like declarations.
|
|
114
|
+
const declTag = FUNCTION_DECL_TAGS.find((t) => grouped[t] !== undefined);
|
|
115
|
+
if (declTag !== undefined) {
|
|
116
|
+
const anchor = grouped[declTag];
|
|
117
|
+
const fnNode = findFunctionNode(tree.rootNode, anchor.range);
|
|
118
|
+
if (fnNode !== null) {
|
|
119
|
+
const arity = computeJavaArityMetadata(fnNode);
|
|
120
|
+
if (arity.parameterCount !== undefined) {
|
|
121
|
+
grouped['@declaration.parameter-count'] = syntheticCapture('@declaration.parameter-count', fnNode, String(arity.parameterCount));
|
|
122
|
+
}
|
|
123
|
+
if (arity.requiredParameterCount !== undefined) {
|
|
124
|
+
grouped['@declaration.required-parameter-count'] = syntheticCapture('@declaration.required-parameter-count', fnNode, String(arity.requiredParameterCount));
|
|
125
|
+
}
|
|
126
|
+
if (arity.parameterTypes !== undefined) {
|
|
127
|
+
grouped['@declaration.parameter-types'] = syntheticCapture('@declaration.parameter-types', fnNode, JSON.stringify(arity.parameterTypes));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Synthesize `@reference.arity` on every callsite.
|
|
132
|
+
const callTag = ['@reference.call.free', '@reference.call.member', '@reference.call.constructor'].find((t) => grouped[t] !== undefined);
|
|
133
|
+
if (callTag !== undefined && grouped['@reference.arity'] === undefined) {
|
|
134
|
+
const anchor = grouped[callTag];
|
|
135
|
+
const callNode = findNodeAtRange(tree.rootNode, anchor.range, 'method_invocation') ??
|
|
136
|
+
findNodeAtRange(tree.rootNode, anchor.range, 'object_creation_expression');
|
|
137
|
+
if (callNode !== null) {
|
|
138
|
+
const argList = callNode.childForFieldName('arguments');
|
|
139
|
+
const args = argList === null
|
|
140
|
+
? []
|
|
141
|
+
: argList.namedChildren.filter((c) => c !== null && c.type !== 'comment');
|
|
142
|
+
grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(args.length));
|
|
143
|
+
const argTypes = args.map((arg) => inferArgType(arg));
|
|
144
|
+
grouped['@reference.parameter-types'] = syntheticCapture('@reference.parameter-types', callNode, JSON.stringify(argTypes));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
out.push(grouped);
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
/** Infer a Java argument's static type from literal patterns. */
|
|
152
|
+
function inferArgType(argNode) {
|
|
153
|
+
switch (argNode.type) {
|
|
154
|
+
case 'decimal_integer_literal':
|
|
155
|
+
case 'hex_integer_literal':
|
|
156
|
+
case 'octal_integer_literal':
|
|
157
|
+
case 'binary_integer_literal':
|
|
158
|
+
return 'int';
|
|
159
|
+
case 'decimal_floating_point_literal':
|
|
160
|
+
case 'hex_floating_point_literal':
|
|
161
|
+
return 'double';
|
|
162
|
+
case 'string_literal':
|
|
163
|
+
return 'String';
|
|
164
|
+
case 'character_literal':
|
|
165
|
+
return 'char';
|
|
166
|
+
case 'true':
|
|
167
|
+
case 'false':
|
|
168
|
+
return 'boolean';
|
|
169
|
+
case 'null_literal':
|
|
170
|
+
return 'null';
|
|
171
|
+
case 'object_creation_expression': {
|
|
172
|
+
const typeNode = argNode.childForFieldName('type');
|
|
173
|
+
return typeNode?.text ?? '';
|
|
174
|
+
}
|
|
175
|
+
default:
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** Find the first Java function-like node at the given range. */
|
|
180
|
+
function findFunctionNode(rootNode, range) {
|
|
181
|
+
for (const nodeType of FUNCTION_NODE_TYPES) {
|
|
182
|
+
const n = findNodeAtRange(rootNode, range, nodeType);
|
|
183
|
+
if (n !== null)
|
|
184
|
+
return n;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decompose a Java `import_declaration` into a `CaptureMatch` carrying
|
|
3
|
+
* the synthesized markers `@import.kind` / `@import.source` /
|
|
4
|
+
* `@import.name` that `interpretJavaImport` consumes.
|
|
5
|
+
*
|
|
6
|
+
* Unlike C#'s using-directive decomposer, Java has four import forms:
|
|
7
|
+
*
|
|
8
|
+
* import com.example.User; → named
|
|
9
|
+
* import com.example.*; → wildcard
|
|
10
|
+
* import static com.example.Utils.format; → static
|
|
11
|
+
* import static com.example.Utils.*; → static-wildcard
|
|
12
|
+
*
|
|
13
|
+
* Each produces exactly one import. The decomposer inspects the raw
|
|
14
|
+
* source text and tree-sitter children to determine the flavor.
|
|
15
|
+
*/
|
|
16
|
+
import type { CaptureMatch } from '../../../../_shared/index.js';
|
|
17
|
+
import { type SyntaxNode } from '../../utils/ast-helpers.js';
|
|
18
|
+
export declare function splitImportDeclaration(stmtNode: SyntaxNode): CaptureMatch | null;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decompose a Java `import_declaration` into a `CaptureMatch` carrying
|
|
3
|
+
* the synthesized markers `@import.kind` / `@import.source` /
|
|
4
|
+
* `@import.name` that `interpretJavaImport` consumes.
|
|
5
|
+
*
|
|
6
|
+
* Unlike C#'s using-directive decomposer, Java has four import forms:
|
|
7
|
+
*
|
|
8
|
+
* import com.example.User; → named
|
|
9
|
+
* import com.example.*; → wildcard
|
|
10
|
+
* import static com.example.Utils.format; → static
|
|
11
|
+
* import static com.example.Utils.*; → static-wildcard
|
|
12
|
+
*
|
|
13
|
+
* Each produces exactly one import. The decomposer inspects the raw
|
|
14
|
+
* source text and tree-sitter children to determine the flavor.
|
|
15
|
+
*/
|
|
16
|
+
import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
|
|
17
|
+
export function splitImportDeclaration(stmtNode) {
|
|
18
|
+
if (stmtNode.type !== 'import_declaration')
|
|
19
|
+
return null;
|
|
20
|
+
const spec = parseImportDeclaration(stmtNode);
|
|
21
|
+
if (spec === null)
|
|
22
|
+
return null;
|
|
23
|
+
return buildImportMatch(stmtNode, spec);
|
|
24
|
+
}
|
|
25
|
+
function parseImportDeclaration(node) {
|
|
26
|
+
// Detect `static` by checking for an anonymous `static` token child.
|
|
27
|
+
let isStatic = false;
|
|
28
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
29
|
+
const child = node.child(i);
|
|
30
|
+
if (child !== null && child.type === 'static') {
|
|
31
|
+
isStatic = true;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Detect wildcard by checking for `asterisk` named child.
|
|
36
|
+
let isWildcard = false;
|
|
37
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
38
|
+
const child = node.namedChild(i);
|
|
39
|
+
if (child !== null && child.type === 'asterisk') {
|
|
40
|
+
isWildcard = true;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Find the scoped_identifier (or identifier for single-segment imports).
|
|
45
|
+
let pathNode = null;
|
|
46
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
47
|
+
const child = node.namedChild(i);
|
|
48
|
+
if (child !== null && (child.type === 'scoped_identifier' || child.type === 'identifier')) {
|
|
49
|
+
pathNode = child;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (pathNode === null)
|
|
54
|
+
return null;
|
|
55
|
+
const fullPath = pathNode.text;
|
|
56
|
+
if (fullPath === '')
|
|
57
|
+
return null;
|
|
58
|
+
if (isStatic && isWildcard) {
|
|
59
|
+
// `import static com.example.Utils.*;`
|
|
60
|
+
return { kind: 'static-wildcard', source: fullPath, name: '*', atNode: node };
|
|
61
|
+
}
|
|
62
|
+
if (isStatic) {
|
|
63
|
+
// `import static com.example.Utils.format;`
|
|
64
|
+
const lastDot = fullPath.lastIndexOf('.');
|
|
65
|
+
const name = lastDot >= 0 ? fullPath.slice(lastDot + 1) : fullPath;
|
|
66
|
+
return { kind: 'static', source: fullPath, name, atNode: node };
|
|
67
|
+
}
|
|
68
|
+
if (isWildcard) {
|
|
69
|
+
// `import com.example.*;`
|
|
70
|
+
return { kind: 'wildcard', source: fullPath, name: '*', atNode: node };
|
|
71
|
+
}
|
|
72
|
+
// `import com.example.User;`
|
|
73
|
+
const lastDot = fullPath.lastIndexOf('.');
|
|
74
|
+
const name = lastDot >= 0 ? fullPath.slice(lastDot + 1) : fullPath;
|
|
75
|
+
return { kind: 'named', source: fullPath, name, atNode: node };
|
|
76
|
+
}
|
|
77
|
+
function buildImportMatch(stmtNode, spec) {
|
|
78
|
+
const m = {
|
|
79
|
+
'@import.statement': nodeToCapture('@import.statement', stmtNode),
|
|
80
|
+
'@import.kind': syntheticCapture('@import.kind', spec.atNode, spec.kind),
|
|
81
|
+
'@import.source': syntheticCapture('@import.source', spec.atNode, spec.source),
|
|
82
|
+
'@import.name': syntheticCapture('@import.name', spec.atNode, spec.name),
|
|
83
|
+
};
|
|
84
|
+
return m;
|
|
85
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter from `(ParsedImport, WorkspaceIndex)` → concrete file path.
|
|
3
|
+
*
|
|
4
|
+
* Converts Java package paths (dots → slashes) and tries:
|
|
5
|
+
* 1. Exact file match: `com/example/User.java`
|
|
6
|
+
* 2. Suffix match for nested layouts
|
|
7
|
+
* 3. Directory match (wildcard imports)
|
|
8
|
+
* 4. Progressive prefix stripping for non-standard layouts
|
|
9
|
+
*
|
|
10
|
+
* Returns `null` for unresolvable / JDK imports.
|
|
11
|
+
*/
|
|
12
|
+
import type { ParsedImport, WorkspaceIndex } from '../../../../_shared/index.js';
|
|
13
|
+
export interface JavaResolveContext {
|
|
14
|
+
readonly fromFile: string;
|
|
15
|
+
readonly allFilePaths: ReadonlySet<string>;
|
|
16
|
+
}
|
|
17
|
+
export declare function resolveJavaImportTarget(parsedImport: ParsedImport, workspaceIndex: WorkspaceIndex): string | null;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter from `(ParsedImport, WorkspaceIndex)` → concrete file path.
|
|
3
|
+
*
|
|
4
|
+
* Converts Java package paths (dots → slashes) and tries:
|
|
5
|
+
* 1. Exact file match: `com/example/User.java`
|
|
6
|
+
* 2. Suffix match for nested layouts
|
|
7
|
+
* 3. Directory match (wildcard imports)
|
|
8
|
+
* 4. Progressive prefix stripping for non-standard layouts
|
|
9
|
+
*
|
|
10
|
+
* Returns `null` for unresolvable / JDK imports.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveJavaImportTarget(parsedImport, workspaceIndex) {
|
|
13
|
+
const ctx = workspaceIndex;
|
|
14
|
+
if (ctx === undefined ||
|
|
15
|
+
typeof ctx.fromFile !== 'string' ||
|
|
16
|
+
!(ctx.allFilePaths instanceof Set)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
if (parsedImport.kind === 'dynamic-unresolved')
|
|
20
|
+
return null;
|
|
21
|
+
if (parsedImport.targetRaw === null || parsedImport.targetRaw === '')
|
|
22
|
+
return null;
|
|
23
|
+
// Strip trailing `.*` for wildcard imports: `com.example.*` → `com.example`
|
|
24
|
+
let target = parsedImport.targetRaw;
|
|
25
|
+
if (target.endsWith('.*')) {
|
|
26
|
+
target = target.slice(0, -2);
|
|
27
|
+
}
|
|
28
|
+
// Package path: `com.example.User` → `com/example/User`
|
|
29
|
+
const pathLike = target.replace(/\./g, '/');
|
|
30
|
+
const suffix = `/${pathLike}`;
|
|
31
|
+
let exactFile = null;
|
|
32
|
+
let suffixFile = null;
|
|
33
|
+
let directoryChild = null;
|
|
34
|
+
const dirPrefix = `${pathLike}/`;
|
|
35
|
+
const suffixDirPrefix = `/${dirPrefix}`;
|
|
36
|
+
for (const raw of ctx.allFilePaths) {
|
|
37
|
+
const f = raw.replace(/\\/g, '/');
|
|
38
|
+
if (!f.endsWith('.java'))
|
|
39
|
+
continue;
|
|
40
|
+
if (f === `${pathLike}.java`) {
|
|
41
|
+
exactFile = raw;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (suffixFile === null && f.endsWith(`${suffix}.java`)) {
|
|
45
|
+
suffixFile = raw;
|
|
46
|
+
}
|
|
47
|
+
if (directoryChild === null) {
|
|
48
|
+
const atRoot = f.startsWith(dirPrefix);
|
|
49
|
+
const atNested = f.includes(suffixDirPrefix);
|
|
50
|
+
if (atRoot || atNested) {
|
|
51
|
+
const idx = atRoot ? 0 : f.indexOf(suffixDirPrefix) + 1;
|
|
52
|
+
const after = f.slice(idx + dirPrefix.length);
|
|
53
|
+
if (after.length > 0 && !after.includes('/')) {
|
|
54
|
+
directoryChild = raw;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (exactFile !== null)
|
|
60
|
+
return exactFile;
|
|
61
|
+
if (suffixFile !== null)
|
|
62
|
+
return suffixFile;
|
|
63
|
+
if (directoryChild !== null)
|
|
64
|
+
return directoryChild;
|
|
65
|
+
// Progressive prefix stripping — handles `import com.example.User;`
|
|
66
|
+
// in a repo laid out `User.java` (no `com/example/` prefix).
|
|
67
|
+
const segments = pathLike.split('/').filter(Boolean);
|
|
68
|
+
for (let skip = 1; skip < segments.length; skip++) {
|
|
69
|
+
const tail = segments.slice(skip).join('/');
|
|
70
|
+
if (tail === '')
|
|
71
|
+
continue;
|
|
72
|
+
const tailFile = `${tail}.java`;
|
|
73
|
+
const tailSuffix = `/${tailFile}`;
|
|
74
|
+
const tailDir = `${tail}/`;
|
|
75
|
+
const tailSuffixDir = `/${tailDir}`;
|
|
76
|
+
let tailDirectChild = null;
|
|
77
|
+
for (const raw of ctx.allFilePaths) {
|
|
78
|
+
const f = raw.replace(/\\/g, '/');
|
|
79
|
+
if (!f.endsWith('.java'))
|
|
80
|
+
continue;
|
|
81
|
+
if (f === tailFile)
|
|
82
|
+
return raw;
|
|
83
|
+
if (f.endsWith(tailSuffix))
|
|
84
|
+
return raw;
|
|
85
|
+
if (tailDirectChild === null) {
|
|
86
|
+
const atRoot = f.startsWith(tailDir);
|
|
87
|
+
const atNested = f.includes(tailSuffixDir);
|
|
88
|
+
if (atRoot || atNested) {
|
|
89
|
+
const idx = atRoot ? 0 : f.indexOf(tailSuffixDir) + 1;
|
|
90
|
+
const after = f.slice(idx + tailDir.length);
|
|
91
|
+
if (after.length > 0 && !after.includes('/'))
|
|
92
|
+
tailDirectChild = raw;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (tailDirectChild !== null)
|
|
97
|
+
return tailDirectChild;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Java scope-resolution hooks (RFC #909 Ring 3).
|
|
3
|
+
*
|
|
4
|
+
* Public API barrel. Consumers should import from this file rather than
|
|
5
|
+
* the individual modules.
|
|
6
|
+
*
|
|
7
|
+
* Module layout:
|
|
8
|
+
*
|
|
9
|
+
* - `query.ts` — tree-sitter query + lazy parser/query singletons
|
|
10
|
+
* - `captures.ts` — `emitJavaScopeCaptures` orchestrator
|
|
11
|
+
* - `import-decomposer.ts` — each `import` → ParsedImport-shaped captures
|
|
12
|
+
* - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
|
|
13
|
+
* - `simple-hooks.ts` — small hooks made explicit
|
|
14
|
+
* - `receiver-binding.ts` — synthesize `this`/`super` type-bindings on
|
|
15
|
+
* instance-method entry
|
|
16
|
+
* - `merge-bindings.ts` — Java import precedence
|
|
17
|
+
* - `arity.ts` — Java arity compatibility (varargs)
|
|
18
|
+
* - `arity-metadata.ts` — synthesize arity metadata from declarations
|
|
19
|
+
* - `import-target.ts` — `(ParsedImport, WorkspaceIndex) → file path` adapter
|
|
20
|
+
* - `scope-resolver.ts` — `ScopeResolver` registered in `SCOPE_RESOLVERS`
|
|
21
|
+
* - `cache-stats.ts` — PROF_SCOPE_RESOLUTION cache hit/miss counters
|
|
22
|
+
*/
|
|
23
|
+
export { emitJavaScopeCaptures } from './captures.js';
|
|
24
|
+
export { getJavaCaptureCacheStats, resetJavaCaptureCacheStats } from './cache-stats.js';
|
|
25
|
+
export { interpretJavaImport, interpretJavaTypeBinding } from './interpret.js';
|
|
26
|
+
export { javaMergeBindings } from './merge-bindings.js';
|
|
27
|
+
export { javaArityCompatibility } from './arity.js';
|
|
28
|
+
export { resolveJavaImportTarget, type JavaResolveContext } from './import-target.js';
|
|
29
|
+
export { javaBindingScopeFor, javaImportOwningScope, javaReceiverBinding } from './simple-hooks.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Java scope-resolution hooks (RFC #909 Ring 3).
|
|
3
|
+
*
|
|
4
|
+
* Public API barrel. Consumers should import from this file rather than
|
|
5
|
+
* the individual modules.
|
|
6
|
+
*
|
|
7
|
+
* Module layout:
|
|
8
|
+
*
|
|
9
|
+
* - `query.ts` — tree-sitter query + lazy parser/query singletons
|
|
10
|
+
* - `captures.ts` — `emitJavaScopeCaptures` orchestrator
|
|
11
|
+
* - `import-decomposer.ts` — each `import` → ParsedImport-shaped captures
|
|
12
|
+
* - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
|
|
13
|
+
* - `simple-hooks.ts` — small hooks made explicit
|
|
14
|
+
* - `receiver-binding.ts` — synthesize `this`/`super` type-bindings on
|
|
15
|
+
* instance-method entry
|
|
16
|
+
* - `merge-bindings.ts` — Java import precedence
|
|
17
|
+
* - `arity.ts` — Java arity compatibility (varargs)
|
|
18
|
+
* - `arity-metadata.ts` — synthesize arity metadata from declarations
|
|
19
|
+
* - `import-target.ts` — `(ParsedImport, WorkspaceIndex) → file path` adapter
|
|
20
|
+
* - `scope-resolver.ts` — `ScopeResolver` registered in `SCOPE_RESOLVERS`
|
|
21
|
+
* - `cache-stats.ts` — PROF_SCOPE_RESOLUTION cache hit/miss counters
|
|
22
|
+
*/
|
|
23
|
+
export { emitJavaScopeCaptures } from './captures.js';
|
|
24
|
+
export { getJavaCaptureCacheStats, resetJavaCaptureCacheStats } from './cache-stats.js';
|
|
25
|
+
export { interpretJavaImport, interpretJavaTypeBinding } from './interpret.js';
|
|
26
|
+
export { javaMergeBindings } from './merge-bindings.js';
|
|
27
|
+
export { javaArityCompatibility } from './arity.js';
|
|
28
|
+
export { resolveJavaImportTarget } from './import-target.js';
|
|
29
|
+
export { javaBindingScopeFor, javaImportOwningScope, javaReceiverBinding } from './simple-hooks.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture-match → semantic-shape interpreters for Java.
|
|
3
|
+
*
|
|
4
|
+
* - `interpretJavaImport` → `ParsedImport`
|
|
5
|
+
* - `interpretJavaTypeBinding` → `ParsedTypeBinding`
|
|
6
|
+
*
|
|
7
|
+
* Import matches arrive pre-decomposed by `emitJavaScopeCaptures`
|
|
8
|
+
* (one import per match, with synthesized `@import.kind/source/name`
|
|
9
|
+
* markers). Type-binding matches arrive from the raw query captures.
|
|
10
|
+
*/
|
|
11
|
+
import type { CaptureMatch, ParsedImport, ParsedTypeBinding } from '../../../../_shared/index.js';
|
|
12
|
+
export declare function interpretJavaImport(captures: CaptureMatch): ParsedImport | null;
|
|
13
|
+
export declare function interpretJavaTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null;
|