gitnexus 1.6.11-rc.17 → 1.6.11-rc.19
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/group/extractors/java-workspace-extractor.js +244 -31
- package/dist/core/ingestion/language-provider.d.ts +43 -0
- package/dist/core/ingestion/languages/java/captures.js +2 -0
- package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
- package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
- package/dist/core/ingestion/languages/java.js +2 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
- package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
- package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +2 -0
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
- package/dist/core/ingestion/languages/kotlin.js +2 -0
- package/dist/core/ingestion/scope-extractor.js +1 -0
- package/dist/core/ingestion/workers/parse-worker.js +24 -0
- package/dist/storage/parse-cache.js +14 -1
- package/package.json +2 -1
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
3
4
|
import { shouldIgnorePath, loadIgnoreRules } from '../../../config/ignore-service.js';
|
|
4
5
|
import { logger } from '../../logger.js';
|
|
6
|
+
// POMs are static metadata. Parse hierarchy with a real XML parser, but do not
|
|
7
|
+
// invoke Maven or resolve the effective model. Properties, profiles, and remote
|
|
8
|
+
// parent resolution remain outside this extractor's deterministic boundary.
|
|
9
|
+
const pomParser = new XMLParser({
|
|
10
|
+
ignoreAttributes: true,
|
|
11
|
+
removeNSPrefix: true,
|
|
12
|
+
trimValues: true,
|
|
13
|
+
parseTagValue: false,
|
|
14
|
+
processEntities: false,
|
|
15
|
+
ignoreDeclaration: true,
|
|
16
|
+
ignorePiTags: true,
|
|
17
|
+
});
|
|
5
18
|
async function parseJavaManifest(repoPath) {
|
|
6
19
|
const pomPath = path.join(repoPath, 'pom.xml');
|
|
7
20
|
try {
|
|
@@ -9,13 +22,14 @@ async function parseJavaManifest(repoPath) {
|
|
|
9
22
|
return parsePom(content);
|
|
10
23
|
}
|
|
11
24
|
catch {
|
|
12
|
-
// fall through to Gradle
|
|
25
|
+
// Missing pom.xml — fall through to Gradle.
|
|
13
26
|
}
|
|
27
|
+
const gradleSidecars = await readGradleSidecars(repoPath);
|
|
14
28
|
for (const name of ['build.gradle.kts', 'build.gradle']) {
|
|
15
29
|
const gradlePath = path.join(repoPath, name);
|
|
16
30
|
try {
|
|
17
31
|
const content = await fs.readFile(gradlePath, 'utf-8');
|
|
18
|
-
return parseGradle(content, repoPath);
|
|
32
|
+
return parseGradle(content, repoPath, gradleSidecars);
|
|
19
33
|
}
|
|
20
34
|
catch {
|
|
21
35
|
continue;
|
|
@@ -23,45 +37,244 @@ async function parseJavaManifest(repoPath) {
|
|
|
23
37
|
}
|
|
24
38
|
return null;
|
|
25
39
|
}
|
|
40
|
+
async function readIfPresent(filePath) {
|
|
41
|
+
try {
|
|
42
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function readGradleSidecars(repoPath) {
|
|
49
|
+
const [properties, settingsKts, settingsGroovy, catalog] = await Promise.all([
|
|
50
|
+
readIfPresent(path.join(repoPath, 'gradle.properties')),
|
|
51
|
+
readIfPresent(path.join(repoPath, 'settings.gradle.kts')),
|
|
52
|
+
readIfPresent(path.join(repoPath, 'settings.gradle')),
|
|
53
|
+
readIfPresent(path.join(repoPath, 'gradle', 'libs.versions.toml')),
|
|
54
|
+
]);
|
|
55
|
+
const sidecars = {
|
|
56
|
+
catalogLibraries: new Map(),
|
|
57
|
+
catalogBundles: new Map(),
|
|
58
|
+
};
|
|
59
|
+
const groupMatch = properties?.match(/(?:^|\n)\s*group\s*=\s*([^\s#]+)/);
|
|
60
|
+
if (groupMatch)
|
|
61
|
+
sidecars.propertiesGroup = groupMatch[1];
|
|
62
|
+
const settings = settingsKts ?? settingsGroovy;
|
|
63
|
+
const nameMatch = settings?.match(/rootProject\.name\s*=\s*['"]([^'"]+)['"]/);
|
|
64
|
+
if (nameMatch)
|
|
65
|
+
sidecars.rootProjectName = nameMatch[1];
|
|
66
|
+
if (catalog) {
|
|
67
|
+
const parsed = parseGradleVersionCatalog(catalog);
|
|
68
|
+
sidecars.catalogLibraries = parsed.libraries;
|
|
69
|
+
sidecars.catalogBundles = parsed.bundles;
|
|
70
|
+
}
|
|
71
|
+
return sidecars;
|
|
72
|
+
}
|
|
73
|
+
function catalogAccessors(alias) {
|
|
74
|
+
const dotted = alias.replace(/[-_]/g, '.');
|
|
75
|
+
const camel = alias.replace(/[-_]+([A-Za-z0-9])/g, (_, char) => char.toUpperCase());
|
|
76
|
+
return [...new Set([alias, dotted, camel])];
|
|
77
|
+
}
|
|
78
|
+
function projectAccessorToArtifactId(accessor) {
|
|
79
|
+
const last = accessor.split('.').pop();
|
|
80
|
+
return last.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`).replace(/^-/, '');
|
|
81
|
+
}
|
|
82
|
+
function moduleToGa(module) {
|
|
83
|
+
const parts = module.split(':');
|
|
84
|
+
return parts.length >= 2 ? `${parts[0]}:${parts[1]}` : undefined;
|
|
85
|
+
}
|
|
86
|
+
function parseInlineTomlTable(rhs) {
|
|
87
|
+
const fields = {};
|
|
88
|
+
for (const match of rhs.matchAll(/([A-Za-z0-9_-]+)\s*=\s*['"]([^'"]+)['"]/g)) {
|
|
89
|
+
fields[match[1]] = match[2];
|
|
90
|
+
}
|
|
91
|
+
return fields;
|
|
92
|
+
}
|
|
93
|
+
/** Default Gradle catalog (`gradle/libs.versions.toml`) — aliases only, no version resolution. */
|
|
94
|
+
function parseGradleVersionCatalog(toml) {
|
|
95
|
+
const libraries = new Map();
|
|
96
|
+
const bundles = new Map();
|
|
97
|
+
let section = 'other';
|
|
98
|
+
const addLibrary = (alias, ga) => {
|
|
99
|
+
for (const accessor of catalogAccessors(alias))
|
|
100
|
+
libraries.set(accessor, ga);
|
|
101
|
+
};
|
|
102
|
+
for (const raw of toml.split(/\r?\n/)) {
|
|
103
|
+
const line = raw.replace(/#.*$/, '').trim();
|
|
104
|
+
if (!line)
|
|
105
|
+
continue;
|
|
106
|
+
const header = line.match(/^\[([^\]]+)\]$/);
|
|
107
|
+
if (header) {
|
|
108
|
+
const name = header[1];
|
|
109
|
+
section =
|
|
110
|
+
name === 'libraries' || name.endsWith('.libraries')
|
|
111
|
+
? 'libraries'
|
|
112
|
+
: name === 'bundles' || name.endsWith('.bundles')
|
|
113
|
+
? 'bundles'
|
|
114
|
+
: 'other';
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (section === 'libraries') {
|
|
118
|
+
const dottedModule = line.match(/^([A-Za-z0-9._-]+)\.module\s*=\s*['"]([^'"]+)['"]$/);
|
|
119
|
+
if (dottedModule) {
|
|
120
|
+
const ga = moduleToGa(dottedModule[2]);
|
|
121
|
+
if (ga)
|
|
122
|
+
addLibrary(dottedModule[1], ga);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const assignment = line.match(/^([A-Za-z0-9._-]+)\s*=\s*(.+)$/);
|
|
126
|
+
if (!assignment)
|
|
127
|
+
continue;
|
|
128
|
+
const alias = assignment[1];
|
|
129
|
+
const rhs = assignment[2].trim();
|
|
130
|
+
const quoted = rhs.match(/^['"]([^'"]+)['"]$/);
|
|
131
|
+
if (quoted) {
|
|
132
|
+
const ga = moduleToGa(quoted[1]);
|
|
133
|
+
if (ga)
|
|
134
|
+
addLibrary(alias, ga);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const table = parseInlineTomlTable(rhs);
|
|
138
|
+
const ga = table.module
|
|
139
|
+
? moduleToGa(table.module)
|
|
140
|
+
: table.group && table.name
|
|
141
|
+
? `${table.group}:${table.name}`
|
|
142
|
+
: undefined;
|
|
143
|
+
if (ga)
|
|
144
|
+
addLibrary(alias, ga);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (section === 'bundles') {
|
|
148
|
+
const assignment = line.match(/^([A-Za-z0-9._-]+)\s*=\s*\[([^\]]*)\]$/);
|
|
149
|
+
if (!assignment)
|
|
150
|
+
continue;
|
|
151
|
+
const members = [...assignment[2].matchAll(/['"]([^'"]+)['"]/g)].map((match) => match[1]);
|
|
152
|
+
for (const accessor of catalogAccessors(assignment[1]))
|
|
153
|
+
bundles.set(accessor, members);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { libraries, bundles };
|
|
157
|
+
}
|
|
158
|
+
const GRADLE_GROUP_PATTERNS = [
|
|
159
|
+
/(?:^|[\n{;])\s*(?:rootProject\.)?group\s*=\s*['"]([^'"]+)['"]/,
|
|
160
|
+
/(?:^|[\n{;])\s*group\s+['"]([^'"]+)['"]/,
|
|
161
|
+
];
|
|
162
|
+
const GRADLE_COORD_CONFIGS = 'implementation|api|compileOnly|runtimeOnly|testImplementation|testApi|testCompileOnly|compile|kapt|ksp|commonMainImplementation|commonMainApi';
|
|
163
|
+
const CATALOG_ALIAS = '([A-Za-z0-9_]+(?:\\.[A-Za-z0-9_]+)*)(?:\\.get\\(\\)|\\.asProvider\\(\\))?';
|
|
164
|
+
function gradleDepRe(suffix) {
|
|
165
|
+
return new RegExp(`(?:${GRADLE_COORD_CONFIGS})\\s*${suffix}`, 'g');
|
|
166
|
+
}
|
|
167
|
+
function parseGradleGroup(content) {
|
|
168
|
+
for (const pattern of GRADLE_GROUP_PATTERNS) {
|
|
169
|
+
const match = content.match(pattern);
|
|
170
|
+
if (match?.[1])
|
|
171
|
+
return match[1];
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
function asXmlNode(value) {
|
|
176
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
177
|
+
? value
|
|
178
|
+
: undefined;
|
|
179
|
+
}
|
|
180
|
+
function xmlText(value) {
|
|
181
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
182
|
+
const text = String(value).trim();
|
|
183
|
+
return text || undefined;
|
|
184
|
+
}
|
|
185
|
+
const nested = asXmlNode(value)?.['#text'];
|
|
186
|
+
if (nested === undefined)
|
|
187
|
+
return undefined;
|
|
188
|
+
return xmlText(nested);
|
|
189
|
+
}
|
|
190
|
+
function xmlChildText(node, name) {
|
|
191
|
+
return node ? xmlText(node[name]) : undefined;
|
|
192
|
+
}
|
|
193
|
+
function asList(value) {
|
|
194
|
+
if (value === undefined || value === null)
|
|
195
|
+
return [];
|
|
196
|
+
return Array.isArray(value) ? value : [value];
|
|
197
|
+
}
|
|
198
|
+
/** Direct project dependencies only — not BOM, profiles, or plugin classpath. */
|
|
199
|
+
function collectProjectDependencies(project, deps) {
|
|
200
|
+
const dependencies = asXmlNode(project.dependencies);
|
|
201
|
+
if (!dependencies)
|
|
202
|
+
return;
|
|
203
|
+
for (const dep of asList(dependencies.dependency)) {
|
|
204
|
+
const depNode = asXmlNode(dep);
|
|
205
|
+
const groupId = xmlChildText(depNode, 'groupId');
|
|
206
|
+
const artifactId = xmlChildText(depNode, 'artifactId');
|
|
207
|
+
if (groupId && artifactId)
|
|
208
|
+
deps.push(`${groupId}:${artifactId}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
26
211
|
function parsePom(content) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
212
|
+
let parsed;
|
|
213
|
+
try {
|
|
214
|
+
// parseSourceSafe guards tree-sitter's Windows SIGSEGV by switching to a
|
|
215
|
+
// chunked input callback above 16 KB; XMLParser only accepts XML text, so
|
|
216
|
+
// routing POMs through it silently yields an empty document.
|
|
217
|
+
// eslint-disable-next-line gitnexus/require-safe-parse
|
|
218
|
+
parsed = pomParser.parse(content);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
30
221
|
return null;
|
|
31
|
-
const groupId = projectGroupMatch[1].trim();
|
|
32
|
-
const artifactId = projectArtifactMatch[1].trim();
|
|
33
|
-
const deps = [];
|
|
34
|
-
const depBlocks = content.matchAll(/<dependency>\s*([\s\S]*?)<\/dependency>/g);
|
|
35
|
-
for (const block of depBlocks) {
|
|
36
|
-
const gMatch = block[1].match(/<groupId>([^<]+)<\/groupId>/);
|
|
37
|
-
const aMatch = block[1].match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
38
|
-
if (gMatch && aMatch) {
|
|
39
|
-
deps.push(`${gMatch[1].trim()}:${aMatch[1].trim()}`);
|
|
40
|
-
}
|
|
41
222
|
}
|
|
223
|
+
const project = asXmlNode(asXmlNode(parsed)?.project);
|
|
224
|
+
if (!project)
|
|
225
|
+
return null;
|
|
226
|
+
// Maven inherits groupId from <parent>, but artifactId is always the
|
|
227
|
+
// project's own direct child and must never fall back to parent.artifactId.
|
|
228
|
+
const groupId = xmlChildText(project, 'groupId') ?? xmlChildText(asXmlNode(project.parent), 'groupId');
|
|
229
|
+
const artifactId = xmlChildText(project, 'artifactId');
|
|
230
|
+
if (!groupId || !artifactId)
|
|
231
|
+
return null;
|
|
232
|
+
const deps = [];
|
|
233
|
+
collectProjectDependencies(project, deps);
|
|
42
234
|
return { groupId, artifactId, deps: [...new Set(deps)] };
|
|
43
235
|
}
|
|
44
|
-
function parseGradle(content, repoPath) {
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const groupId = groupMatch ? groupMatch[1] : '';
|
|
236
|
+
function parseGradle(content, repoPath, sidecars = { catalogLibraries: new Map(), catalogBundles: new Map() }) {
|
|
237
|
+
// Static text + default catalog file. Do not execute Gradle.
|
|
238
|
+
const groupId = parseGradleGroup(content) ?? sidecars.propertiesGroup ?? '';
|
|
48
239
|
if (!groupId)
|
|
49
240
|
return null;
|
|
50
|
-
const artifactId =
|
|
241
|
+
const artifactId = sidecars.rootProjectName ?? path.basename(repoPath);
|
|
242
|
+
const { catalogLibraries, catalogBundles } = sidecars;
|
|
51
243
|
const deps = [];
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
244
|
+
const pushCatalogAlias = (alias) => {
|
|
245
|
+
const ga = catalogLibraries.get(alias);
|
|
246
|
+
if (ga)
|
|
247
|
+
deps.push(ga);
|
|
248
|
+
};
|
|
249
|
+
const namedPattern = gradleDepRe(`(?:\\(\\s*)?(?:group\\s*=\\s*['"](?<group1>[^'"]+)['"]\\s*,\\s*name\\s*=\\s*['"](?<name1>[^'"]+)['"]|name\\s*=\\s*['"](?<name2>[^'"]+)['"]\\s*,\\s*group\\s*=\\s*['"](?<group2>[^'"]+)['"]|group:\\s*['"](?<group3>[^'"]+)['"]\\s*,\\s*name:\\s*['"](?<name3>[^'"]+)['"]|name:\\s*['"](?<name4>[^'"]+)['"]\\s*,\\s*group:\\s*['"](?<group4>[^'"]+)['"])`);
|
|
250
|
+
for (const match of content.matchAll(namedPattern)) {
|
|
251
|
+
const group = match.groups?.group1 ?? match.groups?.group2 ?? match.groups?.group3 ?? match.groups?.group4;
|
|
252
|
+
const name = match.groups?.name1 ?? match.groups?.name2 ?? match.groups?.name3 ?? match.groups?.name4;
|
|
253
|
+
if (group && name)
|
|
254
|
+
deps.push(`${group}:${name}`);
|
|
255
|
+
}
|
|
256
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?libs(?:\\.libraries)?\\.(?!bundles\\.|plugins\\.)${CATALOG_ALIAS}`))) {
|
|
257
|
+
pushCatalogAlias(match[1]);
|
|
258
|
+
}
|
|
259
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?libs\\.bundles\\.${CATALOG_ALIAS}`))) {
|
|
260
|
+
for (const member of catalogBundles.get(match[1]) ?? []) {
|
|
261
|
+
for (const accessor of catalogAccessors(member))
|
|
262
|
+
pushCatalogAlias(accessor);
|
|
58
263
|
}
|
|
59
264
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
265
|
+
for (const match of content.matchAll(gradleDepRe(`\\(\\s*projects\\.([A-Za-z][A-Za-z0-9.]*)`))) {
|
|
266
|
+
deps.push(`${groupId}:${projectAccessorToArtifactId(match[1])}`);
|
|
267
|
+
}
|
|
268
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*['"]([^'"]+)['"]\\s*\\)|['"]([^'"]+)['"])`))) {
|
|
269
|
+
const coord = match[1] ?? match[2];
|
|
270
|
+
if (!coord)
|
|
271
|
+
continue;
|
|
272
|
+
const parts = coord.split(':');
|
|
273
|
+
if (parts.length >= 2)
|
|
274
|
+
deps.push(`${parts[0]}:${parts[1]}`);
|
|
275
|
+
}
|
|
276
|
+
for (const match of content.matchAll(gradleDepRe(`(?:\\(\\s*)?project\\s*\\(\\s*['"]([^'"]+)['"]\\s*\\)`))) {
|
|
277
|
+
deps.push(`${groupId}:${match[1].replace(/^:/, '')}`);
|
|
65
278
|
}
|
|
66
279
|
return { groupId, artifactId, deps: [...new Set(deps)] };
|
|
67
280
|
}
|
|
@@ -297,6 +297,49 @@ interface LanguageProviderConfig {
|
|
|
297
297
|
* Default: undefined (no interface-inheritance route resolution).
|
|
298
298
|
*/
|
|
299
299
|
readonly extractRouteInheritanceTypes?: (tree: Parser.Tree, filePath: string) => SharedSpringType[];
|
|
300
|
+
/**
|
|
301
|
+
* Optional post-capture emission of synthetic structure members (nodes,
|
|
302
|
+
* symbols, ownership edges) that have no AST method node — e.g. Lombok
|
|
303
|
+
* accessors. Called once per file after the capture loop, at the same
|
|
304
|
+
* post-capture site as {@link extractDecoratorRoutes}.
|
|
305
|
+
*
|
|
306
|
+
* `classOwnersByNodeId` maps in-memory tree-sitter node ids of type
|
|
307
|
+
* declarations materialized in THIS file's capture loop to their graph
|
|
308
|
+
* node ids. Keys are never persisted; they exist only for the duration
|
|
309
|
+
* of the worker pass.
|
|
310
|
+
*
|
|
311
|
+
* Default: undefined (no synthetic structure members).
|
|
312
|
+
*/
|
|
313
|
+
readonly synthesizeStructureMembers?: (tree: Parser.Tree, filePath: string, classOwnersByNodeId: ReadonlyMap<number, string>) => {
|
|
314
|
+
nodes: ReadonlyArray<{
|
|
315
|
+
id: string;
|
|
316
|
+
label: string;
|
|
317
|
+
properties: Record<string, unknown>;
|
|
318
|
+
}>;
|
|
319
|
+
symbols: ReadonlyArray<{
|
|
320
|
+
filePath: string;
|
|
321
|
+
name: string;
|
|
322
|
+
nodeId: string;
|
|
323
|
+
type: string;
|
|
324
|
+
ownerId?: string;
|
|
325
|
+
parameterCount?: number;
|
|
326
|
+
requiredParameterCount?: number;
|
|
327
|
+
parameterTypes?: string[];
|
|
328
|
+
returnType?: string;
|
|
329
|
+
visibility?: string;
|
|
330
|
+
isStatic?: boolean;
|
|
331
|
+
isAbstract?: boolean;
|
|
332
|
+
isFinal?: boolean;
|
|
333
|
+
}>;
|
|
334
|
+
relationships: ReadonlyArray<{
|
|
335
|
+
id: string;
|
|
336
|
+
sourceId: string;
|
|
337
|
+
targetId: string;
|
|
338
|
+
type: string;
|
|
339
|
+
confidence: number;
|
|
340
|
+
reason: string;
|
|
341
|
+
}>;
|
|
342
|
+
};
|
|
300
343
|
/**
|
|
301
344
|
* Harvest this file's module-level string constants (#2391 core, #2980 Java
|
|
302
345
|
* parity) into the language-agnostic {@link ModuleConstants} shape, so the
|
|
@@ -18,6 +18,7 @@ import { captureJavaSpringAopFacts } from './spring-aop.js';
|
|
|
18
18
|
import { captureJavaSpringConditionalFacts, } from './spring-conditionals.js';
|
|
19
19
|
import { captureJavaSpringNonHttpHandlerFacts, } from './spring-non-http-handlers.js';
|
|
20
20
|
import { synthesizeJavaRecordComponentAccessorCaptures } from './record-components.js';
|
|
21
|
+
import { synthesizeLombokAccessorCaptures } from './lombok-synthesizer.js';
|
|
21
22
|
/** Declaration anchors that carry function-like arity metadata. */
|
|
22
23
|
const FUNCTION_DECL_TAGS = ['@declaration.method', '@declaration.constructor'];
|
|
23
24
|
/** tree-sitter-java node types that the method extractor accepts. */
|
|
@@ -305,6 +306,7 @@ export function emitJavaScopeCaptures(sourceText, filePath, cachedTree) {
|
|
|
305
306
|
...synthesizeJavaExplicitConstructorReferences(tree.rootNode),
|
|
306
307
|
...synthesizeJavaAnonymousClassDeclarations(tree.rootNode),
|
|
307
308
|
...synthesizeJavaRecordComponentAccessorCaptures(tree.rootNode),
|
|
309
|
+
...synthesizeLombokAccessorCaptures(tree.rootNode),
|
|
308
310
|
...synthesizeCallableFlowCaptures(tree.rootNode, JAVA_CALLABLE_CAPTURE_OPTIONS),
|
|
309
311
|
];
|
|
310
312
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lombok accessor synthesizer for Java.
|
|
3
|
+
*
|
|
4
|
+
* Lombok generates getters/setters at compile time. They are absent from the
|
|
5
|
+
* AST, so calls like `obj.getOrderId()` on a `@Data` class would otherwise
|
|
6
|
+
* leave unresolved CALLS edges. This module walks the tree-sitter Java AST
|
|
7
|
+
* and synthesizes Method graph members for the accessors Lombok would emit
|
|
8
|
+
* under the supported subset.
|
|
9
|
+
*
|
|
10
|
+
* ## Supported subset (v1)
|
|
11
|
+
* - Proven `lombok.Data` / `lombok.Getter` / `lombok.Setter` (FQN or import).
|
|
12
|
+
* - Class- or field-level enable; `AccessLevel.NONE` disables.
|
|
13
|
+
* - Default JavaBeans naming; primitive `boolean isX` → `isX` / `setX`.
|
|
14
|
+
* - Access levels PUBLIC/PROTECTED/PRIVATE/PACKAGE.
|
|
15
|
+
* - `@Accessors(chain=true)` modeled as setter return = declaring type.
|
|
16
|
+
* - `@Accessors(fluent=true)` / `prefix=…`: omit affected accessors (names
|
|
17
|
+
* cannot be proven without full Lombok config).
|
|
18
|
+
* - External `lombok.config`: unsupported (may change semantics invisibly).
|
|
19
|
+
*
|
|
20
|
+
* ## Identity
|
|
21
|
+
* Owner lookup uses in-memory AST node ids only. Method ids are derived from
|
|
22
|
+
* the stable declaring-owner graph key (the Class node id's name segment),
|
|
23
|
+
* never from persisted tree-sitter node ids.
|
|
24
|
+
*/
|
|
25
|
+
import type Parser from 'tree-sitter';
|
|
26
|
+
import type { CaptureMatch } from '../../../../_shared/index.js';
|
|
27
|
+
import { type PlannedJvmAccessor, type SyntheticAccessorResult, type SyntheticVisibility } from '../jvm/accessor-synthesis.js';
|
|
28
|
+
export type LombokVisibility = SyntheticVisibility;
|
|
29
|
+
export type SyntheticSymbol = SyntheticAccessorResult['symbols'][number];
|
|
30
|
+
export type SyntheticNode = SyntheticAccessorResult['nodes'][number];
|
|
31
|
+
export type SyntheticRelationship = SyntheticAccessorResult['relationships'][number];
|
|
32
|
+
export type LombokSynthesisResult = SyntheticAccessorResult;
|
|
33
|
+
export type PlannedLombokAccessor = PlannedJvmAccessor;
|
|
34
|
+
export interface AccessorConfig {
|
|
35
|
+
enabled: boolean;
|
|
36
|
+
visibility: LombokVisibility;
|
|
37
|
+
}
|
|
38
|
+
export declare function getterName(fieldName: string, fieldType: string): string;
|
|
39
|
+
export declare function setterName(fieldName: string, fieldType: string): string;
|
|
40
|
+
export declare function synthesizeLombokAccessors(tree: Parser.Tree, filePath: string, classOwnersById: ReadonlyMap<number, string>): LombokSynthesisResult;
|
|
41
|
+
/** Scope captures for Lombok accessors (dual-path parity with record components). */
|
|
42
|
+
export declare function synthesizeLombokAccessorCaptures(rootNode: Parser.SyntaxNode): CaptureMatch[];
|