@adhisang/minecraft-modding-mcp 1.2.1 → 2.1.0
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/CHANGELOG.md +73 -0
- package/README.md +184 -64
- package/dist/cli.js +31 -4
- package/dist/compat-stdio-transport.d.ts +2 -7
- package/dist/compat-stdio-transport.js +12 -154
- package/dist/index.js +537 -202
- package/dist/json-rpc-framing.d.ts +22 -0
- package/dist/json-rpc-framing.js +168 -0
- package/dist/mapping-pipeline-service.d.ts +1 -1
- package/dist/mapping-pipeline-service.js +13 -5
- package/dist/mapping-service.d.ts +12 -4
- package/dist/mapping-service.js +222 -105
- package/dist/mcp-helpers.d.ts +10 -2
- package/dist/mcp-helpers.js +59 -5
- package/dist/minecraft-explorer-service.d.ts +1 -2
- package/dist/minecraft-explorer-service.js +120 -24
- package/dist/mixin-validator.d.ts +24 -2
- package/dist/mixin-validator.js +228 -103
- package/dist/mod-decompile-service.d.ts +5 -0
- package/dist/mod-decompile-service.js +40 -5
- package/dist/mod-remap-service.js +142 -30
- package/dist/mojang-tiny-mapping-service.js +26 -26
- package/dist/path-resolver.js +41 -4
- package/dist/registry-service.d.ts +10 -1
- package/dist/registry-service.js +154 -22
- package/dist/resources.js +7 -7
- package/dist/search-hit-accumulator.d.ts +0 -3
- package/dist/search-hit-accumulator.js +27 -6
- package/dist/source-jar-reader.js +16 -2
- package/dist/source-resolver.d.ts +1 -0
- package/dist/source-resolver.js +93 -2
- package/dist/source-service.d.ts +76 -47
- package/dist/source-service.js +1344 -763
- package/dist/stdio-supervisor.d.ts +46 -0
- package/dist/stdio-supervisor.js +349 -0
- package/dist/storage/files-repo.d.ts +3 -0
- package/dist/storage/files-repo.js +66 -1
- package/dist/storage/migrations.d.ts +1 -1
- package/dist/storage/migrations.js +6 -2
- package/dist/storage/schema.d.ts +1 -0
- package/dist/storage/schema.js +7 -0
- package/dist/symbols/symbol-extractor.js +6 -4
- package/dist/tool-execution-gate.d.ts +15 -0
- package/dist/tool-execution-gate.js +58 -0
- package/dist/tool-input.d.ts +6 -0
- package/dist/tool-input.js +64 -0
- package/dist/types.d.ts +1 -1
- package/dist/version-diff-service.js +10 -5
- package/dist/version-service.js +7 -2
- package/dist/workspace-mapping-service.js +12 -0
- package/package.json +4 -1
package/dist/source-service.js
CHANGED
|
@@ -12,7 +12,7 @@ import { parseCoordinate } from "./maven-resolver.js";
|
|
|
12
12
|
import { MinecraftExplorerService } from "./minecraft-explorer-service.js";
|
|
13
13
|
import { parseMixinSource } from "./mixin-parser.js";
|
|
14
14
|
import { parseAccessWidener } from "./access-widener-parser.js";
|
|
15
|
-
import { validateParsedMixin, validateParsedAccessWidener } from "./mixin-validator.js";
|
|
15
|
+
import { validateParsedMixin, refreshMixinValidationOutcome, validateParsedAccessWidener } from "./mixin-validator.js";
|
|
16
16
|
import { resolveSourceTarget as resolveSourceTargetInternal } from "./source-resolver.js";
|
|
17
17
|
import { applyMappingPipeline } from "./mapping-pipeline-service.js";
|
|
18
18
|
import { MappingService } from "./mapping-service.js";
|
|
@@ -34,6 +34,19 @@ import { VersionDiffService } from "./version-diff-service.js";
|
|
|
34
34
|
import { ModDecompileService } from "./mod-decompile-service.js";
|
|
35
35
|
import { ModSearchService } from "./mod-search-service.js";
|
|
36
36
|
const utf8Decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
37
|
+
const VERSION_TOKEN_REGEX_CACHE = new Map();
|
|
38
|
+
const GLOB_REGEX_CACHE = new Map();
|
|
39
|
+
const MAX_HELPER_REGEX_CACHE = 128;
|
|
40
|
+
function rememberCachedRegex(cache, key, regex) {
|
|
41
|
+
if (cache.size >= MAX_HELPER_REGEX_CACHE) {
|
|
42
|
+
const oldestKey = cache.keys().next().value;
|
|
43
|
+
if (oldestKey) {
|
|
44
|
+
cache.delete(oldestKey);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
cache.set(key, regex);
|
|
48
|
+
return regex;
|
|
49
|
+
}
|
|
37
50
|
function truncateUtf8ToMaxBytes(content, maxBytes) {
|
|
38
51
|
const encoded = Buffer.from(content, "utf8");
|
|
39
52
|
if (encoded.length <= maxBytes) {
|
|
@@ -51,6 +64,88 @@ function truncateUtf8ToMaxBytes(content, maxBytes) {
|
|
|
51
64
|
}
|
|
52
65
|
return "";
|
|
53
66
|
}
|
|
67
|
+
function dedupeQualityFlags(qualityFlags) {
|
|
68
|
+
const seen = new Set();
|
|
69
|
+
const deduped = [];
|
|
70
|
+
for (const qualityFlag of qualityFlags) {
|
|
71
|
+
if (seen.has(qualityFlag)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
seen.add(qualityFlag);
|
|
75
|
+
deduped.push(qualityFlag);
|
|
76
|
+
}
|
|
77
|
+
return deduped;
|
|
78
|
+
}
|
|
79
|
+
function sameStringArray(left, right) {
|
|
80
|
+
if (left === right) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
if (!left || !right || left.length !== right.length) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
87
|
+
if (left[index] !== right[index]) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
function sameScopeFallback(left, right) {
|
|
94
|
+
if (left === right) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (!left || !right) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return left.requested === right.requested && left.applied === right.applied && left.reason === right.reason;
|
|
101
|
+
}
|
|
102
|
+
function sameResolutionTrace(left, right) {
|
|
103
|
+
if (left === right) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (!left || !right || left.length !== right.length) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
110
|
+
const leftEntry = left[index];
|
|
111
|
+
const rightEntry = right[index];
|
|
112
|
+
if (!leftEntry || !rightEntry) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (leftEntry.target !== rightEntry.target ||
|
|
116
|
+
leftEntry.step !== rightEntry.step ||
|
|
117
|
+
leftEntry.input !== rightEntry.input ||
|
|
118
|
+
leftEntry.output !== rightEntry.output ||
|
|
119
|
+
leftEntry.success !== rightEntry.success ||
|
|
120
|
+
leftEntry.detail !== rightEntry.detail) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
function sameMixinValidationProvenance(left, right) {
|
|
127
|
+
if (left === right) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (!left || !right) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
return (left.version === right.version &&
|
|
134
|
+
left.jarPath === right.jarPath &&
|
|
135
|
+
left.requestedMapping === right.requestedMapping &&
|
|
136
|
+
left.mappingApplied === right.mappingApplied &&
|
|
137
|
+
left.requestedScope === right.requestedScope &&
|
|
138
|
+
left.appliedScope === right.appliedScope &&
|
|
139
|
+
left.requestedSourcePriority === right.requestedSourcePriority &&
|
|
140
|
+
left.appliedSourcePriority === right.appliedSourcePriority &&
|
|
141
|
+
sameStringArray(left.resolutionNotes, right.resolutionNotes) &&
|
|
142
|
+
left.jarType === right.jarType &&
|
|
143
|
+
sameStringArray(left.mappingChain, right.mappingChain) &&
|
|
144
|
+
left.remapFailures === right.remapFailures &&
|
|
145
|
+
left.mappingAutoDetected === right.mappingAutoDetected &&
|
|
146
|
+
sameScopeFallback(left.scopeFallback, right.scopeFallback) &&
|
|
147
|
+
sameResolutionTrace(left.resolutionTrace, right.resolutionTrace));
|
|
148
|
+
}
|
|
54
149
|
const INDEX_SCHEMA_VERSION = 1;
|
|
55
150
|
const SYMBOL_KINDS = ["class", "interface", "enum", "record", "method", "field"];
|
|
56
151
|
function isSymbolKind(value) {
|
|
@@ -77,9 +172,37 @@ function hasExactVersionToken(path, version) {
|
|
|
77
172
|
return false;
|
|
78
173
|
}
|
|
79
174
|
// Avoid prefix false-positives like "1.21.1" matching "1.21.10".
|
|
80
|
-
const
|
|
175
|
+
const cached = VERSION_TOKEN_REGEX_CACHE.get(normalizedVersion);
|
|
176
|
+
const pattern = cached
|
|
177
|
+
?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}([^0-9a-z]|$)`, "i"));
|
|
81
178
|
return pattern.test(normalizedPath);
|
|
82
179
|
}
|
|
180
|
+
function looksLikeDeobfuscatedClassName(value) {
|
|
181
|
+
const trimmed = value.trim();
|
|
182
|
+
if (!trimmed) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
if (trimmed.startsWith("net.minecraft.") || trimmed.startsWith("com.mojang.")) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
const simpleName = trimmed.split(/[.$]/).at(-1) ?? trimmed;
|
|
189
|
+
return /^[A-Z][A-Za-z0-9_$]{2,}$/.test(simpleName);
|
|
190
|
+
}
|
|
191
|
+
function obfuscatedNamespaceHint(className) {
|
|
192
|
+
return `Artifact is indexed in obfuscated runtime names. Deobfuscated names like "${className}" usually require mapping="mojang" or a find-mapping lookup to obfuscated names.`;
|
|
193
|
+
}
|
|
194
|
+
function hasPartialNetMinecraftCoverage(qualityFlags) {
|
|
195
|
+
return qualityFlags.includes("partial-source-no-net-minecraft");
|
|
196
|
+
}
|
|
197
|
+
function buildResolveArtifactParams(target, extra = {}) {
|
|
198
|
+
return {
|
|
199
|
+
target: {
|
|
200
|
+
kind: target.kind,
|
|
201
|
+
value: target.value
|
|
202
|
+
},
|
|
203
|
+
...extra
|
|
204
|
+
};
|
|
205
|
+
}
|
|
83
206
|
function normalizeOptionalProjectPath(projectPath) {
|
|
84
207
|
if (!projectPath) {
|
|
85
208
|
return undefined;
|
|
@@ -91,19 +214,68 @@ function normalizeOptionalProjectPath(projectPath) {
|
|
|
91
214
|
const normalized = normalizePathForHost(trimmed, undefined, "projectPath");
|
|
92
215
|
return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
|
|
93
216
|
}
|
|
217
|
+
function resolveGradleUserHomePath() {
|
|
218
|
+
const configured = process.env.GRADLE_USER_HOME?.trim();
|
|
219
|
+
if (!configured) {
|
|
220
|
+
return resolvePath(homedir(), ".gradle");
|
|
221
|
+
}
|
|
222
|
+
const normalized = normalizePathForHost(configured, undefined, "GRADLE_USER_HOME");
|
|
223
|
+
return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
|
|
224
|
+
}
|
|
94
225
|
function buildVersionSourceSearchRoots(projectPath) {
|
|
95
226
|
const roots = new Set();
|
|
96
227
|
if (projectPath) {
|
|
97
228
|
roots.add(resolvePath(projectPath, ".gradle", "loom-cache"));
|
|
98
229
|
roots.add(resolvePath(projectPath, ".gradle-user", "caches", "fabric-loom"));
|
|
99
230
|
roots.add(resolvePath(projectPath, ".gradle", "caches", "fabric-loom"));
|
|
100
|
-
|
|
231
|
+
const projectParent = dirname(projectPath);
|
|
232
|
+
roots.add(resolvePath(projectParent, ".gradle-user-home", "loom-cache"));
|
|
233
|
+
roots.add(resolvePath(projectParent, ".gradle-user-home", "caches", "fabric-loom"));
|
|
101
234
|
}
|
|
102
|
-
const homeGradle =
|
|
235
|
+
const homeGradle = resolveGradleUserHomePath();
|
|
103
236
|
roots.add(resolvePath(homeGradle, "loom-cache"));
|
|
104
237
|
roots.add(resolvePath(homeGradle, "caches", "fabric-loom"));
|
|
105
238
|
return [...roots];
|
|
106
239
|
}
|
|
240
|
+
function looksLikeMinecraftSourceArtifact(path, hasMinecraftNamespace) {
|
|
241
|
+
if (hasMinecraftNamespace) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
const normalizedPath = normalizePathStyle(path).toLowerCase();
|
|
245
|
+
return (normalizedPath.includes("/minecraftmaven/") ||
|
|
246
|
+
normalizedPath.includes("/net/minecraft/") ||
|
|
247
|
+
/(?:^|\/)minecraft(?:-[a-z0-9._+]+)*-sources\.jar$/i.test(normalizedPath) ||
|
|
248
|
+
normalizedPath.includes("minecraft-merged") ||
|
|
249
|
+
normalizedPath.includes("minecraft-common") ||
|
|
250
|
+
normalizedPath.includes("minecraft-clientonly") ||
|
|
251
|
+
normalizedPath.includes("minecraft-client") ||
|
|
252
|
+
normalizedPath.includes("minecraft-server"));
|
|
253
|
+
}
|
|
254
|
+
function normalizeRequestedArtifactScope(scope) {
|
|
255
|
+
return scope ?? "vanilla";
|
|
256
|
+
}
|
|
257
|
+
function inferAppliedArtifactScope(input) {
|
|
258
|
+
if (input.scopeFallback?.applied === "vanilla") {
|
|
259
|
+
return "vanilla";
|
|
260
|
+
}
|
|
261
|
+
if (input.requestedScope === "vanilla") {
|
|
262
|
+
return "vanilla";
|
|
263
|
+
}
|
|
264
|
+
const joinedPath = `${normalizePathStyle(input.jarPath)} ${normalizePathStyle(input.resolvedSourceJarPath ?? "")}`.toLowerCase();
|
|
265
|
+
if (joinedPath.includes("minecraft-merged")) {
|
|
266
|
+
return "merged";
|
|
267
|
+
}
|
|
268
|
+
if (input.requestedScope === "loader" && joinedPath.includes("merged")) {
|
|
269
|
+
return "merged";
|
|
270
|
+
}
|
|
271
|
+
return input.requestedScope;
|
|
272
|
+
}
|
|
273
|
+
function scopeToJarType(scope) {
|
|
274
|
+
if (scope === "vanilla") {
|
|
275
|
+
return "vanilla-client";
|
|
276
|
+
}
|
|
277
|
+
return scope;
|
|
278
|
+
}
|
|
107
279
|
function parseQualifiedMethodSymbol(symbol) {
|
|
108
280
|
const trimmed = symbol.trim();
|
|
109
281
|
const separator = trimmed.lastIndexOf(".");
|
|
@@ -166,11 +338,18 @@ const COMMON_SOURCE_ROOTS = [
|
|
|
166
338
|
"quilt/src/main/java",
|
|
167
339
|
"quilt/src/client/java"
|
|
168
340
|
];
|
|
341
|
+
const MIXIN_PROJECT_DISCOVERY_IGNORES = [
|
|
342
|
+
"**/.git/**",
|
|
343
|
+
"**/.gradle/**",
|
|
344
|
+
"**/build/**",
|
|
345
|
+
"**/out/**",
|
|
346
|
+
"**/node_modules/**"
|
|
347
|
+
];
|
|
169
348
|
function normalizeMapping(mapping) {
|
|
170
349
|
if (mapping == null) {
|
|
171
|
-
return "
|
|
350
|
+
return "obfuscated";
|
|
172
351
|
}
|
|
173
|
-
if (mapping === "
|
|
352
|
+
if (mapping === "obfuscated" ||
|
|
174
353
|
mapping === "mojang" ||
|
|
175
354
|
mapping === "intermediary" ||
|
|
176
355
|
mapping === "yarn") {
|
|
@@ -181,8 +360,8 @@ function normalizeMapping(mapping) {
|
|
|
181
360
|
message: `Unsupported mapping "${mapping}".`,
|
|
182
361
|
details: {
|
|
183
362
|
mapping,
|
|
184
|
-
nextAction: "Try mapping=
|
|
185
|
-
suggestedCall: { tool: "resolve-artifact", params: { mapping: "
|
|
363
|
+
nextAction: "Try mapping=obfuscated which is always available.",
|
|
364
|
+
suggestedCall: { tool: "resolve-artifact", params: { mapping: "obfuscated" } }
|
|
186
365
|
}
|
|
187
366
|
});
|
|
188
367
|
}
|
|
@@ -194,7 +373,7 @@ function normalizeAccessWidenerNamespace(namespace) {
|
|
|
194
373
|
if (normalized === "named") {
|
|
195
374
|
return "yarn";
|
|
196
375
|
}
|
|
197
|
-
if (normalized === "
|
|
376
|
+
if (normalized === "obfuscated" ||
|
|
198
377
|
normalized === "mojang" ||
|
|
199
378
|
normalized === "intermediary" ||
|
|
200
379
|
normalized === "yarn") {
|
|
@@ -234,11 +413,11 @@ function sortDiffMemberChanges(changes) {
|
|
|
234
413
|
if (keyCompare !== 0) {
|
|
235
414
|
return keyCompare;
|
|
236
415
|
}
|
|
237
|
-
const fromOwnerCompare = left.from
|
|
416
|
+
const fromOwnerCompare = (left.from?.ownerFqn ?? "").localeCompare(right.from?.ownerFqn ?? "");
|
|
238
417
|
if (fromOwnerCompare !== 0) {
|
|
239
418
|
return fromOwnerCompare;
|
|
240
419
|
}
|
|
241
|
-
return left.to
|
|
420
|
+
return (left.to?.ownerFqn ?? "").localeCompare(right.to?.ownerFqn ?? "");
|
|
242
421
|
});
|
|
243
422
|
}
|
|
244
423
|
function changedMemberFields(fromMember, toMember, includeDescriptor) {
|
|
@@ -311,6 +490,16 @@ function emptyDiffDelta() {
|
|
|
311
490
|
modified: []
|
|
312
491
|
};
|
|
313
492
|
}
|
|
493
|
+
function compactDiffDelta(delta) {
|
|
494
|
+
return {
|
|
495
|
+
added: delta.added,
|
|
496
|
+
removed: delta.removed,
|
|
497
|
+
modified: delta.modified.map((change) => ({
|
|
498
|
+
key: change.key,
|
|
499
|
+
changed: [...change.changed]
|
|
500
|
+
}))
|
|
501
|
+
};
|
|
502
|
+
}
|
|
314
503
|
function normalizeIntent(intent) {
|
|
315
504
|
if (intent === "path" || intent === "text") {
|
|
316
505
|
return intent;
|
|
@@ -333,10 +522,16 @@ function canUseIndexedSearchPath(indexedSearchEnabled, intent, match, _scope) {
|
|
|
333
522
|
if (match === "regex") {
|
|
334
523
|
return false;
|
|
335
524
|
}
|
|
336
|
-
//
|
|
525
|
+
// packagePrefix and fileGlob are applied as post-filters on indexed candidates.
|
|
337
526
|
return true;
|
|
338
527
|
}
|
|
339
528
|
function buildGlobRegex(pattern) {
|
|
529
|
+
const cached = GLOB_REGEX_CACHE.get(pattern);
|
|
530
|
+
if (cached) {
|
|
531
|
+
GLOB_REGEX_CACHE.delete(pattern);
|
|
532
|
+
GLOB_REGEX_CACHE.set(pattern, cached);
|
|
533
|
+
return cached;
|
|
534
|
+
}
|
|
340
535
|
const REGEX_META = /[-/\\^$+.()|[\]{}]/;
|
|
341
536
|
let result = "";
|
|
342
537
|
let i = 0;
|
|
@@ -363,7 +558,7 @@ function buildGlobRegex(pattern) {
|
|
|
363
558
|
i += 1;
|
|
364
559
|
}
|
|
365
560
|
}
|
|
366
|
-
return new RegExp(`^${result}$`);
|
|
561
|
+
return rememberCachedRegex(GLOB_REGEX_CACHE, pattern, new RegExp(`^${result}$`));
|
|
367
562
|
}
|
|
368
563
|
function globToSqlLike(pattern) {
|
|
369
564
|
let result = "";
|
|
@@ -412,14 +607,6 @@ function checkPackagePrefix(filePath, packagePrefix) {
|
|
|
412
607
|
const normalizedPrefix = packagePrefix.replace(/\.+/g, "/").replace(/\/+$/, "");
|
|
413
608
|
return normalizePathStyle(filePath).startsWith(`${normalizedPrefix}/`);
|
|
414
609
|
}
|
|
415
|
-
function buildSnippetWindow(lines) {
|
|
416
|
-
const totalLines = clampLimit(lines, 8, 80);
|
|
417
|
-
const before = Math.floor((totalLines - 1) / 2);
|
|
418
|
-
return {
|
|
419
|
-
before,
|
|
420
|
-
after: Math.max(0, totalLines - 1 - before)
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
610
|
function buildSearchCursorContext(input) {
|
|
424
611
|
return JSON.stringify({
|
|
425
612
|
artifactId: input.artifactId,
|
|
@@ -427,7 +614,6 @@ function buildSearchCursorContext(input) {
|
|
|
427
614
|
intent: input.intent,
|
|
428
615
|
match: input.match,
|
|
429
616
|
queryMode: input.queryMode,
|
|
430
|
-
includeDefinition: input.includeDefinition,
|
|
431
617
|
packagePrefix: input.scope?.packagePrefix ?? "",
|
|
432
618
|
fileGlob: input.scope?.fileGlob ?? "",
|
|
433
619
|
symbolKind: input.scope?.symbolKind ?? ""
|
|
@@ -521,41 +707,6 @@ function matchRegexIndex(target, regex) {
|
|
|
521
707
|
const result = regex.exec(target);
|
|
522
708
|
return result?.index ?? -1;
|
|
523
709
|
}
|
|
524
|
-
function indexToLine(content, index) {
|
|
525
|
-
if (index <= 0) {
|
|
526
|
-
return 1;
|
|
527
|
-
}
|
|
528
|
-
return content.slice(0, index).split(/\r?\n/).length;
|
|
529
|
-
}
|
|
530
|
-
function lineToSymbol(symbol) {
|
|
531
|
-
if (!isSymbolKind(symbol.symbolKind)) {
|
|
532
|
-
return undefined;
|
|
533
|
-
}
|
|
534
|
-
return {
|
|
535
|
-
symbolKind: symbol.symbolKind,
|
|
536
|
-
symbolName: symbol.symbolName,
|
|
537
|
-
qualifiedName: symbol.qualifiedName,
|
|
538
|
-
line: symbol.line
|
|
539
|
-
};
|
|
540
|
-
}
|
|
541
|
-
function toContextSnippet(content, centerLineInput, beforeLines, afterLines, withLineNumbers) {
|
|
542
|
-
const lines = content.split(/\r?\n/);
|
|
543
|
-
const centerLine = Math.min(Math.max(1, centerLineInput), Math.max(lines.length, 1));
|
|
544
|
-
const requestedStart = Math.max(1, centerLine - beforeLines);
|
|
545
|
-
const requestedEnd = centerLine + afterLines;
|
|
546
|
-
const startLine = Math.min(requestedStart, Math.max(lines.length, 1));
|
|
547
|
-
const endLine = Math.min(requestedEnd, Math.max(lines.length, 1));
|
|
548
|
-
const snippetLines = lines.slice(startLine - 1, endLine);
|
|
549
|
-
const snippet = withLineNumbers
|
|
550
|
-
? snippetLines.map((line, index) => `${startLine + index}: ${line}`).join("\n")
|
|
551
|
-
: snippetLines.join("\n");
|
|
552
|
-
return {
|
|
553
|
-
startLine,
|
|
554
|
-
endLine,
|
|
555
|
-
snippet,
|
|
556
|
-
truncated: requestedStart !== startLine || requestedEnd !== endLine
|
|
557
|
-
};
|
|
558
|
-
}
|
|
559
710
|
function chunkArray(items, chunkSize) {
|
|
560
711
|
const size = Math.max(1, Math.trunc(chunkSize));
|
|
561
712
|
if (items.length === 0) {
|
|
@@ -583,6 +734,11 @@ export class SourceService {
|
|
|
583
734
|
versionDiffService;
|
|
584
735
|
modDecompileService;
|
|
585
736
|
modSearchService;
|
|
737
|
+
cacheMetricsState = {
|
|
738
|
+
entries: 0,
|
|
739
|
+
totalContentBytes: 0,
|
|
740
|
+
lru: []
|
|
741
|
+
};
|
|
586
742
|
constructor(explicitConfig, metrics = new RuntimeMetrics()) {
|
|
587
743
|
this.config = explicitConfig ?? loadConfig();
|
|
588
744
|
this.metrics = metrics;
|
|
@@ -642,14 +798,18 @@ export class SourceService {
|
|
|
642
798
|
continue;
|
|
643
799
|
}
|
|
644
800
|
const hasMinecraftNamespace = javaEntries.some((entry) => normalizePathStyle(entry).startsWith("net/minecraft/"));
|
|
645
|
-
const
|
|
801
|
+
const looksLikeMinecraftArtifact = looksLikeMinecraftSourceArtifact(normalizedPath, hasMinecraftNamespace);
|
|
802
|
+
const exactVersionMatch = hasExactVersionToken(normalizedPath, input.version);
|
|
803
|
+
const score = (looksLikeMinecraftArtifact ? 20_000 : 0) +
|
|
804
|
+
(hasMinecraftNamespace ? 10_000 : 0) +
|
|
646
805
|
(lower.includes("minecraft-merged") ? 2_000 : 0) +
|
|
647
|
-
(
|
|
806
|
+
(exactVersionMatch ? 1_000 : 0) +
|
|
648
807
|
Math.min(javaEntries.length, 500);
|
|
649
808
|
candidates.push({
|
|
650
809
|
jarPath: normalizedPath,
|
|
651
810
|
javaEntryCount: javaEntries.length,
|
|
652
811
|
hasMinecraftNamespace,
|
|
812
|
+
looksLikeMinecraftArtifact,
|
|
653
813
|
score
|
|
654
814
|
});
|
|
655
815
|
}
|
|
@@ -660,14 +820,16 @@ export class SourceService {
|
|
|
660
820
|
}
|
|
661
821
|
return left.jarPath.localeCompare(right.jarPath);
|
|
662
822
|
});
|
|
663
|
-
const selected = candidates.find((candidate) => candidate.hasMinecraftNamespace) ??
|
|
823
|
+
const selected = candidates.find((candidate) => candidate.looksLikeMinecraftArtifact && candidate.hasMinecraftNamespace) ??
|
|
824
|
+
candidates.find((candidate) => candidate.looksLikeMinecraftArtifact);
|
|
664
825
|
const candidateArtifacts = candidates
|
|
665
826
|
.slice(0, 20)
|
|
666
|
-
.map((candidate) => `${candidate.jarPath}#java=${candidate.javaEntryCount}`);
|
|
827
|
+
.map((candidate) => `${candidate.jarPath}#java=${candidate.javaEntryCount}#net_minecraft=${candidate.hasMinecraftNamespace ? 1 : 0}`);
|
|
667
828
|
return {
|
|
668
829
|
searchedPaths,
|
|
669
830
|
candidateArtifacts,
|
|
670
|
-
selectedSourceJarPath: selected?.jarPath
|
|
831
|
+
selectedSourceJarPath: selected?.jarPath,
|
|
832
|
+
selectedHasMinecraftNamespace: selected?.hasMinecraftNamespace
|
|
671
833
|
};
|
|
672
834
|
}
|
|
673
835
|
buildVersionSourceRecoveryCommand(projectPath) {
|
|
@@ -677,6 +839,58 @@ export class SourceService {
|
|
|
677
839
|
: "";
|
|
678
840
|
return `${prefix}./gradlew genSources --no-daemon`;
|
|
679
841
|
}
|
|
842
|
+
buildArtifactContentsSummary(input) {
|
|
843
|
+
const sourceKind = input.isDecompiled || input.origin === "decompiled" || !normalizeOptionalString(input.sourceJarPath)
|
|
844
|
+
? "decompiled-binary"
|
|
845
|
+
: "source-jar";
|
|
846
|
+
const sourceCoverage = hasPartialNetMinecraftCoverage(input.qualityFlags) ? "partial" : "full";
|
|
847
|
+
return {
|
|
848
|
+
sourceKind,
|
|
849
|
+
indexedContentKinds: ["java-source"],
|
|
850
|
+
resourcesIncluded: false,
|
|
851
|
+
sourceCoverage
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
inferVersionFromContext(input) {
|
|
855
|
+
const direct = normalizeOptionalString(input.version);
|
|
856
|
+
if (direct) {
|
|
857
|
+
return direct;
|
|
858
|
+
}
|
|
859
|
+
const resolvedFromVersion = normalizeOptionalString(input.provenance?.resolvedFrom.version);
|
|
860
|
+
if (resolvedFromVersion) {
|
|
861
|
+
return resolvedFromVersion;
|
|
862
|
+
}
|
|
863
|
+
if (input.provenance?.target.kind === "version") {
|
|
864
|
+
const targetVersion = normalizeOptionalString(input.provenance.target.value);
|
|
865
|
+
if (targetVersion) {
|
|
866
|
+
return targetVersion;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
const coordinate = normalizeOptionalString(input.coordinate);
|
|
870
|
+
if (coordinate) {
|
|
871
|
+
try {
|
|
872
|
+
return parseCoordinate(coordinate).version;
|
|
873
|
+
}
|
|
874
|
+
catch {
|
|
875
|
+
return undefined;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
return undefined;
|
|
879
|
+
}
|
|
880
|
+
async resolveVersionContext(input) {
|
|
881
|
+
const inferredVersion = this.inferVersionFromContext(input);
|
|
882
|
+
if (inferredVersion) {
|
|
883
|
+
return inferredVersion;
|
|
884
|
+
}
|
|
885
|
+
if (!input.preferProjectVersion || !input.projectPath) {
|
|
886
|
+
return undefined;
|
|
887
|
+
}
|
|
888
|
+
const detected = await this.workspaceMappingService.detectProjectMinecraftVersion(input.projectPath);
|
|
889
|
+
if (detected) {
|
|
890
|
+
input.warnings.push(`Using project version "${detected}" from gradle.properties because the artifact metadata did not include a version.`);
|
|
891
|
+
}
|
|
892
|
+
return detected;
|
|
893
|
+
}
|
|
680
894
|
async resolveArtifact(input) {
|
|
681
895
|
const kind = input.target.kind;
|
|
682
896
|
let value = input.target.value?.trim();
|
|
@@ -734,13 +948,13 @@ export class SourceService {
|
|
|
734
948
|
// coordinate validity is validated by resolver, keep version undefined on parse failure.
|
|
735
949
|
}
|
|
736
950
|
}
|
|
737
|
-
// Unobfuscated versions (MC 26.1+) ship with
|
|
951
|
+
// Unobfuscated versions (MC 26.1+) ship with deobfuscated runtime names; intermediary/yarn are not applicable.
|
|
738
952
|
let effectiveMapping = mapping;
|
|
739
953
|
if ((mapping === "intermediary" || mapping === "yarn") &&
|
|
740
954
|
resolvedVersion &&
|
|
741
955
|
isUnobfuscatedVersion(resolvedVersion)) {
|
|
742
|
-
warnings.push(`Version ${resolvedVersion} is unobfuscated; ${mapping} mappings are not applicable. Using
|
|
743
|
-
effectiveMapping = "
|
|
956
|
+
warnings.push(`Version ${resolvedVersion} is unobfuscated; ${mapping} mappings are not applicable. Using the obfuscated namespace label for the deobfuscated runtime names.`);
|
|
957
|
+
effectiveMapping = "obfuscated";
|
|
744
958
|
}
|
|
745
959
|
if (kind === "version" && resolvedVersion && effectiveMapping === "mojang" && scope !== "vanilla") {
|
|
746
960
|
versionSourceDiscovery = await this.discoverVersionSourceJar({
|
|
@@ -788,7 +1002,7 @@ export class SourceService {
|
|
|
788
1002
|
if (isVanillaMojang && input.projectPath) {
|
|
789
1003
|
suggestedCall = {
|
|
790
1004
|
tool: "resolve-artifact",
|
|
791
|
-
params: {
|
|
1005
|
+
params: buildResolveArtifactParams({ kind, value }, { mapping: "mojang", scope: "merged", projectPath: input.projectPath })
|
|
792
1006
|
};
|
|
793
1007
|
nextAction =
|
|
794
1008
|
"scope=vanilla blocks Loom cache discovery needed for mojang mapping. " +
|
|
@@ -797,24 +1011,25 @@ export class SourceService {
|
|
|
797
1011
|
else if (isVanillaMojang) {
|
|
798
1012
|
suggestedCall = {
|
|
799
1013
|
tool: "resolve-artifact",
|
|
800
|
-
params: {
|
|
1014
|
+
params: buildResolveArtifactParams({ kind, value }, { mapping: "obfuscated", scope: "vanilla" })
|
|
801
1015
|
};
|
|
802
1016
|
nextAction =
|
|
803
1017
|
"scope=vanilla blocks Loom cache discovery needed for mojang mapping. " +
|
|
804
|
-
"Without a projectPath, use mapping=
|
|
1018
|
+
"Without a projectPath, use mapping=obfuscated to read vanilla runtime names directly.";
|
|
805
1019
|
}
|
|
806
1020
|
else {
|
|
807
1021
|
suggestedCall = {
|
|
808
1022
|
tool: "resolve-artifact",
|
|
809
|
-
params: {
|
|
1023
|
+
params: buildResolveArtifactParams({ kind, value }, { mapping: "obfuscated", ...(scope ? { scope } : {}) })
|
|
810
1024
|
};
|
|
811
|
-
nextAction = "Retry with mapping=
|
|
1025
|
+
nextAction = "Retry with mapping=obfuscated to use the runtime obfuscated namespace.";
|
|
812
1026
|
}
|
|
813
1027
|
throw createError({
|
|
814
1028
|
code: ERROR_CODES.MAPPING_NOT_APPLIED,
|
|
815
1029
|
message: caughtError.message,
|
|
816
1030
|
details: {
|
|
817
1031
|
...(caughtError.details ?? {}),
|
|
1032
|
+
artifactOrigin: resolved.origin,
|
|
818
1033
|
searchedPaths: versionSourceDiscovery?.searchedPaths ?? [],
|
|
819
1034
|
candidateArtifacts: versionSourceDiscovery?.candidateArtifacts ?? resolved.adjacentSourceCandidates ?? [],
|
|
820
1035
|
recommendedCommand: this.buildVersionSourceRecoveryCommand(input.projectPath),
|
|
@@ -834,14 +1049,17 @@ export class SourceService {
|
|
|
834
1049
|
details: {
|
|
835
1050
|
mapping: effectiveMapping,
|
|
836
1051
|
target: { kind, value },
|
|
837
|
-
nextAction: "Use
|
|
838
|
-
suggestedCall: {
|
|
1052
|
+
nextAction: "Use target: { kind: \"version\", value } or a versioned Maven coordinate so mapping artifacts can be resolved.",
|
|
1053
|
+
suggestedCall: {
|
|
1054
|
+
tool: "resolve-artifact",
|
|
1055
|
+
params: buildResolveArtifactParams({ kind: "version", value }, { ...(scope ? { scope } : {}) })
|
|
1056
|
+
}
|
|
839
1057
|
}
|
|
840
1058
|
});
|
|
841
1059
|
}
|
|
842
1060
|
const mappingAvailability = await this.mappingService.ensureMappingAvailable({
|
|
843
1061
|
version: resolved.version,
|
|
844
|
-
sourceMapping: "
|
|
1062
|
+
sourceMapping: "obfuscated",
|
|
845
1063
|
targetMapping: effectiveMapping,
|
|
846
1064
|
sourcePriority: input.sourcePriority
|
|
847
1065
|
});
|
|
@@ -864,6 +1082,10 @@ export class SourceService {
|
|
|
864
1082
|
}
|
|
865
1083
|
if (versionSourceDiscovery?.selectedSourceJarPath) {
|
|
866
1084
|
resolved.qualityFlags.push("source-jar-validated");
|
|
1085
|
+
if (versionSourceDiscovery.selectedHasMinecraftNamespace === false) {
|
|
1086
|
+
resolved.qualityFlags.push("partial-source-no-net-minecraft");
|
|
1087
|
+
warnings.push(`Source coverage does not include net.minecraft for ${versionSourceDiscovery.selectedSourceJarPath}; class lookups may fall back to the binary artifact.`);
|
|
1088
|
+
}
|
|
867
1089
|
if (kind === "version" && !hasExactVersionToken(versionSourceDiscovery.selectedSourceJarPath, value)) {
|
|
868
1090
|
if (input.strictVersion) {
|
|
869
1091
|
throw createError({
|
|
@@ -874,7 +1096,10 @@ export class SourceService {
|
|
|
874
1096
|
selectedSourceJar: versionSourceDiscovery.selectedSourceJarPath,
|
|
875
1097
|
candidateArtifacts: versionSourceDiscovery.candidateArtifacts,
|
|
876
1098
|
nextAction: "Use strictVersion=false (default) to allow approximation, or ensure the exact version source jar is in the Loom cache.",
|
|
877
|
-
suggestedCall: {
|
|
1099
|
+
suggestedCall: {
|
|
1100
|
+
tool: "resolve-artifact",
|
|
1101
|
+
params: buildResolveArtifactParams({ kind: "version", value }, { strictVersion: false })
|
|
1102
|
+
}
|
|
878
1103
|
}
|
|
879
1104
|
});
|
|
880
1105
|
}
|
|
@@ -882,7 +1107,7 @@ export class SourceService {
|
|
|
882
1107
|
warnings.push(`Requested version "${value}" but resolved source jar does not contain exact version string: ${versionSourceDiscovery.selectedSourceJarPath}`);
|
|
883
1108
|
}
|
|
884
1109
|
}
|
|
885
|
-
resolved.qualityFlags =
|
|
1110
|
+
resolved.qualityFlags = dedupeQualityFlags(resolved.qualityFlags);
|
|
886
1111
|
await this.ingestIfNeeded(resolved);
|
|
887
1112
|
let sampleEntries;
|
|
888
1113
|
if (resolved.sourceJarPath) {
|
|
@@ -912,6 +1137,12 @@ export class SourceService {
|
|
|
912
1137
|
provenance,
|
|
913
1138
|
qualityFlags: resolved.qualityFlags,
|
|
914
1139
|
repoUrl: resolved.repoUrl,
|
|
1140
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1141
|
+
origin: resolved.origin,
|
|
1142
|
+
sourceJarPath: resolved.sourceJarPath,
|
|
1143
|
+
isDecompiled: resolved.isDecompiled,
|
|
1144
|
+
qualityFlags: resolved.qualityFlags
|
|
1145
|
+
}),
|
|
915
1146
|
warnings,
|
|
916
1147
|
sampleEntries
|
|
917
1148
|
};
|
|
@@ -942,8 +1173,14 @@ export class SourceService {
|
|
|
942
1173
|
if (!query) {
|
|
943
1174
|
return {
|
|
944
1175
|
hits: [],
|
|
945
|
-
|
|
946
|
-
|
|
1176
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
1177
|
+
returnedNamespace: artifact.mappingApplied ?? "obfuscated",
|
|
1178
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1179
|
+
origin: artifact.origin,
|
|
1180
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
1181
|
+
isDecompiled: artifact.isDecompiled,
|
|
1182
|
+
qualityFlags: artifact.qualityFlags
|
|
1183
|
+
})
|
|
947
1184
|
};
|
|
948
1185
|
}
|
|
949
1186
|
const intent = normalizeIntent(input.intent);
|
|
@@ -961,12 +1198,15 @@ export class SourceService {
|
|
|
961
1198
|
const searchLimitCap = match === "regex"
|
|
962
1199
|
? Math.max(1, Math.min(this.config.maxSearchHits, MAX_REGEX_RESULT_LIMIT))
|
|
963
1200
|
: this.config.maxSearchHits;
|
|
1201
|
+
const scope = input.scope;
|
|
1202
|
+
if (scope?.symbolKind && intent !== "symbol") {
|
|
1203
|
+
throw createError({
|
|
1204
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
1205
|
+
message: 'symbolKind filter is only supported when intent="symbol".'
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
964
1208
|
const limit = clampLimit(input.limit, 20, searchLimitCap);
|
|
965
|
-
const includeDefinition = input.include?.includeDefinition ?? false;
|
|
966
|
-
const includeOneHop = input.include?.includeOneHop ?? false;
|
|
967
|
-
const snippetWindow = buildSnippetWindow(input.include?.snippetLines);
|
|
968
1209
|
const regexPattern = match === "regex" ? compileRegex(query) : undefined;
|
|
969
|
-
const scope = input.scope;
|
|
970
1210
|
const queryMode = input.queryMode ?? "auto";
|
|
971
1211
|
const cursorContext = buildSearchCursorContext({
|
|
972
1212
|
artifactId: artifact.artifactId,
|
|
@@ -974,8 +1214,7 @@ export class SourceService {
|
|
|
974
1214
|
intent,
|
|
975
1215
|
match,
|
|
976
1216
|
queryMode,
|
|
977
|
-
scope
|
|
978
|
-
includeDefinition
|
|
1217
|
+
scope
|
|
979
1218
|
});
|
|
980
1219
|
const decodedCursor = decodeSearchCursor(input.cursor);
|
|
981
1220
|
const cursor = decodedCursor?.contextKey === cursorContext ? decodedCursor : undefined;
|
|
@@ -991,52 +1230,35 @@ export class SourceService {
|
|
|
991
1230
|
const hasSeparators = /[._$]/.test(query);
|
|
992
1231
|
const tokenOnlyTextIntent = intent === "text" && queryMode === "token";
|
|
993
1232
|
if (intent === "symbol") {
|
|
994
|
-
this.searchSymbolIntent(artifact.artifactId, query, match, scope,
|
|
995
|
-
// WS4: Use repo-level COUNT for symbol totalApprox when not regex
|
|
996
|
-
if (match !== "regex") {
|
|
997
|
-
const approxCount = this.symbolsRepo.countScopedSymbols({
|
|
998
|
-
artifactId: artifact.artifactId,
|
|
999
|
-
query,
|
|
1000
|
-
match,
|
|
1001
|
-
symbolKind: scope?.symbolKind,
|
|
1002
|
-
packagePrefix: scope?.packagePrefix
|
|
1003
|
-
});
|
|
1004
|
-
accumulator.setTotalApproxOverride(approxCount);
|
|
1005
|
-
}
|
|
1233
|
+
this.searchSymbolIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1006
1234
|
}
|
|
1007
1235
|
else if (queryMode === "literal" && intent === "text") {
|
|
1008
1236
|
// F-03: queryMode=literal forces substring scan for text intent
|
|
1009
1237
|
this.metrics.recordSearchFallback();
|
|
1010
|
-
this.searchTextIntent(artifact.artifactId, query, match, scope,
|
|
1238
|
+
this.searchTextIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1011
1239
|
}
|
|
1012
1240
|
else if (!indexedSearchEnabled) {
|
|
1013
1241
|
this.metrics.recordIndexedDisabled();
|
|
1014
1242
|
if (!tokenOnlyTextIntent) {
|
|
1015
1243
|
this.metrics.recordSearchFallback();
|
|
1016
1244
|
if (intent === "path") {
|
|
1017
|
-
this.searchPathIntent(artifact.artifactId, query, match, scope,
|
|
1245
|
+
this.searchPathIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1018
1246
|
}
|
|
1019
1247
|
else {
|
|
1020
|
-
this.searchTextIntent(artifact.artifactId, query, match, scope,
|
|
1248
|
+
this.searchTextIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1021
1249
|
}
|
|
1022
1250
|
}
|
|
1023
1251
|
}
|
|
1024
1252
|
else if (canUseIndexedSearchPath(indexedSearchEnabled, intent, match, scope)) {
|
|
1025
1253
|
try {
|
|
1026
1254
|
if (intent === "path") {
|
|
1027
|
-
this.searchPathIntentIndexed(artifact.artifactId, query, match, scope,
|
|
1028
|
-
// WS4: Use repo-level COUNT for totalApprox instead of accumulator count
|
|
1029
|
-
const approxCount = this.filesRepo.countPathCandidates(artifact.artifactId, query);
|
|
1030
|
-
accumulator.setTotalApproxOverride(approxCount);
|
|
1255
|
+
this.searchPathIntentIndexed(artifact.artifactId, query, match, scope, recordHit);
|
|
1031
1256
|
}
|
|
1032
1257
|
else {
|
|
1033
|
-
this.searchTextIntentIndexed(artifact.artifactId, query, match, scope,
|
|
1034
|
-
// WS4: Use repo-level COUNT for totalApprox instead of accumulator count
|
|
1035
|
-
const approxCount = this.filesRepo.countTextCandidates(artifact.artifactId, query);
|
|
1036
|
-
accumulator.setTotalApproxOverride(approxCount);
|
|
1258
|
+
this.searchTextIntentIndexed(artifact.artifactId, query, match, scope, recordHit);
|
|
1037
1259
|
// F-03: queryMode=auto fallback — when indexed returns 0 hits and query has separators, retry with literal scan
|
|
1038
1260
|
if (queryMode === "auto" && hasSeparators && accumulator.currentCount() === 0) {
|
|
1039
|
-
this.searchTextIntent(artifact.artifactId, query, match, scope,
|
|
1261
|
+
this.searchTextIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1040
1262
|
}
|
|
1041
1263
|
}
|
|
1042
1264
|
this.metrics.recordSearchIndexedHit();
|
|
@@ -1052,10 +1274,10 @@ export class SourceService {
|
|
|
1052
1274
|
// F-03: queryMode=token suppresses error-path fallback to brute-force scan
|
|
1053
1275
|
if (!tokenOnlyTextIntent) {
|
|
1054
1276
|
if (intent === "path") {
|
|
1055
|
-
this.searchPathIntent(artifact.artifactId, query, match, scope,
|
|
1277
|
+
this.searchPathIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1056
1278
|
}
|
|
1057
1279
|
else {
|
|
1058
|
-
this.searchTextIntent(artifact.artifactId, query, match, scope,
|
|
1280
|
+
this.searchTextIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1059
1281
|
}
|
|
1060
1282
|
}
|
|
1061
1283
|
}
|
|
@@ -1064,10 +1286,10 @@ export class SourceService {
|
|
|
1064
1286
|
if (!tokenOnlyTextIntent) {
|
|
1065
1287
|
this.metrics.recordSearchFallback();
|
|
1066
1288
|
if (intent === "path") {
|
|
1067
|
-
this.searchPathIntent(artifact.artifactId, query, match, scope,
|
|
1289
|
+
this.searchPathIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1068
1290
|
}
|
|
1069
1291
|
else {
|
|
1070
|
-
this.searchTextIntent(artifact.artifactId, query, match, scope,
|
|
1292
|
+
this.searchTextIntent(artifact.artifactId, query, match, scope, regexPattern, recordHit);
|
|
1071
1293
|
}
|
|
1072
1294
|
}
|
|
1073
1295
|
}
|
|
@@ -1078,31 +1300,18 @@ export class SourceService {
|
|
|
1078
1300
|
const nextCursor = finalizedHits.nextCursorHit
|
|
1079
1301
|
? encodeSearchCursor(finalizedHits.nextCursorHit, cursorContext)
|
|
1080
1302
|
: undefined;
|
|
1081
|
-
|
|
1082
|
-
? this.buildOneHopRelations(artifact.artifactId, page.flatMap((hit) => hit.symbol && isSymbolKind(hit.symbol.symbolKind)
|
|
1083
|
-
? [
|
|
1084
|
-
{
|
|
1085
|
-
symbolKind: hit.symbol.symbolKind,
|
|
1086
|
-
symbolName: hit.symbol.symbolName,
|
|
1087
|
-
filePath: hit.filePath,
|
|
1088
|
-
line: hit.symbol.line
|
|
1089
|
-
}
|
|
1090
|
-
]
|
|
1091
|
-
: []), 10)
|
|
1092
|
-
: undefined;
|
|
1093
|
-
if (relations?.length) {
|
|
1094
|
-
this.metrics.recordOneHopExpansion(relations.length);
|
|
1095
|
-
}
|
|
1096
|
-
this.metrics.recordSearchTokenBytesReturned(Buffer.byteLength(JSON.stringify({ hits: page, relations }), "utf8"));
|
|
1097
|
-
// B5: If post-filtering eliminated all hits on the first page, the SQL-based
|
|
1098
|
-
// totalApprox is misleading — correct it to 0.
|
|
1099
|
-
const totalApprox = page.length === 0 && !cursor ? 0 : finalizedHits.totalApprox;
|
|
1303
|
+
this.metrics.recordSearchTokenBytesReturned(Buffer.byteLength(JSON.stringify({ hits: page }), "utf8"));
|
|
1100
1304
|
return {
|
|
1101
1305
|
hits: page,
|
|
1102
|
-
relations: relations && relations.length > 0 ? relations : undefined,
|
|
1103
1306
|
nextCursor,
|
|
1104
|
-
|
|
1105
|
-
|
|
1307
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
1308
|
+
returnedNamespace: artifact.mappingApplied ?? "obfuscated",
|
|
1309
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1310
|
+
origin: artifact.origin,
|
|
1311
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
1312
|
+
isDecompiled: artifact.isDecompiled,
|
|
1313
|
+
qualityFlags: artifact.qualityFlags
|
|
1314
|
+
})
|
|
1106
1315
|
};
|
|
1107
1316
|
}
|
|
1108
1317
|
finally {
|
|
@@ -1139,7 +1348,14 @@ export class SourceService {
|
|
|
1139
1348
|
content,
|
|
1140
1349
|
contentBytes: fullBytes,
|
|
1141
1350
|
truncated,
|
|
1142
|
-
mappingApplied: artifact.mappingApplied ?? "
|
|
1351
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
1352
|
+
returnedNamespace: artifact.mappingApplied ?? "obfuscated",
|
|
1353
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1354
|
+
origin: artifact.origin,
|
|
1355
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
1356
|
+
isDecompiled: artifact.isDecompiled,
|
|
1357
|
+
qualityFlags: artifact.qualityFlags
|
|
1358
|
+
})
|
|
1143
1359
|
};
|
|
1144
1360
|
}
|
|
1145
1361
|
finally {
|
|
@@ -1151,15 +1367,29 @@ export class SourceService {
|
|
|
1151
1367
|
try {
|
|
1152
1368
|
const artifact = this.getArtifact(input.artifactId);
|
|
1153
1369
|
const limit = clampLimit(input.limit, 200, 2000);
|
|
1370
|
+
const warnings = [];
|
|
1154
1371
|
const page = this.filesRepo.listFiles(artifact.artifactId, {
|
|
1155
1372
|
limit,
|
|
1156
1373
|
cursor: input.cursor,
|
|
1157
1374
|
prefix: input.prefix
|
|
1158
1375
|
});
|
|
1376
|
+
const normalizedPrefix = normalizeOptionalString(input.prefix);
|
|
1377
|
+
if (normalizedPrefix &&
|
|
1378
|
+
page.items.length === 0 &&
|
|
1379
|
+
(normalizedPrefix.startsWith("assets/") || normalizedPrefix.startsWith("data/"))) {
|
|
1380
|
+
warnings.push("Indexed artifacts currently include Java source only; non-Java resources are not indexed. Inspect the original jar on disk if you need assets or data files.");
|
|
1381
|
+
}
|
|
1159
1382
|
return {
|
|
1160
1383
|
items: page.items,
|
|
1161
1384
|
nextCursor: page.nextCursor,
|
|
1162
|
-
mappingApplied: artifact.mappingApplied ?? "
|
|
1385
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated",
|
|
1386
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
1387
|
+
origin: artifact.origin,
|
|
1388
|
+
sourceJarPath: artifact.sourceJarPath,
|
|
1389
|
+
isDecompiled: artifact.isDecompiled,
|
|
1390
|
+
qualityFlags: artifact.qualityFlags
|
|
1391
|
+
}),
|
|
1392
|
+
warnings
|
|
1163
1393
|
};
|
|
1164
1394
|
}
|
|
1165
1395
|
finally {
|
|
@@ -1298,21 +1528,24 @@ export class SourceService {
|
|
|
1298
1528
|
resolved: false,
|
|
1299
1529
|
status: "mapping_unavailable",
|
|
1300
1530
|
candidates: [],
|
|
1531
|
+
candidateCount: 0,
|
|
1301
1532
|
workspaceDetection,
|
|
1302
1533
|
warnings
|
|
1303
1534
|
};
|
|
1304
1535
|
}
|
|
1305
1536
|
const mappingApplied = workspaceDetection.mappingApplied;
|
|
1306
1537
|
if (kind === "method") {
|
|
1538
|
+
const methodOwner = owner;
|
|
1539
|
+
const methodDescriptor = descriptor;
|
|
1307
1540
|
const exact = await this.mappingService.resolveMethodMappingExact({
|
|
1308
1541
|
version,
|
|
1309
|
-
|
|
1310
|
-
owner,
|
|
1542
|
+
owner: methodOwner,
|
|
1311
1543
|
name,
|
|
1312
|
-
descriptor:
|
|
1544
|
+
descriptor: methodDescriptor,
|
|
1313
1545
|
sourceMapping: input.sourceMapping,
|
|
1314
1546
|
targetMapping: mappingApplied,
|
|
1315
|
-
sourcePriority: input.sourcePriority
|
|
1547
|
+
sourcePriority: input.sourcePriority,
|
|
1548
|
+
maxCandidates: input.maxCandidates
|
|
1316
1549
|
});
|
|
1317
1550
|
return {
|
|
1318
1551
|
...exact,
|
|
@@ -1342,6 +1575,7 @@ export class SourceService {
|
|
|
1342
1575
|
resolved: false,
|
|
1343
1576
|
status: "not_found",
|
|
1344
1577
|
candidates: [],
|
|
1578
|
+
candidateCount: 0,
|
|
1345
1579
|
workspaceDetection,
|
|
1346
1580
|
warnings: [...warnings, ...matrix.warnings]
|
|
1347
1581
|
};
|
|
@@ -1369,6 +1603,7 @@ export class SourceService {
|
|
|
1369
1603
|
status: "resolved",
|
|
1370
1604
|
resolvedSymbol,
|
|
1371
1605
|
candidates: [resolvedCandidate],
|
|
1606
|
+
candidateCount: 1,
|
|
1372
1607
|
workspaceDetection,
|
|
1373
1608
|
warnings: [...warnings, ...matrix.warnings]
|
|
1374
1609
|
};
|
|
@@ -1381,7 +1616,8 @@ export class SourceService {
|
|
|
1381
1616
|
descriptor,
|
|
1382
1617
|
sourceMapping: input.sourceMapping,
|
|
1383
1618
|
targetMapping: mappingApplied,
|
|
1384
|
-
sourcePriority: input.sourcePriority
|
|
1619
|
+
sourcePriority: input.sourcePriority,
|
|
1620
|
+
maxCandidates: input.maxCandidates
|
|
1385
1621
|
});
|
|
1386
1622
|
const filtered = mapped.candidates.filter((candidate) => candidate.kind === kind);
|
|
1387
1623
|
let status;
|
|
@@ -1404,6 +1640,8 @@ export class SourceService {
|
|
|
1404
1640
|
status,
|
|
1405
1641
|
resolvedSymbol: status === "resolved" ? filtered[0] : undefined,
|
|
1406
1642
|
candidates: filtered,
|
|
1643
|
+
candidateCount: mapped.candidateCount,
|
|
1644
|
+
candidatesTruncated: mapped.candidatesTruncated,
|
|
1407
1645
|
workspaceDetection,
|
|
1408
1646
|
warnings: [...warnings, ...mapped.warnings]
|
|
1409
1647
|
};
|
|
@@ -1473,14 +1711,14 @@ export class SourceService {
|
|
|
1473
1711
|
try {
|
|
1474
1712
|
let resolvedSymbols = resolvedSymbolsByVersion.get(version);
|
|
1475
1713
|
if (!resolvedSymbols) {
|
|
1476
|
-
const [
|
|
1477
|
-
this.
|
|
1478
|
-
this.
|
|
1714
|
+
const [obfuscatedClassName, obfuscatedMethod] = await Promise.all([
|
|
1715
|
+
this.resolveToObfuscatedClassName(userClassName, version, mapping, input.sourcePriority, warnings),
|
|
1716
|
+
this.resolveToObfuscatedMemberName(userMethodName, userClassName, descriptor, "method", version, mapping, input.sourcePriority, warnings)
|
|
1479
1717
|
]);
|
|
1480
1718
|
resolvedSymbols = {
|
|
1481
|
-
className:
|
|
1482
|
-
methodName:
|
|
1483
|
-
methodDescriptor:
|
|
1719
|
+
className: obfuscatedClassName,
|
|
1720
|
+
methodName: obfuscatedMethod.name,
|
|
1721
|
+
methodDescriptor: obfuscatedMethod.descriptor
|
|
1484
1722
|
};
|
|
1485
1723
|
resolvedSymbolsByVersion.set(version, resolvedSymbols);
|
|
1486
1724
|
}
|
|
@@ -1574,6 +1812,7 @@ export class SourceService {
|
|
|
1574
1812
|
const className = input.className.trim();
|
|
1575
1813
|
const fromVersion = input.fromVersion.trim();
|
|
1576
1814
|
const toVersion = input.toVersion.trim();
|
|
1815
|
+
const includeFullDiff = input.includeFullDiff ?? true;
|
|
1577
1816
|
if (!className || !fromVersion || !toVersion) {
|
|
1578
1817
|
throw createError({
|
|
1579
1818
|
code: ERROR_CODES.INVALID_INPUT,
|
|
@@ -1630,18 +1869,18 @@ export class SourceService {
|
|
|
1630
1869
|
});
|
|
1631
1870
|
}
|
|
1632
1871
|
const mappingWarnings = [];
|
|
1633
|
-
const
|
|
1634
|
-
const
|
|
1635
|
-
?
|
|
1636
|
-
: await this.
|
|
1872
|
+
const obfuscatedFromClassName = await this.resolveToObfuscatedClassName(className, fromVersion, mapping, input.sourcePriority, mappingWarnings);
|
|
1873
|
+
const obfuscatedToClassName = fromVersion === toVersion
|
|
1874
|
+
? obfuscatedFromClassName
|
|
1875
|
+
: await this.resolveToObfuscatedClassName(className, toVersion, mapping, input.sourcePriority, mappingWarnings);
|
|
1637
1876
|
const [fromResolved, toResolved] = await Promise.all([
|
|
1638
1877
|
this.versionService.resolveVersionJar(fromVersion),
|
|
1639
1878
|
this.versionService.resolveVersionJar(toVersion)
|
|
1640
1879
|
]);
|
|
1641
|
-
const loadSignature = async (version, jarPath,
|
|
1880
|
+
const loadSignature = async (version, jarPath, obfuscatedClassName) => {
|
|
1642
1881
|
try {
|
|
1643
1882
|
const signature = await this.explorerService.getSignature({
|
|
1644
|
-
fqn:
|
|
1883
|
+
fqn: obfuscatedClassName,
|
|
1645
1884
|
jarPath,
|
|
1646
1885
|
access: "all",
|
|
1647
1886
|
includeSynthetic: false,
|
|
@@ -1662,8 +1901,8 @@ export class SourceService {
|
|
|
1662
1901
|
}
|
|
1663
1902
|
};
|
|
1664
1903
|
const [fromSignature, toSignature] = await Promise.all([
|
|
1665
|
-
loadSignature(fromVersion, fromResolved.jarPath,
|
|
1666
|
-
loadSignature(toVersion, toResolved.jarPath,
|
|
1904
|
+
loadSignature(fromVersion, fromResolved.jarPath, obfuscatedFromClassName),
|
|
1905
|
+
loadSignature(toVersion, toResolved.jarPath, obfuscatedToClassName)
|
|
1667
1906
|
]);
|
|
1668
1907
|
const warnings = [...mappingWarnings];
|
|
1669
1908
|
if (fromSignature) {
|
|
@@ -1740,18 +1979,46 @@ export class SourceService {
|
|
|
1740
1979
|
: classChange === "absent_in_both"
|
|
1741
1980
|
? emptyDiffDelta()
|
|
1742
1981
|
: diffMembersByKey(fromMembers.fields, toMembers.fields, (member) => member.name, true);
|
|
1743
|
-
// Remap diff delta members for non-
|
|
1982
|
+
// Remap diff delta members for non-obfuscated mappings
|
|
1744
1983
|
const remapDelta = async (delta, kind) => {
|
|
1745
1984
|
const [addedResult, removedResult] = await Promise.all([
|
|
1746
|
-
this.remapSignatureMembers(delta.added, kind, toVersion, mapping, input.sourcePriority, warnings),
|
|
1747
|
-
this.remapSignatureMembers(delta.removed, kind, fromVersion, mapping, input.sourcePriority, warnings)
|
|
1985
|
+
this.remapSignatureMembers(delta.added, kind, toVersion, "obfuscated", mapping, input.sourcePriority, warnings),
|
|
1986
|
+
this.remapSignatureMembers(delta.removed, kind, fromVersion, "obfuscated", mapping, input.sourcePriority, warnings)
|
|
1748
1987
|
]);
|
|
1749
1988
|
const remappedModified = await Promise.all(delta.modified.map(async (change) => {
|
|
1989
|
+
if (!change.from || !change.to) {
|
|
1990
|
+
throw createError({
|
|
1991
|
+
code: ERROR_CODES.INTERNAL,
|
|
1992
|
+
message: "Modified diff members are missing before remap.",
|
|
1993
|
+
details: {
|
|
1994
|
+
key: change.key,
|
|
1995
|
+
kind,
|
|
1996
|
+
fromVersion,
|
|
1997
|
+
toVersion,
|
|
1998
|
+
mapping
|
|
1999
|
+
}
|
|
2000
|
+
});
|
|
2001
|
+
}
|
|
1750
2002
|
const [fromResult, toResult] = await Promise.all([
|
|
1751
|
-
this.remapSignatureMembers([change.from], kind, fromVersion, mapping, input.sourcePriority, warnings),
|
|
1752
|
-
this.remapSignatureMembers([change.to], kind, toVersion, mapping, input.sourcePriority, warnings)
|
|
2003
|
+
this.remapSignatureMembers([change.from], kind, fromVersion, "obfuscated", mapping, input.sourcePriority, warnings),
|
|
2004
|
+
this.remapSignatureMembers([change.to], kind, toVersion, "obfuscated", mapping, input.sourcePriority, warnings)
|
|
1753
2005
|
]);
|
|
1754
|
-
|
|
2006
|
+
const fromMember = fromResult.members[0];
|
|
2007
|
+
const toMember = toResult.members[0];
|
|
2008
|
+
if (!fromMember || !toMember) {
|
|
2009
|
+
throw createError({
|
|
2010
|
+
code: ERROR_CODES.INTERNAL,
|
|
2011
|
+
message: "Failed to remap modified diff members.",
|
|
2012
|
+
details: {
|
|
2013
|
+
key: change.key,
|
|
2014
|
+
kind,
|
|
2015
|
+
fromVersion,
|
|
2016
|
+
toVersion,
|
|
2017
|
+
mapping
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
return { ...change, from: fromMember, to: toMember };
|
|
1755
2022
|
}));
|
|
1756
2023
|
return { added: addedResult.members, removed: removedResult.members, modified: remappedModified };
|
|
1757
2024
|
};
|
|
@@ -1794,9 +2061,9 @@ export class SourceService {
|
|
|
1794
2061
|
toVersion
|
|
1795
2062
|
},
|
|
1796
2063
|
classChange,
|
|
1797
|
-
constructors: remappedConstructors,
|
|
1798
|
-
methods: remappedMethods,
|
|
1799
|
-
fields: remappedFields,
|
|
2064
|
+
constructors: includeFullDiff ? remappedConstructors : compactDiffDelta(remappedConstructors),
|
|
2065
|
+
methods: includeFullDiff ? remappedMethods : compactDiffDelta(remappedMethods),
|
|
2066
|
+
fields: includeFullDiff ? remappedFields : compactDiffDelta(remappedFields),
|
|
1800
2067
|
summary,
|
|
1801
2068
|
warnings
|
|
1802
2069
|
};
|
|
@@ -1817,7 +2084,7 @@ export class SourceService {
|
|
|
1817
2084
|
});
|
|
1818
2085
|
}
|
|
1819
2086
|
// Verify artifact exists
|
|
1820
|
-
this.getArtifact(artifactId);
|
|
2087
|
+
const artifact = this.getArtifact(artifactId);
|
|
1821
2088
|
const limit = Math.max(1, Math.min(input.limit ?? 20, 200));
|
|
1822
2089
|
const warnings = [];
|
|
1823
2090
|
const isQualified = className.includes(".");
|
|
@@ -1847,7 +2114,17 @@ export class SourceService {
|
|
|
1847
2114
|
symbolKind: row.symbolKind
|
|
1848
2115
|
}))
|
|
1849
2116
|
.slice(0, limit);
|
|
1850
|
-
|
|
2117
|
+
const partialVanillaLookup = hasPartialNetMinecraftCoverage(artifact.qualityFlags) && looksLikeDeobfuscatedClassName(className);
|
|
2118
|
+
const filteredMatches = partialVanillaLookup && matches.every((match) => !match.qualifiedName.startsWith("net.minecraft.") && !match.qualifiedName.startsWith("com.mojang."))
|
|
2119
|
+
? []
|
|
2120
|
+
: matches;
|
|
2121
|
+
if (filteredMatches.length === 0 && partialVanillaLookup) {
|
|
2122
|
+
warnings.push(`Artifact source coverage is partial and excludes net.minecraft; returning non-vanilla matches for "${className}" would be misleading. Use get-class-source/get-class-members for binary fallback or get-class-api-matrix for mapped API inspection.`);
|
|
2123
|
+
}
|
|
2124
|
+
if (filteredMatches.length === 0 && artifact.mappingApplied === "obfuscated" && looksLikeDeobfuscatedClassName(className)) {
|
|
2125
|
+
warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
|
|
2126
|
+
}
|
|
2127
|
+
return { matches: filteredMatches, total: filteredMatches.length, warnings };
|
|
1851
2128
|
}
|
|
1852
2129
|
// Simple name: search for exact symbol name match among type symbols
|
|
1853
2130
|
const result = this.symbolsRepo.findScopedSymbols({
|
|
@@ -1871,7 +2148,17 @@ export class SourceService {
|
|
|
1871
2148
|
symbolKind: row.symbolKind
|
|
1872
2149
|
});
|
|
1873
2150
|
}
|
|
1874
|
-
|
|
2151
|
+
const partialVanillaLookup = hasPartialNetMinecraftCoverage(artifact.qualityFlags) && looksLikeDeobfuscatedClassName(className);
|
|
2152
|
+
const filteredMatches = partialVanillaLookup && matches.every((match) => !match.qualifiedName.startsWith("net.minecraft.") && !match.qualifiedName.startsWith("com.mojang."))
|
|
2153
|
+
? []
|
|
2154
|
+
: matches;
|
|
2155
|
+
if (filteredMatches.length === 0 && partialVanillaLookup) {
|
|
2156
|
+
warnings.push(`Artifact source coverage is partial and excludes net.minecraft; returning non-vanilla matches for "${className}" would be misleading. Use get-class-source/get-class-members for binary fallback or get-class-api-matrix for mapped API inspection.`);
|
|
2157
|
+
}
|
|
2158
|
+
if (filteredMatches.length === 0 && artifact.mappingApplied === "obfuscated" && looksLikeDeobfuscatedClassName(className)) {
|
|
2159
|
+
warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
|
|
2160
|
+
}
|
|
2161
|
+
return { matches: filteredMatches, total: filteredMatches.length, warnings };
|
|
1875
2162
|
}
|
|
1876
2163
|
async getClassSource(input) {
|
|
1877
2164
|
const className = input.className.trim();
|
|
@@ -1905,7 +2192,7 @@ export class SourceService {
|
|
|
1905
2192
|
if (normalizedArtifactId && input.target) {
|
|
1906
2193
|
throw createError({
|
|
1907
2194
|
code: ERROR_CODES.INVALID_INPUT,
|
|
1908
|
-
message: "artifactId and
|
|
2195
|
+
message: "artifactId and target are mutually exclusive.",
|
|
1909
2196
|
details: {
|
|
1910
2197
|
artifactId: normalizedArtifactId,
|
|
1911
2198
|
target: input.target
|
|
@@ -1919,6 +2206,10 @@ export class SourceService {
|
|
|
1919
2206
|
let mappingApplied = requestedMapping;
|
|
1920
2207
|
let provenance;
|
|
1921
2208
|
let qualityFlags = [];
|
|
2209
|
+
let sourceJarPath;
|
|
2210
|
+
let binaryJarPath;
|
|
2211
|
+
let version;
|
|
2212
|
+
let coordinate;
|
|
1922
2213
|
if (!artifactId) {
|
|
1923
2214
|
if (!input.target) {
|
|
1924
2215
|
throw createError({
|
|
@@ -1943,6 +2234,10 @@ export class SourceService {
|
|
|
1943
2234
|
mappingApplied = resolved.mappingApplied;
|
|
1944
2235
|
provenance = resolved.provenance;
|
|
1945
2236
|
qualityFlags = [...resolved.qualityFlags];
|
|
2237
|
+
sourceJarPath = resolved.resolvedSourceJarPath;
|
|
2238
|
+
binaryJarPath = resolved.binaryJarPath;
|
|
2239
|
+
version = resolved.version;
|
|
2240
|
+
coordinate = resolved.coordinate;
|
|
1946
2241
|
}
|
|
1947
2242
|
else {
|
|
1948
2243
|
const artifact = this.getArtifact(artifactId);
|
|
@@ -1951,53 +2246,130 @@ export class SourceService {
|
|
|
1951
2246
|
mappingApplied = artifact.mappingApplied ?? requestedMapping;
|
|
1952
2247
|
provenance = artifact.provenance;
|
|
1953
2248
|
qualityFlags = artifact.qualityFlags;
|
|
2249
|
+
sourceJarPath = artifact.sourceJarPath;
|
|
2250
|
+
binaryJarPath = artifact.binaryJarPath;
|
|
2251
|
+
version = artifact.version;
|
|
2252
|
+
coordinate = artifact.coordinate;
|
|
1954
2253
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2254
|
+
version = await this.resolveVersionContext({
|
|
2255
|
+
version,
|
|
2256
|
+
provenance,
|
|
2257
|
+
coordinate,
|
|
2258
|
+
projectPath: input.projectPath,
|
|
2259
|
+
preferProjectVersion: input.preferProjectVersion,
|
|
2260
|
+
warnings
|
|
2261
|
+
});
|
|
2262
|
+
let activeArtifactId = artifactId;
|
|
2263
|
+
let activeOrigin = origin;
|
|
2264
|
+
let activeProvenance = provenance;
|
|
2265
|
+
let activeQualityFlags = [...qualityFlags];
|
|
2266
|
+
let activeMappingApplied = mappingApplied;
|
|
2267
|
+
let activeSourceJarPath = sourceJarPath;
|
|
2268
|
+
let attemptedBinaryFallback = false;
|
|
2269
|
+
const tryBinaryFallback = async () => {
|
|
2270
|
+
if (attemptedBinaryFallback) {
|
|
2271
|
+
return false;
|
|
1965
2272
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2273
|
+
attemptedBinaryFallback = true;
|
|
2274
|
+
const normalizedBinaryJarPath = normalizeOptionalString(binaryJarPath);
|
|
2275
|
+
if (!normalizedBinaryJarPath) {
|
|
2276
|
+
return false;
|
|
1969
2277
|
}
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2278
|
+
if (activeSourceJarPath &&
|
|
2279
|
+
normalizePathStyle(activeSourceJarPath) === normalizePathStyle(normalizedBinaryJarPath)) {
|
|
2280
|
+
return false;
|
|
2281
|
+
}
|
|
2282
|
+
const fallbackResolved = await this.resolveBinaryFallbackArtifact({
|
|
2283
|
+
binaryJarPath: normalizedBinaryJarPath,
|
|
2284
|
+
version,
|
|
2285
|
+
coordinate,
|
|
2286
|
+
requestedMapping,
|
|
2287
|
+
mappingApplied,
|
|
2288
|
+
provenance: activeProvenance,
|
|
2289
|
+
qualityFlags: activeQualityFlags
|
|
2290
|
+
});
|
|
2291
|
+
if (!fallbackResolved || fallbackResolved.artifactId === activeArtifactId) {
|
|
2292
|
+
return false;
|
|
2293
|
+
}
|
|
2294
|
+
activeArtifactId = fallbackResolved.artifactId;
|
|
2295
|
+
activeOrigin = fallbackResolved.origin;
|
|
2296
|
+
activeMappingApplied = fallbackResolved.mappingApplied ?? activeMappingApplied;
|
|
2297
|
+
activeProvenance = fallbackResolved.provenance ?? activeProvenance;
|
|
2298
|
+
activeQualityFlags = dedupeQualityFlags([...(fallbackResolved.qualityFlags ?? []), "binary-fallback"]);
|
|
2299
|
+
activeSourceJarPath = fallbackResolved.sourceJarPath;
|
|
2300
|
+
warnings.push(`Falling back to binary artifact "${normalizedBinaryJarPath}" because source coverage for "${className}" was incomplete.`);
|
|
2301
|
+
if (activeMappingApplied !== requestedMapping) {
|
|
2302
|
+
warnings.push(`Fallback source text is indexed in ${activeMappingApplied} names; returned source is not remapped to ${requestedMapping}.`);
|
|
2303
|
+
}
|
|
2304
|
+
return true;
|
|
2305
|
+
};
|
|
2306
|
+
let activeLookupClassName = await this.resolveClassNameForLookup({
|
|
2307
|
+
className,
|
|
2308
|
+
version,
|
|
2309
|
+
sourceMapping: requestedMapping,
|
|
2310
|
+
targetMapping: activeMappingApplied,
|
|
2311
|
+
sourcePriority: input.sourcePriority,
|
|
2312
|
+
warnings,
|
|
2313
|
+
context: "source lookup"
|
|
2314
|
+
});
|
|
2315
|
+
let filePath = this.resolveClassFilePath(activeArtifactId, activeLookupClassName);
|
|
2316
|
+
if (!filePath && (await tryBinaryFallback())) {
|
|
2317
|
+
activeLookupClassName = await this.resolveClassNameForLookup({
|
|
2318
|
+
className,
|
|
2319
|
+
version,
|
|
2320
|
+
sourceMapping: requestedMapping,
|
|
2321
|
+
targetMapping: activeMappingApplied,
|
|
2322
|
+
sourcePriority: input.sourcePriority,
|
|
2323
|
+
warnings,
|
|
2324
|
+
context: "source lookup"
|
|
1983
2325
|
});
|
|
2326
|
+
filePath = this.resolveClassFilePath(activeArtifactId, activeLookupClassName);
|
|
2327
|
+
}
|
|
2328
|
+
if (!filePath) {
|
|
2329
|
+
throw this.buildClassSourceNotFoundError({
|
|
2330
|
+
artifactId: activeArtifactId,
|
|
2331
|
+
className,
|
|
2332
|
+
lookupClassName: activeLookupClassName,
|
|
2333
|
+
mappingApplied: activeMappingApplied,
|
|
2334
|
+
requestedMapping,
|
|
2335
|
+
qualityFlags: activeQualityFlags,
|
|
2336
|
+
attemptedBinaryFallback,
|
|
2337
|
+
targetKind: input.target?.kind,
|
|
2338
|
+
targetValue: input.target?.value,
|
|
2339
|
+
scope: input.scope,
|
|
2340
|
+
projectPath: input.projectPath,
|
|
2341
|
+
version
|
|
2342
|
+
});
|
|
2343
|
+
}
|
|
2344
|
+
let row = this.filesRepo.getFileContent(activeArtifactId, filePath);
|
|
2345
|
+
if (!row && (await tryBinaryFallback())) {
|
|
2346
|
+
activeLookupClassName = await this.resolveClassNameForLookup({
|
|
2347
|
+
className,
|
|
2348
|
+
version,
|
|
2349
|
+
sourceMapping: requestedMapping,
|
|
2350
|
+
targetMapping: activeMappingApplied,
|
|
2351
|
+
sourcePriority: input.sourcePriority,
|
|
2352
|
+
warnings,
|
|
2353
|
+
context: "source lookup"
|
|
2354
|
+
});
|
|
2355
|
+
filePath = this.resolveClassFilePath(activeArtifactId, activeLookupClassName) ?? filePath;
|
|
2356
|
+
row = this.filesRepo.getFileContent(activeArtifactId, filePath);
|
|
1984
2357
|
}
|
|
1985
|
-
const row = this.filesRepo.getFileContent(artifactId, filePath);
|
|
1986
2358
|
if (!row) {
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2359
|
+
throw this.buildClassSourceNotFoundError({
|
|
2360
|
+
artifactId: activeArtifactId,
|
|
2361
|
+
className,
|
|
2362
|
+
lookupClassName: activeLookupClassName,
|
|
2363
|
+
mappingApplied: activeMappingApplied,
|
|
2364
|
+
requestedMapping,
|
|
2365
|
+
qualityFlags: activeQualityFlags,
|
|
2366
|
+
attemptedBinaryFallback,
|
|
2367
|
+
filePath,
|
|
2368
|
+
targetKind: input.target?.kind,
|
|
2369
|
+
targetValue: input.target?.value,
|
|
2370
|
+
scope: input.scope,
|
|
2371
|
+
projectPath: input.projectPath,
|
|
2372
|
+
version
|
|
2001
2373
|
});
|
|
2002
2374
|
}
|
|
2003
2375
|
const lines = row.content.split(/\r?\n/);
|
|
@@ -2048,12 +2420,12 @@ export class SourceService {
|
|
|
2048
2420
|
resolvedOutputFile = outputPath;
|
|
2049
2421
|
sourceText = `[Written to ${outputPath}]`;
|
|
2050
2422
|
}
|
|
2051
|
-
const normalizedProvenance =
|
|
2423
|
+
const normalizedProvenance = activeProvenance ??
|
|
2052
2424
|
this.buildFallbackProvenance({
|
|
2053
|
-
artifactId,
|
|
2054
|
-
origin,
|
|
2425
|
+
artifactId: activeArtifactId,
|
|
2426
|
+
origin: activeOrigin,
|
|
2055
2427
|
requestedMapping,
|
|
2056
|
-
mappingApplied
|
|
2428
|
+
mappingApplied: activeMappingApplied
|
|
2057
2429
|
});
|
|
2058
2430
|
return {
|
|
2059
2431
|
className,
|
|
@@ -2066,12 +2438,19 @@ export class SourceService {
|
|
|
2066
2438
|
},
|
|
2067
2439
|
truncated,
|
|
2068
2440
|
...(charsTruncated ? { charsTruncated } : {}),
|
|
2069
|
-
origin,
|
|
2070
|
-
artifactId,
|
|
2441
|
+
origin: activeOrigin,
|
|
2442
|
+
artifactId: activeArtifactId,
|
|
2071
2443
|
requestedMapping,
|
|
2072
|
-
mappingApplied,
|
|
2444
|
+
mappingApplied: activeMappingApplied,
|
|
2445
|
+
returnedNamespace: activeMappingApplied,
|
|
2073
2446
|
provenance: normalizedProvenance,
|
|
2074
|
-
qualityFlags,
|
|
2447
|
+
qualityFlags: activeQualityFlags,
|
|
2448
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
2449
|
+
origin: activeOrigin,
|
|
2450
|
+
sourceJarPath: activeSourceJarPath,
|
|
2451
|
+
isDecompiled: activeOrigin === "decompiled",
|
|
2452
|
+
qualityFlags: activeQualityFlags
|
|
2453
|
+
}),
|
|
2075
2454
|
...(resolvedOutputFile ? { outputFile: resolvedOutputFile } : {}),
|
|
2076
2455
|
warnings
|
|
2077
2456
|
};
|
|
@@ -2095,7 +2474,7 @@ export class SourceService {
|
|
|
2095
2474
|
if (normalizedArtifactId && input.target) {
|
|
2096
2475
|
throw createError({
|
|
2097
2476
|
code: ERROR_CODES.INVALID_INPUT,
|
|
2098
|
-
message: "artifactId and
|
|
2477
|
+
message: "artifactId and target are mutually exclusive.",
|
|
2099
2478
|
details: {
|
|
2100
2479
|
artifactId: normalizedArtifactId,
|
|
2101
2480
|
target: input.target
|
|
@@ -2109,6 +2488,8 @@ export class SourceService {
|
|
|
2109
2488
|
let provenance;
|
|
2110
2489
|
let qualityFlags = [];
|
|
2111
2490
|
let binaryJarPath;
|
|
2491
|
+
let sourceJarPath;
|
|
2492
|
+
let coordinate;
|
|
2112
2493
|
if (parsedMaxMembers != null && parsedMaxMembers > 5000) {
|
|
2113
2494
|
warnings.push(`maxMembers was clamped to 5000 from ${parsedMaxMembers}.`);
|
|
2114
2495
|
}
|
|
@@ -2137,7 +2518,9 @@ export class SourceService {
|
|
|
2137
2518
|
provenance = resolved.provenance;
|
|
2138
2519
|
qualityFlags = [...resolved.qualityFlags];
|
|
2139
2520
|
binaryJarPath = resolved.binaryJarPath;
|
|
2521
|
+
sourceJarPath = resolved.resolvedSourceJarPath;
|
|
2140
2522
|
version = resolved.version;
|
|
2523
|
+
coordinate = resolved.coordinate;
|
|
2141
2524
|
}
|
|
2142
2525
|
else {
|
|
2143
2526
|
const artifact = this.getArtifact(artifactId);
|
|
@@ -2146,16 +2529,29 @@ export class SourceService {
|
|
|
2146
2529
|
provenance = artifact.provenance;
|
|
2147
2530
|
qualityFlags = artifact.qualityFlags;
|
|
2148
2531
|
binaryJarPath = artifact.binaryJarPath;
|
|
2532
|
+
sourceJarPath = artifact.sourceJarPath;
|
|
2149
2533
|
version = artifact.version;
|
|
2534
|
+
coordinate = artifact.coordinate;
|
|
2150
2535
|
}
|
|
2151
|
-
|
|
2536
|
+
version = await this.resolveVersionContext({
|
|
2537
|
+
version,
|
|
2538
|
+
provenance,
|
|
2539
|
+
coordinate,
|
|
2540
|
+
projectPath: input.projectPath,
|
|
2541
|
+
preferProjectVersion: input.preferProjectVersion,
|
|
2542
|
+
warnings
|
|
2543
|
+
});
|
|
2544
|
+
if (requestedMapping !== "obfuscated" && !version) {
|
|
2152
2545
|
throw createError({
|
|
2153
2546
|
code: ERROR_CODES.MAPPING_NOT_APPLIED,
|
|
2154
|
-
message: `Non-
|
|
2547
|
+
message: `Non-obfuscated mapping "${requestedMapping}" requires a version, but none was resolved.`,
|
|
2155
2548
|
details: {
|
|
2156
2549
|
mapping: requestedMapping,
|
|
2157
|
-
nextAction: "Resolve with
|
|
2158
|
-
suggestedCall: {
|
|
2550
|
+
nextAction: "Resolve with target: { kind: \"version\", value: ... } or specify a versioned coordinate.",
|
|
2551
|
+
suggestedCall: {
|
|
2552
|
+
tool: "resolve-artifact",
|
|
2553
|
+
params: buildResolveArtifactParams({ kind: "version", value: "latest" })
|
|
2554
|
+
}
|
|
2159
2555
|
}
|
|
2160
2556
|
});
|
|
2161
2557
|
}
|
|
@@ -2166,33 +2562,39 @@ export class SourceService {
|
|
|
2166
2562
|
details: {
|
|
2167
2563
|
artifactId,
|
|
2168
2564
|
className,
|
|
2169
|
-
nextAction: "Resolve with
|
|
2565
|
+
nextAction: "Resolve with target: { kind: \"jar\" | \"version\", value: ... } or use an artifact that has a binary jar."
|
|
2170
2566
|
}
|
|
2171
2567
|
});
|
|
2172
2568
|
}
|
|
2173
|
-
const
|
|
2174
|
-
|
|
2175
|
-
|
|
2569
|
+
const lookupClassName = await this.resolveClassNameForLookup({
|
|
2570
|
+
className,
|
|
2571
|
+
version,
|
|
2572
|
+
sourceMapping: requestedMapping,
|
|
2573
|
+
targetMapping: mappingApplied,
|
|
2574
|
+
sourcePriority: input.sourcePriority,
|
|
2575
|
+
warnings,
|
|
2576
|
+
context: "binary lookup"
|
|
2577
|
+
});
|
|
2176
2578
|
const signature = await this.explorerService.getSignature({
|
|
2177
|
-
fqn:
|
|
2579
|
+
fqn: lookupClassName,
|
|
2178
2580
|
jarPath: binaryJarPath,
|
|
2179
2581
|
access,
|
|
2180
2582
|
includeSynthetic,
|
|
2181
2583
|
includeInherited,
|
|
2182
|
-
memberPattern: requestedMapping ===
|
|
2584
|
+
memberPattern: requestedMapping === mappingApplied ? memberPattern : undefined
|
|
2183
2585
|
});
|
|
2184
2586
|
warnings.push(...signature.warnings);
|
|
2185
2587
|
let remappedConstructors = version != null
|
|
2186
|
-
? (await this.remapSignatureMembers(signature.constructors, "method", version, requestedMapping, input.sourcePriority, warnings)).members
|
|
2588
|
+
? (await this.remapSignatureMembers(signature.constructors, "method", version, mappingApplied, requestedMapping, input.sourcePriority, warnings)).members
|
|
2187
2589
|
: signature.constructors;
|
|
2188
2590
|
let remappedFields = version != null
|
|
2189
|
-
? (await this.remapSignatureMembers(signature.fields, "field", version, requestedMapping, input.sourcePriority, warnings)).members
|
|
2591
|
+
? (await this.remapSignatureMembers(signature.fields, "field", version, mappingApplied, requestedMapping, input.sourcePriority, warnings)).members
|
|
2190
2592
|
: signature.fields;
|
|
2191
2593
|
let remappedMethods = version != null
|
|
2192
|
-
? (await this.remapSignatureMembers(signature.methods, "method", version, requestedMapping, input.sourcePriority, warnings)).members
|
|
2594
|
+
? (await this.remapSignatureMembers(signature.methods, "method", version, mappingApplied, requestedMapping, input.sourcePriority, warnings)).members
|
|
2193
2595
|
: signature.methods;
|
|
2194
|
-
// Apply memberPattern
|
|
2195
|
-
if (requestedMapping !==
|
|
2596
|
+
// Apply memberPattern after remap when the lookup namespace differs from the requested namespace.
|
|
2597
|
+
if (requestedMapping !== mappingApplied && memberPattern) {
|
|
2196
2598
|
const lowerPattern = memberPattern.toLowerCase();
|
|
2197
2599
|
remappedConstructors = remappedConstructors.filter((m) => m.name.toLowerCase().includes(lowerPattern));
|
|
2198
2600
|
remappedFields = remappedFields.filter((m) => m.name.toLowerCase().includes(lowerPattern));
|
|
@@ -2242,98 +2644,169 @@ export class SourceService {
|
|
|
2242
2644
|
artifactId,
|
|
2243
2645
|
requestedMapping,
|
|
2244
2646
|
mappingApplied,
|
|
2647
|
+
returnedNamespace: requestedMapping,
|
|
2245
2648
|
provenance: normalizedProvenance,
|
|
2246
2649
|
qualityFlags,
|
|
2650
|
+
artifactContents: this.buildArtifactContentsSummary({
|
|
2651
|
+
origin,
|
|
2652
|
+
sourceJarPath,
|
|
2653
|
+
isDecompiled: origin === "decompiled",
|
|
2654
|
+
qualityFlags
|
|
2655
|
+
}),
|
|
2247
2656
|
warnings
|
|
2248
2657
|
};
|
|
2249
2658
|
}
|
|
2250
2659
|
async validateMixin(input) {
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
const
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
catch (err) {
|
|
2266
|
-
throw createError({
|
|
2267
|
-
code: ERROR_CODES.INVALID_INPUT,
|
|
2268
|
-
message: `Could not read/parse mixinConfigPath "${rawConfigPath}": ${err instanceof Error ? err.message : String(err)}`
|
|
2269
|
-
});
|
|
2270
|
-
}
|
|
2271
|
-
const pkg = configJson.package ?? "";
|
|
2272
|
-
const classNames = [
|
|
2273
|
-
...(configJson.mixins ?? []),
|
|
2274
|
-
...(configJson.client ?? []),
|
|
2275
|
-
...(configJson.server ?? [])
|
|
2276
|
-
];
|
|
2277
|
-
if (classNames.length === 0) {
|
|
2278
|
-
continue; // Skip empty configs in array mode
|
|
2279
|
-
}
|
|
2280
|
-
// Determine source root(s)
|
|
2281
|
-
const projectBase = input.projectPath
|
|
2282
|
-
? (isAbsolute(input.projectPath) ? input.projectPath : resolvePath(process.cwd(), input.projectPath))
|
|
2283
|
-
: dirname(resolvedConfigPath);
|
|
2284
|
-
let sourceRootCandidates;
|
|
2285
|
-
if (input.sourceRoots && input.sourceRoots.length > 0) {
|
|
2286
|
-
sourceRootCandidates = input.sourceRoots;
|
|
2287
|
-
}
|
|
2288
|
-
else if (input.sourceRoot) {
|
|
2289
|
-
sourceRootCandidates = [input.sourceRoot];
|
|
2660
|
+
const { input: sourceInput, ...sharedInput } = input;
|
|
2661
|
+
const mode = sourceInput.mode;
|
|
2662
|
+
if (mode === "inline") {
|
|
2663
|
+
const singleResult = await this.validateMixinSingle({
|
|
2664
|
+
...sharedInput,
|
|
2665
|
+
source: sourceInput.source
|
|
2666
|
+
});
|
|
2667
|
+
return this.applyValidateMixinOutputCompaction(this.buildValidateMixinOutput(mode, [
|
|
2668
|
+
{
|
|
2669
|
+
source: {
|
|
2670
|
+
kind: "inline",
|
|
2671
|
+
label: "<inline>"
|
|
2672
|
+
},
|
|
2673
|
+
result: singleResult
|
|
2290
2674
|
}
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2675
|
+
]), input);
|
|
2676
|
+
}
|
|
2677
|
+
if (mode === "path") {
|
|
2678
|
+
const resolvedPath = this.resolveMixinInputPath(sourceInput.path, "path");
|
|
2679
|
+
const singleResult = await this.validateMixinSingle({
|
|
2680
|
+
...sharedInput,
|
|
2681
|
+
sourcePath: sourceInput.path
|
|
2682
|
+
});
|
|
2683
|
+
return this.applyValidateMixinOutputCompaction(this.buildValidateMixinOutput(mode, [
|
|
2684
|
+
{
|
|
2685
|
+
source: {
|
|
2686
|
+
kind: "path",
|
|
2687
|
+
label: resolvedPath,
|
|
2688
|
+
path: resolvedPath
|
|
2689
|
+
},
|
|
2690
|
+
result: singleResult
|
|
2304
2691
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2692
|
+
]), input);
|
|
2693
|
+
}
|
|
2694
|
+
if (mode === "paths") {
|
|
2695
|
+
return this.validateMixinMany(mode, sourceInput.paths.map((path) => ({
|
|
2696
|
+
source: {
|
|
2697
|
+
kind: "path",
|
|
2698
|
+
label: this.resolveMixinInputPath(path, "path"),
|
|
2699
|
+
path: this.resolveMixinInputPath(path, "path")
|
|
2700
|
+
},
|
|
2701
|
+
sourcePath: path
|
|
2702
|
+
})), input);
|
|
2703
|
+
}
|
|
2704
|
+
const resolvedInput = mode === "project"
|
|
2705
|
+
? this.createProjectValidateMixinConfigInput(input)
|
|
2706
|
+
: input;
|
|
2707
|
+
const configSources = await this.resolveMixinConfigSources(resolvedInput);
|
|
2708
|
+
if (configSources.length === 0) {
|
|
2709
|
+
throw createError({
|
|
2710
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
2711
|
+
message: "Mixin config(s) contain no mixin class entries."
|
|
2712
|
+
});
|
|
2713
|
+
}
|
|
2714
|
+
return this.validateMixinMany(mode, configSources.map((entry) => ({
|
|
2715
|
+
source: {
|
|
2716
|
+
kind: "config",
|
|
2717
|
+
label: entry.sourcePath,
|
|
2718
|
+
path: entry.sourcePath,
|
|
2719
|
+
configPath: entry.configPath
|
|
2720
|
+
},
|
|
2721
|
+
sourcePath: entry.sourcePath
|
|
2722
|
+
})), resolvedInput);
|
|
2723
|
+
}
|
|
2724
|
+
createProjectValidateMixinConfigInput(input) {
|
|
2725
|
+
if (input.input.mode !== "project") {
|
|
2726
|
+
return input;
|
|
2727
|
+
}
|
|
2728
|
+
const resolvedProjectPath = this.resolveMixinInputPath(input.input.path, "path");
|
|
2729
|
+
const configPaths = fastGlob.sync(["**/*.mixins.json"], {
|
|
2730
|
+
cwd: resolvedProjectPath,
|
|
2731
|
+
absolute: true,
|
|
2732
|
+
onlyFiles: true,
|
|
2733
|
+
ignore: [...MIXIN_PROJECT_DISCOVERY_IGNORES]
|
|
2734
|
+
}).sort((left, right) => left.localeCompare(right));
|
|
2735
|
+
if (configPaths.length === 0) {
|
|
2736
|
+
throw createError({
|
|
2737
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
2738
|
+
message: `No mixin config JSON files were found under project path "${input.input.path}".`,
|
|
2739
|
+
details: {
|
|
2740
|
+
nextAction: "Use input.mode='config' with explicit configPaths[], or point input.path at the workspace root that contains *.mixins.json files."
|
|
2322
2741
|
}
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
return {
|
|
2745
|
+
...input,
|
|
2746
|
+
projectPath: input.projectPath ?? resolvedProjectPath,
|
|
2747
|
+
input: {
|
|
2748
|
+
mode: "config",
|
|
2749
|
+
configPaths
|
|
2323
2750
|
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
shouldRetryValidateMixinWithMavenFirst(input, result) {
|
|
2754
|
+
const initialPriority = input.retryState?.initialSourcePriority ?? input.sourcePriority ?? this.config.mappingSourcePriority;
|
|
2755
|
+
if (input.retryState?.attempted || initialPriority !== "loom-first") {
|
|
2756
|
+
return false;
|
|
2757
|
+
}
|
|
2758
|
+
if (result.validationStatus !== "partial") {
|
|
2759
|
+
return false;
|
|
2331
2760
|
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
return this.validateMixinBatch(input);
|
|
2761
|
+
if (result.summary.membersSkipped > 0) {
|
|
2762
|
+
return true;
|
|
2335
2763
|
}
|
|
2764
|
+
return result.issues.some((issue) => issue.resolutionPath === "source-signature-unavailable" ||
|
|
2765
|
+
issue.resolutionPath === "target-mapping-failed" ||
|
|
2766
|
+
issue.resolutionPath === "member-remap-failed");
|
|
2767
|
+
}
|
|
2768
|
+
findValidateMixinClassMapping(input) {
|
|
2769
|
+
const cache = input.batchCaches?.classMappings;
|
|
2770
|
+
if (!cache) {
|
|
2771
|
+
return this.mappingService.findMapping({
|
|
2772
|
+
version: input.version,
|
|
2773
|
+
kind: "class",
|
|
2774
|
+
name: input.className,
|
|
2775
|
+
sourceMapping: input.sourceMapping,
|
|
2776
|
+
targetMapping: input.targetMapping,
|
|
2777
|
+
sourcePriority: input.sourcePriority
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
const cacheKey = [
|
|
2781
|
+
input.version,
|
|
2782
|
+
input.className,
|
|
2783
|
+
input.sourceMapping,
|
|
2784
|
+
input.targetMapping,
|
|
2785
|
+
input.sourcePriority
|
|
2786
|
+
].join("\0");
|
|
2787
|
+
const cached = cache.get(cacheKey);
|
|
2788
|
+
if (cached) {
|
|
2789
|
+
return cached;
|
|
2790
|
+
}
|
|
2791
|
+
const pending = this.mappingService.findMapping({
|
|
2792
|
+
version: input.version,
|
|
2793
|
+
kind: "class",
|
|
2794
|
+
name: input.className,
|
|
2795
|
+
sourceMapping: input.sourceMapping,
|
|
2796
|
+
targetMapping: input.targetMapping,
|
|
2797
|
+
sourcePriority: input.sourcePriority
|
|
2798
|
+
}).catch((error) => {
|
|
2799
|
+
cache.delete(cacheKey);
|
|
2800
|
+
throw error;
|
|
2801
|
+
});
|
|
2802
|
+
cache.set(cacheKey, pending);
|
|
2803
|
+
return pending;
|
|
2804
|
+
}
|
|
2805
|
+
async validateMixinSingle(input) {
|
|
2336
2806
|
let version = input.version.trim();
|
|
2807
|
+
const requestedScope = normalizeRequestedArtifactScope(input.scope);
|
|
2808
|
+
const currentSourcePriority = input.sourcePriority ?? this.config.mappingSourcePriority;
|
|
2809
|
+
const initialSourcePriority = input.retryState?.initialSourcePriority ?? currentSourcePriority;
|
|
2337
2810
|
if (!version) {
|
|
2338
2811
|
throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "version must be non-empty." });
|
|
2339
2812
|
}
|
|
@@ -2394,22 +2867,25 @@ export class SourceService {
|
|
|
2394
2867
|
}
|
|
2395
2868
|
// Resolve jar: use Loom cache for non-vanilla scope with projectPath
|
|
2396
2869
|
let jarPath;
|
|
2870
|
+
let resolvedArtifact;
|
|
2871
|
+
let signatureLookupMapping = "obfuscated";
|
|
2397
2872
|
let scopeFallback;
|
|
2398
2873
|
if (input.scope && input.scope !== "vanilla" && input.projectPath) {
|
|
2399
2874
|
try {
|
|
2400
|
-
|
|
2875
|
+
resolvedArtifact = await this.resolveArtifact({
|
|
2401
2876
|
target: { kind: "version", value: version },
|
|
2402
2877
|
mapping: requestedMapping,
|
|
2403
|
-
sourcePriority:
|
|
2878
|
+
sourcePriority: currentSourcePriority,
|
|
2404
2879
|
projectPath: input.projectPath,
|
|
2405
2880
|
scope: input.scope,
|
|
2406
2881
|
preferProjectVersion: false
|
|
2407
2882
|
});
|
|
2408
|
-
jarPath =
|
|
2409
|
-
warnings.push(...
|
|
2410
|
-
mappingApplied =
|
|
2411
|
-
|
|
2412
|
-
|
|
2883
|
+
jarPath = resolvedArtifact.binaryJarPath ?? (await this.versionService.resolveVersionJar(version)).jarPath;
|
|
2884
|
+
warnings.push(...resolvedArtifact.warnings);
|
|
2885
|
+
mappingApplied = resolvedArtifact.mappingApplied;
|
|
2886
|
+
signatureLookupMapping = resolvedArtifact.mappingApplied;
|
|
2887
|
+
if (resolvedArtifact.version) {
|
|
2888
|
+
version = resolvedArtifact.version;
|
|
2413
2889
|
}
|
|
2414
2890
|
}
|
|
2415
2891
|
catch (scopeErr) {
|
|
@@ -2430,6 +2906,7 @@ export class SourceService {
|
|
|
2430
2906
|
if (jarPath.includes("-sources.jar")) {
|
|
2431
2907
|
warnings.push(`Resolved jar appears to be a sources jar. Falling back to vanilla client jar.`);
|
|
2432
2908
|
jarPath = (await this.versionService.resolveVersionJar(version)).jarPath;
|
|
2909
|
+
signatureLookupMapping = "obfuscated";
|
|
2433
2910
|
scopeFallback = {
|
|
2434
2911
|
requested: input.scope ?? "vanilla",
|
|
2435
2912
|
applied: "vanilla",
|
|
@@ -2442,7 +2919,7 @@ export class SourceService {
|
|
|
2442
2919
|
const health = await this.mappingService.checkMappingHealth({
|
|
2443
2920
|
version,
|
|
2444
2921
|
requestedMapping,
|
|
2445
|
-
sourcePriority:
|
|
2922
|
+
sourcePriority: currentSourcePriority
|
|
2446
2923
|
});
|
|
2447
2924
|
const jarAvailable = existsSync(jarPath);
|
|
2448
2925
|
healthReport = {
|
|
@@ -2489,51 +2966,51 @@ export class SourceService {
|
|
|
2489
2966
|
}
|
|
2490
2967
|
}
|
|
2491
2968
|
}
|
|
2492
|
-
let
|
|
2493
|
-
if (requestedMapping !==
|
|
2969
|
+
let obfuscatedName = resolvedClassName;
|
|
2970
|
+
if (requestedMapping !== signatureLookupMapping) {
|
|
2494
2971
|
try {
|
|
2495
|
-
const mapped = await this.
|
|
2972
|
+
const mapped = await this.findValidateMixinClassMapping({
|
|
2496
2973
|
version,
|
|
2497
|
-
|
|
2498
|
-
name: resolvedClassName,
|
|
2974
|
+
className: resolvedClassName,
|
|
2499
2975
|
sourceMapping: requestedMapping,
|
|
2500
|
-
targetMapping:
|
|
2501
|
-
sourcePriority:
|
|
2976
|
+
targetMapping: signatureLookupMapping,
|
|
2977
|
+
sourcePriority: currentSourcePriority,
|
|
2978
|
+
batchCaches: input.batchCaches
|
|
2502
2979
|
});
|
|
2503
2980
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
2504
|
-
|
|
2505
|
-
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output:
|
|
2981
|
+
obfuscatedName = mapped.resolvedSymbol.name;
|
|
2982
|
+
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output: obfuscatedName, success: true });
|
|
2506
2983
|
}
|
|
2507
2984
|
else {
|
|
2508
|
-
warnings.push(`Could not map class "${resolvedClassName}" from ${requestedMapping} to
|
|
2985
|
+
warnings.push(`Could not map class "${resolvedClassName}" from ${requestedMapping} to ${signatureLookupMapping}; using "${obfuscatedName}" for lookup.`);
|
|
2509
2986
|
mappingFailedTargets.add(target.className);
|
|
2510
|
-
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output:
|
|
2987
|
+
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output: obfuscatedName, success: false, detail: "No mapping found" });
|
|
2511
2988
|
}
|
|
2512
2989
|
}
|
|
2513
2990
|
catch (mapErr) {
|
|
2514
|
-
warnings.push(`Mapping lookup failed for class "${resolvedClassName}"; using "${
|
|
2991
|
+
warnings.push(`Mapping lookup failed for class "${resolvedClassName}" while preparing ${signatureLookupMapping} lookup; using "${obfuscatedName}" for lookup.`);
|
|
2515
2992
|
mappingFailedTargets.add(target.className);
|
|
2516
|
-
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output:
|
|
2993
|
+
resolutionTrace?.push({ target: target.className, step: "mapping", input: resolvedClassName, output: obfuscatedName, success: false, detail: mapErr instanceof Error ? mapErr.message : String(mapErr) });
|
|
2517
2994
|
}
|
|
2518
2995
|
}
|
|
2519
2996
|
try {
|
|
2520
2997
|
const sig = await this.explorerService.getSignature({
|
|
2521
|
-
fqn:
|
|
2998
|
+
fqn: obfuscatedName,
|
|
2522
2999
|
jarPath,
|
|
2523
3000
|
access: "all"
|
|
2524
3001
|
});
|
|
2525
3002
|
warnings.push(...sig.warnings);
|
|
2526
|
-
resolutionTrace?.push({ target: target.className, step: "signature", input:
|
|
3003
|
+
resolutionTrace?.push({ target: target.className, step: "signature", input: obfuscatedName, output: `${sig.methods.length} methods, ${sig.fields.length} fields`, success: true });
|
|
2527
3004
|
// Bug 2 fix: remap signature members to requested mapping
|
|
2528
3005
|
let constructors = sig.constructors;
|
|
2529
3006
|
let methods = sig.methods;
|
|
2530
3007
|
let fields = sig.fields;
|
|
2531
|
-
if (requestedMapping !==
|
|
3008
|
+
if (requestedMapping !== signatureLookupMapping) {
|
|
2532
3009
|
try {
|
|
2533
3010
|
const [ctorResult, methodResult, fieldResult] = await Promise.all([
|
|
2534
|
-
this.remapSignatureMembers(sig.constructors, "method", version, requestedMapping,
|
|
2535
|
-
this.remapSignatureMembers(sig.methods, "method", version, requestedMapping,
|
|
2536
|
-
this.remapSignatureMembers(sig.fields, "field", version, requestedMapping,
|
|
3011
|
+
this.remapSignatureMembers(sig.constructors, "method", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings),
|
|
3012
|
+
this.remapSignatureMembers(sig.methods, "method", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings),
|
|
3013
|
+
this.remapSignatureMembers(sig.fields, "field", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings)
|
|
2537
3014
|
]);
|
|
2538
3015
|
constructors = ctorResult.members;
|
|
2539
3016
|
methods = methodResult.members;
|
|
@@ -2555,9 +3032,17 @@ export class SourceService {
|
|
|
2555
3032
|
}
|
|
2556
3033
|
}
|
|
2557
3034
|
catch (remapErr) {
|
|
2558
|
-
warnings.push(`Member remapping failed for "${resolvedClassName}"; falling back to
|
|
2559
|
-
|
|
2560
|
-
|
|
3035
|
+
warnings.push(`Member remapping failed for "${resolvedClassName}"; falling back to ${signatureLookupMapping} names. ` +
|
|
3036
|
+
`Member names shown may be in the ${signatureLookupMapping} runtime namespace.`);
|
|
3037
|
+
mappingApplied = signatureLookupMapping;
|
|
3038
|
+
resolutionTrace?.push({
|
|
3039
|
+
target: target.className,
|
|
3040
|
+
step: "remap",
|
|
3041
|
+
input: resolvedClassName,
|
|
3042
|
+
output: `${signatureLookupMapping} fallback`,
|
|
3043
|
+
success: false,
|
|
3044
|
+
detail: remapErr instanceof Error ? remapErr.message : String(remapErr)
|
|
3045
|
+
});
|
|
2561
3046
|
}
|
|
2562
3047
|
}
|
|
2563
3048
|
targetMembers.set(target.className, {
|
|
@@ -2568,17 +3053,15 @@ export class SourceService {
|
|
|
2568
3053
|
});
|
|
2569
3054
|
}
|
|
2570
3055
|
catch (sigErr) {
|
|
2571
|
-
warnings.push(`Could not load signature for class "${resolvedClassName}" (
|
|
2572
|
-
|
|
2573
|
-
resolutionTrace?.push({ target: target.className, step: "signature", input: officialName, output: "CLASS_NOT_FOUND", success: false, detail: sigErr instanceof Error ? sigErr.message : String(sigErr) });
|
|
3056
|
+
warnings.push(`Could not load signature for class "${resolvedClassName}" (obfuscated: "${obfuscatedName}").`);
|
|
3057
|
+
resolutionTrace?.push({ target: target.className, step: "signature", input: obfuscatedName, output: "CLASS_NOT_FOUND", success: false, detail: sigErr instanceof Error ? sigErr.message : String(sigErr) });
|
|
2574
3058
|
// Fallback: check if the symbol exists in the mapping graph even though getSignature failed
|
|
2575
3059
|
try {
|
|
2576
3060
|
const existenceCheck = await this.mappingService.checkSymbolExists({
|
|
2577
3061
|
version, kind: "class", name: resolvedClassName,
|
|
2578
|
-
sourceMapping: requestedMapping, nameMode: "auto"
|
|
3062
|
+
sourceMapping: requestedMapping, nameMode: "auto", sourcePriority: currentSourcePriority
|
|
2579
3063
|
});
|
|
2580
3064
|
if (existenceCheck.resolved) {
|
|
2581
|
-
signatureFailedTargets.delete(target.className);
|
|
2582
3065
|
symbolExistsButSignatureFailed.add(target.className);
|
|
2583
3066
|
resolutionTrace?.push({ target: target.className, step: "fallback-check", input: resolvedClassName, output: "exists in mapping graph", success: true });
|
|
2584
3067
|
}
|
|
@@ -2587,23 +3070,35 @@ export class SourceService {
|
|
|
2587
3070
|
}
|
|
2588
3071
|
}
|
|
2589
3072
|
catch {
|
|
2590
|
-
// Fallback check failed —
|
|
3073
|
+
// Fallback check failed — treat as tool-limited partial validation.
|
|
3074
|
+
signatureFailedTargets.add(target.className);
|
|
2591
3075
|
resolutionTrace?.push({ target: target.className, step: "fallback-check", input: resolvedClassName, output: "check failed", success: false });
|
|
2592
3076
|
}
|
|
2593
3077
|
}
|
|
2594
3078
|
}
|
|
2595
3079
|
// Fix toolHealth accuracy: reflect actual failures after target resolution
|
|
2596
3080
|
if (healthReport) {
|
|
2597
|
-
const hasFailures = signatureFailedTargets.size > 0 ||
|
|
3081
|
+
const hasFailures = signatureFailedTargets.size > 0 ||
|
|
3082
|
+
mappingFailedTargets.size > 0 ||
|
|
3083
|
+
symbolExistsButSignatureFailed.size > 0;
|
|
2598
3084
|
if (hasFailures && healthReport.overallHealthy) {
|
|
2599
3085
|
healthReport.overallHealthy = false;
|
|
2600
|
-
healthReport.degradations.push(`${mappingFailedTargets.size} mapping failure(s), ${signatureFailedTargets.size} signature failure(s).`);
|
|
3086
|
+
healthReport.degradations.push(`${mappingFailedTargets.size} mapping failure(s), ${signatureFailedTargets.size} signature failure(s), ${symbolExistsButSignatureFailed.size} partial validation target(s).`);
|
|
2601
3087
|
}
|
|
2602
3088
|
}
|
|
2603
3089
|
const resolutionNotes = [];
|
|
2604
3090
|
if (requestedMapping !== mappingApplied) {
|
|
2605
3091
|
resolutionNotes.push(`Mapping fallback: requested "${requestedMapping}" but applied "${mappingApplied}" due to remapping failure.`);
|
|
2606
3092
|
}
|
|
3093
|
+
const appliedScope = inferAppliedArtifactScope({
|
|
3094
|
+
requestedScope,
|
|
3095
|
+
scopeFallback,
|
|
3096
|
+
jarPath,
|
|
3097
|
+
resolvedSourceJarPath: resolvedArtifact?.resolvedSourceJarPath
|
|
3098
|
+
});
|
|
3099
|
+
if (!scopeFallback && requestedScope !== appliedScope) {
|
|
3100
|
+
resolutionNotes.push(`Scope adjusted during validation: requested "${requestedScope}" but resolved artifact looks like "${appliedScope}".`);
|
|
3101
|
+
}
|
|
2607
3102
|
// Count remap failures from warnings
|
|
2608
3103
|
const REMAP_WARNING_RE = /^(?:Could not remap|Remap failed for)\b/;
|
|
2609
3104
|
const remapFailures = warnings.filter((w) => REMAP_WARNING_RE.test(w)).length;
|
|
@@ -2617,26 +3112,30 @@ export class SourceService {
|
|
|
2617
3112
|
}
|
|
2618
3113
|
// Build mapping chain description
|
|
2619
3114
|
const mappingChain = [];
|
|
2620
|
-
if (requestedMapping !==
|
|
2621
|
-
mappingChain.push(`${requestedMapping} →
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
}
|
|
3115
|
+
if (requestedMapping !== signatureLookupMapping) {
|
|
3116
|
+
mappingChain.push(`${requestedMapping} → ${signatureLookupMapping}`);
|
|
3117
|
+
}
|
|
3118
|
+
if (mappingApplied !== signatureLookupMapping) {
|
|
3119
|
+
mappingChain.push(`fallback to ${mappingApplied}`);
|
|
2625
3120
|
}
|
|
2626
3121
|
const provenance = {
|
|
2627
3122
|
version,
|
|
2628
3123
|
jarPath,
|
|
2629
3124
|
requestedMapping,
|
|
2630
3125
|
mappingApplied,
|
|
3126
|
+
requestedScope,
|
|
3127
|
+
appliedScope,
|
|
3128
|
+
requestedSourcePriority: initialSourcePriority,
|
|
3129
|
+
appliedSourcePriority: currentSourcePriority,
|
|
2631
3130
|
resolutionNotes: resolutionNotes.length > 0 ? resolutionNotes : undefined,
|
|
2632
|
-
jarType:
|
|
3131
|
+
jarType: scopeToJarType(appliedScope),
|
|
2633
3132
|
mappingChain: mappingChain.length > 0 ? mappingChain : undefined,
|
|
2634
3133
|
remapFailures: remapFailures > 0 ? remapFailures : undefined,
|
|
2635
3134
|
mappingAutoDetected: mappingAutoDetected || undefined,
|
|
2636
3135
|
scopeFallback,
|
|
2637
3136
|
resolutionTrace: resolutionTrace && resolutionTrace.length > 0 ? resolutionTrace : undefined
|
|
2638
3137
|
};
|
|
2639
|
-
const result = validateParsedMixin(parsed, targetMembers, warnings, provenance, confidence, mappingFailedTargets, input.explain, remapFailedMembers, signatureFailedTargets, input.explain ? { scope:
|
|
3138
|
+
const result = refreshMixinValidationOutcome(validateParsedMixin(parsed, targetMembers, warnings, provenance, confidence, mappingFailedTargets, input.explain, remapFailedMembers, signatureFailedTargets, input.explain ? { scope: requestedScope, sourcePriority: currentSourcePriority, projectPath: input.projectPath, mapping: requestedMapping } : undefined, input.warningMode, healthReport, symbolExistsButSignatureFailed.size > 0 ? symbolExistsButSignatureFailed : undefined));
|
|
2640
3139
|
// Apply minSeverity / hideUncertain filters
|
|
2641
3140
|
const minSeverity = input.minSeverity ?? "all";
|
|
2642
3141
|
const hideUncertain = input.hideUncertain ?? false;
|
|
@@ -2669,7 +3168,6 @@ export class SourceService {
|
|
|
2669
3168
|
parseWarnings: filteredParseWarnings
|
|
2670
3169
|
};
|
|
2671
3170
|
result.unfilteredSummary = unfilteredSummary;
|
|
2672
|
-
result.valid = filteredDefiniteErrors === 0;
|
|
2673
3171
|
}
|
|
2674
3172
|
// Apply warningCategoryFilter
|
|
2675
3173
|
if (input.warningCategoryFilter && input.warningCategoryFilter.length > 0) {
|
|
@@ -2693,7 +3191,6 @@ export class SourceService {
|
|
|
2693
3191
|
resolutionErrors: result.issues.filter((i) => i.resolutionPath != null).length,
|
|
2694
3192
|
parseWarnings: result.issues.filter((i) => i.category === "parse").length
|
|
2695
3193
|
};
|
|
2696
|
-
result.valid = catDefiniteErrors === 0;
|
|
2697
3194
|
}
|
|
2698
3195
|
// Apply treatInfoAsWarning filter
|
|
2699
3196
|
if (input.treatInfoAsWarning === false && result.structuredWarnings) {
|
|
@@ -2707,51 +3204,217 @@ export class SourceService {
|
|
|
2707
3204
|
result.structuredWarnings = undefined;
|
|
2708
3205
|
result.aggregatedWarnings = undefined;
|
|
2709
3206
|
result.toolHealth = undefined;
|
|
3207
|
+
result.confidenceBreakdown = undefined;
|
|
2710
3208
|
if (result.provenance) {
|
|
2711
3209
|
result.provenance.resolutionTrace = undefined;
|
|
2712
3210
|
}
|
|
2713
3211
|
}
|
|
3212
|
+
refreshMixinValidationOutcome(result);
|
|
3213
|
+
if (this.shouldRetryValidateMixinWithMavenFirst(input, result)) {
|
|
3214
|
+
const retryWarning = `Retrying validate-mixin with sourcePriority="maven-first" after partial validation using "${currentSourcePriority}".`;
|
|
3215
|
+
try {
|
|
3216
|
+
const retried = await this.validateMixinSingle({
|
|
3217
|
+
...input,
|
|
3218
|
+
source,
|
|
3219
|
+
sourcePath: undefined,
|
|
3220
|
+
sourcePriority: "maven-first",
|
|
3221
|
+
retryState: {
|
|
3222
|
+
attempted: true,
|
|
3223
|
+
initialSourcePriority
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
retried.warnings = [retryWarning, ...retried.warnings];
|
|
3227
|
+
if (retried.provenance) {
|
|
3228
|
+
retried.provenance.requestedSourcePriority = initialSourcePriority;
|
|
3229
|
+
retried.provenance.appliedSourcePriority = "maven-first";
|
|
3230
|
+
retried.provenance.resolutionNotes = [
|
|
3231
|
+
...(retried.provenance.resolutionNotes ?? []),
|
|
3232
|
+
`Validation retried with sourcePriority "maven-first" after partial result from "${currentSourcePriority}".`
|
|
3233
|
+
];
|
|
3234
|
+
}
|
|
3235
|
+
return refreshMixinValidationOutcome(retried);
|
|
3236
|
+
}
|
|
3237
|
+
catch (retryErr) {
|
|
3238
|
+
result.warnings.unshift(`${retryWarning} Retry failed: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`);
|
|
3239
|
+
return result;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
2714
3242
|
return result;
|
|
2715
3243
|
}
|
|
2716
|
-
|
|
2717
|
-
const
|
|
3244
|
+
resolveMixinInputPath(rawPath, fieldName) {
|
|
3245
|
+
const normalizedPath = normalizePathForHost(rawPath, undefined, fieldName);
|
|
3246
|
+
return isAbsolute(normalizedPath)
|
|
3247
|
+
? normalizedPath
|
|
3248
|
+
: resolvePath(process.cwd(), normalizedPath);
|
|
3249
|
+
}
|
|
3250
|
+
async resolveMixinConfigSources(input) {
|
|
3251
|
+
if (input.input.mode !== "config") {
|
|
3252
|
+
return [];
|
|
3253
|
+
}
|
|
2718
3254
|
const results = [];
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
// P5: default warningMode to "aggregated" in batch mode
|
|
2723
|
-
const batchWarningMode = input.warningMode ?? "aggregated";
|
|
2724
|
-
for (const sp of paths) {
|
|
3255
|
+
for (const rawConfigPath of input.input.configPaths) {
|
|
3256
|
+
const resolvedConfigPath = this.resolveMixinInputPath(rawConfigPath, "configPath");
|
|
3257
|
+
let configJson;
|
|
2725
3258
|
try {
|
|
2726
|
-
const
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
3259
|
+
const raw = await readFile(resolvedConfigPath, "utf-8");
|
|
3260
|
+
configJson = JSON.parse(raw);
|
|
3261
|
+
}
|
|
3262
|
+
catch (err) {
|
|
3263
|
+
throw createError({
|
|
3264
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
3265
|
+
message: `Could not read/parse mixin config "${rawConfigPath}": ${err instanceof Error ? err.message : String(err)}`
|
|
2732
3266
|
});
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
3267
|
+
}
|
|
3268
|
+
const pkg = configJson.package ?? "";
|
|
3269
|
+
const classNames = [
|
|
3270
|
+
...(configJson.mixins ?? []),
|
|
3271
|
+
...(configJson.client ?? []),
|
|
3272
|
+
...(configJson.server ?? [])
|
|
3273
|
+
];
|
|
3274
|
+
if (classNames.length === 0) {
|
|
3275
|
+
continue;
|
|
3276
|
+
}
|
|
3277
|
+
const projectBase = input.projectPath
|
|
3278
|
+
? (isAbsolute(input.projectPath) ? input.projectPath : resolvePath(process.cwd(), input.projectPath))
|
|
3279
|
+
: dirname(resolvedConfigPath);
|
|
3280
|
+
let sourceRootCandidates;
|
|
3281
|
+
if (input.sourceRoots && input.sourceRoots.length > 0) {
|
|
3282
|
+
sourceRootCandidates = input.sourceRoots;
|
|
3283
|
+
}
|
|
3284
|
+
else {
|
|
3285
|
+
const detected = COMMON_SOURCE_ROOTS.filter((candidateRoot) => classNames.some((className) => {
|
|
3286
|
+
const fqcn = pkg ? `${pkg}.${className}` : className;
|
|
3287
|
+
const relative = fqcn.replace(/\./g, "/") + ".java";
|
|
3288
|
+
return existsSync(resolvePath(projectBase, candidateRoot, relative));
|
|
3289
|
+
}));
|
|
3290
|
+
sourceRootCandidates = detected.length > 0 ? detected : ["src/main/java"];
|
|
3291
|
+
}
|
|
3292
|
+
for (const cls of classNames) {
|
|
3293
|
+
const fqcn = pkg ? `${pkg}.${cls}` : cls;
|
|
3294
|
+
const relativePath = fqcn.replace(/\./g, "/") + ".java";
|
|
3295
|
+
let sourcePath = resolvePath(projectBase, sourceRootCandidates[0], relativePath);
|
|
3296
|
+
for (const root of sourceRootCandidates) {
|
|
3297
|
+
const candidate = resolvePath(projectBase, root, relativePath);
|
|
3298
|
+
if (existsSync(candidate)) {
|
|
3299
|
+
sourcePath = candidate;
|
|
3300
|
+
break;
|
|
3301
|
+
}
|
|
2739
3302
|
}
|
|
3303
|
+
results.push({
|
|
3304
|
+
sourcePath,
|
|
3305
|
+
configPath: resolvedConfigPath
|
|
3306
|
+
});
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
return results;
|
|
3310
|
+
}
|
|
3311
|
+
async validateMixinMany(mode, entries, input) {
|
|
3312
|
+
const results = [];
|
|
3313
|
+
const batchWarningMode = input.warningMode ?? "aggregated";
|
|
3314
|
+
const { input: _discardedInput, ...sharedInput } = input;
|
|
3315
|
+
const batchCaches = {
|
|
3316
|
+
classMappings: new Map()
|
|
3317
|
+
};
|
|
3318
|
+
for (const entry of entries) {
|
|
3319
|
+
try {
|
|
3320
|
+
const singleResult = await this.validateMixinSingle({
|
|
3321
|
+
...sharedInput,
|
|
3322
|
+
sourcePath: entry.sourcePath,
|
|
3323
|
+
warningMode: batchWarningMode,
|
|
3324
|
+
batchCaches
|
|
3325
|
+
});
|
|
3326
|
+
results.push({
|
|
3327
|
+
source: entry.source,
|
|
3328
|
+
result: singleResult
|
|
3329
|
+
});
|
|
2740
3330
|
}
|
|
2741
3331
|
catch (err) {
|
|
2742
3332
|
results.push({
|
|
2743
|
-
|
|
3333
|
+
source: entry.source,
|
|
2744
3334
|
error: err instanceof Error ? err.message : String(err)
|
|
2745
3335
|
});
|
|
2746
|
-
errorCount++;
|
|
2747
3336
|
}
|
|
2748
3337
|
}
|
|
2749
|
-
|
|
3338
|
+
return this.applyValidateMixinOutputCompaction(this.buildValidateMixinOutput(mode, results), input);
|
|
3339
|
+
}
|
|
3340
|
+
applyValidateMixinOutputCompaction(output, input) {
|
|
3341
|
+
let nextOutput = output;
|
|
3342
|
+
const canHoistProvenance = nextOutput.provenance != null;
|
|
3343
|
+
const warningCandidates = nextOutput.results
|
|
3344
|
+
.map((entry) => entry.result?.warnings)
|
|
3345
|
+
.filter((entry) => entry != null);
|
|
3346
|
+
const canHoistWarnings = warningCandidates.length === 0
|
|
3347
|
+
? true
|
|
3348
|
+
: warningCandidates.every((entry) => sameStringArray(entry, warningCandidates[0]));
|
|
3349
|
+
if (input.reportMode === "summary-first") {
|
|
3350
|
+
nextOutput = {
|
|
3351
|
+
...nextOutput,
|
|
3352
|
+
results: nextOutput.results.map((entry) => (entry.result
|
|
3353
|
+
? {
|
|
3354
|
+
...entry,
|
|
3355
|
+
result: {
|
|
3356
|
+
...entry.result,
|
|
3357
|
+
warnings: canHoistWarnings ? [] : entry.result.warnings,
|
|
3358
|
+
structuredWarnings: undefined,
|
|
3359
|
+
aggregatedWarnings: undefined,
|
|
3360
|
+
resolvedMembers: undefined,
|
|
3361
|
+
toolHealth: undefined,
|
|
3362
|
+
confidenceBreakdown: undefined,
|
|
3363
|
+
provenance: canHoistProvenance ? undefined : entry.result.provenance
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
: entry))
|
|
3367
|
+
};
|
|
3368
|
+
}
|
|
3369
|
+
if (input.includeIssues !== false) {
|
|
3370
|
+
return nextOutput;
|
|
3371
|
+
}
|
|
3372
|
+
return {
|
|
3373
|
+
...nextOutput,
|
|
3374
|
+
results: nextOutput.results.map((entry) => (entry.result
|
|
3375
|
+
? {
|
|
3376
|
+
...entry,
|
|
3377
|
+
result: {
|
|
3378
|
+
...entry.result,
|
|
3379
|
+
issues: []
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
: entry))
|
|
3383
|
+
};
|
|
3384
|
+
}
|
|
3385
|
+
buildValidateMixinOutput(mode, results) {
|
|
3386
|
+
let valid = 0;
|
|
3387
|
+
let partial = 0;
|
|
3388
|
+
let invalid = 0;
|
|
3389
|
+
let processingErrors = 0;
|
|
3390
|
+
let totalValidationErrors = 0;
|
|
3391
|
+
let totalValidationWarnings = 0;
|
|
3392
|
+
const warningSet = new Set();
|
|
3393
|
+
const incompleteReasonSet = new Set();
|
|
2750
3394
|
const issueGroupMap = new Map();
|
|
2751
|
-
for (const
|
|
2752
|
-
if (!
|
|
3395
|
+
for (const entry of results) {
|
|
3396
|
+
if (!entry.result) {
|
|
3397
|
+
processingErrors++;
|
|
2753
3398
|
continue;
|
|
2754
|
-
|
|
3399
|
+
}
|
|
3400
|
+
if (entry.result.valid) {
|
|
3401
|
+
valid++;
|
|
3402
|
+
}
|
|
3403
|
+
else {
|
|
3404
|
+
invalid++;
|
|
3405
|
+
}
|
|
3406
|
+
if (entry.result.validationStatus === "partial") {
|
|
3407
|
+
partial++;
|
|
3408
|
+
}
|
|
3409
|
+
totalValidationErrors += entry.result.summary.errors;
|
|
3410
|
+
totalValidationWarnings += entry.result.summary.warnings;
|
|
3411
|
+
for (const warning of entry.result.warnings) {
|
|
3412
|
+
warningSet.add(warning);
|
|
3413
|
+
}
|
|
3414
|
+
for (const issue of entry.result.issues) {
|
|
3415
|
+
if (issue.kind === "validation-incomplete") {
|
|
3416
|
+
incompleteReasonSet.add(`validation-incomplete: ${issue.message}`);
|
|
3417
|
+
}
|
|
2755
3418
|
const key = `${issue.kind}\0${issue.confidence ?? "unknown"}\0${issue.category ?? "validation"}`;
|
|
2756
3419
|
const existing = issueGroupMap.get(key);
|
|
2757
3420
|
if (existing) {
|
|
@@ -2771,39 +3434,37 @@ export class SourceService {
|
|
|
2771
3434
|
}
|
|
2772
3435
|
}
|
|
2773
3436
|
}
|
|
2774
|
-
const issueSummary = issueGroupMap.size > 0
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
const sharedHealth = results.find((r) => r.result?.toolHealth)?.result?.toolHealth;
|
|
2788
|
-
// Batch confidenceScore = min of all individual scores
|
|
2789
|
-
const scores = results
|
|
2790
|
-
.map((r) => r.result?.confidenceScore)
|
|
2791
|
-
.filter((s) => s != null);
|
|
2792
|
-
const batchConfidenceScore = scores.length > 0 ? Math.min(...scores) : undefined;
|
|
3437
|
+
const issueSummary = issueGroupMap.size > 0 ? [...issueGroupMap.values()] : undefined;
|
|
3438
|
+
const provenanceCandidates = results
|
|
3439
|
+
.map((entry) => entry.result?.provenance)
|
|
3440
|
+
.filter((entry) => entry != null);
|
|
3441
|
+
const provenance = provenanceCandidates.length === 0
|
|
3442
|
+
? undefined
|
|
3443
|
+
: provenanceCandidates.every((entry) => sameMixinValidationProvenance(entry, provenanceCandidates[0]))
|
|
3444
|
+
? provenanceCandidates[0]
|
|
3445
|
+
: undefined;
|
|
3446
|
+
const toolHealth = results.find((entry) => entry.result?.toolHealth)?.result?.toolHealth;
|
|
3447
|
+
const confidenceScores = results
|
|
3448
|
+
.map((entry) => entry.result?.confidenceScore)
|
|
3449
|
+
.filter((score) => score != null);
|
|
2793
3450
|
return {
|
|
3451
|
+
mode,
|
|
2794
3452
|
results,
|
|
2795
3453
|
summary: {
|
|
2796
|
-
total:
|
|
2797
|
-
valid
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
processingErrors
|
|
3454
|
+
total: results.length,
|
|
3455
|
+
valid,
|
|
3456
|
+
partial,
|
|
3457
|
+
invalid,
|
|
3458
|
+
processingErrors,
|
|
2801
3459
|
totalValidationErrors,
|
|
2802
|
-
totalValidationWarnings
|
|
2803
|
-
confidenceScore: batchConfidenceScore
|
|
3460
|
+
totalValidationWarnings
|
|
2804
3461
|
},
|
|
2805
3462
|
issueSummary,
|
|
2806
|
-
|
|
3463
|
+
provenance,
|
|
3464
|
+
incompleteReasons: incompleteReasonSet.size > 0 ? [...incompleteReasonSet] : undefined,
|
|
3465
|
+
toolHealth,
|
|
3466
|
+
confidenceScore: confidenceScores.length > 0 ? Math.min(...confidenceScores) : undefined,
|
|
3467
|
+
warnings: [...warningSet]
|
|
2807
3468
|
};
|
|
2808
3469
|
}
|
|
2809
3470
|
async validateAccessWidener(input) {
|
|
@@ -2828,7 +3489,7 @@ export class SourceService {
|
|
|
2828
3489
|
if (overrideMapping && headerNamespace && overrideMapping !== headerNamespace) {
|
|
2829
3490
|
warnings.push(`Using mapping override "${overrideMapping}" instead of header namespace "${headerNamespaceRaw}".`);
|
|
2830
3491
|
}
|
|
2831
|
-
const needsMapping = awNamespace !== "
|
|
3492
|
+
const needsMapping = awNamespace !== "obfuscated";
|
|
2832
3493
|
// Collect unique class FQNs from entries
|
|
2833
3494
|
const classFqns = new Set();
|
|
2834
3495
|
for (const entry of parsed.entries) {
|
|
@@ -2837,7 +3498,7 @@ export class SourceService {
|
|
|
2837
3498
|
}
|
|
2838
3499
|
const membersByClass = new Map();
|
|
2839
3500
|
for (const fqn of classFqns) {
|
|
2840
|
-
let
|
|
3501
|
+
let obfuscatedFqn = fqn;
|
|
2841
3502
|
if (needsMapping) {
|
|
2842
3503
|
try {
|
|
2843
3504
|
const mapped = await this.mappingService.findMapping({
|
|
@@ -2845,14 +3506,14 @@ export class SourceService {
|
|
|
2845
3506
|
kind: "class",
|
|
2846
3507
|
name: fqn,
|
|
2847
3508
|
sourceMapping: awNamespace,
|
|
2848
|
-
targetMapping: "
|
|
3509
|
+
targetMapping: "obfuscated",
|
|
2849
3510
|
sourcePriority: input.sourcePriority
|
|
2850
3511
|
});
|
|
2851
3512
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
2852
|
-
|
|
3513
|
+
obfuscatedFqn = mapped.resolvedSymbol.name;
|
|
2853
3514
|
}
|
|
2854
3515
|
else {
|
|
2855
|
-
warnings.push(`Could not map class "${fqn}" from ${awNamespace} to
|
|
3516
|
+
warnings.push(`Could not map class "${fqn}" from ${awNamespace} to obfuscated.`);
|
|
2856
3517
|
}
|
|
2857
3518
|
}
|
|
2858
3519
|
catch {
|
|
@@ -2861,7 +3522,7 @@ export class SourceService {
|
|
|
2861
3522
|
}
|
|
2862
3523
|
try {
|
|
2863
3524
|
const sig = await this.explorerService.getSignature({
|
|
2864
|
-
fqn:
|
|
3525
|
+
fqn: obfuscatedFqn,
|
|
2865
3526
|
jarPath,
|
|
2866
3527
|
access: "all"
|
|
2867
3528
|
});
|
|
@@ -2874,7 +3535,7 @@ export class SourceService {
|
|
|
2874
3535
|
});
|
|
2875
3536
|
}
|
|
2876
3537
|
catch {
|
|
2877
|
-
warnings.push(`Could not load signature for class "${
|
|
3538
|
+
warnings.push(`Could not load signature for class "${obfuscatedFqn}".`);
|
|
2878
3539
|
}
|
|
2879
3540
|
}
|
|
2880
3541
|
return validateParsedAccessWidener(parsed, membersByClass, warnings);
|
|
@@ -2915,7 +3576,7 @@ export class SourceService {
|
|
|
2915
3576
|
},
|
|
2916
3577
|
indexedAt: currentMeta.indexedAt,
|
|
2917
3578
|
durationMs: 0,
|
|
2918
|
-
mappingApplied: artifact.mappingApplied ?? "
|
|
3579
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated"
|
|
2919
3580
|
};
|
|
2920
3581
|
}
|
|
2921
3582
|
const resolved = this.toResolvedArtifact(artifact);
|
|
@@ -2932,33 +3593,16 @@ export class SourceService {
|
|
|
2932
3593
|
},
|
|
2933
3594
|
indexedAt: rebuilt.indexedAt,
|
|
2934
3595
|
durationMs: rebuilt.indexDurationMs,
|
|
2935
|
-
mappingApplied: artifact.mappingApplied ?? "
|
|
3596
|
+
mappingApplied: artifact.mappingApplied ?? "obfuscated"
|
|
2936
3597
|
};
|
|
2937
3598
|
}
|
|
2938
|
-
searchSymbolIntent(artifactId, query, match, scope,
|
|
3599
|
+
searchSymbolIntent(artifactId, query, match, scope, regexPattern, onHit) {
|
|
2939
3600
|
const matchedSymbols = this.findSymbolHits(artifactId, query, match, scope, regexPattern);
|
|
2940
|
-
const filePaths = [...new Set(matchedSymbols.map((item) => item.symbol.filePath))];
|
|
2941
|
-
const rows = this.filesRepo.getFileContentsByPaths(artifactId, filePaths);
|
|
2942
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
2943
|
-
this.metrics.recordSearchRowsScanned(rows.length);
|
|
2944
|
-
const rowsByPath = new Map(rows.map((row) => [row.filePath, row]));
|
|
2945
3601
|
for (const item of matchedSymbols) {
|
|
2946
|
-
const row = rowsByPath.get(item.symbol.filePath);
|
|
2947
|
-
const snippet = row
|
|
2948
|
-
? toContextSnippet(row.content, item.symbol.line, snippetWindow.before, snippetWindow.after, true)
|
|
2949
|
-
: {
|
|
2950
|
-
startLine: item.symbol.line,
|
|
2951
|
-
endLine: item.symbol.line,
|
|
2952
|
-
snippet: "",
|
|
2953
|
-
truncated: false
|
|
2954
|
-
};
|
|
2955
3602
|
onHit({
|
|
2956
3603
|
filePath: item.symbol.filePath,
|
|
2957
3604
|
score: item.score,
|
|
2958
3605
|
matchedIn: "symbol",
|
|
2959
|
-
startLine: snippet.startLine,
|
|
2960
|
-
endLine: snippet.endLine,
|
|
2961
|
-
snippet: snippet.snippet,
|
|
2962
3606
|
reasonCodes: [`symbol_${match}`],
|
|
2963
3607
|
symbol: {
|
|
2964
3608
|
symbolKind: item.symbol.symbolKind,
|
|
@@ -2969,7 +3613,7 @@ export class SourceService {
|
|
|
2969
3613
|
});
|
|
2970
3614
|
}
|
|
2971
3615
|
}
|
|
2972
|
-
searchTextIntentIndexed(artifactId, query, match, scope,
|
|
3616
|
+
searchTextIntentIndexed(artifactId, query, match, scope, onHit) {
|
|
2973
3617
|
const candidateLimit = this.indexedCandidateLimitForMatch(match);
|
|
2974
3618
|
const indexed = this.filesRepo.searchFileCandidates(artifactId, {
|
|
2975
3619
|
query,
|
|
@@ -2998,45 +3642,21 @@ export class SourceService {
|
|
|
2998
3642
|
if (contentIndex < 0) {
|
|
2999
3643
|
continue;
|
|
3000
3644
|
}
|
|
3001
|
-
const line = indexToLine(candidate.content, contentIndex);
|
|
3002
3645
|
candidateRows.push({
|
|
3003
3646
|
filePath: candidate.filePath,
|
|
3004
|
-
content: candidate.content,
|
|
3005
|
-
line,
|
|
3006
3647
|
contentIndex
|
|
3007
3648
|
});
|
|
3008
3649
|
}
|
|
3009
|
-
const needSymbols = includeDefinition || !!scope?.symbolKind;
|
|
3010
|
-
const symbolsByFile = needSymbols
|
|
3011
|
-
? this.symbolsRepo.listSymbolsForFiles(artifactId, candidateRows.map((candidate) => candidate.filePath), scope?.symbolKind)
|
|
3012
|
-
: new Map();
|
|
3013
|
-
if (needSymbols) {
|
|
3014
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3015
|
-
this.metrics.recordSearchRowsScanned([...symbolsByFile.values()].reduce((acc, symbols) => acc + symbols.length, 0));
|
|
3016
|
-
}
|
|
3017
3650
|
for (const candidate of candidateRows) {
|
|
3018
|
-
// When symbolKind filter is set, skip files that have no symbols of that kind
|
|
3019
|
-
if (scope?.symbolKind && !symbolsByFile.has(candidate.filePath)) {
|
|
3020
|
-
continue;
|
|
3021
|
-
}
|
|
3022
|
-
const snippet = toContextSnippet(candidate.content, candidate.line, snippetWindow.before, snippetWindow.after, true);
|
|
3023
|
-
const definition = includeDefinition
|
|
3024
|
-
? this.findNearestSymbolFromList(symbolsByFile.get(candidate.filePath) ?? [], candidate.line)
|
|
3025
|
-
: undefined;
|
|
3026
|
-
const resolvedSymbol = definition ? lineToSymbol(definition) : undefined;
|
|
3027
3651
|
onHit({
|
|
3028
3652
|
filePath: candidate.filePath,
|
|
3029
|
-
score: scoreTextMatch(match, candidate.contentIndex)
|
|
3653
|
+
score: scoreTextMatch(match, candidate.contentIndex),
|
|
3030
3654
|
matchedIn: "content",
|
|
3031
|
-
|
|
3032
|
-
endLine: snippet.endLine,
|
|
3033
|
-
snippet: snippet.snippet,
|
|
3034
|
-
reasonCodes: ["content_match", `text_${match}`, "indexed"],
|
|
3035
|
-
symbol: resolvedSymbol
|
|
3655
|
+
reasonCodes: ["content_match", `text_${match}`, "indexed"]
|
|
3036
3656
|
});
|
|
3037
3657
|
}
|
|
3038
3658
|
}
|
|
3039
|
-
searchPathIntentIndexed(artifactId, query, match, scope,
|
|
3659
|
+
searchPathIntentIndexed(artifactId, query, match, scope, onHit) {
|
|
3040
3660
|
const candidateLimit = this.indexedCandidateLimitForMatch(match);
|
|
3041
3661
|
const indexed = this.filesRepo.searchFileCandidates(artifactId, {
|
|
3042
3662
|
query,
|
|
@@ -3071,59 +3691,22 @@ export class SourceService {
|
|
|
3071
3691
|
pathIndex
|
|
3072
3692
|
});
|
|
3073
3693
|
}
|
|
3074
|
-
const candidateContentRows = this.filesRepo.getFileContentsByPaths(artifactId, candidateRows.map((candidate) => candidate.filePath));
|
|
3075
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3076
|
-
this.metrics.recordSearchRowsScanned(candidateContentRows.length);
|
|
3077
|
-
const contentByPath = new Map(candidateContentRows.map((row) => [row.filePath, row.content]));
|
|
3078
|
-
const needSymbols = includeDefinition || !!scope?.symbolKind;
|
|
3079
|
-
const symbolsByFile = needSymbols
|
|
3080
|
-
? this.symbolsRepo.listSymbolsForFiles(artifactId, candidateRows.map((candidate) => candidate.filePath), scope?.symbolKind)
|
|
3081
|
-
: new Map();
|
|
3082
|
-
if (needSymbols) {
|
|
3083
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3084
|
-
this.metrics.recordSearchRowsScanned([...symbolsByFile.values()].reduce((acc, symbols) => acc + symbols.length, 0));
|
|
3085
|
-
}
|
|
3086
3694
|
for (const candidate of candidateRows) {
|
|
3087
|
-
const content = contentByPath.get(candidate.filePath);
|
|
3088
|
-
if (!content) {
|
|
3089
|
-
continue;
|
|
3090
|
-
}
|
|
3091
|
-
// When symbolKind filter is set, skip files that have no symbols of that kind
|
|
3092
|
-
if (scope?.symbolKind && !symbolsByFile.has(candidate.filePath)) {
|
|
3093
|
-
continue;
|
|
3094
|
-
}
|
|
3095
|
-
const definition = includeDefinition
|
|
3096
|
-
? this.findNearestSymbolFromList(symbolsByFile.get(candidate.filePath) ?? [], 1)
|
|
3097
|
-
: undefined;
|
|
3098
|
-
const centerLine = definition?.line ?? 1;
|
|
3099
|
-
const snippet = toContextSnippet(content, centerLine, snippetWindow.before, snippetWindow.after, true);
|
|
3100
|
-
const resolvedSymbol = definition ? lineToSymbol(definition) : undefined;
|
|
3101
3695
|
onHit({
|
|
3102
3696
|
filePath: candidate.filePath,
|
|
3103
|
-
score: scorePathMatch(match, candidate.pathIndex)
|
|
3697
|
+
score: scorePathMatch(match, candidate.pathIndex),
|
|
3104
3698
|
matchedIn: "path",
|
|
3105
|
-
|
|
3106
|
-
endLine: snippet.endLine,
|
|
3107
|
-
snippet: snippet.snippet,
|
|
3108
|
-
reasonCodes: ["path_match", `path_${match}`, "indexed"],
|
|
3109
|
-
symbol: resolvedSymbol
|
|
3699
|
+
reasonCodes: ["path_match", `path_${match}`, "indexed"]
|
|
3110
3700
|
});
|
|
3111
3701
|
}
|
|
3112
3702
|
}
|
|
3113
|
-
searchTextIntent(artifactId, query, match, scope,
|
|
3703
|
+
searchTextIntent(artifactId, query, match, scope, regexPattern, onHit) {
|
|
3114
3704
|
const filePaths = this.loadScopedFilePaths(artifactId, scope);
|
|
3115
3705
|
const pageSize = Math.max(1, this.config.searchScanPageSize ?? 250);
|
|
3116
3706
|
for (const chunk of chunkArray(filePaths, pageSize)) {
|
|
3117
3707
|
const rows = this.filesRepo.getFileContentsByPaths(artifactId, chunk);
|
|
3118
3708
|
this.metrics.recordSearchDbRoundtrip();
|
|
3119
3709
|
this.metrics.recordSearchRowsScanned(rows.length);
|
|
3120
|
-
const symbolsByFile = includeDefinition
|
|
3121
|
-
? this.symbolsRepo.listSymbolsForFiles(artifactId, rows.map((row) => row.filePath), scope?.symbolKind)
|
|
3122
|
-
: new Map();
|
|
3123
|
-
if (includeDefinition) {
|
|
3124
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3125
|
-
this.metrics.recordSearchRowsScanned([...symbolsByFile.values()].reduce((acc, symbols) => acc + symbols.length, 0));
|
|
3126
|
-
}
|
|
3127
3710
|
for (const row of rows) {
|
|
3128
3711
|
const contentIndex = match === "regex"
|
|
3129
3712
|
? matchRegexIndex(row.content, regexPattern)
|
|
@@ -3131,26 +3714,16 @@ export class SourceService {
|
|
|
3131
3714
|
if (contentIndex < 0) {
|
|
3132
3715
|
continue;
|
|
3133
3716
|
}
|
|
3134
|
-
const line = indexToLine(row.content, contentIndex);
|
|
3135
|
-
const snippet = toContextSnippet(row.content, line, snippetWindow.before, snippetWindow.after, true);
|
|
3136
|
-
const definition = includeDefinition
|
|
3137
|
-
? this.findNearestSymbolFromList(symbolsByFile.get(row.filePath) ?? [], line)
|
|
3138
|
-
: undefined;
|
|
3139
|
-
const resolvedSymbol = definition ? lineToSymbol(definition) : undefined;
|
|
3140
3717
|
onHit({
|
|
3141
3718
|
filePath: row.filePath,
|
|
3142
|
-
score: scoreTextMatch(match, contentIndex)
|
|
3719
|
+
score: scoreTextMatch(match, contentIndex),
|
|
3143
3720
|
matchedIn: "content",
|
|
3144
|
-
|
|
3145
|
-
endLine: snippet.endLine,
|
|
3146
|
-
snippet: snippet.snippet,
|
|
3147
|
-
reasonCodes: ["content_match", `text_${match}`],
|
|
3148
|
-
symbol: resolvedSymbol
|
|
3721
|
+
reasonCodes: ["content_match", `text_${match}`]
|
|
3149
3722
|
});
|
|
3150
3723
|
}
|
|
3151
3724
|
}
|
|
3152
3725
|
}
|
|
3153
|
-
searchPathIntent(artifactId, query, match, scope,
|
|
3726
|
+
searchPathIntent(artifactId, query, match, scope, regexPattern, onHit) {
|
|
3154
3727
|
const filePaths = this.loadScopedFilePaths(artifactId, scope);
|
|
3155
3728
|
const matching = filePaths.flatMap((filePath) => {
|
|
3156
3729
|
const pathIndex = match === "regex"
|
|
@@ -3163,37 +3736,12 @@ export class SourceService {
|
|
|
3163
3736
|
});
|
|
3164
3737
|
const pageSize = Math.max(1, this.config.searchScanPageSize ?? 250);
|
|
3165
3738
|
for (const chunk of chunkArray(matching, pageSize)) {
|
|
3166
|
-
const rows = this.filesRepo.getFileContentsByPaths(artifactId, chunk.map((item) => item.filePath));
|
|
3167
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3168
|
-
this.metrics.recordSearchRowsScanned(rows.length);
|
|
3169
|
-
const contentByPath = new Map(rows.map((row) => [row.filePath, row.content]));
|
|
3170
|
-
const symbolsByFile = includeDefinition
|
|
3171
|
-
? this.symbolsRepo.listSymbolsForFiles(artifactId, chunk.map((item) => item.filePath), scope?.symbolKind)
|
|
3172
|
-
: new Map();
|
|
3173
|
-
if (includeDefinition) {
|
|
3174
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3175
|
-
this.metrics.recordSearchRowsScanned([...symbolsByFile.values()].reduce((acc, symbols) => acc + symbols.length, 0));
|
|
3176
|
-
}
|
|
3177
3739
|
for (const candidate of chunk) {
|
|
3178
|
-
const content = contentByPath.get(candidate.filePath);
|
|
3179
|
-
if (!content) {
|
|
3180
|
-
continue;
|
|
3181
|
-
}
|
|
3182
|
-
const definition = includeDefinition
|
|
3183
|
-
? this.findNearestSymbolFromList(symbolsByFile.get(candidate.filePath) ?? [], 1)
|
|
3184
|
-
: undefined;
|
|
3185
|
-
const centerLine = definition?.line ?? 1;
|
|
3186
|
-
const snippet = toContextSnippet(content, centerLine, snippetWindow.before, snippetWindow.after, true);
|
|
3187
|
-
const resolvedSymbol = definition ? lineToSymbol(definition) : undefined;
|
|
3188
3740
|
onHit({
|
|
3189
3741
|
filePath: candidate.filePath,
|
|
3190
|
-
score: scorePathMatch(match, candidate.pathIndex)
|
|
3742
|
+
score: scorePathMatch(match, candidate.pathIndex),
|
|
3191
3743
|
matchedIn: "path",
|
|
3192
|
-
|
|
3193
|
-
endLine: snippet.endLine,
|
|
3194
|
-
snippet: snippet.snippet,
|
|
3195
|
-
reasonCodes: ["path_match", `path_${match}`],
|
|
3196
|
-
symbol: resolvedSymbol
|
|
3744
|
+
reasonCodes: ["path_match", `path_${match}`]
|
|
3197
3745
|
});
|
|
3198
3746
|
}
|
|
3199
3747
|
}
|
|
@@ -3233,15 +3781,13 @@ export class SourceService {
|
|
|
3233
3781
|
this.metrics.recordSearchDbRoundtrip();
|
|
3234
3782
|
this.metrics.recordSearchRowsScanned(candidates.length);
|
|
3235
3783
|
const result = [];
|
|
3784
|
+
const glob = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
3236
3785
|
for (const symbol of candidates) {
|
|
3237
3786
|
if (!checkPackagePrefix(symbol.filePath, scope?.packagePrefix)) {
|
|
3238
3787
|
continue;
|
|
3239
3788
|
}
|
|
3240
|
-
if (
|
|
3241
|
-
|
|
3242
|
-
if (!glob.test(symbol.filePath)) {
|
|
3243
|
-
continue;
|
|
3244
|
-
}
|
|
3789
|
+
if (glob && !glob.test(symbol.filePath)) {
|
|
3790
|
+
continue;
|
|
3245
3791
|
}
|
|
3246
3792
|
if (!isSymbolKind(symbol.symbolKind)) {
|
|
3247
3793
|
continue;
|
|
@@ -3262,13 +3808,6 @@ export class SourceService {
|
|
|
3262
3808
|
}
|
|
3263
3809
|
loadScopedFilePaths(artifactId, scope) {
|
|
3264
3810
|
const glob = scope?.fileGlob ? buildGlobRegex(normalizePathStyle(scope.fileGlob)) : undefined;
|
|
3265
|
-
const scopedFilesBySymbolKind = scope?.symbolKind
|
|
3266
|
-
? new Set(this.symbolsRepo.listDistinctFilePathsByKind(artifactId, scope.symbolKind))
|
|
3267
|
-
: undefined;
|
|
3268
|
-
if (scopedFilesBySymbolKind) {
|
|
3269
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3270
|
-
this.metrics.recordSearchRowsScanned(scopedFilesBySymbolKind.size);
|
|
3271
|
-
}
|
|
3272
3811
|
const result = [];
|
|
3273
3812
|
let cursor = undefined;
|
|
3274
3813
|
const pageSize = Math.max(1, this.config.searchScanPageSize ?? 250);
|
|
@@ -3283,9 +3822,6 @@ export class SourceService {
|
|
|
3283
3822
|
if (glob && !glob.test(filePath)) {
|
|
3284
3823
|
continue;
|
|
3285
3824
|
}
|
|
3286
|
-
if (scopedFilesBySymbolKind && !scopedFilesBySymbolKind.has(filePath)) {
|
|
3287
|
-
continue;
|
|
3288
|
-
}
|
|
3289
3825
|
result.push(filePath);
|
|
3290
3826
|
}
|
|
3291
3827
|
if (!page.nextCursor) {
|
|
@@ -3345,161 +3881,37 @@ export class SourceService {
|
|
|
3345
3881
|
if (innerIndex > 0) {
|
|
3346
3882
|
candidates.add(`${classPath.slice(0, innerIndex)}.java`);
|
|
3347
3883
|
}
|
|
3348
|
-
for (const candidate of candidates) {
|
|
3349
|
-
const row = this.filesRepo.getFileContent(artifactId, candidate);
|
|
3350
|
-
if (row) {
|
|
3351
|
-
return row.filePath;
|
|
3352
|
-
}
|
|
3353
|
-
}
|
|
3354
3884
|
const simpleName = normalizedClassName.split(/[.$]/).at(-1);
|
|
3355
3885
|
if (!simpleName) {
|
|
3356
3886
|
return undefined;
|
|
3357
3887
|
}
|
|
3358
|
-
const
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
}
|
|
3362
|
-
const byName = this.filesRepo.findFirstFilePathByName(artifactId, `${simpleName}.java`);
|
|
3363
|
-
if (byName && isPackageCompatible(byName, classPath)) {
|
|
3364
|
-
return byName;
|
|
3365
|
-
}
|
|
3366
|
-
return undefined;
|
|
3888
|
+
const lastSlash = classPath.lastIndexOf("/");
|
|
3889
|
+
const expectedPrefix = lastSlash < 0 ? "" : classPath.slice(0, lastSlash + 1);
|
|
3890
|
+
return this.filesRepo.findBestClassLookupPath(artifactId, [...candidates], normalizedClassName, simpleName, expectedPrefix);
|
|
3367
3891
|
}
|
|
3368
|
-
|
|
3369
|
-
const
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
return this.findNearestSymbolFromList(symbols, line);
|
|
3373
|
-
}
|
|
3374
|
-
findNearestSymbolFromList(symbols, line) {
|
|
3375
|
-
let best;
|
|
3376
|
-
for (const symbol of symbols) {
|
|
3377
|
-
if (symbol.line > line) {
|
|
3378
|
-
continue;
|
|
3379
|
-
}
|
|
3380
|
-
if (!best || symbol.line >= best.line) {
|
|
3381
|
-
best = symbol;
|
|
3382
|
-
}
|
|
3383
|
-
}
|
|
3384
|
-
return best ?? symbols[0];
|
|
3385
|
-
}
|
|
3386
|
-
buildOneHopRelations(artifactId, roots, maxRelations) {
|
|
3387
|
-
if (roots.length === 0 || maxRelations <= 0) {
|
|
3388
|
-
return [];
|
|
3892
|
+
async resolveBinaryFallbackArtifact(input) {
|
|
3893
|
+
const binaryJarPath = normalizeOptionalString(input.binaryJarPath);
|
|
3894
|
+
if (!binaryJarPath) {
|
|
3895
|
+
return undefined;
|
|
3389
3896
|
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
const aroundRoot = toContextSnippet(contentRow.content, root.line, 2, 3, false).snippet;
|
|
3405
|
-
return {
|
|
3406
|
-
root,
|
|
3407
|
-
calls: Array.from(aroundRoot.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g))
|
|
3408
|
-
.map((match) => match[1])
|
|
3409
|
-
.filter((token) => Boolean(token)),
|
|
3410
|
-
types: Array.from(aroundRoot.matchAll(/\b([A-Z][A-Za-z0-9_$]*)\b/g))
|
|
3411
|
-
.map((match) => match[1])
|
|
3412
|
-
.filter((token) => Boolean(token)),
|
|
3413
|
-
imports: Array.from(aroundRoot.matchAll(/import\s+([\w.$]+);/g))
|
|
3414
|
-
.map((match) => match[1]?.split(".").at(-1))
|
|
3415
|
-
.filter((token) => Boolean(token))
|
|
3416
|
-
};
|
|
3417
|
-
});
|
|
3418
|
-
const tokenSet = new Set();
|
|
3419
|
-
for (const entry of rootTokens) {
|
|
3420
|
-
for (const token of entry.calls) {
|
|
3421
|
-
tokenSet.add(toLower(token));
|
|
3422
|
-
}
|
|
3423
|
-
for (const token of entry.types) {
|
|
3424
|
-
tokenSet.add(toLower(token));
|
|
3425
|
-
}
|
|
3426
|
-
for (const token of entry.imports) {
|
|
3427
|
-
tokenSet.add(toLower(token));
|
|
3428
|
-
}
|
|
3897
|
+
try {
|
|
3898
|
+
const fallbackResolved = await resolveSourceTargetInternal({ kind: "jar", value: binaryJarPath }, { allowDecompile: true, preferBinaryOnly: true }, this.config);
|
|
3899
|
+
fallbackResolved.version = fallbackResolved.version ?? input.version;
|
|
3900
|
+
fallbackResolved.coordinate = fallbackResolved.coordinate ?? input.coordinate;
|
|
3901
|
+
fallbackResolved.requestedMapping = input.requestedMapping;
|
|
3902
|
+
fallbackResolved.mappingApplied = input.mappingApplied;
|
|
3903
|
+
fallbackResolved.provenance = input.provenance;
|
|
3904
|
+
fallbackResolved.qualityFlags = dedupeQualityFlags([
|
|
3905
|
+
...(fallbackResolved.qualityFlags ?? []),
|
|
3906
|
+
...input.qualityFlags,
|
|
3907
|
+
"binary-fallback"
|
|
3908
|
+
]);
|
|
3909
|
+
await this.ingestIfNeeded(fallbackResolved);
|
|
3910
|
+
return fallbackResolved;
|
|
3429
3911
|
}
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
.filter((symbol) => isSymbolKind(symbol.symbolKind));
|
|
3433
|
-
this.metrics.recordSearchDbRoundtrip();
|
|
3434
|
-
this.metrics.recordSearchRowsScanned(matchedSymbols.length);
|
|
3435
|
-
const symbolMap = new Map();
|
|
3436
|
-
for (const symbol of matchedSymbols) {
|
|
3437
|
-
const key = toLower(symbol.symbolName);
|
|
3438
|
-
const bucket = symbolMap.get(key) ?? [];
|
|
3439
|
-
bucket.push(symbol);
|
|
3440
|
-
symbolMap.set(key, bucket);
|
|
3441
|
-
}
|
|
3442
|
-
const dedupe = new Set();
|
|
3443
|
-
const relations = [];
|
|
3444
|
-
for (const entry of rootTokens) {
|
|
3445
|
-
const root = entry.root;
|
|
3446
|
-
const attach = (token, relationKind) => {
|
|
3447
|
-
const matches = symbolMap.get(toLower(token)) ?? [];
|
|
3448
|
-
for (const target of matches) {
|
|
3449
|
-
if (!isSymbolKind(target.symbolKind)) {
|
|
3450
|
-
continue;
|
|
3451
|
-
}
|
|
3452
|
-
if (target.filePath === root.filePath &&
|
|
3453
|
-
target.line === root.line &&
|
|
3454
|
-
target.symbolName === root.symbolName &&
|
|
3455
|
-
target.symbolKind === root.symbolKind) {
|
|
3456
|
-
continue;
|
|
3457
|
-
}
|
|
3458
|
-
const key = `${root.symbolKind}:${root.symbolName}:${root.filePath}:${root.line}->${target.symbolKind}:${target.symbolName}:${target.filePath}:${target.line}:${relationKind}`;
|
|
3459
|
-
if (dedupe.has(key)) {
|
|
3460
|
-
continue;
|
|
3461
|
-
}
|
|
3462
|
-
dedupe.add(key);
|
|
3463
|
-
relations.push({
|
|
3464
|
-
fromSymbol: {
|
|
3465
|
-
symbolKind: root.symbolKind,
|
|
3466
|
-
symbolName: root.symbolName,
|
|
3467
|
-
filePath: root.filePath,
|
|
3468
|
-
line: root.line
|
|
3469
|
-
},
|
|
3470
|
-
toSymbol: {
|
|
3471
|
-
symbolKind: target.symbolKind,
|
|
3472
|
-
symbolName: target.symbolName,
|
|
3473
|
-
filePath: target.filePath,
|
|
3474
|
-
line: target.line
|
|
3475
|
-
},
|
|
3476
|
-
relation: relationKind
|
|
3477
|
-
});
|
|
3478
|
-
if (relations.length >= maxRelations) {
|
|
3479
|
-
return;
|
|
3480
|
-
}
|
|
3481
|
-
}
|
|
3482
|
-
};
|
|
3483
|
-
for (const token of entry.calls) {
|
|
3484
|
-
attach(token, "calls");
|
|
3485
|
-
if (relations.length >= maxRelations) {
|
|
3486
|
-
return relations;
|
|
3487
|
-
}
|
|
3488
|
-
}
|
|
3489
|
-
for (const token of entry.types) {
|
|
3490
|
-
attach(token, "uses-type");
|
|
3491
|
-
if (relations.length >= maxRelations) {
|
|
3492
|
-
return relations;
|
|
3493
|
-
}
|
|
3494
|
-
}
|
|
3495
|
-
for (const token of entry.imports) {
|
|
3496
|
-
attach(token, "imports");
|
|
3497
|
-
if (relations.length >= maxRelations) {
|
|
3498
|
-
return relations;
|
|
3499
|
-
}
|
|
3500
|
-
}
|
|
3912
|
+
catch {
|
|
3913
|
+
return undefined;
|
|
3501
3914
|
}
|
|
3502
|
-
return relations;
|
|
3503
3915
|
}
|
|
3504
3916
|
buildProvenance(input) {
|
|
3505
3917
|
const provenance = {
|
|
@@ -3551,66 +3963,158 @@ export class SourceService {
|
|
|
3551
3963
|
transformChain
|
|
3552
3964
|
};
|
|
3553
3965
|
}
|
|
3554
|
-
async
|
|
3555
|
-
if (
|
|
3556
|
-
return className;
|
|
3966
|
+
async resolveClassNameForLookup(input) {
|
|
3967
|
+
if (input.sourceMapping === input.targetMapping) {
|
|
3968
|
+
return input.className;
|
|
3969
|
+
}
|
|
3970
|
+
if (!input.version) {
|
|
3971
|
+
input.warnings.push(`Could not map class "${input.className}" from ${input.sourceMapping} to ${input.targetMapping} for ${input.context} because version is unavailable.`);
|
|
3972
|
+
return input.className;
|
|
3557
3973
|
}
|
|
3558
3974
|
try {
|
|
3559
3975
|
const mapped = await this.mappingService.findMapping({
|
|
3560
|
-
version,
|
|
3976
|
+
version: input.version,
|
|
3561
3977
|
kind: "class",
|
|
3562
|
-
name: className,
|
|
3563
|
-
sourceMapping:
|
|
3564
|
-
targetMapping:
|
|
3565
|
-
sourcePriority
|
|
3978
|
+
name: input.className,
|
|
3979
|
+
sourceMapping: input.sourceMapping,
|
|
3980
|
+
targetMapping: input.targetMapping,
|
|
3981
|
+
sourcePriority: input.sourcePriority
|
|
3566
3982
|
});
|
|
3567
3983
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
3568
3984
|
return mapped.resolvedSymbol.name;
|
|
3569
3985
|
}
|
|
3570
|
-
warnings.push(`Could not map class "${className}" from ${
|
|
3986
|
+
input.warnings.push(`Could not map class "${input.className}" from ${input.sourceMapping} to ${input.targetMapping} for ${input.context}.`);
|
|
3571
3987
|
}
|
|
3572
3988
|
catch {
|
|
3573
|
-
warnings.push(`Mapping lookup failed for class "${className}".`);
|
|
3989
|
+
input.warnings.push(`Mapping lookup failed for class "${input.className}" while preparing ${input.context} in ${input.targetMapping}.`);
|
|
3990
|
+
}
|
|
3991
|
+
return input.className;
|
|
3992
|
+
}
|
|
3993
|
+
buildClassSourceNotFoundError(input) {
|
|
3994
|
+
const simpleName = input.className.split(/[.$]/).at(-1) ?? input.className;
|
|
3995
|
+
const details = {
|
|
3996
|
+
artifactId: input.artifactId,
|
|
3997
|
+
className: input.className,
|
|
3998
|
+
mapping: input.mappingApplied,
|
|
3999
|
+
qualityFlags: input.qualityFlags,
|
|
4000
|
+
...(input.lookupClassName !== input.className ? { lookupClassName: input.lookupClassName } : {}),
|
|
4001
|
+
...(input.filePath ? { filePath: input.filePath } : {}),
|
|
4002
|
+
...(input.scope ? { scope: input.scope } : {}),
|
|
4003
|
+
...(input.targetKind ? { targetKind: input.targetKind } : {}),
|
|
4004
|
+
...(input.targetValue ? { targetValue: input.targetValue } : {}),
|
|
4005
|
+
...(input.attemptedBinaryFallback ? { binaryFallbackAttempted: true } : {})
|
|
4006
|
+
};
|
|
4007
|
+
let nextAction = `Use find-class to resolve the correct fully-qualified name for "${simpleName}".`;
|
|
4008
|
+
let suggestedCall = {
|
|
4009
|
+
tool: "find-class",
|
|
4010
|
+
params: { className: simpleName, artifactId: input.artifactId }
|
|
4011
|
+
};
|
|
4012
|
+
if (input.targetKind === "version" && input.scope && input.scope !== "merged" && !input.projectPath) {
|
|
4013
|
+
nextAction +=
|
|
4014
|
+
` If the class exists in a modded environment, retry with scope: "merged" and projectPath pointing to your mod project.`;
|
|
4015
|
+
}
|
|
4016
|
+
else if (input.targetKind === "version" && input.scope && input.scope !== "merged" && input.projectPath) {
|
|
4017
|
+
nextAction += ` The class may exist in merged sources; retry with scope: "merged".`;
|
|
4018
|
+
}
|
|
4019
|
+
if (hasPartialNetMinecraftCoverage(input.qualityFlags)) {
|
|
4020
|
+
nextAction =
|
|
4021
|
+
`Resolved source coverage does not include net.minecraft for "${input.className}",` +
|
|
4022
|
+
(input.attemptedBinaryFallback
|
|
4023
|
+
? " and binary fallback did not produce source for that class."
|
|
4024
|
+
: " and a binary fallback has not produced source for that class.") +
|
|
4025
|
+
" Use get-class-api-matrix or find-mapping instead of find-class for vanilla API discovery.";
|
|
4026
|
+
if (input.version) {
|
|
4027
|
+
suggestedCall = {
|
|
4028
|
+
tool: "get-class-api-matrix",
|
|
4029
|
+
params: {
|
|
4030
|
+
version: input.version,
|
|
4031
|
+
className: input.className,
|
|
4032
|
+
classNameMapping: input.requestedMapping
|
|
4033
|
+
}
|
|
4034
|
+
};
|
|
4035
|
+
}
|
|
4036
|
+
else {
|
|
4037
|
+
suggestedCall = {
|
|
4038
|
+
tool: "find-class",
|
|
4039
|
+
params: { className: simpleName, artifactId: input.artifactId }
|
|
4040
|
+
};
|
|
4041
|
+
}
|
|
3574
4042
|
}
|
|
3575
|
-
|
|
4043
|
+
if (input.mappingApplied === "obfuscated" && looksLikeDeobfuscatedClassName(input.className)) {
|
|
4044
|
+
nextAction += ` ${obfuscatedNamespaceHint(input.className)}`;
|
|
4045
|
+
}
|
|
4046
|
+
details.nextAction = nextAction;
|
|
4047
|
+
details.suggestedCall = suggestedCall;
|
|
4048
|
+
return createError({
|
|
4049
|
+
code: ERROR_CODES.CLASS_NOT_FOUND,
|
|
4050
|
+
message: `Source for class "${input.className}" was not found.`,
|
|
4051
|
+
details
|
|
4052
|
+
});
|
|
3576
4053
|
}
|
|
3577
|
-
async
|
|
3578
|
-
|
|
4054
|
+
async resolveToObfuscatedClassName(className, version, mapping, sourcePriority, warnings) {
|
|
4055
|
+
return this.resolveClassNameForLookup({
|
|
4056
|
+
className,
|
|
4057
|
+
version,
|
|
4058
|
+
sourceMapping: mapping,
|
|
4059
|
+
targetMapping: "obfuscated",
|
|
4060
|
+
sourcePriority,
|
|
4061
|
+
warnings,
|
|
4062
|
+
context: "bytecode lookup"
|
|
4063
|
+
});
|
|
4064
|
+
}
|
|
4065
|
+
async resolveToObfuscatedMemberName(name, ownerInSourceMapping, descriptor, kind, version, mapping, sourcePriority, warnings) {
|
|
4066
|
+
if (mapping === "obfuscated") {
|
|
3579
4067
|
return {
|
|
3580
4068
|
name,
|
|
3581
4069
|
descriptor: kind === "method" ? descriptor : undefined
|
|
3582
4070
|
};
|
|
3583
4071
|
}
|
|
3584
4072
|
try {
|
|
3585
|
-
const
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
4073
|
+
const canResolveMethodExactly = kind === "method" &&
|
|
4074
|
+
descriptor &&
|
|
4075
|
+
"resolveMethodMappingExact" in this.mappingService &&
|
|
4076
|
+
typeof this.mappingService.resolveMethodMappingExact === "function";
|
|
4077
|
+
const mapped = canResolveMethodExactly
|
|
4078
|
+
? await this.mappingService.resolveMethodMappingExact({
|
|
4079
|
+
version,
|
|
4080
|
+
owner: ownerInSourceMapping,
|
|
4081
|
+
name,
|
|
4082
|
+
descriptor,
|
|
4083
|
+
sourceMapping: mapping,
|
|
4084
|
+
targetMapping: "obfuscated",
|
|
4085
|
+
sourcePriority
|
|
4086
|
+
})
|
|
4087
|
+
: await this.mappingService.findMapping({
|
|
4088
|
+
version,
|
|
4089
|
+
kind,
|
|
4090
|
+
name,
|
|
4091
|
+
owner: ownerInSourceMapping,
|
|
4092
|
+
descriptor,
|
|
4093
|
+
signatureMode: kind === "method" && !descriptor ? "name-only" : undefined,
|
|
4094
|
+
sourceMapping: mapping,
|
|
4095
|
+
targetMapping: "obfuscated",
|
|
4096
|
+
sourcePriority
|
|
4097
|
+
});
|
|
4098
|
+
warnings.push(...mapped.warnings);
|
|
3595
4099
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
3596
4100
|
return {
|
|
3597
4101
|
name: mapped.resolvedSymbol.name,
|
|
3598
4102
|
descriptor: kind === "method" ? mapped.resolvedSymbol.descriptor ?? descriptor : undefined
|
|
3599
4103
|
};
|
|
3600
4104
|
}
|
|
3601
|
-
warnings.push(`Could not map ${kind} "${name}" from ${mapping} to
|
|
4105
|
+
warnings.push(`Could not map ${kind} "${name}" from ${mapping} to obfuscated.`);
|
|
3602
4106
|
}
|
|
3603
|
-
catch {
|
|
3604
|
-
warnings.push(`Mapping lookup failed for ${kind} "${name}"
|
|
4107
|
+
catch (caughtError) {
|
|
4108
|
+
warnings.push(`Mapping lookup failed for ${kind} "${name}": ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`);
|
|
3605
4109
|
}
|
|
3606
4110
|
return {
|
|
3607
4111
|
name,
|
|
3608
4112
|
descriptor: kind === "method" ? descriptor : undefined
|
|
3609
4113
|
};
|
|
3610
4114
|
}
|
|
3611
|
-
async remapSignatureMembers(members, kind, version,
|
|
4115
|
+
async remapSignatureMembers(members, kind, version, sourceMapping, targetMapping, sourcePriority, warnings) {
|
|
3612
4116
|
const failedNames = new Set();
|
|
3613
|
-
if (
|
|
4117
|
+
if (sourceMapping === targetMapping) {
|
|
3614
4118
|
return { members, failedNames };
|
|
3615
4119
|
}
|
|
3616
4120
|
// Build deduplicated lookup tables for member names and owner FQNs
|
|
@@ -3619,35 +4123,35 @@ export class SourceService {
|
|
|
3619
4123
|
for (const member of members) {
|
|
3620
4124
|
const memberKey = `${member.ownerFqn}\0${member.name}\0${member.jvmDescriptor}`;
|
|
3621
4125
|
if (!memberKeyToRemapped.has(memberKey)) {
|
|
3622
|
-
memberKeyToRemapped.set(memberKey, member.name); // default =
|
|
4126
|
+
memberKeyToRemapped.set(memberKey, member.name); // default = obfuscated name
|
|
3623
4127
|
}
|
|
3624
4128
|
if (!ownerToRemapped.has(member.ownerFqn)) {
|
|
3625
|
-
ownerToRemapped.set(member.ownerFqn, member.ownerFqn); // default =
|
|
4129
|
+
ownerToRemapped.set(member.ownerFqn, member.ownerFqn); // default = obfuscated FQN
|
|
3626
4130
|
}
|
|
3627
4131
|
}
|
|
3628
4132
|
// Phase 1: Remap owner FQNs first (needed for member disambiguation)
|
|
3629
4133
|
const ownerEntries = [...ownerToRemapped.entries()];
|
|
3630
|
-
await Promise.all(ownerEntries.map(async ([
|
|
4134
|
+
await Promise.all(ownerEntries.map(async ([obfuscatedFqn]) => {
|
|
3631
4135
|
try {
|
|
3632
4136
|
const mapped = await this.mappingService.findMapping({
|
|
3633
4137
|
version,
|
|
3634
4138
|
kind: "class",
|
|
3635
|
-
name:
|
|
3636
|
-
sourceMapping
|
|
3637
|
-
targetMapping
|
|
4139
|
+
name: obfuscatedFqn,
|
|
4140
|
+
sourceMapping,
|
|
4141
|
+
targetMapping,
|
|
3638
4142
|
sourcePriority
|
|
3639
4143
|
});
|
|
3640
4144
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
3641
|
-
ownerToRemapped.set(
|
|
4145
|
+
ownerToRemapped.set(obfuscatedFqn, mapped.resolvedSymbol.name);
|
|
3642
4146
|
}
|
|
3643
4147
|
}
|
|
3644
4148
|
catch {
|
|
3645
|
-
// keep
|
|
4149
|
+
// keep obfuscated FQN as fallback
|
|
3646
4150
|
}
|
|
3647
4151
|
}));
|
|
3648
4152
|
// Phase 2: Remap member names using resolved owners for disambiguation
|
|
3649
4153
|
const memberEntries = [...memberKeyToRemapped.entries()];
|
|
3650
|
-
await Promise.all(memberEntries.map(async ([key,
|
|
4154
|
+
await Promise.all(memberEntries.map(async ([key, _obfuscatedName]) => {
|
|
3651
4155
|
const [ownerFqn, name, descriptor] = key.split("\0");
|
|
3652
4156
|
try {
|
|
3653
4157
|
const targetOwner = ownerToRemapped.get(ownerFqn) ?? ownerFqn;
|
|
@@ -3657,8 +4161,8 @@ export class SourceService {
|
|
|
3657
4161
|
name,
|
|
3658
4162
|
owner: ownerFqn,
|
|
3659
4163
|
descriptor: kind === "method" ? descriptor : undefined,
|
|
3660
|
-
sourceMapping
|
|
3661
|
-
targetMapping
|
|
4164
|
+
sourceMapping,
|
|
4165
|
+
targetMapping,
|
|
3662
4166
|
sourcePriority,
|
|
3663
4167
|
disambiguation: { ownerHint: targetOwner }
|
|
3664
4168
|
});
|
|
@@ -3677,17 +4181,17 @@ export class SourceService {
|
|
|
3677
4181
|
}
|
|
3678
4182
|
}
|
|
3679
4183
|
else {
|
|
3680
|
-
warnings.push(`Could not remap ${kind} "${name}" to ${
|
|
4184
|
+
warnings.push(`Could not remap ${kind} "${name}" from ${sourceMapping} to ${targetMapping}.`);
|
|
3681
4185
|
failedNames.add(name);
|
|
3682
4186
|
}
|
|
3683
4187
|
}
|
|
3684
4188
|
else {
|
|
3685
|
-
warnings.push(`Could not remap ${kind} "${name}" to ${
|
|
4189
|
+
warnings.push(`Could not remap ${kind} "${name}" from ${sourceMapping} to ${targetMapping}.`);
|
|
3686
4190
|
failedNames.add(name);
|
|
3687
4191
|
}
|
|
3688
4192
|
}
|
|
3689
4193
|
catch {
|
|
3690
|
-
warnings.push(`Remap failed for ${kind} "${name}".`);
|
|
4194
|
+
warnings.push(`Remap failed for ${kind} "${name}" from ${sourceMapping} to ${targetMapping}.`);
|
|
3691
4195
|
failedNames.add(name);
|
|
3692
4196
|
}
|
|
3693
4197
|
}));
|
|
@@ -3780,6 +4284,7 @@ export class SourceService {
|
|
|
3780
4284
|
});
|
|
3781
4285
|
});
|
|
3782
4286
|
tx();
|
|
4287
|
+
this.upsertCacheMetrics(resolved.artifactId, rebuilt.totalContentBytes, timestamp);
|
|
3783
4288
|
log("info", "index.rebuild.done", {
|
|
3784
4289
|
artifactId: resolved.artifactId,
|
|
3785
4290
|
reason,
|
|
@@ -3860,7 +4365,8 @@ export class SourceService {
|
|
|
3860
4365
|
files,
|
|
3861
4366
|
symbols,
|
|
3862
4367
|
indexedAt: new Date().toISOString(),
|
|
3863
|
-
indexDurationMs: Date.now() - indexStartedAt
|
|
4368
|
+
indexDurationMs: Date.now() - indexStartedAt,
|
|
4369
|
+
totalContentBytes: files.reduce((sum, file) => sum + file.contentBytes, 0)
|
|
3864
4370
|
};
|
|
3865
4371
|
}
|
|
3866
4372
|
getArtifact(artifactId) {
|
|
@@ -3880,7 +4386,10 @@ export class SourceService {
|
|
|
3880
4386
|
details: {
|
|
3881
4387
|
artifactId,
|
|
3882
4388
|
nextAction: "Use resolve-artifact to resolve a source artifact first.",
|
|
3883
|
-
suggestedCall: {
|
|
4389
|
+
suggestedCall: {
|
|
4390
|
+
tool: "resolve-artifact",
|
|
4391
|
+
params: buildResolveArtifactParams({ kind: "version", value: "latest" })
|
|
4392
|
+
}
|
|
3884
4393
|
}
|
|
3885
4394
|
});
|
|
3886
4395
|
}
|
|
@@ -3898,8 +4407,9 @@ export class SourceService {
|
|
|
3898
4407
|
});
|
|
3899
4408
|
if (existing && reason === "already_current") {
|
|
3900
4409
|
this.metrics.recordArtifactCacheHit();
|
|
3901
|
-
|
|
3902
|
-
this.
|
|
4410
|
+
const touchedAt = new Date().toISOString();
|
|
4411
|
+
this.artifactsRepo.touchArtifact(resolved.artifactId, touchedAt);
|
|
4412
|
+
this.touchCacheMetrics(resolved.artifactId, touchedAt);
|
|
3903
4413
|
return;
|
|
3904
4414
|
}
|
|
3905
4415
|
this.metrics.recordArtifactCacheMiss();
|
|
@@ -3910,7 +4420,6 @@ export class SourceService {
|
|
|
3910
4420
|
});
|
|
3911
4421
|
await this.rebuildAndPersistArtifactIndex(resolved, reason === "already_current" ? "missing_meta" : reason);
|
|
3912
4422
|
this.enforceCacheLimits();
|
|
3913
|
-
this.refreshCacheMetrics();
|
|
3914
4423
|
}
|
|
3915
4424
|
async loadFromSourceJar(sourceJarPath) {
|
|
3916
4425
|
const files = [];
|
|
@@ -3928,21 +4437,22 @@ export class SourceService {
|
|
|
3928
4437
|
return this.filesRepo.listFiles(artifactId, { limit: 1 }).items.length > 0;
|
|
3929
4438
|
}
|
|
3930
4439
|
enforceCacheLimits() {
|
|
3931
|
-
let artifactCount = this.
|
|
3932
|
-
let totalBytes = this.
|
|
4440
|
+
let artifactCount = this.cacheMetricsState.entries;
|
|
4441
|
+
let totalBytes = this.cacheMetricsState.totalContentBytes;
|
|
3933
4442
|
if (artifactCount <= this.config.maxArtifacts && totalBytes <= this.config.maxCacheBytes) {
|
|
3934
4443
|
return;
|
|
3935
4444
|
}
|
|
3936
|
-
const candidates = this.
|
|
4445
|
+
const candidates = [...this.cacheMetricsState.lru];
|
|
3937
4446
|
for (const candidate of candidates) {
|
|
3938
4447
|
const shouldEvict = artifactCount > this.config.maxArtifacts || totalBytes > this.config.maxCacheBytes;
|
|
3939
4448
|
if (!shouldEvict || artifactCount <= 1) {
|
|
3940
|
-
|
|
4449
|
+
break;
|
|
3941
4450
|
}
|
|
3942
4451
|
const artifactCountBefore = artifactCount;
|
|
3943
4452
|
const totalBytesBefore = totalBytes;
|
|
3944
4453
|
this.filesRepo.deleteFilesForArtifact(candidate.artifactId);
|
|
3945
4454
|
this.artifactsRepo.deleteArtifact(candidate.artifactId);
|
|
4455
|
+
this.removeCacheMetrics(candidate.artifactId, false);
|
|
3946
4456
|
artifactCount = Math.max(0, artifactCount - 1);
|
|
3947
4457
|
totalBytes = Math.max(0, totalBytes - candidate.totalContentBytes);
|
|
3948
4458
|
this.metrics.recordCacheEviction();
|
|
@@ -3953,6 +4463,7 @@ export class SourceService {
|
|
|
3953
4463
|
artifactBytes: candidate.totalContentBytes
|
|
3954
4464
|
});
|
|
3955
4465
|
}
|
|
4466
|
+
this.publishCacheMetrics();
|
|
3956
4467
|
}
|
|
3957
4468
|
refreshCacheMetrics() {
|
|
3958
4469
|
const cacheEntries = this.artifactsRepo.countArtifacts();
|
|
@@ -3960,13 +4471,83 @@ export class SourceService {
|
|
|
3960
4471
|
const lruAccounting = this.artifactsRepo
|
|
3961
4472
|
.listArtifactsByLruWithContentBytes(Math.max(cacheEntries, 1))
|
|
3962
4473
|
.map((row) => ({
|
|
4474
|
+
artifactId: row.artifactId,
|
|
4475
|
+
totalContentBytes: row.totalContentBytes,
|
|
4476
|
+
updatedAt: row.updatedAt
|
|
4477
|
+
}));
|
|
4478
|
+
this.cacheMetricsState = {
|
|
4479
|
+
entries: cacheEntries,
|
|
4480
|
+
totalContentBytes,
|
|
4481
|
+
lru: lruAccounting
|
|
4482
|
+
};
|
|
4483
|
+
this.publishCacheMetrics();
|
|
4484
|
+
}
|
|
4485
|
+
touchCacheMetrics(artifactId, updatedAt) {
|
|
4486
|
+
const existingIndex = this.cacheMetricsState.lru.findIndex((row) => row.artifactId === artifactId);
|
|
4487
|
+
if (existingIndex < 0) {
|
|
4488
|
+
this.refreshCacheMetrics();
|
|
4489
|
+
return;
|
|
4490
|
+
}
|
|
4491
|
+
const [existing] = this.cacheMetricsState.lru.splice(existingIndex, 1);
|
|
4492
|
+
if (!existing) {
|
|
4493
|
+
this.refreshCacheMetrics();
|
|
4494
|
+
return;
|
|
4495
|
+
}
|
|
4496
|
+
existing.updatedAt = updatedAt;
|
|
4497
|
+
this.cacheMetricsState.lru.push(existing);
|
|
4498
|
+
this.publishCacheMetrics();
|
|
4499
|
+
}
|
|
4500
|
+
upsertCacheMetrics(artifactId, totalContentBytes, updatedAt) {
|
|
4501
|
+
const normalizedBytes = Math.max(0, Math.trunc(totalContentBytes));
|
|
4502
|
+
const existingIndex = this.cacheMetricsState.lru.findIndex((row) => row.artifactId === artifactId);
|
|
4503
|
+
if (existingIndex >= 0) {
|
|
4504
|
+
const [existing] = this.cacheMetricsState.lru.splice(existingIndex, 1);
|
|
4505
|
+
if (!existing) {
|
|
4506
|
+
this.refreshCacheMetrics();
|
|
4507
|
+
return;
|
|
4508
|
+
}
|
|
4509
|
+
this.cacheMetricsState.totalContentBytes = Math.max(0, this.cacheMetricsState.totalContentBytes - existing.totalContentBytes + normalizedBytes);
|
|
4510
|
+
existing.totalContentBytes = normalizedBytes;
|
|
4511
|
+
existing.updatedAt = updatedAt;
|
|
4512
|
+
this.cacheMetricsState.lru.push(existing);
|
|
4513
|
+
}
|
|
4514
|
+
else {
|
|
4515
|
+
this.cacheMetricsState.entries += 1;
|
|
4516
|
+
this.cacheMetricsState.totalContentBytes += normalizedBytes;
|
|
4517
|
+
this.cacheMetricsState.lru.push({
|
|
4518
|
+
artifactId,
|
|
4519
|
+
totalContentBytes: normalizedBytes,
|
|
4520
|
+
updatedAt
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
this.cacheMetricsState.entries = this.cacheMetricsState.lru.length;
|
|
4524
|
+
this.publishCacheMetrics();
|
|
4525
|
+
}
|
|
4526
|
+
removeCacheMetrics(artifactId, publish = true) {
|
|
4527
|
+
const existingIndex = this.cacheMetricsState.lru.findIndex((row) => row.artifactId === artifactId);
|
|
4528
|
+
if (existingIndex < 0) {
|
|
4529
|
+
this.refreshCacheMetrics();
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
const [existing] = this.cacheMetricsState.lru.splice(existingIndex, 1);
|
|
4533
|
+
if (!existing) {
|
|
4534
|
+
this.refreshCacheMetrics();
|
|
4535
|
+
return;
|
|
4536
|
+
}
|
|
4537
|
+
this.cacheMetricsState.entries = this.cacheMetricsState.lru.length;
|
|
4538
|
+
this.cacheMetricsState.totalContentBytes = Math.max(0, this.cacheMetricsState.totalContentBytes - existing.totalContentBytes);
|
|
4539
|
+
if (publish) {
|
|
4540
|
+
this.publishCacheMetrics();
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
publishCacheMetrics() {
|
|
4544
|
+
this.metrics.setCacheEntries(this.cacheMetricsState.entries);
|
|
4545
|
+
this.metrics.setCacheTotalContentBytes(this.cacheMetricsState.totalContentBytes);
|
|
4546
|
+
this.metrics.setCacheArtifactByteAccounting(this.cacheMetricsState.lru.map((row) => ({
|
|
3963
4547
|
artifact_id: row.artifactId,
|
|
3964
4548
|
content_bytes: row.totalContentBytes,
|
|
3965
4549
|
updated_at: row.updatedAt
|
|
3966
|
-
}));
|
|
3967
|
-
this.metrics.setCacheEntries(cacheEntries);
|
|
3968
|
-
this.metrics.setCacheTotalContentBytes(totalContentBytes);
|
|
3969
|
-
this.metrics.setCacheArtifactByteAccounting(lruAccounting);
|
|
4550
|
+
})));
|
|
3970
4551
|
}
|
|
3971
4552
|
}
|
|
3972
4553
|
//# sourceMappingURL=source-service.js.map
|