@adhisang/minecraft-modding-mcp 3.1.1 → 3.2.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 +24 -0
- package/README.md +20 -8
- package/dist/access-transformer-parser.d.ts +17 -0
- package/dist/access-transformer-parser.js +97 -0
- package/dist/concurrency.d.ts +1 -0
- package/dist/concurrency.js +24 -0
- package/dist/decompiler/vineflower.js +22 -21
- package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
- package/dist/entry-tools/analyze-symbol-service.d.ts +20 -20
- package/dist/entry-tools/inspect-minecraft-service.d.ts +148 -148
- package/dist/entry-tools/validate-project-service.d.ts +153 -16
- package/dist/entry-tools/validate-project-service.js +360 -23
- package/dist/gradle-paths.d.ts +4 -0
- package/dist/gradle-paths.js +57 -0
- package/dist/index.js +65 -13
- package/dist/mapping-pipeline-service.d.ts +3 -1
- package/dist/mapping-pipeline-service.js +16 -1
- package/dist/mapping-service.d.ts +4 -0
- package/dist/mapping-service.js +155 -60
- package/dist/minecraft-explorer-service.d.ts +13 -0
- package/dist/minecraft-explorer-service.js +8 -4
- package/dist/mixin-validator.d.ts +33 -2
- package/dist/mixin-validator.js +197 -11
- package/dist/mod-analyzer.d.ts +1 -0
- package/dist/mod-analyzer.js +17 -1
- package/dist/mod-decompile-service.js +4 -4
- package/dist/mod-remap-service.js +1 -54
- package/dist/mod-search-service.d.ts +1 -0
- package/dist/mod-search-service.js +84 -51
- package/dist/response-utils.d.ts +35 -0
- package/dist/response-utils.js +113 -0
- package/dist/source-jar-reader.d.ts +16 -0
- package/dist/source-jar-reader.js +103 -1
- package/dist/source-resolver.js +9 -10
- package/dist/source-service.d.ts +22 -2
- package/dist/source-service.js +914 -105
- package/dist/tool-contract-manifest.js +8 -6
- package/dist/types.d.ts +17 -0
- package/dist/workspace-mapping-service.d.ts +13 -0
- package/dist/workspace-mapping-service.js +146 -14
- package/package.json +1 -1
package/dist/source-service.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
-
import { homedir } from "node:os";
|
|
3
|
+
import { access, readFile, writeFile } from "node:fs/promises";
|
|
5
4
|
import { dirname, isAbsolute, resolve as resolvePath } from "node:path";
|
|
6
5
|
import fastGlob from "fast-glob";
|
|
6
|
+
import { mapWithConcurrencyLimit } from "./concurrency.js";
|
|
7
7
|
import { createError, ERROR_CODES, isAppError } from "./errors.js";
|
|
8
8
|
import { loadConfig } from "./config.js";
|
|
9
9
|
import { decompileBinaryJar } from "./decompiler/vineflower.js";
|
|
10
10
|
import { resolveVineflowerJar } from "./vineflower-resolver.js";
|
|
11
11
|
import { parseCoordinate } from "./maven-resolver.js";
|
|
12
|
-
import { MinecraftExplorerService } from "./minecraft-explorer-service.js";
|
|
12
|
+
import { MinecraftExplorerService, modifierPrefix, parseFieldType, parseMethodDescriptor } from "./minecraft-explorer-service.js";
|
|
13
13
|
import { parseMixinSource } from "./mixin-parser.js";
|
|
14
14
|
import { parseAccessWidener } from "./access-widener-parser.js";
|
|
15
|
-
import {
|
|
15
|
+
import { parseAccessTransformer } from "./access-transformer-parser.js";
|
|
16
|
+
import { validateParsedMixin, refreshMixinValidationOutcome, validateParsedAccessWidener, validateParsedAccessTransformer } from "./mixin-validator.js";
|
|
16
17
|
import { resolveSourceTarget as resolveSourceTargetInternal } from "./source-resolver.js";
|
|
17
18
|
import { applyMappingPipeline } from "./mapping-pipeline-service.js";
|
|
18
19
|
import { MappingService } from "./mapping-service.js";
|
|
19
20
|
import { extractSymbolsFromSource } from "./symbols/symbol-extractor.js";
|
|
20
|
-
import { iterateJavaEntriesAsUtf8, listJavaEntries } from "./source-jar-reader.js";
|
|
21
|
+
import { detectFabricLikeInputNamespace, iterateJavaEntriesAsUtf8, listJavaEntries } from "./source-jar-reader.js";
|
|
21
22
|
import { openDatabase } from "./storage/db.js";
|
|
22
23
|
import { ArtifactsRepo } from "./storage/artifacts-repo.js";
|
|
23
24
|
import { FilesRepo } from "./storage/files-repo.js";
|
|
@@ -26,6 +27,7 @@ import { SymbolsRepo } from "./storage/symbols-repo.js";
|
|
|
26
27
|
import { RuntimeMetrics } from "./observability.js";
|
|
27
28
|
import { log } from "./logger.js";
|
|
28
29
|
import { normalizePathForHost } from "./path-converter.js";
|
|
30
|
+
import { buildLoaderRuntimeSearchRoots, buildVersionSourceSearchRoots, normalizeOptionalProjectPath } from "./gradle-paths.js";
|
|
29
31
|
import { createSearchHitAccumulator, decodeSearchCursor, encodeSearchCursor } from "./search-hit-accumulator.js";
|
|
30
32
|
import { WorkspaceMappingService } from "./workspace-mapping-service.js";
|
|
31
33
|
import { VersionService, isUnobfuscatedVersion } from "./version-service.js";
|
|
@@ -178,6 +180,30 @@ function hasExactVersionToken(path, version) {
|
|
|
178
180
|
?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, normalizedVersion, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}([^0-9a-z]|$)`, "i"));
|
|
179
181
|
return pattern.test(normalizedPath);
|
|
180
182
|
}
|
|
183
|
+
function inferMergedRuntimeNamespaceHint(path) {
|
|
184
|
+
const normalizedPath = normalizePathStyle(path).toLowerCase();
|
|
185
|
+
if (normalizedPath.includes("merged-intermediary-v2") ||
|
|
186
|
+
normalizedPath.includes("merged-intermediary")) {
|
|
187
|
+
return "intermediary";
|
|
188
|
+
}
|
|
189
|
+
if (normalizedPath.includes("minecraft-merged-mojang") ||
|
|
190
|
+
normalizedPath.includes("merged-mojang")) {
|
|
191
|
+
return "mojang";
|
|
192
|
+
}
|
|
193
|
+
if (normalizedPath.includes("merged-named")) {
|
|
194
|
+
return "named";
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
function runtimeJarNamespaceHintScore(hint) {
|
|
199
|
+
if (hint === "intermediary" || hint === "mojang") {
|
|
200
|
+
return 8_000;
|
|
201
|
+
}
|
|
202
|
+
if (hint === "named") {
|
|
203
|
+
return 1_000;
|
|
204
|
+
}
|
|
205
|
+
return 0;
|
|
206
|
+
}
|
|
181
207
|
function looksLikeDeobfuscatedClassName(value) {
|
|
182
208
|
const trimmed = value.trim();
|
|
183
209
|
if (!trimmed) {
|
|
@@ -204,17 +230,6 @@ function buildResolveArtifactParams(target, extra = {}) {
|
|
|
204
230
|
...extra
|
|
205
231
|
};
|
|
206
232
|
}
|
|
207
|
-
function normalizeOptionalProjectPath(projectPath) {
|
|
208
|
-
if (!projectPath) {
|
|
209
|
-
return undefined;
|
|
210
|
-
}
|
|
211
|
-
const trimmed = projectPath.trim();
|
|
212
|
-
if (!trimmed) {
|
|
213
|
-
return undefined;
|
|
214
|
-
}
|
|
215
|
-
const normalized = normalizePathForHost(trimmed, undefined, "projectPath");
|
|
216
|
-
return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
|
|
217
|
-
}
|
|
218
233
|
function looksLikeClassSegment(name) {
|
|
219
234
|
const trimmed = name.trim();
|
|
220
235
|
return /^[A-Z_$]/.test(trimmed);
|
|
@@ -227,29 +242,6 @@ function looksLikeJvmMethodDescriptor(descriptor) {
|
|
|
227
242
|
const closing = trimmed.indexOf(")");
|
|
228
243
|
return closing > 0 && closing < trimmed.length - 1;
|
|
229
244
|
}
|
|
230
|
-
function resolveGradleUserHomePath() {
|
|
231
|
-
const configured = process.env.GRADLE_USER_HOME?.trim();
|
|
232
|
-
if (!configured) {
|
|
233
|
-
return resolvePath(homedir(), ".gradle");
|
|
234
|
-
}
|
|
235
|
-
const normalized = normalizePathForHost(configured, undefined, "GRADLE_USER_HOME");
|
|
236
|
-
return isAbsolute(normalized) ? normalized : resolvePath(process.cwd(), normalized);
|
|
237
|
-
}
|
|
238
|
-
function buildVersionSourceSearchRoots(projectPath) {
|
|
239
|
-
const roots = new Set();
|
|
240
|
-
if (projectPath) {
|
|
241
|
-
roots.add(resolvePath(projectPath, ".gradle", "loom-cache"));
|
|
242
|
-
roots.add(resolvePath(projectPath, ".gradle-user", "caches", "fabric-loom"));
|
|
243
|
-
roots.add(resolvePath(projectPath, ".gradle", "caches", "fabric-loom"));
|
|
244
|
-
const projectParent = dirname(projectPath);
|
|
245
|
-
roots.add(resolvePath(projectParent, ".gradle-user-home", "loom-cache"));
|
|
246
|
-
roots.add(resolvePath(projectParent, ".gradle-user-home", "caches", "fabric-loom"));
|
|
247
|
-
}
|
|
248
|
-
const homeGradle = resolveGradleUserHomePath();
|
|
249
|
-
roots.add(resolvePath(homeGradle, "loom-cache"));
|
|
250
|
-
roots.add(resolvePath(homeGradle, "caches", "fabric-loom"));
|
|
251
|
-
return [...roots];
|
|
252
|
-
}
|
|
253
245
|
function looksLikeMinecraftSourceArtifact(path, hasMinecraftNamespace) {
|
|
254
246
|
if (hasMinecraftNamespace) {
|
|
255
247
|
return true;
|
|
@@ -328,27 +320,6 @@ function normalizeOptionalString(value) {
|
|
|
328
320
|
const trimmed = value.trim();
|
|
329
321
|
return trimmed ? trimmed : undefined;
|
|
330
322
|
}
|
|
331
|
-
async function mapWithConcurrencyLimit(items, limit, mapper) {
|
|
332
|
-
if (items.length === 0) {
|
|
333
|
-
return [];
|
|
334
|
-
}
|
|
335
|
-
const results = new Array(items.length);
|
|
336
|
-
let nextIndex = 0;
|
|
337
|
-
const workerCount = Math.max(1, Math.min(Math.trunc(limit), items.length));
|
|
338
|
-
await Promise.all(Array.from({ length: workerCount }, async () => {
|
|
339
|
-
while (true) {
|
|
340
|
-
// Safe in Node's single-threaded event loop because no await occurs between
|
|
341
|
-
// reading and incrementing nextIndex inside this synchronous dispatch section.
|
|
342
|
-
const currentIndex = nextIndex;
|
|
343
|
-
nextIndex += 1;
|
|
344
|
-
if (currentIndex >= items.length) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
results[currentIndex] = await mapper(items[currentIndex], currentIndex);
|
|
348
|
-
}
|
|
349
|
-
}));
|
|
350
|
-
return results;
|
|
351
|
-
}
|
|
352
323
|
function normalizeStrictPositiveInt(value, field) {
|
|
353
324
|
if (value == null) {
|
|
354
325
|
return undefined;
|
|
@@ -386,6 +357,15 @@ const MIXIN_PROJECT_DISCOVERY_IGNORES = [
|
|
|
386
357
|
"**/out/**",
|
|
387
358
|
"**/node_modules/**"
|
|
388
359
|
];
|
|
360
|
+
async function pathExists(filePath) {
|
|
361
|
+
try {
|
|
362
|
+
await access(filePath);
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
389
369
|
function normalizeMapping(mapping) {
|
|
390
370
|
if (mapping == null) {
|
|
391
371
|
return "obfuscated";
|
|
@@ -422,6 +402,19 @@ function normalizeAccessWidenerNamespace(namespace) {
|
|
|
422
402
|
}
|
|
423
403
|
return undefined;
|
|
424
404
|
}
|
|
405
|
+
function normalizeAccessTransformerNamespace(namespace) {
|
|
406
|
+
const normalized = namespace?.trim().toLowerCase();
|
|
407
|
+
if (!normalized) {
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
if (normalized === "srg" || normalized === "mojang" || normalized === "obfuscated") {
|
|
411
|
+
return normalized;
|
|
412
|
+
}
|
|
413
|
+
return undefined;
|
|
414
|
+
}
|
|
415
|
+
function isSourceMappingNamespace(namespace) {
|
|
416
|
+
return namespace === "obfuscated" || namespace === "mojang" || namespace === "intermediary" || namespace === "yarn";
|
|
417
|
+
}
|
|
425
418
|
function normalizeMemberAccess(access) {
|
|
426
419
|
if (access == null) {
|
|
427
420
|
return "public";
|
|
@@ -809,7 +802,7 @@ export class SourceService {
|
|
|
809
802
|
searchedPaths.push(root);
|
|
810
803
|
let discovered = [];
|
|
811
804
|
try {
|
|
812
|
-
discovered = fastGlob.
|
|
805
|
+
discovered = await fastGlob.glob("**/*sources.jar", {
|
|
813
806
|
cwd: root,
|
|
814
807
|
absolute: true,
|
|
815
808
|
onlyFiles: true
|
|
@@ -873,6 +866,371 @@ export class SourceService {
|
|
|
873
866
|
selectedHasMinecraftNamespace: selected?.hasMinecraftNamespace
|
|
874
867
|
};
|
|
875
868
|
}
|
|
869
|
+
async discoverAccessWidenerRuntimeCandidates(input) {
|
|
870
|
+
const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
871
|
+
const normalizedProjectPathLower = normalizedProjectPath
|
|
872
|
+
? normalizePathStyle(normalizedProjectPath).toLowerCase()
|
|
873
|
+
: undefined;
|
|
874
|
+
const searchRoots = buildVersionSourceSearchRoots(normalizedProjectPath);
|
|
875
|
+
const searchedPaths = [];
|
|
876
|
+
const candidates = [];
|
|
877
|
+
const seen = new Set();
|
|
878
|
+
for (const root of searchRoots) {
|
|
879
|
+
searchedPaths.push(root);
|
|
880
|
+
let discovered = [];
|
|
881
|
+
try {
|
|
882
|
+
discovered = await fastGlob.glob(["**/*minecraft*.jar", "**/*merged*.jar"], {
|
|
883
|
+
cwd: root,
|
|
884
|
+
absolute: true,
|
|
885
|
+
onlyFiles: true,
|
|
886
|
+
ignore: ["**/*sources.jar", "**/node_modules/**", "**/.git/**", "**/build/**", "**/out/**"]
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
for (const candidatePath of discovered) {
|
|
893
|
+
const normalizedPath = normalizePathStyle(candidatePath);
|
|
894
|
+
if (seen.has(normalizedPath)) {
|
|
895
|
+
continue;
|
|
896
|
+
}
|
|
897
|
+
seen.add(normalizedPath);
|
|
898
|
+
const lower = normalizedPath.toLowerCase();
|
|
899
|
+
if (!lower.includes("minecraft")) {
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const exactVersionMatch = hasExactVersionToken(normalizedPath, input.version);
|
|
903
|
+
const looksMerged = lower.includes("minecraft-merged") || lower.includes("/merged/") || lower.includes("-merged");
|
|
904
|
+
const namespaceHint = inferMergedRuntimeNamespaceHint(normalizedPath);
|
|
905
|
+
const appliedScope = looksMerged
|
|
906
|
+
? "merged"
|
|
907
|
+
: input.requestedScope === "loader"
|
|
908
|
+
? "merged"
|
|
909
|
+
: input.requestedScope;
|
|
910
|
+
const score = (exactVersionMatch ? 5_000 : 0) +
|
|
911
|
+
(looksMerged ? 4_000 : 0) +
|
|
912
|
+
runtimeJarNamespaceHintScore(namespaceHint) +
|
|
913
|
+
(normalizedProjectPathLower && lower.startsWith(normalizedProjectPathLower) ? 2_000 : 0) +
|
|
914
|
+
(lower.includes("loom-cache") || lower.includes("/caches/fabric-loom/") ? 500 : 0) +
|
|
915
|
+
(lower.includes("minecraft-client") || lower.includes("client") ? 100 : 0);
|
|
916
|
+
candidates.push({
|
|
917
|
+
jarPath: normalizedPath,
|
|
918
|
+
score,
|
|
919
|
+
appliedScope,
|
|
920
|
+
origin: lower.includes("loom-cache") || lower.includes("/caches/fabric-loom/")
|
|
921
|
+
? "loom-cache"
|
|
922
|
+
: "local-jar",
|
|
923
|
+
namespaceHint
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
candidates.sort((left, right) => {
|
|
928
|
+
if (right.score !== left.score) {
|
|
929
|
+
return right.score - left.score;
|
|
930
|
+
}
|
|
931
|
+
return left.jarPath.localeCompare(right.jarPath);
|
|
932
|
+
});
|
|
933
|
+
return {
|
|
934
|
+
searchedPaths,
|
|
935
|
+
candidateArtifacts: candidates.slice(0, 20).map((candidate) => candidate.jarPath),
|
|
936
|
+
selected: candidates[0]
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
async discoverAccessTransformerRuntimeCandidates(input) {
|
|
940
|
+
const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
941
|
+
const normalizedProjectPathLower = normalizedProjectPath
|
|
942
|
+
? normalizePathStyle(normalizedProjectPath).toLowerCase()
|
|
943
|
+
: undefined;
|
|
944
|
+
const searchRoots = buildLoaderRuntimeSearchRoots(normalizedProjectPath);
|
|
945
|
+
const searchedPaths = [];
|
|
946
|
+
const candidates = [];
|
|
947
|
+
const seen = new Set();
|
|
948
|
+
const globs = [
|
|
949
|
+
"**/*minecraft*.jar",
|
|
950
|
+
"**/*patched*.jar",
|
|
951
|
+
"**/*srg*.jar",
|
|
952
|
+
"**/*joined*.jar",
|
|
953
|
+
"**/*client-extra*.jar",
|
|
954
|
+
"**/*forge*.jar",
|
|
955
|
+
"**/*neoforge*.jar",
|
|
956
|
+
"**/*moddev*.jar",
|
|
957
|
+
"**/*neoform*.jar"
|
|
958
|
+
];
|
|
959
|
+
for (const root of searchRoots) {
|
|
960
|
+
searchedPaths.push(root);
|
|
961
|
+
if (!(await pathExists(root))) {
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
let discovered = [];
|
|
965
|
+
try {
|
|
966
|
+
discovered = await fastGlob.glob(globs, {
|
|
967
|
+
cwd: root,
|
|
968
|
+
absolute: true,
|
|
969
|
+
onlyFiles: true,
|
|
970
|
+
ignore: ["**/*sources.jar", "**/node_modules/**", "**/.git/**", "**/out/**"]
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
catch {
|
|
974
|
+
continue;
|
|
975
|
+
}
|
|
976
|
+
for (const candidatePath of discovered) {
|
|
977
|
+
const normalizedPath = normalizePathStyle(candidatePath);
|
|
978
|
+
if (seen.has(normalizedPath)) {
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
seen.add(normalizedPath);
|
|
982
|
+
const lower = normalizedPath.toLowerCase();
|
|
983
|
+
if (!hasExactVersionToken(normalizedPath, input.version)) {
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
const looksMerged = lower.includes("merged");
|
|
987
|
+
const looksSrg = lower.includes("srg");
|
|
988
|
+
const looksForge = lower.includes("forge");
|
|
989
|
+
const looksNeoForge = lower.includes("neoforge") || lower.includes("moddev") || lower.includes("neoform");
|
|
990
|
+
const looksPatchedRuntime = lower.includes("patched") || lower.includes("client-extra") || lower.includes("joined");
|
|
991
|
+
const appliedScope = looksMerged
|
|
992
|
+
? "merged"
|
|
993
|
+
: "loader";
|
|
994
|
+
if (input.atNamespace === "srg" && !looksSrg) {
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (input.loader === "forge" && !looksForge && !looksSrg && !looksPatchedRuntime) {
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (input.loader === "neoforge" && !looksNeoForge && !looksPatchedRuntime && !lower.includes("minecraft")) {
|
|
1001
|
+
continue;
|
|
1002
|
+
}
|
|
1003
|
+
const score = 10_000 +
|
|
1004
|
+
(normalizedProjectPathLower && lower.startsWith(normalizedProjectPathLower) ? 4_000 : 0) +
|
|
1005
|
+
(looksPatchedRuntime ? 3_000 : 0) +
|
|
1006
|
+
(looksSrg ? 2_500 : 0) +
|
|
1007
|
+
(input.loader === "forge" && looksForge ? 1_500 : 0) +
|
|
1008
|
+
(input.loader === "neoforge" && looksNeoForge ? 1_500 : 0) +
|
|
1009
|
+
(input.requestedScope === appliedScope ? 1_000 : 0) +
|
|
1010
|
+
(looksMerged ? -500 : 0);
|
|
1011
|
+
candidates.push({
|
|
1012
|
+
jarPath: normalizedPath,
|
|
1013
|
+
score,
|
|
1014
|
+
appliedScope,
|
|
1015
|
+
origin: "local-jar"
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
candidates.sort((left, right) => {
|
|
1020
|
+
if (right.score !== left.score) {
|
|
1021
|
+
return right.score - left.score;
|
|
1022
|
+
}
|
|
1023
|
+
return left.jarPath.localeCompare(right.jarPath);
|
|
1024
|
+
});
|
|
1025
|
+
return {
|
|
1026
|
+
searchedPaths,
|
|
1027
|
+
candidateArtifacts: candidates.slice(0, 20).map((candidate) => candidate.jarPath),
|
|
1028
|
+
selected: candidates[0]
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
async resolveAccessWidenerRuntimeArtifact(input) {
|
|
1032
|
+
const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
1033
|
+
let version = input.version;
|
|
1034
|
+
if (input.preferProjectVersion && normalizedProjectPath) {
|
|
1035
|
+
const detected = await this.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath);
|
|
1036
|
+
version = detected ?? version;
|
|
1037
|
+
}
|
|
1038
|
+
const requestedScope = input.scope ?? (normalizedProjectPath ? "loader" : "vanilla");
|
|
1039
|
+
if (requestedScope === "vanilla") {
|
|
1040
|
+
const versionJar = await this.versionService.resolveVersionJar(version);
|
|
1041
|
+
return {
|
|
1042
|
+
version: versionJar.version,
|
|
1043
|
+
jarPath: versionJar.jarPath,
|
|
1044
|
+
requestedScope,
|
|
1045
|
+
appliedScope: "vanilla",
|
|
1046
|
+
requestedMapping: input.awNamespace,
|
|
1047
|
+
mappingApplied: "obfuscated",
|
|
1048
|
+
origin: "version-jar"
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
const discovery = await this.discoverAccessWidenerRuntimeCandidates({
|
|
1052
|
+
version,
|
|
1053
|
+
projectPath: normalizedProjectPath,
|
|
1054
|
+
requestedScope
|
|
1055
|
+
});
|
|
1056
|
+
if (!discovery.selected) {
|
|
1057
|
+
throw createError({
|
|
1058
|
+
code: ERROR_CODES.CONTEXT_UNRESOLVED,
|
|
1059
|
+
message: "Could not resolve a runtime jar for Access Widener validation.",
|
|
1060
|
+
details: {
|
|
1061
|
+
version,
|
|
1062
|
+
requestedScope,
|
|
1063
|
+
projectPath: normalizedProjectPath,
|
|
1064
|
+
searchedPaths: discovery.searchedPaths,
|
|
1065
|
+
candidateArtifacts: discovery.candidateArtifacts,
|
|
1066
|
+
nextAction: "Provide projectPath for a Loom workspace with generated runtime jars, or run Gradle tasks that populate the Loom cache before retrying.",
|
|
1067
|
+
suggestedCall: {
|
|
1068
|
+
tool: "validate-access-widener",
|
|
1069
|
+
params: {
|
|
1070
|
+
version,
|
|
1071
|
+
scope: requestedScope,
|
|
1072
|
+
...(normalizedProjectPath ? { projectPath: normalizedProjectPath } : {})
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
const appliedScope = discovery.selected.appliedScope;
|
|
1079
|
+
const scopeFallback = requestedScope !== appliedScope
|
|
1080
|
+
? {
|
|
1081
|
+
requested: requestedScope,
|
|
1082
|
+
applied: appliedScope,
|
|
1083
|
+
reason: requestedScope === "loader"
|
|
1084
|
+
? "Fabric loader runtime validation currently reuses the merged runtime jar."
|
|
1085
|
+
: "Selected runtime jar matched a nearby merged artifact."
|
|
1086
|
+
}
|
|
1087
|
+
: undefined;
|
|
1088
|
+
let detectedMapping;
|
|
1089
|
+
const notes = [];
|
|
1090
|
+
if (scopeFallback) {
|
|
1091
|
+
notes.push(scopeFallback.reason);
|
|
1092
|
+
}
|
|
1093
|
+
if (isUnobfuscatedVersion(version)) {
|
|
1094
|
+
detectedMapping = "obfuscated";
|
|
1095
|
+
}
|
|
1096
|
+
else if (discovery.selected.namespaceHint === "intermediary" ||
|
|
1097
|
+
discovery.selected.namespaceHint === "mojang") {
|
|
1098
|
+
detectedMapping = discovery.selected.namespaceHint;
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
const detection = await detectFabricLikeInputNamespace(discovery.selected.jarPath);
|
|
1102
|
+
detectedMapping = detection.fromNamespace;
|
|
1103
|
+
if (detection.warnings.length > 0) {
|
|
1104
|
+
notes.push(...detection.warnings);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
return {
|
|
1108
|
+
version,
|
|
1109
|
+
jarPath: discovery.selected.jarPath,
|
|
1110
|
+
requestedScope,
|
|
1111
|
+
appliedScope,
|
|
1112
|
+
requestedMapping: input.awNamespace,
|
|
1113
|
+
mappingApplied: detectedMapping,
|
|
1114
|
+
origin: discovery.selected.origin,
|
|
1115
|
+
resolutionNotes: notes.length > 0 ? notes : undefined,
|
|
1116
|
+
scopeFallback
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
async resolveAccessTransformerNamespace(input) {
|
|
1120
|
+
const explicit = normalizeAccessTransformerNamespace(input.atNamespace);
|
|
1121
|
+
if (explicit) {
|
|
1122
|
+
return explicit;
|
|
1123
|
+
}
|
|
1124
|
+
const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
1125
|
+
if (!normalizedProjectPath) {
|
|
1126
|
+
throw createError({
|
|
1127
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
1128
|
+
message: "atNamespace is required when projectPath is not provided.",
|
|
1129
|
+
details: {
|
|
1130
|
+
nextAction: "Pass atNamespace explicitly, or provide projectPath for a Forge/NeoForge workspace so the namespace can be inferred."
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
const loaderDetection = await this.workspaceMappingService.detectProjectLoader(normalizedProjectPath);
|
|
1135
|
+
if (loaderDetection.resolved && loaderDetection.loader === "forge") {
|
|
1136
|
+
return "srg";
|
|
1137
|
+
}
|
|
1138
|
+
if (loaderDetection.resolved && loaderDetection.loader === "neoforge") {
|
|
1139
|
+
return "mojang";
|
|
1140
|
+
}
|
|
1141
|
+
throw createError({
|
|
1142
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
1143
|
+
message: "Could not infer atNamespace from the workspace.",
|
|
1144
|
+
details: {
|
|
1145
|
+
projectPath: normalizedProjectPath,
|
|
1146
|
+
evidence: loaderDetection.evidence,
|
|
1147
|
+
warnings: loaderDetection.warnings,
|
|
1148
|
+
nextAction: "Pass atNamespace explicitly, or point projectPath at a Forge/NeoForge workspace."
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
async resolveAccessTransformerRuntimeArtifact(input) {
|
|
1153
|
+
const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
1154
|
+
let version = input.version;
|
|
1155
|
+
if (input.preferProjectVersion && normalizedProjectPath) {
|
|
1156
|
+
const detected = await this.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath);
|
|
1157
|
+
version = detected ?? version;
|
|
1158
|
+
}
|
|
1159
|
+
const requestedScope = input.scope ?? (normalizedProjectPath ? "loader" : "vanilla");
|
|
1160
|
+
if (requestedScope === "vanilla") {
|
|
1161
|
+
if (input.atNamespace === "srg") {
|
|
1162
|
+
throw createError({
|
|
1163
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
1164
|
+
message: "atNamespace=srg requires projectPath and scope=loader so a Forge runtime jar can be resolved."
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
const versionJar = await this.versionService.resolveVersionJar(version);
|
|
1168
|
+
return {
|
|
1169
|
+
version: versionJar.version,
|
|
1170
|
+
jarPath: versionJar.jarPath,
|
|
1171
|
+
requestedScope,
|
|
1172
|
+
appliedScope: "vanilla",
|
|
1173
|
+
requestedMapping: input.atNamespace,
|
|
1174
|
+
mappingApplied: "obfuscated",
|
|
1175
|
+
origin: "version-jar"
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
const loaderDetection = normalizedProjectPath
|
|
1179
|
+
? await this.workspaceMappingService.detectProjectLoader(normalizedProjectPath)
|
|
1180
|
+
: { resolved: false, loader: undefined, evidence: [], warnings: [] };
|
|
1181
|
+
const loader = loaderDetection.resolved ? loaderDetection.loader ?? "unknown" : "unknown";
|
|
1182
|
+
const discovery = await this.discoverAccessTransformerRuntimeCandidates({
|
|
1183
|
+
version,
|
|
1184
|
+
projectPath: normalizedProjectPath,
|
|
1185
|
+
requestedScope,
|
|
1186
|
+
atNamespace: input.atNamespace,
|
|
1187
|
+
loader
|
|
1188
|
+
});
|
|
1189
|
+
if (!discovery.selected) {
|
|
1190
|
+
throw createError({
|
|
1191
|
+
code: ERROR_CODES.CONTEXT_UNRESOLVED,
|
|
1192
|
+
message: "Could not resolve a runtime jar for Access Transformer validation.",
|
|
1193
|
+
details: {
|
|
1194
|
+
version,
|
|
1195
|
+
requestedScope,
|
|
1196
|
+
atNamespace: input.atNamespace,
|
|
1197
|
+
projectPath: normalizedProjectPath,
|
|
1198
|
+
searchedPaths: discovery.searchedPaths,
|
|
1199
|
+
candidateArtifacts: discovery.candidateArtifacts,
|
|
1200
|
+
loaderEvidence: loaderDetection.evidence,
|
|
1201
|
+
loaderWarnings: loaderDetection.warnings,
|
|
1202
|
+
nextAction: "Provide projectPath for a Forge/NeoForge workspace with generated runtime jars, or run the Gradle tasks that populate transformed runtime artifacts before retrying."
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
const selected = discovery.selected;
|
|
1207
|
+
const selectedLower = selected.jarPath.toLowerCase();
|
|
1208
|
+
const mappingApplied = input.atNamespace === "srg" || selectedLower.includes("srg")
|
|
1209
|
+
? "srg"
|
|
1210
|
+
: loader === "neoforge" || selectedLower.includes("moddev") || selectedLower.includes("neoforge")
|
|
1211
|
+
? "mojang"
|
|
1212
|
+
: "obfuscated";
|
|
1213
|
+
const scopeFallback = requestedScope !== selected.appliedScope
|
|
1214
|
+
? {
|
|
1215
|
+
requested: requestedScope,
|
|
1216
|
+
applied: selected.appliedScope,
|
|
1217
|
+
reason: selected.appliedScope === "merged"
|
|
1218
|
+
? "Resolved a nearby merged runtime jar because no transformed loader artifact was available."
|
|
1219
|
+
: "Resolved the closest transformed runtime artifact for validation."
|
|
1220
|
+
}
|
|
1221
|
+
: undefined;
|
|
1222
|
+
return {
|
|
1223
|
+
version,
|
|
1224
|
+
jarPath: selected.jarPath,
|
|
1225
|
+
requestedScope,
|
|
1226
|
+
appliedScope: selected.appliedScope,
|
|
1227
|
+
requestedMapping: input.atNamespace,
|
|
1228
|
+
mappingApplied,
|
|
1229
|
+
origin: selected.origin,
|
|
1230
|
+
resolutionNotes: scopeFallback ? [scopeFallback.reason] : undefined,
|
|
1231
|
+
scopeFallback
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
876
1234
|
buildVersionSourceRecoveryCommand(projectPath) {
|
|
877
1235
|
const normalizedProjectPath = normalizeOptionalProjectPath(projectPath);
|
|
878
1236
|
const prefix = normalizedProjectPath
|
|
@@ -972,9 +1330,11 @@ export class SourceService {
|
|
|
972
1330
|
let resolvedTarget = { kind, value };
|
|
973
1331
|
let resolvedVersion;
|
|
974
1332
|
let versionSourceDiscovery;
|
|
1333
|
+
let runtimeNamesUnobfuscated = false;
|
|
975
1334
|
if (kind === "version") {
|
|
976
1335
|
const versionJar = await this.versionService.resolveVersionJar(value);
|
|
977
1336
|
resolvedVersion = versionJar.version;
|
|
1337
|
+
runtimeNamesUnobfuscated = isUnobfuscatedVersion(resolvedVersion);
|
|
978
1338
|
resolvedTarget = {
|
|
979
1339
|
kind: "jar",
|
|
980
1340
|
value: versionJar.jarPath
|
|
@@ -989,6 +1349,9 @@ export class SourceService {
|
|
|
989
1349
|
// coordinate validity is validated by resolver, keep version undefined on parse failure.
|
|
990
1350
|
}
|
|
991
1351
|
}
|
|
1352
|
+
if (!runtimeNamesUnobfuscated && resolvedVersion && isUnobfuscatedVersion(resolvedVersion)) {
|
|
1353
|
+
runtimeNamesUnobfuscated = true;
|
|
1354
|
+
}
|
|
992
1355
|
// Unobfuscated versions (MC 26.1+) ship with deobfuscated runtime names; intermediary/yarn are not applicable.
|
|
993
1356
|
let effectiveMapping = mapping;
|
|
994
1357
|
if ((mapping === "intermediary" || mapping === "yarn") &&
|
|
@@ -997,7 +1360,11 @@ export class SourceService {
|
|
|
997
1360
|
warnings.push(`Version ${resolvedVersion} is unobfuscated; ${mapping} mappings are not applicable. Using the obfuscated namespace label for the deobfuscated runtime names.`);
|
|
998
1361
|
effectiveMapping = "obfuscated";
|
|
999
1362
|
}
|
|
1000
|
-
if (kind === "version" &&
|
|
1363
|
+
if (kind === "version" &&
|
|
1364
|
+
resolvedVersion &&
|
|
1365
|
+
effectiveMapping === "mojang" &&
|
|
1366
|
+
!runtimeNamesUnobfuscated &&
|
|
1367
|
+
scope !== "vanilla") {
|
|
1001
1368
|
versionSourceDiscovery = await this.discoverVersionSourceJar({
|
|
1002
1369
|
version: resolvedVersion,
|
|
1003
1370
|
projectPath: input.projectPath
|
|
@@ -1032,7 +1399,8 @@ export class SourceService {
|
|
|
1032
1399
|
mappingDecision = applyMappingPipeline({
|
|
1033
1400
|
requestedMapping: effectiveMapping,
|
|
1034
1401
|
target: { kind, value },
|
|
1035
|
-
resolved
|
|
1402
|
+
resolved,
|
|
1403
|
+
runtimeNamesUnobfuscated
|
|
1036
1404
|
});
|
|
1037
1405
|
}
|
|
1038
1406
|
catch (caughtError) {
|
|
@@ -1461,7 +1829,14 @@ export class SourceService {
|
|
|
1461
1829
|
return this.mappingService.getClassApiMatrix(input);
|
|
1462
1830
|
}
|
|
1463
1831
|
async checkSymbolExists(input) {
|
|
1464
|
-
|
|
1832
|
+
const result = await this.mappingService.checkSymbolExists(input);
|
|
1833
|
+
if (result.status !== "mapping_unavailable" ||
|
|
1834
|
+
!isUnobfuscatedVersion(input.version) ||
|
|
1835
|
+
(input.sourceMapping !== "mojang" && input.sourceMapping !== "obfuscated")) {
|
|
1836
|
+
return result;
|
|
1837
|
+
}
|
|
1838
|
+
const runtimeFallback = await this.checkSymbolExistsInUnobfuscatedRuntime(input, result);
|
|
1839
|
+
return runtimeFallback ?? result;
|
|
1465
1840
|
}
|
|
1466
1841
|
async resolveWorkspaceSymbol(input) {
|
|
1467
1842
|
const projectPath = input.projectPath?.trim();
|
|
@@ -1683,6 +2058,147 @@ export class SourceService {
|
|
|
1683
2058
|
warnings: [...warnings, ...mapped.warnings]
|
|
1684
2059
|
};
|
|
1685
2060
|
}
|
|
2061
|
+
async checkSymbolExistsInUnobfuscatedRuntime(input, fallbackBase) {
|
|
2062
|
+
const version = input.version.trim();
|
|
2063
|
+
const name = input.name.trim();
|
|
2064
|
+
const owner = input.owner?.trim();
|
|
2065
|
+
if (!version || !name) {
|
|
2066
|
+
return undefined;
|
|
2067
|
+
}
|
|
2068
|
+
if (input.kind === "class" && input.nameMode !== "fqcn" && !name.includes(".")) {
|
|
2069
|
+
return {
|
|
2070
|
+
...fallbackBase,
|
|
2071
|
+
warnings: [
|
|
2072
|
+
...fallbackBase.warnings,
|
|
2073
|
+
`Version ${version} is unobfuscated, but short class name "${name}" could not be checked against runtime bytecode without a fully-qualified name.`
|
|
2074
|
+
]
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
const querySymbol = input.kind === "class"
|
|
2078
|
+
? {
|
|
2079
|
+
kind: "class",
|
|
2080
|
+
name,
|
|
2081
|
+
symbol: name
|
|
2082
|
+
}
|
|
2083
|
+
: input.kind === "field"
|
|
2084
|
+
? {
|
|
2085
|
+
kind: "field",
|
|
2086
|
+
owner,
|
|
2087
|
+
name,
|
|
2088
|
+
symbol: `${owner}.${name}`
|
|
2089
|
+
}
|
|
2090
|
+
: {
|
|
2091
|
+
kind: "method",
|
|
2092
|
+
owner,
|
|
2093
|
+
name,
|
|
2094
|
+
descriptor: input.descriptor?.trim(),
|
|
2095
|
+
symbol: `${owner}.${name}${input.descriptor?.trim() ?? ""}`
|
|
2096
|
+
};
|
|
2097
|
+
const targetClass = input.kind === "class" ? name : owner;
|
|
2098
|
+
if (!targetClass) {
|
|
2099
|
+
return fallbackBase;
|
|
2100
|
+
}
|
|
2101
|
+
let jarPath;
|
|
2102
|
+
try {
|
|
2103
|
+
({ jarPath } = await this.versionService.resolveVersionJar(version));
|
|
2104
|
+
}
|
|
2105
|
+
catch {
|
|
2106
|
+
return undefined;
|
|
2107
|
+
}
|
|
2108
|
+
let signature;
|
|
2109
|
+
try {
|
|
2110
|
+
signature = await this.explorerService.getSignature({
|
|
2111
|
+
fqn: targetClass,
|
|
2112
|
+
jarPath,
|
|
2113
|
+
access: "all"
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
catch {
|
|
2117
|
+
return {
|
|
2118
|
+
...fallbackBase,
|
|
2119
|
+
querySymbol,
|
|
2120
|
+
warnings: [
|
|
2121
|
+
...fallbackBase.warnings,
|
|
2122
|
+
`Version ${version} is unobfuscated; runtime bytecode lookup could not load class "${targetClass}".`
|
|
2123
|
+
]
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
const warnings = [
|
|
2127
|
+
...fallbackBase.warnings,
|
|
2128
|
+
...signature.warnings,
|
|
2129
|
+
`Version ${version} is unobfuscated; validated symbol existence against runtime bytecode.`
|
|
2130
|
+
];
|
|
2131
|
+
const buildResolved = (resolvedSymbol) => ({
|
|
2132
|
+
...fallbackBase,
|
|
2133
|
+
querySymbol,
|
|
2134
|
+
resolved: true,
|
|
2135
|
+
status: "resolved",
|
|
2136
|
+
resolvedSymbol,
|
|
2137
|
+
candidates: resolvedSymbol
|
|
2138
|
+
? [{
|
|
2139
|
+
...resolvedSymbol,
|
|
2140
|
+
matchKind: "exact",
|
|
2141
|
+
confidence: 1
|
|
2142
|
+
}]
|
|
2143
|
+
: [],
|
|
2144
|
+
candidateCount: resolvedSymbol ? 1 : 0,
|
|
2145
|
+
warnings
|
|
2146
|
+
});
|
|
2147
|
+
const buildUnresolved = (status) => ({
|
|
2148
|
+
...fallbackBase,
|
|
2149
|
+
querySymbol,
|
|
2150
|
+
resolved: false,
|
|
2151
|
+
status,
|
|
2152
|
+
resolvedSymbol: undefined,
|
|
2153
|
+
candidates: [],
|
|
2154
|
+
candidateCount: 0,
|
|
2155
|
+
warnings
|
|
2156
|
+
});
|
|
2157
|
+
if (input.kind === "class") {
|
|
2158
|
+
return buildResolved({
|
|
2159
|
+
kind: "class",
|
|
2160
|
+
name,
|
|
2161
|
+
symbol: name
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2164
|
+
if (input.kind === "field") {
|
|
2165
|
+
const matched = signature.fields.filter((field) => field.name === name);
|
|
2166
|
+
if (matched.length !== 1) {
|
|
2167
|
+
return buildUnresolved(matched.length > 1 ? "ambiguous" : "not_found");
|
|
2168
|
+
}
|
|
2169
|
+
return buildResolved({
|
|
2170
|
+
kind: "field",
|
|
2171
|
+
owner,
|
|
2172
|
+
name,
|
|
2173
|
+
symbol: `${owner}.${name}`
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
const methodCandidates = signature.methods.filter((method) => method.name === name);
|
|
2177
|
+
if (input.signatureMode === "name-only") {
|
|
2178
|
+
if (methodCandidates.length !== 1) {
|
|
2179
|
+
return buildUnresolved(methodCandidates.length > 1 ? "ambiguous" : "not_found");
|
|
2180
|
+
}
|
|
2181
|
+
return buildResolved({
|
|
2182
|
+
kind: "method",
|
|
2183
|
+
owner,
|
|
2184
|
+
name,
|
|
2185
|
+
descriptor: methodCandidates[0]?.jvmDescriptor,
|
|
2186
|
+
symbol: `${owner}.${name}${methodCandidates[0]?.jvmDescriptor ?? ""}`
|
|
2187
|
+
});
|
|
2188
|
+
}
|
|
2189
|
+
const descriptor = input.descriptor?.trim();
|
|
2190
|
+
const matched = methodCandidates.filter((method) => method.jvmDescriptor === descriptor);
|
|
2191
|
+
if (matched.length !== 1) {
|
|
2192
|
+
return buildUnresolved(matched.length > 1 ? "ambiguous" : "not_found");
|
|
2193
|
+
}
|
|
2194
|
+
return buildResolved({
|
|
2195
|
+
kind: "method",
|
|
2196
|
+
owner,
|
|
2197
|
+
name,
|
|
2198
|
+
descriptor,
|
|
2199
|
+
symbol: `${owner}.${name}${descriptor ?? ""}`
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
1686
2202
|
async traceSymbolLifecycle(input) {
|
|
1687
2203
|
const mapping = normalizeMapping(input.mapping);
|
|
1688
2204
|
const { className: userClassName, methodName: userMethodName, inlineDescriptor } = parseQualifiedMethodSymbol(input.symbol);
|
|
@@ -2755,7 +3271,7 @@ export class SourceService {
|
|
|
2755
3271
|
})), input, []);
|
|
2756
3272
|
}
|
|
2757
3273
|
const resolvedInput = mode === "project"
|
|
2758
|
-
? this.createProjectValidateMixinConfigInput(input)
|
|
3274
|
+
? await this.createProjectValidateMixinConfigInput(input)
|
|
2759
3275
|
: input;
|
|
2760
3276
|
const { sources: configSources, warnings: configWarnings } = await this.resolveMixinConfigSources(resolvedInput);
|
|
2761
3277
|
if (configSources.length === 0) {
|
|
@@ -2775,17 +3291,17 @@ export class SourceService {
|
|
|
2775
3291
|
sourcePath: entry.sourcePath
|
|
2776
3292
|
})), resolvedInput, configWarnings);
|
|
2777
3293
|
}
|
|
2778
|
-
createProjectValidateMixinConfigInput(input) {
|
|
3294
|
+
async createProjectValidateMixinConfigInput(input) {
|
|
2779
3295
|
if (input.input.mode !== "project") {
|
|
2780
3296
|
return input;
|
|
2781
3297
|
}
|
|
2782
3298
|
const resolvedProjectPath = this.resolveMixinInputPath(input.input.path, "path");
|
|
2783
|
-
const configPaths = fastGlob.
|
|
3299
|
+
const configPaths = (await fastGlob.glob(["**/*.mixins.json"], {
|
|
2784
3300
|
cwd: resolvedProjectPath,
|
|
2785
3301
|
absolute: true,
|
|
2786
3302
|
onlyFiles: true,
|
|
2787
3303
|
ignore: [...MIXIN_PROJECT_DISCOVERY_IGNORES]
|
|
2788
|
-
}).sort((left, right) => left.localeCompare(right));
|
|
3304
|
+
})).sort((left, right) => left.localeCompare(right));
|
|
2789
3305
|
if (configPaths.length === 0) {
|
|
2790
3306
|
throw createError({
|
|
2791
3307
|
code: ERROR_CODES.INVALID_INPUT,
|
|
@@ -2828,7 +3344,8 @@ export class SourceService {
|
|
|
2828
3344
|
name: input.className,
|
|
2829
3345
|
sourceMapping: input.sourceMapping,
|
|
2830
3346
|
targetMapping: input.targetMapping,
|
|
2831
|
-
sourcePriority: input.sourcePriority
|
|
3347
|
+
sourcePriority: input.sourcePriority,
|
|
3348
|
+
projectPath: input.projectPath
|
|
2832
3349
|
});
|
|
2833
3350
|
}
|
|
2834
3351
|
const cacheKey = [
|
|
@@ -2836,7 +3353,8 @@ export class SourceService {
|
|
|
2836
3353
|
input.className,
|
|
2837
3354
|
input.sourceMapping,
|
|
2838
3355
|
input.targetMapping,
|
|
2839
|
-
input.sourcePriority
|
|
3356
|
+
input.sourcePriority,
|
|
3357
|
+
input.projectPath ?? ""
|
|
2840
3358
|
].join("\0");
|
|
2841
3359
|
const cached = cache.get(cacheKey);
|
|
2842
3360
|
if (cached) {
|
|
@@ -2848,7 +3366,8 @@ export class SourceService {
|
|
|
2848
3366
|
name: input.className,
|
|
2849
3367
|
sourceMapping: input.sourceMapping,
|
|
2850
3368
|
targetMapping: input.targetMapping,
|
|
2851
|
-
sourcePriority: input.sourcePriority
|
|
3369
|
+
sourcePriority: input.sourcePriority,
|
|
3370
|
+
projectPath: input.projectPath
|
|
2852
3371
|
}).catch((error) => {
|
|
2853
3372
|
cache.delete(cacheKey);
|
|
2854
3373
|
throw error;
|
|
@@ -3029,6 +3548,7 @@ export class SourceService {
|
|
|
3029
3548
|
sourceMapping: requestedMapping,
|
|
3030
3549
|
targetMapping: signatureLookupMapping,
|
|
3031
3550
|
sourcePriority: currentSourcePriority,
|
|
3551
|
+
projectPath: input.projectPath,
|
|
3032
3552
|
batchCaches: input.batchCaches
|
|
3033
3553
|
});
|
|
3034
3554
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
@@ -3062,9 +3582,9 @@ export class SourceService {
|
|
|
3062
3582
|
if (requestedMapping !== signatureLookupMapping) {
|
|
3063
3583
|
try {
|
|
3064
3584
|
const [ctorResult, methodResult, fieldResult] = await Promise.all([
|
|
3065
|
-
this.remapSignatureMembers(sig.constructors, "method", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings),
|
|
3066
|
-
this.remapSignatureMembers(sig.methods, "method", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings),
|
|
3067
|
-
this.remapSignatureMembers(sig.fields, "field", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings)
|
|
3585
|
+
this.remapSignatureMembers(sig.constructors, "method", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings, input.projectPath),
|
|
3586
|
+
this.remapSignatureMembers(sig.methods, "method", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings, input.projectPath),
|
|
3587
|
+
this.remapSignatureMembers(sig.fields, "field", version, signatureLookupMapping, requestedMapping, currentSourcePriority, warnings, input.projectPath)
|
|
3068
3588
|
]);
|
|
3069
3589
|
constructors = ctorResult.members;
|
|
3070
3590
|
methods = methodResult.members;
|
|
@@ -3341,11 +3861,21 @@ export class SourceService {
|
|
|
3341
3861
|
sourceRootCandidates = input.sourceRoots;
|
|
3342
3862
|
}
|
|
3343
3863
|
else {
|
|
3344
|
-
const detected =
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3864
|
+
const detected = [];
|
|
3865
|
+
for (const candidateRoot of COMMON_SOURCE_ROOTS) {
|
|
3866
|
+
let foundInRoot = false;
|
|
3867
|
+
for (const className of classNames) {
|
|
3868
|
+
const fqcn = pkg ? `${pkg}.${className}` : className;
|
|
3869
|
+
const relative = fqcn.replace(/\./g, "/") + ".java";
|
|
3870
|
+
if (await pathExists(resolvePath(projectBase, candidateRoot, relative))) {
|
|
3871
|
+
foundInRoot = true;
|
|
3872
|
+
break;
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
if (foundInRoot) {
|
|
3876
|
+
detected.push(candidateRoot);
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3349
3879
|
sourceRootCandidates = detected.length > 0 ? detected : ["src/main/java"];
|
|
3350
3880
|
}
|
|
3351
3881
|
for (const cls of classNames) {
|
|
@@ -3354,7 +3884,7 @@ export class SourceService {
|
|
|
3354
3884
|
let sourcePath = resolvePath(projectBase, sourceRootCandidates[0], relativePath);
|
|
3355
3885
|
for (const root of sourceRootCandidates) {
|
|
3356
3886
|
const candidate = resolvePath(projectBase, root, relativePath);
|
|
3357
|
-
if (
|
|
3887
|
+
if (await pathExists(candidate)) {
|
|
3358
3888
|
sourcePath = candidate;
|
|
3359
3889
|
break;
|
|
3360
3890
|
}
|
|
@@ -3545,7 +4075,6 @@ export class SourceService {
|
|
|
3545
4075
|
throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "content must be non-empty." });
|
|
3546
4076
|
}
|
|
3547
4077
|
const warnings = [];
|
|
3548
|
-
const { jarPath } = await this.versionService.resolveVersionJar(version);
|
|
3549
4078
|
const parsed = parseAccessWidener(content);
|
|
3550
4079
|
const headerNamespaceRaw = normalizeOptionalString(parsed.namespace);
|
|
3551
4080
|
const overrideMapping = input.mapping ? normalizeMapping(input.mapping) : undefined;
|
|
@@ -3557,7 +4086,27 @@ export class SourceService {
|
|
|
3557
4086
|
if (overrideMapping && headerNamespace && overrideMapping !== headerNamespace) {
|
|
3558
4087
|
warnings.push(`Using mapping override "${overrideMapping}" instead of header namespace "${headerNamespaceRaw}".`);
|
|
3559
4088
|
}
|
|
3560
|
-
const
|
|
4089
|
+
const runtimeAware = input.projectPath != null || input.scope != null || input.preferProjectVersion === true;
|
|
4090
|
+
let resolvedVersion = version;
|
|
4091
|
+
let jarPath;
|
|
4092
|
+
let lookupMapping = "obfuscated";
|
|
4093
|
+
let provenance;
|
|
4094
|
+
if (runtimeAware) {
|
|
4095
|
+
provenance = await this.resolveAccessWidenerRuntimeArtifact({
|
|
4096
|
+
version,
|
|
4097
|
+
awNamespace,
|
|
4098
|
+
projectPath: input.projectPath,
|
|
4099
|
+
scope: input.scope,
|
|
4100
|
+
preferProjectVersion: input.preferProjectVersion
|
|
4101
|
+
});
|
|
4102
|
+
resolvedVersion = provenance.version;
|
|
4103
|
+
jarPath = provenance.jarPath;
|
|
4104
|
+
lookupMapping = provenance.mappingApplied;
|
|
4105
|
+
}
|
|
4106
|
+
else {
|
|
4107
|
+
({ jarPath } = await this.versionService.resolveVersionJar(version));
|
|
4108
|
+
}
|
|
4109
|
+
const needsLookupMapping = awNamespace !== lookupMapping;
|
|
3561
4110
|
// Collect unique class FQNs from entries
|
|
3562
4111
|
const classFqns = new Set();
|
|
3563
4112
|
for (const entry of parsed.entries) {
|
|
@@ -3566,22 +4115,23 @@ export class SourceService {
|
|
|
3566
4115
|
}
|
|
3567
4116
|
const membersByClass = new Map();
|
|
3568
4117
|
for (const fqn of classFqns) {
|
|
3569
|
-
let
|
|
3570
|
-
if (
|
|
4118
|
+
let lookupFqn = fqn;
|
|
4119
|
+
if (needsLookupMapping) {
|
|
3571
4120
|
try {
|
|
3572
4121
|
const mapped = await this.mappingService.findMapping({
|
|
3573
|
-
version,
|
|
4122
|
+
version: resolvedVersion,
|
|
3574
4123
|
kind: "class",
|
|
3575
4124
|
name: fqn,
|
|
3576
4125
|
sourceMapping: awNamespace,
|
|
3577
|
-
targetMapping:
|
|
3578
|
-
sourcePriority: input.sourcePriority
|
|
4126
|
+
targetMapping: lookupMapping,
|
|
4127
|
+
sourcePriority: input.sourcePriority,
|
|
4128
|
+
projectPath: input.projectPath
|
|
3579
4129
|
});
|
|
3580
4130
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
3581
|
-
|
|
4131
|
+
lookupFqn = mapped.resolvedSymbol.name;
|
|
3582
4132
|
}
|
|
3583
4133
|
else {
|
|
3584
|
-
warnings.push(`Could not map class "${fqn}" from ${awNamespace} to
|
|
4134
|
+
warnings.push(`Could not map class "${fqn}" from ${awNamespace} to ${lookupMapping}.`);
|
|
3585
4135
|
}
|
|
3586
4136
|
}
|
|
3587
4137
|
catch {
|
|
@@ -3590,23 +4140,156 @@ export class SourceService {
|
|
|
3590
4140
|
}
|
|
3591
4141
|
try {
|
|
3592
4142
|
const sig = await this.explorerService.getSignature({
|
|
3593
|
-
fqn:
|
|
4143
|
+
fqn: lookupFqn,
|
|
4144
|
+
jarPath,
|
|
4145
|
+
access: "all"
|
|
4146
|
+
});
|
|
4147
|
+
warnings.push(...sig.warnings);
|
|
4148
|
+
let constructors = sig.constructors;
|
|
4149
|
+
let methods = sig.methods;
|
|
4150
|
+
let fields = sig.fields;
|
|
4151
|
+
if (needsLookupMapping) {
|
|
4152
|
+
const [ctorResult, methodResult, fieldResult] = await Promise.all([
|
|
4153
|
+
this.remapSignatureMembers(sig.constructors, "method", resolvedVersion, lookupMapping, awNamespace, input.sourcePriority, warnings, input.projectPath),
|
|
4154
|
+
this.remapSignatureMembers(sig.methods, "method", resolvedVersion, lookupMapping, awNamespace, input.sourcePriority, warnings, input.projectPath),
|
|
4155
|
+
this.remapSignatureMembers(sig.fields, "field", resolvedVersion, lookupMapping, awNamespace, input.sourcePriority, warnings, input.projectPath)
|
|
4156
|
+
]);
|
|
4157
|
+
constructors = ctorResult.members;
|
|
4158
|
+
methods = methodResult.members;
|
|
4159
|
+
fields = fieldResult.members;
|
|
4160
|
+
}
|
|
4161
|
+
membersByClass.set(fqn, {
|
|
4162
|
+
className: fqn,
|
|
4163
|
+
classAccessFlags: sig.classAccessFlags,
|
|
4164
|
+
constructors,
|
|
4165
|
+
methods,
|
|
4166
|
+
fields
|
|
4167
|
+
});
|
|
4168
|
+
}
|
|
4169
|
+
catch {
|
|
4170
|
+
warnings.push(`Could not load signature for class "${lookupFqn}".`);
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
const result = validateParsedAccessWidener(parsed, membersByClass, warnings, {
|
|
4174
|
+
includeRuntimeEvidence: runtimeAware
|
|
4175
|
+
});
|
|
4176
|
+
if (provenance) {
|
|
4177
|
+
result.provenance = provenance;
|
|
4178
|
+
}
|
|
4179
|
+
return result;
|
|
4180
|
+
}
|
|
4181
|
+
async validateAccessTransformer(input) {
|
|
4182
|
+
const version = input.version.trim();
|
|
4183
|
+
if (!version) {
|
|
4184
|
+
throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "version must be non-empty." });
|
|
4185
|
+
}
|
|
4186
|
+
const content = input.content;
|
|
4187
|
+
if (!content.trim()) {
|
|
4188
|
+
throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "content must be non-empty." });
|
|
4189
|
+
}
|
|
4190
|
+
const warnings = [];
|
|
4191
|
+
const parsed = parseAccessTransformer(content);
|
|
4192
|
+
const atNamespace = await this.resolveAccessTransformerNamespace({
|
|
4193
|
+
atNamespace: input.atNamespace,
|
|
4194
|
+
projectPath: input.projectPath
|
|
4195
|
+
});
|
|
4196
|
+
const runtimeAware = input.projectPath != null || input.scope != null || input.preferProjectVersion === true;
|
|
4197
|
+
let resolvedVersion = version;
|
|
4198
|
+
let jarPath;
|
|
4199
|
+
let lookupMapping = "obfuscated";
|
|
4200
|
+
let provenance;
|
|
4201
|
+
if (runtimeAware) {
|
|
4202
|
+
provenance = await this.resolveAccessTransformerRuntimeArtifact({
|
|
4203
|
+
version,
|
|
4204
|
+
atNamespace,
|
|
4205
|
+
projectPath: input.projectPath,
|
|
4206
|
+
scope: input.scope,
|
|
4207
|
+
preferProjectVersion: input.preferProjectVersion
|
|
4208
|
+
});
|
|
4209
|
+
resolvedVersion = provenance.version;
|
|
4210
|
+
jarPath = provenance.jarPath;
|
|
4211
|
+
lookupMapping = provenance.mappingApplied;
|
|
4212
|
+
}
|
|
4213
|
+
else {
|
|
4214
|
+
if (atNamespace === "srg") {
|
|
4215
|
+
throw createError({
|
|
4216
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
4217
|
+
message: "atNamespace=srg requires projectPath and scope=loader so a Forge runtime jar can be resolved."
|
|
4218
|
+
});
|
|
4219
|
+
}
|
|
4220
|
+
({ jarPath } = await this.versionService.resolveVersionJar(version));
|
|
4221
|
+
}
|
|
4222
|
+
const needsLookupMapping = atNamespace !== lookupMapping;
|
|
4223
|
+
const classFqns = new Set(parsed.entries.map((entry) => entry.owner));
|
|
4224
|
+
const membersByClass = new Map();
|
|
4225
|
+
for (const fqn of classFqns) {
|
|
4226
|
+
let lookupFqn = fqn;
|
|
4227
|
+
if (needsLookupMapping) {
|
|
4228
|
+
if (!isSourceMappingNamespace(atNamespace) || !isSourceMappingNamespace(lookupMapping)) {
|
|
4229
|
+
warnings.push(`Could not map class "${fqn}" from ${atNamespace} to ${lookupMapping}.`);
|
|
4230
|
+
}
|
|
4231
|
+
else {
|
|
4232
|
+
try {
|
|
4233
|
+
const mapped = await this.mappingService.findMapping({
|
|
4234
|
+
version: resolvedVersion,
|
|
4235
|
+
kind: "class",
|
|
4236
|
+
name: fqn,
|
|
4237
|
+
sourceMapping: atNamespace,
|
|
4238
|
+
targetMapping: lookupMapping,
|
|
4239
|
+
sourcePriority: input.sourcePriority,
|
|
4240
|
+
projectPath: input.projectPath
|
|
4241
|
+
});
|
|
4242
|
+
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
4243
|
+
lookupFqn = mapped.resolvedSymbol.name;
|
|
4244
|
+
}
|
|
4245
|
+
else {
|
|
4246
|
+
warnings.push(`Could not map class "${fqn}" from ${atNamespace} to ${lookupMapping}.`);
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
catch {
|
|
4250
|
+
warnings.push(`Mapping lookup failed for class "${fqn}".`);
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
try {
|
|
4255
|
+
const sig = await this.explorerService.getSignature({
|
|
4256
|
+
fqn: lookupFqn,
|
|
3594
4257
|
jarPath,
|
|
3595
4258
|
access: "all"
|
|
3596
4259
|
});
|
|
3597
4260
|
warnings.push(...sig.warnings);
|
|
4261
|
+
let constructors = sig.constructors;
|
|
4262
|
+
let methods = sig.methods;
|
|
4263
|
+
let fields = sig.fields;
|
|
4264
|
+
if (needsLookupMapping && isSourceMappingNamespace(atNamespace) && isSourceMappingNamespace(lookupMapping)) {
|
|
4265
|
+
const [ctorResult, methodResult, fieldResult] = await Promise.all([
|
|
4266
|
+
this.remapSignatureMembers(sig.constructors, "method", resolvedVersion, lookupMapping, atNamespace, input.sourcePriority, warnings, input.projectPath),
|
|
4267
|
+
this.remapSignatureMembers(sig.methods, "method", resolvedVersion, lookupMapping, atNamespace, input.sourcePriority, warnings, input.projectPath),
|
|
4268
|
+
this.remapSignatureMembers(sig.fields, "field", resolvedVersion, lookupMapping, atNamespace, input.sourcePriority, warnings, input.projectPath)
|
|
4269
|
+
]);
|
|
4270
|
+
constructors = ctorResult.members;
|
|
4271
|
+
methods = methodResult.members;
|
|
4272
|
+
fields = fieldResult.members;
|
|
4273
|
+
}
|
|
3598
4274
|
membersByClass.set(fqn, {
|
|
3599
4275
|
className: fqn,
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
4276
|
+
classAccessFlags: sig.classAccessFlags,
|
|
4277
|
+
constructors,
|
|
4278
|
+
methods,
|
|
4279
|
+
fields
|
|
3603
4280
|
});
|
|
3604
4281
|
}
|
|
3605
4282
|
catch {
|
|
3606
|
-
warnings.push(`Could not load signature for class "${
|
|
4283
|
+
warnings.push(`Could not load signature for class "${lookupFqn}".`);
|
|
3607
4284
|
}
|
|
3608
4285
|
}
|
|
3609
|
-
|
|
4286
|
+
const result = validateParsedAccessTransformer(parsed, membersByClass, warnings, {
|
|
4287
|
+
includeRuntimeEvidence: runtimeAware
|
|
4288
|
+
});
|
|
4289
|
+
if (provenance) {
|
|
4290
|
+
result.provenance = provenance;
|
|
4291
|
+
}
|
|
4292
|
+
return result;
|
|
3610
4293
|
}
|
|
3611
4294
|
getRuntimeMetrics() {
|
|
3612
4295
|
return this.metrics.snapshot();
|
|
@@ -4210,21 +4893,22 @@ export class SourceService {
|
|
|
4210
4893
|
descriptor: kind === "method" ? descriptor : undefined
|
|
4211
4894
|
};
|
|
4212
4895
|
}
|
|
4213
|
-
async remapSignatureMembers(members, kind, version, sourceMapping, targetMapping, sourcePriority, warnings) {
|
|
4896
|
+
async remapSignatureMembers(members, kind, version, sourceMapping, targetMapping, sourcePriority, warnings, projectPath) {
|
|
4214
4897
|
const failedNames = new Set();
|
|
4215
4898
|
if (sourceMapping === targetMapping) {
|
|
4216
4899
|
return { members, failedNames };
|
|
4217
4900
|
}
|
|
4218
4901
|
// Build deduplicated lookup tables for member names and owner FQNs
|
|
4219
4902
|
const memberKeyToRemapped = new Map();
|
|
4903
|
+
const memberDescriptorRemapped = new Map();
|
|
4220
4904
|
const ownerToRemapped = new Map();
|
|
4221
4905
|
for (const member of members) {
|
|
4222
4906
|
const memberKey = `${member.ownerFqn}\0${member.name}\0${member.jvmDescriptor}`;
|
|
4223
4907
|
if (!memberKeyToRemapped.has(memberKey)) {
|
|
4224
|
-
memberKeyToRemapped.set(memberKey, member.name); // default =
|
|
4908
|
+
memberKeyToRemapped.set(memberKey, member.name); // default = source name
|
|
4225
4909
|
}
|
|
4226
4910
|
if (!ownerToRemapped.has(member.ownerFqn)) {
|
|
4227
|
-
ownerToRemapped.set(member.ownerFqn, member.ownerFqn); // default =
|
|
4911
|
+
ownerToRemapped.set(member.ownerFqn, member.ownerFqn); // default = source FQN
|
|
4228
4912
|
}
|
|
4229
4913
|
}
|
|
4230
4914
|
// Phase 1: Remap owner FQNs first (needed for member disambiguation)
|
|
@@ -4237,22 +4921,98 @@ export class SourceService {
|
|
|
4237
4921
|
name: obfuscatedFqn,
|
|
4238
4922
|
sourceMapping,
|
|
4239
4923
|
targetMapping,
|
|
4240
|
-
sourcePriority
|
|
4924
|
+
sourcePriority,
|
|
4925
|
+
projectPath
|
|
4241
4926
|
});
|
|
4242
4927
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
4243
4928
|
ownerToRemapped.set(obfuscatedFqn, mapped.resolvedSymbol.name);
|
|
4244
4929
|
}
|
|
4245
4930
|
}
|
|
4246
4931
|
catch {
|
|
4247
|
-
// keep
|
|
4932
|
+
// keep source FQN as fallback
|
|
4248
4933
|
}
|
|
4249
4934
|
}));
|
|
4250
|
-
// Phase
|
|
4935
|
+
// Phase 1.5: Collect class references from descriptors and remap them
|
|
4936
|
+
const descriptorClassRefs = new Set();
|
|
4937
|
+
for (const member of members) {
|
|
4938
|
+
for (const match of member.jvmDescriptor.matchAll(/L([^;]+);/g)) {
|
|
4939
|
+
const dotFqn = match[1].replace(/\//g, ".");
|
|
4940
|
+
if (!ownerToRemapped.has(dotFqn)) {
|
|
4941
|
+
descriptorClassRefs.add(dotFqn);
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
if (descriptorClassRefs.size > 0) {
|
|
4946
|
+
const refs = [...descriptorClassRefs];
|
|
4947
|
+
for (const ref of refs) {
|
|
4948
|
+
ownerToRemapped.set(ref, ref); // default = source name
|
|
4949
|
+
}
|
|
4950
|
+
await Promise.all(refs.map(async (dotFqn) => {
|
|
4951
|
+
try {
|
|
4952
|
+
const mapped = await this.mappingService.findMapping({
|
|
4953
|
+
version,
|
|
4954
|
+
kind: "class",
|
|
4955
|
+
name: dotFqn,
|
|
4956
|
+
sourceMapping,
|
|
4957
|
+
targetMapping,
|
|
4958
|
+
sourcePriority,
|
|
4959
|
+
projectPath
|
|
4960
|
+
});
|
|
4961
|
+
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
4962
|
+
ownerToRemapped.set(dotFqn, mapped.resolvedSymbol.name);
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
catch {
|
|
4966
|
+
// keep source name as fallback
|
|
4967
|
+
}
|
|
4968
|
+
}));
|
|
4969
|
+
}
|
|
4970
|
+
// Build a class map for descriptor remapping (dot-FQN → dot-FQN)
|
|
4971
|
+
const classMap = new Map();
|
|
4972
|
+
for (const [src, tgt] of ownerToRemapped) {
|
|
4973
|
+
if (src !== tgt) {
|
|
4974
|
+
classMap.set(src, tgt);
|
|
4975
|
+
}
|
|
4976
|
+
}
|
|
4977
|
+
// Phase 2: Remap member names (and descriptors for methods) using resolved owners
|
|
4978
|
+
const canResolveMethodExactly = kind === "method" &&
|
|
4979
|
+
"resolveMethodMappingExact" in this.mappingService &&
|
|
4980
|
+
typeof this.mappingService.resolveMethodMappingExact === "function";
|
|
4251
4981
|
const memberEntries = [...memberKeyToRemapped.entries()];
|
|
4252
|
-
await Promise.all(memberEntries.map(async ([key,
|
|
4982
|
+
await Promise.all(memberEntries.map(async ([key, _sourceName]) => {
|
|
4253
4983
|
const [ownerFqn, name, descriptor] = key.split("\0");
|
|
4254
4984
|
try {
|
|
4255
4985
|
const targetOwner = ownerToRemapped.get(ownerFqn) ?? ownerFqn;
|
|
4986
|
+
// For methods with descriptors, try exact resolution first
|
|
4987
|
+
if (canResolveMethodExactly && descriptor) {
|
|
4988
|
+
try {
|
|
4989
|
+
const exactResult = await this.mappingService.resolveMethodMappingExact({
|
|
4990
|
+
version,
|
|
4991
|
+
owner: ownerFqn,
|
|
4992
|
+
name: name,
|
|
4993
|
+
descriptor,
|
|
4994
|
+
sourceMapping,
|
|
4995
|
+
targetMapping,
|
|
4996
|
+
sourcePriority,
|
|
4997
|
+
projectPath
|
|
4998
|
+
});
|
|
4999
|
+
if (exactResult.resolved && exactResult.resolvedSymbol) {
|
|
5000
|
+
memberKeyToRemapped.set(key, exactResult.resolvedSymbol.name);
|
|
5001
|
+
if (exactResult.resolvedSymbol.descriptor) {
|
|
5002
|
+
memberDescriptorRemapped.set(key, exactResult.resolvedSymbol.descriptor);
|
|
5003
|
+
}
|
|
5004
|
+
return; // exact resolution succeeded
|
|
5005
|
+
}
|
|
5006
|
+
// Fall through to findMapping with descriptorHint
|
|
5007
|
+
}
|
|
5008
|
+
catch (exactError) {
|
|
5009
|
+
warnings.push(`Exact method resolution failed for "${name}" (falling back to name-based lookup): ${exactError instanceof Error ? exactError.message : String(exactError)}`);
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
// Fallback: findMapping with descriptorHint for overload disambiguation
|
|
5013
|
+
const remappedDescriptorHint = kind === "method" && descriptor
|
|
5014
|
+
? remapJvmDescriptor(descriptor, classMap)
|
|
5015
|
+
: undefined;
|
|
4256
5016
|
const mapped = await this.mappingService.findMapping({
|
|
4257
5017
|
version,
|
|
4258
5018
|
kind,
|
|
@@ -4262,10 +5022,17 @@ export class SourceService {
|
|
|
4262
5022
|
sourceMapping,
|
|
4263
5023
|
targetMapping,
|
|
4264
5024
|
sourcePriority,
|
|
4265
|
-
|
|
5025
|
+
projectPath,
|
|
5026
|
+
disambiguation: {
|
|
5027
|
+
ownerHint: targetOwner,
|
|
5028
|
+
descriptorHint: remappedDescriptorHint
|
|
5029
|
+
}
|
|
4266
5030
|
});
|
|
4267
5031
|
if (mapped.resolved && mapped.resolvedSymbol) {
|
|
4268
5032
|
memberKeyToRemapped.set(key, mapped.resolvedSymbol.name);
|
|
5033
|
+
if (kind === "method" && mapped.resolvedSymbol.descriptor) {
|
|
5034
|
+
memberDescriptorRemapped.set(key, mapped.resolvedSymbol.descriptor);
|
|
5035
|
+
}
|
|
4269
5036
|
}
|
|
4270
5037
|
else if (mapped.status === "ambiguous" && mapped.candidates && mapped.candidates.length > 0) {
|
|
4271
5038
|
// Disambiguate: filter by target owner and pick the best candidate
|
|
@@ -4293,13 +5060,20 @@ export class SourceService {
|
|
|
4293
5060
|
failedNames.add(name);
|
|
4294
5061
|
}
|
|
4295
5062
|
}));
|
|
5063
|
+
const isField = kind === "field";
|
|
4296
5064
|
return {
|
|
4297
5065
|
members: members.map((member) => {
|
|
4298
5066
|
const memberKey = `${member.ownerFqn}\0${member.name}\0${member.jvmDescriptor}`;
|
|
5067
|
+
const remappedName = memberKeyToRemapped.get(memberKey) ?? member.name;
|
|
5068
|
+
const remappedOwner = ownerToRemapped.get(member.ownerFqn) ?? member.ownerFqn;
|
|
5069
|
+
const remappedDescriptor = memberDescriptorRemapped.get(memberKey)
|
|
5070
|
+
?? remapJvmDescriptor(member.jvmDescriptor, classMap);
|
|
4299
5071
|
return {
|
|
4300
5072
|
...member,
|
|
4301
|
-
name:
|
|
4302
|
-
ownerFqn:
|
|
5073
|
+
name: remappedName,
|
|
5074
|
+
ownerFqn: remappedOwner,
|
|
5075
|
+
jvmDescriptor: remappedDescriptor,
|
|
5076
|
+
javaSignature: rebuildJavaSignature({ name: remappedName, ownerFqn: remappedOwner, accessFlags: member.accessFlags }, remappedDescriptor, isField)
|
|
4303
5077
|
};
|
|
4304
5078
|
}),
|
|
4305
5079
|
failedNames
|
|
@@ -4644,4 +5418,39 @@ export class SourceService {
|
|
|
4644
5418
|
this.metrics.setCacheArtifactByteAccountingRef(this.cacheMetricsState.lru);
|
|
4645
5419
|
}
|
|
4646
5420
|
}
|
|
5421
|
+
function remapJvmDescriptor(descriptor, classMap) {
|
|
5422
|
+
if (classMap.size === 0) {
|
|
5423
|
+
return descriptor;
|
|
5424
|
+
}
|
|
5425
|
+
return descriptor.replace(/L([^;]+);/g, (match, ref) => {
|
|
5426
|
+
const dotFqn = ref.replace(/\//g, ".");
|
|
5427
|
+
const remapped = classMap.get(dotFqn);
|
|
5428
|
+
return remapped ? `L${remapped.replace(/\./g, "/")};` : match;
|
|
5429
|
+
});
|
|
5430
|
+
}
|
|
5431
|
+
function rebuildJavaSignature(member, remappedDescriptor, isField) {
|
|
5432
|
+
const modifiers = modifierPrefix(member.accessFlags, isField ? "field" : "method");
|
|
5433
|
+
const prefix = modifiers ? `${modifiers} ` : "";
|
|
5434
|
+
if (isField) {
|
|
5435
|
+
try {
|
|
5436
|
+
const { type } = parseFieldType(remappedDescriptor, 0, { allowVoid: false });
|
|
5437
|
+
return `${prefix}${type} ${member.name}`.trim();
|
|
5438
|
+
}
|
|
5439
|
+
catch {
|
|
5440
|
+
return `${prefix}${member.name}`.trim();
|
|
5441
|
+
}
|
|
5442
|
+
}
|
|
5443
|
+
try {
|
|
5444
|
+
const { args, returnType } = parseMethodDescriptor(remappedDescriptor);
|
|
5445
|
+
const argStr = args.join(", ");
|
|
5446
|
+
if (member.name === "<init>") {
|
|
5447
|
+
const ownerSimple = member.ownerFqn.split(".").pop();
|
|
5448
|
+
return `${prefix}${ownerSimple}(${argStr})`.trim();
|
|
5449
|
+
}
|
|
5450
|
+
return `${prefix}${returnType} ${member.name}(${argStr})`.trim();
|
|
5451
|
+
}
|
|
5452
|
+
catch {
|
|
5453
|
+
return `${prefix}${member.name}`.trim();
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
4647
5456
|
//# sourceMappingURL=source-service.js.map
|