@adhisang/minecraft-modding-mcp 4.0.0 → 4.1.1
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 +61 -0
- package/README.md +40 -23
- package/dist/build-suggested-call.d.ts +29 -0
- package/dist/build-suggested-call.js +58 -0
- package/dist/cache-registry.d.ts +3 -1
- package/dist/cache-registry.js +50 -6
- package/dist/entry-tools/analyze-symbol-service.d.ts +16 -16
- package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
- package/dist/entry-tools/batch-class-members-service.js +97 -0
- package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
- package/dist/entry-tools/batch-class-source-service.js +100 -0
- package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
- package/dist/entry-tools/batch-mappings-service.js +66 -0
- package/dist/entry-tools/batch-runner.d.ts +72 -0
- package/dist/entry-tools/batch-runner.js +90 -0
- package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
- package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
- package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
- package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
- package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
- package/dist/entry-tools/inspect-minecraft-service.d.ts +193 -308
- package/dist/entry-tools/inspect-minecraft-service.js +20 -1244
- package/dist/entry-tools/manage-cache-service.d.ts +16 -16
- package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
- package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
- package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +102 -0
- package/dist/entry-tools/validate-project/cases/project-summary.js +415 -0
- package/dist/entry-tools/validate-project/internal.d.ts +142 -0
- package/dist/entry-tools/validate-project/internal.js +303 -0
- package/dist/entry-tools/validate-project-service.d.ts +67 -47
- package/dist/entry-tools/validate-project-service.js +13 -563
- package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
- package/dist/entry-tools/verify-mixin-target-service.js +323 -0
- package/dist/error-mapping.d.ts +40 -0
- package/dist/error-mapping.js +139 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +147 -1354
- package/dist/mapping/internal-types.d.ts +54 -0
- package/dist/mapping/internal-types.js +14 -0
- package/dist/mapping/loaders/mojang.d.ts +2 -0
- package/dist/mapping/loaders/mojang.js +64 -0
- package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
- package/dist/mapping/loaders/tiny-loom.js +73 -0
- package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
- package/dist/mapping/loaders/tiny-maven.js +104 -0
- package/dist/mapping/loaders/types.d.ts +14 -0
- package/dist/mapping/loaders/types.js +2 -0
- package/dist/mapping/lookup.d.ts +52 -0
- package/dist/mapping/lookup.js +496 -0
- package/dist/mapping/parsers/normalize.d.ts +10 -0
- package/dist/mapping/parsers/normalize.js +52 -0
- package/dist/mapping/parsers/proguard.d.ts +20 -0
- package/dist/mapping/parsers/proguard.js +138 -0
- package/dist/mapping/parsers/symbol-records.d.ts +27 -0
- package/dist/mapping/parsers/symbol-records.js +216 -0
- package/dist/mapping/parsers/tiny.d.ts +9 -0
- package/dist/mapping/parsers/tiny.js +96 -0
- package/dist/mapping/types.d.ts +147 -0
- package/dist/mapping/types.js +2 -0
- package/dist/mapping-pipeline-service.js +3 -2
- package/dist/mapping-service.d.ts +8 -145
- package/dist/mapping-service.js +30 -1207
- package/dist/mixin/access-validators.d.ts +9 -0
- package/dist/mixin/access-validators.js +257 -0
- package/dist/mixin/annotation-validators.d.ts +5 -0
- package/dist/mixin/annotation-validators.js +162 -0
- package/dist/mixin/helpers.d.ts +28 -0
- package/dist/mixin/helpers.js +315 -0
- package/dist/mixin/parsed-validator.d.ts +8 -0
- package/dist/mixin/parsed-validator.js +337 -0
- package/dist/mixin/types.d.ts +208 -0
- package/dist/mixin/types.js +28 -0
- package/dist/mixin-validator.d.ts +9 -201
- package/dist/mixin-validator.js +8 -1020
- package/dist/source/access-validate.d.ts +4 -0
- package/dist/source/access-validate.js +254 -0
- package/dist/source/artifact-resolver.d.ts +111 -0
- package/dist/source/artifact-resolver.js +1271 -0
- package/dist/source/cache-metrics.d.ts +26 -0
- package/dist/source/cache-metrics.js +172 -0
- package/dist/source/class-source/members-builder.d.ts +34 -0
- package/dist/source/class-source/members-builder.js +46 -0
- package/dist/source/class-source/snippet-builder.d.ts +19 -0
- package/dist/source/class-source/snippet-builder.js +46 -0
- package/dist/source/class-source-helpers.d.ts +34 -0
- package/dist/source/class-source-helpers.js +140 -0
- package/dist/source/class-source.d.ts +42 -0
- package/dist/source/class-source.js +883 -0
- package/dist/source/descriptor-utils.d.ts +6 -0
- package/dist/source/descriptor-utils.js +37 -0
- package/dist/source/file-access.d.ts +4 -0
- package/dist/source/file-access.js +102 -0
- package/dist/source/indexer.d.ts +82 -0
- package/dist/source/indexer.js +522 -0
- package/dist/source/lifecycle/diff-utils.d.ts +9 -0
- package/dist/source/lifecycle/diff-utils.js +107 -0
- package/dist/source/lifecycle/diff.d.ts +2 -0
- package/dist/source/lifecycle/diff.js +265 -0
- package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
- package/dist/source/lifecycle/mapping-helpers.js +327 -0
- package/dist/source/lifecycle/runtime-check.d.ts +2 -0
- package/dist/source/lifecycle/runtime-check.js +142 -0
- package/dist/source/lifecycle/trace.d.ts +2 -0
- package/dist/source/lifecycle/trace.js +231 -0
- package/dist/source/lifecycle.d.ts +4 -0
- package/dist/source/lifecycle.js +5 -0
- package/dist/source/search.d.ts +51 -0
- package/dist/source/search.js +676 -0
- package/dist/source/shared-utils.d.ts +6 -0
- package/dist/source/shared-utils.js +55 -0
- package/dist/source/state.d.ts +26 -0
- package/dist/source/state.js +24 -0
- package/dist/source/symbol-resolver.d.ts +3 -0
- package/dist/source/symbol-resolver.js +212 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
- package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
- package/dist/source/validate-mixin/pipeline/parse.js +10 -0
- package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
- package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
- package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
- package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
- package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
- package/dist/source/validate-mixin/pipeline-context.js +93 -0
- package/dist/source/validate-mixin.d.ts +22 -0
- package/dist/source/validate-mixin.js +799 -0
- package/dist/source/workspace-target.d.ts +18 -0
- package/dist/source/workspace-target.js +305 -0
- package/dist/source-resolver.d.ts +1 -0
- package/dist/source-resolver.js +1 -1
- package/dist/source-service.d.ts +164 -170
- package/dist/source-service.js +70 -6116
- package/dist/stage-emitter.d.ts +13 -0
- package/dist/stage-emitter.js +30 -0
- package/dist/stdio-supervisor.d.ts +61 -0
- package/dist/stdio-supervisor.js +326 -9
- package/dist/tool-contract-manifest.d.ts +1 -1
- package/dist/tool-contract-manifest.js +23 -6
- package/dist/tool-guidance.d.ts +82 -0
- package/dist/tool-guidance.js +734 -0
- package/dist/tool-schema-registry.d.ts +16 -0
- package/dist/tool-schema-registry.js +37 -0
- package/dist/tool-schemas.d.ts +3518 -0
- package/dist/tool-schemas.js +813 -0
- package/dist/types.d.ts +36 -0
- package/dist/version-service.js +7 -6
- package/dist/workspace-context-cache.d.ts +32 -0
- package/dist/workspace-context-cache.js +66 -0
- package/dist/workspace-mapping-service.d.ts +16 -0
- package/dist/workspace-mapping-service.js +173 -1
- package/docs/README-ja.md +416 -0
- package/docs/examples.md +483 -0
- package/docs/tool-reference.md +462 -0
- package/package.json +17 -4
package/dist/mixin-validator.js
CHANGED
|
@@ -1,1023 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Public barrel for the mixin validation engine. The implementation lives
|
|
3
|
+
* under `src/mixin/`; this file preserves the historical entry point used
|
|
4
|
+
* by source-service, entry tools, and tests.
|
|
4
5
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const MAPPING_WARNING_RE = /(?:mapping|remap|fallback|could not map)/i;
|
|
11
|
-
const CONFIG_WARNING_RE = /(?:version|gradle|jar\b|properties|project)/i;
|
|
12
|
-
const PARSE_WARNING_RE = /(?:could not parse|parse\s+warning|missing method attribute)/i;
|
|
13
|
-
function classifyStructuredWarning(message) {
|
|
14
|
-
return {
|
|
15
|
-
severity: MAPPING_WARNING_RE.test(message) ? "warning" : PARSE_WARNING_RE.test(message) ? "warning" : "info",
|
|
16
|
-
message,
|
|
17
|
-
category: MAPPING_WARNING_RE.test(message)
|
|
18
|
-
? "mapping"
|
|
19
|
-
: PARSE_WARNING_RE.test(message)
|
|
20
|
-
? "parse"
|
|
21
|
-
: CONFIG_WARNING_RE.test(message)
|
|
22
|
-
? "configuration"
|
|
23
|
-
: "validation"
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
/* ------------------------------------------------------------------ */
|
|
27
|
-
/* Levenshtein distance */
|
|
28
|
-
/* ------------------------------------------------------------------ */
|
|
29
|
-
export function levenshteinDistance(a, b) {
|
|
30
|
-
const la = a.length;
|
|
31
|
-
const lb = b.length;
|
|
32
|
-
if (la === 0)
|
|
33
|
-
return lb;
|
|
34
|
-
if (lb === 0)
|
|
35
|
-
return la;
|
|
36
|
-
// Single-row DP
|
|
37
|
-
const prev = new Array(lb + 1);
|
|
38
|
-
for (let j = 0; j <= lb; j++)
|
|
39
|
-
prev[j] = j;
|
|
40
|
-
for (let i = 1; i <= la; i++) {
|
|
41
|
-
let diagPrev = prev[0];
|
|
42
|
-
prev[0] = i;
|
|
43
|
-
for (let j = 1; j <= lb; j++) {
|
|
44
|
-
const temp = prev[j];
|
|
45
|
-
if (a[i - 1] === b[j - 1]) {
|
|
46
|
-
prev[j] = diagPrev;
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
prev[j] = 1 + Math.min(diagPrev, prev[j - 1], prev[j]);
|
|
50
|
-
}
|
|
51
|
-
diagPrev = temp;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return prev[lb];
|
|
55
|
-
}
|
|
56
|
-
export function suggestSimilar(name, candidates, maxDistance = 3, maxResults = 3) {
|
|
57
|
-
const normalizedName = name.toLowerCase();
|
|
58
|
-
const scored = [];
|
|
59
|
-
for (const candidate of candidates) {
|
|
60
|
-
const normalizedCandidate = candidate.toLowerCase();
|
|
61
|
-
if (Math.abs(normalizedName.length - normalizedCandidate.length) > maxDistance) {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
const distance = levenshteinDistance(normalizedName, normalizedCandidate);
|
|
65
|
-
if (distance <= maxDistance && distance > 0) {
|
|
66
|
-
scored.push({ candidate, distance });
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
scored.sort((a, b) => a.distance - b.distance);
|
|
70
|
-
return scored.slice(0, maxResults).map((s) => s.candidate);
|
|
71
|
-
}
|
|
72
|
-
/* ------------------------------------------------------------------ */
|
|
73
|
-
/* Method reference helpers */
|
|
74
|
-
/* ------------------------------------------------------------------ */
|
|
75
|
-
/**
|
|
76
|
-
* Strip owner prefix (`Lowner;`) and JVM descriptor (`(...)V`) from a
|
|
77
|
-
* Mixin method reference, returning just the method name.
|
|
78
|
-
*
|
|
79
|
-
* Examples:
|
|
80
|
-
* "playerTouch(Lnet/minecraft/world/entity/player/Player;)V" → "playerTouch"
|
|
81
|
-
* "Lnet/minecraft/SomeClass;tick(I)V" → "tick"
|
|
82
|
-
* "<init>" → "<init>"
|
|
83
|
-
* "<init>()V" → "<init>"
|
|
84
|
-
* "tick" → "tick"
|
|
85
|
-
*/
|
|
86
|
-
function stripOwnerPrefix(ref) {
|
|
87
|
-
if (!ref.startsWith("L"))
|
|
88
|
-
return ref;
|
|
89
|
-
const ownerEnd = ref.indexOf(";");
|
|
90
|
-
if (ownerEnd === -1)
|
|
91
|
-
return ref;
|
|
92
|
-
const parenIdx = ref.indexOf("(");
|
|
93
|
-
// Owner prefixes appear before the descriptor, e.g. Lpkg/Class;method(I)V.
|
|
94
|
-
// If ';' appears inside the descriptor, this is not an owner prefix.
|
|
95
|
-
if (parenIdx !== -1 && ownerEnd > parenIdx)
|
|
96
|
-
return ref;
|
|
97
|
-
return ref.substring(ownerEnd + 1);
|
|
98
|
-
}
|
|
99
|
-
export function extractMethodName(ref) {
|
|
100
|
-
let s = stripOwnerPrefix(ref);
|
|
101
|
-
// Remove descriptor: everything from '(' onwards
|
|
102
|
-
const parenIdx = s.indexOf("(");
|
|
103
|
-
if (parenIdx !== -1) {
|
|
104
|
-
s = s.substring(0, parenIdx);
|
|
105
|
-
}
|
|
106
|
-
return s;
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Extract the JVM descriptor portion from a method reference, if present.
|
|
110
|
-
*
|
|
111
|
-
* Examples:
|
|
112
|
-
* "playerTouch(Lnet/minecraft/world/entity/player/Player;)V" → "(Lnet/minecraft/world/entity/player/Player;)V"
|
|
113
|
-
* "tick" → undefined
|
|
114
|
-
*/
|
|
115
|
-
export function extractMethodDescriptor(ref) {
|
|
116
|
-
// After stripping optional owner prefix, find '('
|
|
117
|
-
const s = stripOwnerPrefix(ref);
|
|
118
|
-
const parenIdx = s.indexOf("(");
|
|
119
|
-
if (parenIdx !== -1) {
|
|
120
|
-
return s.substring(parenIdx);
|
|
121
|
-
}
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
/* ------------------------------------------------------------------ */
|
|
125
|
-
/* Mixin validation */
|
|
126
|
-
/* ------------------------------------------------------------------ */
|
|
127
|
-
function allMethodNames(members) {
|
|
128
|
-
return [
|
|
129
|
-
...members.constructors.map((m) => m.name),
|
|
130
|
-
...members.methods.map((m) => m.name)
|
|
131
|
-
];
|
|
132
|
-
}
|
|
133
|
-
function allFieldNames(members) {
|
|
134
|
-
return members.fields.map((m) => m.name);
|
|
135
|
-
}
|
|
136
|
-
function accessLevelFromFlags(accessFlags) {
|
|
137
|
-
if (accessFlags == null) {
|
|
138
|
-
return undefined;
|
|
139
|
-
}
|
|
140
|
-
if ((accessFlags & 0x0001) !== 0) {
|
|
141
|
-
return "public";
|
|
142
|
-
}
|
|
143
|
-
if ((accessFlags & 0x0004) !== 0) {
|
|
144
|
-
return "protected";
|
|
145
|
-
}
|
|
146
|
-
if ((accessFlags & 0x0002) !== 0) {
|
|
147
|
-
return "private";
|
|
148
|
-
}
|
|
149
|
-
return "package-private";
|
|
150
|
-
}
|
|
151
|
-
function computeFalsePositiveRisk(healthReport, resolutionPath, issueConfidence) {
|
|
152
|
-
if (!healthReport)
|
|
153
|
-
return undefined;
|
|
154
|
-
if (healthReport.overallHealthy === false) {
|
|
155
|
-
if (resolutionPath === "source-signature-unavailable" ||
|
|
156
|
-
resolutionPath === "target-mapping-failed" ||
|
|
157
|
-
resolutionPath === "member-remap-failed")
|
|
158
|
-
return "high";
|
|
159
|
-
if (issueConfidence === "uncertain")
|
|
160
|
-
return "medium";
|
|
161
|
-
return "medium";
|
|
162
|
-
}
|
|
163
|
-
if (healthReport.memberRemapAvailable === false) {
|
|
164
|
-
if (resolutionPath === "member-remap-failed")
|
|
165
|
-
return "high";
|
|
166
|
-
if (issueConfidence === "uncertain")
|
|
167
|
-
return "medium";
|
|
168
|
-
}
|
|
169
|
-
return undefined;
|
|
170
|
-
}
|
|
171
|
-
function computeConfidenceBreakdown(healthReport, provenance, remapFailureCount, skippedMemberCount) {
|
|
172
|
-
const baseScore = 100;
|
|
173
|
-
const penalties = [];
|
|
174
|
-
let score = baseScore;
|
|
175
|
-
if (healthReport) {
|
|
176
|
-
if (!healthReport.overallHealthy) {
|
|
177
|
-
penalties.push({ reason: "mapping-health", points: 30 });
|
|
178
|
-
score -= 30;
|
|
179
|
-
}
|
|
180
|
-
if (!healthReport.tinyMappingsAvailable) {
|
|
181
|
-
penalties.push({ reason: "tiny-mappings-unavailable", points: 20 });
|
|
182
|
-
score -= 20;
|
|
183
|
-
}
|
|
184
|
-
if (!healthReport.memberRemapAvailable) {
|
|
185
|
-
penalties.push({ reason: "member-remap-unavailable", points: 15 });
|
|
186
|
-
score -= 15;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
if (provenance?.scopeFallback) {
|
|
190
|
-
penalties.push({ reason: "scope-fallback", points: 10 });
|
|
191
|
-
score -= 10;
|
|
192
|
-
}
|
|
193
|
-
if (provenance && provenance.requestedMapping !== provenance.mappingApplied) {
|
|
194
|
-
penalties.push({ reason: "mapping-mismatch", points: 15 });
|
|
195
|
-
score -= 15;
|
|
196
|
-
}
|
|
197
|
-
if (skippedMemberCount > 0) {
|
|
198
|
-
penalties.push({ reason: "members-skipped", points: 25 });
|
|
199
|
-
score -= 25;
|
|
200
|
-
}
|
|
201
|
-
const remapPenalty = Math.min(remapFailureCount * 2, 20);
|
|
202
|
-
if (remapPenalty > 0) {
|
|
203
|
-
penalties.push({ reason: "remap-failures", points: remapPenalty });
|
|
204
|
-
score -= remapPenalty;
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
baseScore,
|
|
208
|
-
score: Math.max(score, 0),
|
|
209
|
-
penalties
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
function summarizeResolvedMembers(resolvedMembers) {
|
|
213
|
-
return {
|
|
214
|
-
membersValidated: resolvedMembers.filter((member) => member.status === "resolved").length,
|
|
215
|
-
membersSkipped: resolvedMembers.filter((member) => member.status === "skipped").length,
|
|
216
|
-
membersMissing: resolvedMembers.filter((member) => member.status === "not-found").length
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
function computeValidationStatus(summary) {
|
|
220
|
-
if (summary.errors > 0 || summary.definiteErrors > 0) {
|
|
221
|
-
return "invalid";
|
|
222
|
-
}
|
|
223
|
-
if (summary.warnings > 0 || summary.membersSkipped > 0) {
|
|
224
|
-
return "partial";
|
|
225
|
-
}
|
|
226
|
-
return "full";
|
|
227
|
-
}
|
|
228
|
-
function buildQuickSummary(status, summary, context) {
|
|
229
|
-
const base = status === "full"
|
|
230
|
-
? `${summary.membersValidated} member(s) validated successfully.`
|
|
231
|
-
: `${summary.definiteErrors} error(s), ${summary.uncertainErrors} uncertain, ${summary.warnings} warning(s). ${summary.membersValidated} validated, ${summary.membersSkipped} member(s) skipped, ${summary.membersMissing} member(s) missing.`;
|
|
232
|
-
const notes = [];
|
|
233
|
-
const scopeFallback = context?.provenance?.scopeFallback;
|
|
234
|
-
if (scopeFallback) {
|
|
235
|
-
notes.push(`Scope fell back from "${scopeFallback.requested}" to "${scopeFallback.applied}" (${scopeFallback.reason}).`);
|
|
236
|
-
}
|
|
237
|
-
const healthReport = context?.healthReport;
|
|
238
|
-
if (healthReport && !healthReport.overallHealthy) {
|
|
239
|
-
const degradations = healthReport.degradations.length > 0
|
|
240
|
-
? healthReport.degradations.join("; ")
|
|
241
|
-
: "mapping infrastructure degraded";
|
|
242
|
-
notes.push(`Mapping health degraded: ${degradations}.`);
|
|
243
|
-
}
|
|
244
|
-
return notes.length > 0 ? `${base} ${notes.join(" ")}` : base;
|
|
245
|
-
}
|
|
246
|
-
function addSkippedMembers(parsed, resolvedMembers) {
|
|
247
|
-
for (const inj of parsed.injections) {
|
|
248
|
-
resolvedMembers.push({
|
|
249
|
-
annotation: `@${inj.annotation}`,
|
|
250
|
-
name: extractMethodName(inj.method),
|
|
251
|
-
line: inj.line,
|
|
252
|
-
status: "skipped"
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
for (const shadow of parsed.shadows) {
|
|
256
|
-
resolvedMembers.push({
|
|
257
|
-
annotation: "@Shadow",
|
|
258
|
-
name: shadow.name,
|
|
259
|
-
line: shadow.line,
|
|
260
|
-
status: "skipped"
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
for (const accessor of parsed.accessors) {
|
|
264
|
-
resolvedMembers.push({
|
|
265
|
-
annotation: `@${accessor.annotation}`,
|
|
266
|
-
name: accessor.targetName,
|
|
267
|
-
line: accessor.line,
|
|
268
|
-
status: "skipped"
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
export function refreshMixinValidationOutcome(result) {
|
|
273
|
-
const memberSummary = result.resolvedMembers
|
|
274
|
-
? summarizeResolvedMembers(result.resolvedMembers)
|
|
275
|
-
: {
|
|
276
|
-
membersValidated: result.summary.membersValidated,
|
|
277
|
-
membersSkipped: result.summary.membersSkipped,
|
|
278
|
-
membersMissing: result.summary.membersMissing
|
|
279
|
-
};
|
|
280
|
-
result.summary = {
|
|
281
|
-
...result.summary,
|
|
282
|
-
...memberSummary
|
|
283
|
-
};
|
|
284
|
-
result.validationStatus = computeValidationStatus(result.summary);
|
|
285
|
-
result.valid = result.summary.definiteErrors === 0;
|
|
286
|
-
result.quickSummary = buildQuickSummary(result.validationStatus, result.summary, {
|
|
287
|
-
provenance: result.provenance,
|
|
288
|
-
healthReport: result.toolHealth
|
|
289
|
-
});
|
|
290
|
-
return result;
|
|
291
|
-
}
|
|
292
|
-
function validateInjection(inj, targetMembers, targetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport) {
|
|
293
|
-
for (const targetName of targetNames) {
|
|
294
|
-
const members = targetMembers.get(targetName);
|
|
295
|
-
if (!members)
|
|
296
|
-
continue;
|
|
297
|
-
const methodNames = allMethodNames(members);
|
|
298
|
-
// Strip owner prefix and JVM descriptor from the method reference
|
|
299
|
-
const methodName = extractMethodName(inj.method);
|
|
300
|
-
if (!methodNames.includes(methodName)) {
|
|
301
|
-
const suggestions = suggestSimilar(methodName, methodNames);
|
|
302
|
-
const descriptor = extractMethodDescriptor(inj.method);
|
|
303
|
-
const descriptorHint = descriptor ? ` (descriptor: ${descriptor})` : "";
|
|
304
|
-
// Determine if this is a remap artifact or signature unavailability
|
|
305
|
-
const isRemapFailed = remapFailedMembers?.get(targetName)?.has(methodName);
|
|
306
|
-
const isSigFailed = signatureFailedTargets?.has(targetName);
|
|
307
|
-
const issueConfidence = isRemapFailed ? "uncertain" : confidence;
|
|
308
|
-
const issueConfidenceReason = isRemapFailed
|
|
309
|
-
? `Member remap from obfuscated→mapping failed; name mismatch may be a remap artifact, not a true missing member.`
|
|
310
|
-
: confidenceReason;
|
|
311
|
-
const resolutionPath = isRemapFailed
|
|
312
|
-
? "member-remap-failed"
|
|
313
|
-
: isSigFailed ? "source-signature-unavailable" : undefined;
|
|
314
|
-
const memberDegraded = isRemapFailed && healthReport?.memberRemapAvailable === false;
|
|
315
|
-
issues.push({
|
|
316
|
-
severity: memberDegraded ? "warning" : "error",
|
|
317
|
-
kind: "method-not-found",
|
|
318
|
-
annotation: `@${inj.annotation}`,
|
|
319
|
-
target: `${targetName}#${inj.method}`,
|
|
320
|
-
message: `Method "${methodName}" not found in target class "${targetName}".${descriptorHint}${memberDegraded ? " (infrastructure degraded; may be false positive)" : ""}`,
|
|
321
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
322
|
-
line: inj.line,
|
|
323
|
-
confidence: issueConfidence,
|
|
324
|
-
confidenceReason: issueConfidenceReason,
|
|
325
|
-
resolutionPath,
|
|
326
|
-
falsePositiveRisk: computeFalsePositiveRisk(healthReport, resolutionPath, issueConfidence)
|
|
327
|
-
});
|
|
328
|
-
resolvedMembers.push({
|
|
329
|
-
annotation: `@${inj.annotation}`,
|
|
330
|
-
name: methodName,
|
|
331
|
-
line: inj.line,
|
|
332
|
-
status: "not-found"
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
resolvedMembers.push({
|
|
337
|
-
annotation: `@${inj.annotation}`,
|
|
338
|
-
name: methodName,
|
|
339
|
-
line: inj.line,
|
|
340
|
-
resolvedTo: `${targetName}#${methodName}`,
|
|
341
|
-
status: "resolved"
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
function validateShadow(shadow, targetMembers, targetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport) {
|
|
347
|
-
for (const targetName of targetNames) {
|
|
348
|
-
const members = targetMembers.get(targetName);
|
|
349
|
-
if (!members)
|
|
350
|
-
continue;
|
|
351
|
-
const isRemapFailed = remapFailedMembers?.get(targetName)?.has(shadow.name);
|
|
352
|
-
const isSigFailed = signatureFailedTargets?.has(targetName);
|
|
353
|
-
const issueConfidence = isRemapFailed ? "uncertain" : confidence;
|
|
354
|
-
const issueConfidenceReason = isRemapFailed
|
|
355
|
-
? `Member remap from obfuscated→mapping failed; name mismatch may be a remap artifact, not a true missing member.`
|
|
356
|
-
: confidenceReason;
|
|
357
|
-
const resolutionPath = isRemapFailed
|
|
358
|
-
? "member-remap-failed"
|
|
359
|
-
: isSigFailed ? "source-signature-unavailable" : undefined;
|
|
360
|
-
const memberDegraded = isRemapFailed && healthReport?.memberRemapAvailable === false;
|
|
361
|
-
if (shadow.kind === "field") {
|
|
362
|
-
const fieldNames = allFieldNames(members);
|
|
363
|
-
if (!fieldNames.includes(shadow.name)) {
|
|
364
|
-
const suggestions = suggestSimilar(shadow.name, fieldNames);
|
|
365
|
-
issues.push({
|
|
366
|
-
severity: memberDegraded ? "warning" : "error",
|
|
367
|
-
kind: "field-not-found",
|
|
368
|
-
annotation: "@Shadow",
|
|
369
|
-
target: `${targetName}#${shadow.name}`,
|
|
370
|
-
message: `Field "${shadow.name}" not found in target class "${targetName}" (${fieldNames.length} field(s) available).${memberDegraded ? " (infrastructure degraded; may be false positive)" : ""}`,
|
|
371
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
372
|
-
line: shadow.line,
|
|
373
|
-
confidence: issueConfidence,
|
|
374
|
-
confidenceReason: issueConfidenceReason,
|
|
375
|
-
resolutionPath,
|
|
376
|
-
falsePositiveRisk: computeFalsePositiveRisk(healthReport, resolutionPath, issueConfidence)
|
|
377
|
-
});
|
|
378
|
-
resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, status: "not-found" });
|
|
379
|
-
}
|
|
380
|
-
else {
|
|
381
|
-
resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, resolvedTo: `${targetName}#${shadow.name}`, status: "resolved" });
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
const methodNames = allMethodNames(members);
|
|
386
|
-
if (!methodNames.includes(shadow.name)) {
|
|
387
|
-
const suggestions = suggestSimilar(shadow.name, methodNames);
|
|
388
|
-
issues.push({
|
|
389
|
-
severity: memberDegraded ? "warning" : "error",
|
|
390
|
-
kind: "method-not-found",
|
|
391
|
-
annotation: "@Shadow",
|
|
392
|
-
target: `${targetName}#${shadow.name}`,
|
|
393
|
-
message: `Method "${shadow.name}" not found in target class "${targetName}" (${methodNames.length} method(s) available).${memberDegraded ? " (infrastructure degraded; may be false positive)" : ""}`,
|
|
394
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
395
|
-
line: shadow.line,
|
|
396
|
-
confidence: issueConfidence,
|
|
397
|
-
confidenceReason: issueConfidenceReason,
|
|
398
|
-
resolutionPath,
|
|
399
|
-
falsePositiveRisk: computeFalsePositiveRisk(healthReport, resolutionPath, issueConfidence)
|
|
400
|
-
});
|
|
401
|
-
resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, status: "not-found" });
|
|
402
|
-
}
|
|
403
|
-
else {
|
|
404
|
-
resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, resolvedTo: `${targetName}#${shadow.name}`, status: "resolved" });
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
function validateAccessor(accessor, targetMembers, targetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport) {
|
|
410
|
-
for (const targetName of targetNames) {
|
|
411
|
-
const members = targetMembers.get(targetName);
|
|
412
|
-
if (!members)
|
|
413
|
-
continue;
|
|
414
|
-
const candidateNames = accessor.annotation === "Invoker"
|
|
415
|
-
? allMethodNames(members)
|
|
416
|
-
: allFieldNames(members);
|
|
417
|
-
if (!candidateNames.includes(accessor.targetName)) {
|
|
418
|
-
const isRemapFailed = remapFailedMembers?.get(targetName)?.has(accessor.targetName);
|
|
419
|
-
const isSigFailed = signatureFailedTargets?.has(targetName);
|
|
420
|
-
const issueConfidence = isRemapFailed ? "uncertain" : confidence;
|
|
421
|
-
const issueConfidenceReason = isRemapFailed
|
|
422
|
-
? `Member remap from obfuscated→mapping failed; name mismatch may be a remap artifact, not a true missing member.`
|
|
423
|
-
: confidenceReason;
|
|
424
|
-
const resolutionPath = isRemapFailed
|
|
425
|
-
? "member-remap-failed"
|
|
426
|
-
: isSigFailed ? "source-signature-unavailable" : undefined;
|
|
427
|
-
const memberDegraded = isRemapFailed && healthReport?.memberRemapAvailable === false;
|
|
428
|
-
const suggestions = suggestSimilar(accessor.targetName, candidateNames);
|
|
429
|
-
const inferenceHint = accessor.targetName !== accessor.name
|
|
430
|
-
? ` (inferred "${accessor.targetName}" from "${accessor.name}" via prefix removal)`
|
|
431
|
-
: "";
|
|
432
|
-
issues.push({
|
|
433
|
-
severity: memberDegraded ? "warning" : "error",
|
|
434
|
-
kind: accessor.annotation === "Invoker" ? "method-not-found" : "field-not-found",
|
|
435
|
-
annotation: `@${accessor.annotation}`,
|
|
436
|
-
target: `${targetName}#${accessor.targetName}`,
|
|
437
|
-
message: `Target "${accessor.targetName}" not found in class "${targetName}".${inferenceHint}${memberDegraded ? " (infrastructure degraded; may be false positive)" : ""}`,
|
|
438
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
439
|
-
line: accessor.line,
|
|
440
|
-
confidence: issueConfidence,
|
|
441
|
-
confidenceReason: issueConfidenceReason,
|
|
442
|
-
resolutionPath,
|
|
443
|
-
falsePositiveRisk: computeFalsePositiveRisk(healthReport, resolutionPath, issueConfidence)
|
|
444
|
-
});
|
|
445
|
-
resolvedMembers.push({ annotation: `@${accessor.annotation}`, name: accessor.targetName, line: accessor.line, status: "not-found" });
|
|
446
|
-
}
|
|
447
|
-
else {
|
|
448
|
-
resolvedMembers.push({ annotation: `@${accessor.annotation}`, name: accessor.targetName, line: accessor.line, resolvedTo: `${targetName}#${accessor.targetName}`, status: "resolved" });
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
export function validateParsedMixin(parsed, targetMembers, warnings, provenance, confidence, mappingFailedTargets, explain, remapFailedMembers, signatureFailedTargets, suggestedCallContext, warningMode, healthReport, symbolExistsButSignatureFailed) {
|
|
453
|
-
const issues = [];
|
|
454
|
-
const targetNames = parsed.targets.map((t) => t.className);
|
|
455
|
-
const confidenceReason = confidence === "uncertain"
|
|
456
|
-
? `Mapping fallback: requested "${provenance?.requestedMapping}" but applied "${provenance?.mappingApplied}".`
|
|
457
|
-
: confidence === "likely"
|
|
458
|
-
? "Some members could not be remapped."
|
|
459
|
-
: undefined;
|
|
460
|
-
const resolvedMembers = [];
|
|
461
|
-
// Check target classes exist
|
|
462
|
-
for (const target of parsed.targets) {
|
|
463
|
-
if (!targetMembers.has(target.className)) {
|
|
464
|
-
if (mappingFailedTargets?.has(target.className)) {
|
|
465
|
-
// Mapping failure — report as warning with distinct kind
|
|
466
|
-
issues.push({
|
|
467
|
-
severity: "warning",
|
|
468
|
-
kind: "target-mapping-failed",
|
|
469
|
-
annotation: "@Mixin",
|
|
470
|
-
target: target.className,
|
|
471
|
-
message: `Could not map target class "${target.className}" to obfuscated namespace; class may still exist under a different mapping.`,
|
|
472
|
-
confidence: "uncertain",
|
|
473
|
-
confidenceReason: `Mapping from "${provenance?.requestedMapping}" to obfuscated failed for this class.`,
|
|
474
|
-
category: "mapping",
|
|
475
|
-
resolutionPath: "target-mapping-failed",
|
|
476
|
-
falsePositiveRisk: healthReport?.overallHealthy === false ? "high" : "medium"
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
else if (symbolExistsButSignatureFailed?.has(target.className)) {
|
|
480
|
-
// Symbol exists in mapping graph but getSignature failed — tool limitation, not code issue
|
|
481
|
-
issues.push({
|
|
482
|
-
severity: "warning",
|
|
483
|
-
kind: "validation-incomplete",
|
|
484
|
-
annotation: "@Mixin",
|
|
485
|
-
target: target.className,
|
|
486
|
-
message: `Target class "${target.className}" exists in mapping data but could not be loaded from game jar (tool limitation). Members not validated.`,
|
|
487
|
-
confidence: "uncertain",
|
|
488
|
-
confidenceReason: "Class exists in mapping graph but bytecode signature extraction failed.",
|
|
489
|
-
category: "resolution",
|
|
490
|
-
resolutionPath: "source-signature-unavailable",
|
|
491
|
-
issueOrigin: "tool_issue",
|
|
492
|
-
falsePositiveRisk: "high"
|
|
493
|
-
});
|
|
494
|
-
addSkippedMembers(parsed, resolvedMembers);
|
|
495
|
-
}
|
|
496
|
-
else if (signatureFailedTargets?.has(target.className)) {
|
|
497
|
-
issues.push({
|
|
498
|
-
severity: "warning",
|
|
499
|
-
kind: "validation-incomplete",
|
|
500
|
-
annotation: "@Mixin",
|
|
501
|
-
target: target.className,
|
|
502
|
-
message: `Target class "${target.className}" could not load enough target metadata for reliable validation. Members were not validated.`,
|
|
503
|
-
confidence: "uncertain",
|
|
504
|
-
confidenceReason: "Target bytecode could not be loaded and fallback existence checks were unavailable.",
|
|
505
|
-
category: "resolution",
|
|
506
|
-
resolutionPath: "source-signature-unavailable",
|
|
507
|
-
issueOrigin: "tool_issue",
|
|
508
|
-
falsePositiveRisk: "high"
|
|
509
|
-
});
|
|
510
|
-
addSkippedMembers(parsed, resolvedMembers);
|
|
511
|
-
}
|
|
512
|
-
else {
|
|
513
|
-
issues.push({
|
|
514
|
-
severity: "error",
|
|
515
|
-
kind: "target-not-found",
|
|
516
|
-
annotation: "@Mixin",
|
|
517
|
-
target: target.className,
|
|
518
|
-
message: `Target class "${target.className}" not found in game jar.`,
|
|
519
|
-
confidence,
|
|
520
|
-
confidenceReason,
|
|
521
|
-
category: "validation",
|
|
522
|
-
resolutionPath: "target-class-missing"
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
// Only validate members against targets that were resolved
|
|
528
|
-
const resolvedTargetNames = targetNames.filter((t) => targetMembers.has(t));
|
|
529
|
-
for (const inj of parsed.injections) {
|
|
530
|
-
validateInjection(inj, targetMembers, resolvedTargetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
|
|
531
|
-
}
|
|
532
|
-
for (const shadow of parsed.shadows) {
|
|
533
|
-
validateShadow(shadow, targetMembers, resolvedTargetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
|
|
534
|
-
}
|
|
535
|
-
for (const accessor of parsed.accessors) {
|
|
536
|
-
validateAccessor(accessor, targetMembers, resolvedTargetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
|
|
537
|
-
}
|
|
538
|
-
// Add parse warnings — escalate @Accessor/@Invoker/@Shadow parse failures to issues
|
|
539
|
-
for (const pw of parsed.parseWarnings) {
|
|
540
|
-
if (/@(Accessor|Invoker|Shadow)\b/.test(pw)) {
|
|
541
|
-
const annotation = pw.includes("@Accessor") ? "@Accessor"
|
|
542
|
-
: pw.includes("@Invoker") ? "@Invoker" : "@Shadow";
|
|
543
|
-
issues.push({
|
|
544
|
-
severity: "warning",
|
|
545
|
-
kind: "unknown-annotation",
|
|
546
|
-
annotation,
|
|
547
|
-
target: parsed.className,
|
|
548
|
-
message: pw,
|
|
549
|
-
confidence: "uncertain",
|
|
550
|
-
confidenceReason: "Parser could not extract member declaration; the annotation may be valid.",
|
|
551
|
-
category: "parse",
|
|
552
|
-
issueOrigin: "parser_limitation",
|
|
553
|
-
falsePositiveRisk: "high"
|
|
554
|
-
});
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
warnings.push(pw);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
// Contradiction detection: if some same-annotation members resolved OK but parse failed for others, note it
|
|
561
|
-
const resolvedAnnotations = new Set();
|
|
562
|
-
for (const member of resolvedMembers) {
|
|
563
|
-
if (member.status === "resolved") {
|
|
564
|
-
resolvedAnnotations.add(member.annotation);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
let errorCount = 0;
|
|
568
|
-
let warningCount = 0;
|
|
569
|
-
let definiteErrors = 0;
|
|
570
|
-
let uncertainErrors = 0;
|
|
571
|
-
let resolutionErrors = 0;
|
|
572
|
-
let parseWarningCount = 0;
|
|
573
|
-
for (const issue of issues) {
|
|
574
|
-
if (issue.category === "parse" && resolvedAnnotations.has(issue.annotation)) {
|
|
575
|
-
issue.message += " (Note: other members with the same annotation resolved successfully.)";
|
|
576
|
-
}
|
|
577
|
-
if (!issue.category) {
|
|
578
|
-
issue.category = issue.resolutionPath ? "resolution" : "validation";
|
|
579
|
-
}
|
|
580
|
-
if (!issue.issueOrigin) {
|
|
581
|
-
if (issue.category === "parse") {
|
|
582
|
-
issue.issueOrigin = "parser_limitation";
|
|
583
|
-
}
|
|
584
|
-
else {
|
|
585
|
-
issue.issueOrigin = issue.resolutionPath && TOOL_RESOLUTION_PATHS.includes(issue.resolutionPath)
|
|
586
|
-
? "tool_issue"
|
|
587
|
-
: "code_issue";
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
if (issue.severity === "error") {
|
|
591
|
-
errorCount++;
|
|
592
|
-
if (issue.confidence === "uncertain") {
|
|
593
|
-
uncertainErrors++;
|
|
594
|
-
}
|
|
595
|
-
else {
|
|
596
|
-
definiteErrors++;
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
else {
|
|
600
|
-
warningCount++;
|
|
601
|
-
}
|
|
602
|
-
if (issue.resolutionPath != null) {
|
|
603
|
-
resolutionErrors++;
|
|
604
|
-
}
|
|
605
|
-
if (issue.category === "parse") {
|
|
606
|
-
parseWarningCount++;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
// Enrich issues with explanations and suggested calls when explain=true
|
|
610
|
-
if (explain) {
|
|
611
|
-
const version = provenance?.version;
|
|
612
|
-
const mapping = provenance?.requestedMapping;
|
|
613
|
-
const symbolLookupContext = {};
|
|
614
|
-
if (suggestedCallContext?.sourcePriority) {
|
|
615
|
-
symbolLookupContext.sourcePriority = suggestedCallContext.sourcePriority;
|
|
616
|
-
}
|
|
617
|
-
const classSourceContext = {};
|
|
618
|
-
if (suggestedCallContext?.scope)
|
|
619
|
-
classSourceContext.scope = suggestedCallContext.scope;
|
|
620
|
-
if (suggestedCallContext?.sourcePriority)
|
|
621
|
-
classSourceContext.sourcePriority = suggestedCallContext.sourcePriority;
|
|
622
|
-
if (suggestedCallContext?.projectPath)
|
|
623
|
-
classSourceContext.projectPath = suggestedCallContext.projectPath;
|
|
624
|
-
if (suggestedCallContext?.mapping)
|
|
625
|
-
classSourceContext.mapping = suggestedCallContext.mapping;
|
|
626
|
-
for (const issue of issues) {
|
|
627
|
-
switch (issue.kind) {
|
|
628
|
-
case "target-not-found":
|
|
629
|
-
issue.explanation = `The class "${issue.target}" was not found in the game jar. It may be misspelled, from a different version, or use a different mapping namespace.`;
|
|
630
|
-
if (version && mapping) {
|
|
631
|
-
issue.suggestedCall = {
|
|
632
|
-
tool: "check-symbol-exists",
|
|
633
|
-
params: { kind: "class", name: issue.target, version, sourceMapping: mapping, nameMode: "auto", ...symbolLookupContext }
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
break;
|
|
637
|
-
case "validation-incomplete":
|
|
638
|
-
issue.explanation = `Target metadata for "${issue.target}" could not be loaded reliably, so validation was only partial. This usually indicates a tool or environment limitation rather than a confirmed code error.`;
|
|
639
|
-
if (version) {
|
|
640
|
-
issue.suggestedCall = {
|
|
641
|
-
tool: "get-class-source",
|
|
642
|
-
params: {
|
|
643
|
-
className: issue.target,
|
|
644
|
-
target: { type: "resolve", kind: "version", value: version },
|
|
645
|
-
...(mapping ? { mapping } : {}),
|
|
646
|
-
mode: "metadata",
|
|
647
|
-
...classSourceContext
|
|
648
|
-
}
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
break;
|
|
652
|
-
case "target-mapping-failed":
|
|
653
|
-
issue.explanation = `Mapping lookup failed for "${issue.target}". The class may exist under a different name in the target namespace.`;
|
|
654
|
-
if (version && mapping) {
|
|
655
|
-
issue.suggestedCall = {
|
|
656
|
-
tool: "check-symbol-exists",
|
|
657
|
-
params: { kind: "class", name: issue.target, version, sourceMapping: mapping, nameMode: "auto", ...symbolLookupContext }
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
break;
|
|
661
|
-
case "method-not-found": {
|
|
662
|
-
const parts = issue.target.split("#");
|
|
663
|
-
const className = parts[0] ?? issue.target;
|
|
664
|
-
issue.explanation = `The method was not found in the target class. It may be named differently in the current mapping, or might not exist in this version.`;
|
|
665
|
-
if (version) {
|
|
666
|
-
issue.suggestedCall = {
|
|
667
|
-
tool: "get-class-source",
|
|
668
|
-
params: {
|
|
669
|
-
className,
|
|
670
|
-
target: { type: "resolve", kind: "version", value: version },
|
|
671
|
-
...(mapping ? { mapping } : {}),
|
|
672
|
-
mode: "metadata",
|
|
673
|
-
...classSourceContext
|
|
674
|
-
}
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
break;
|
|
678
|
-
}
|
|
679
|
-
case "field-not-found": {
|
|
680
|
-
const parts = issue.target.split("#");
|
|
681
|
-
const ownerName = parts[0] ?? issue.target;
|
|
682
|
-
const fieldName = parts[1] ?? issue.target;
|
|
683
|
-
issue.explanation = `The field "${fieldName}" was not found in the target class. Verify the field name matches the expected mapping namespace.`;
|
|
684
|
-
if (version && mapping) {
|
|
685
|
-
issue.suggestedCall = {
|
|
686
|
-
tool: "check-symbol-exists",
|
|
687
|
-
params: { kind: "field", owner: ownerName, name: fieldName, version, sourceMapping: mapping, ...symbolLookupContext }
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
break;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
const structuredWarnings = warnings.map(classifyStructuredWarning);
|
|
696
|
-
// Warning aggregation mode
|
|
697
|
-
let aggregatedWarnings;
|
|
698
|
-
let outputWarnings = warnings;
|
|
699
|
-
let outputStructuredWarnings = structuredWarnings.length > 0 ? structuredWarnings : undefined;
|
|
700
|
-
if (warningMode === "aggregated" && structuredWarnings.length > 0) {
|
|
701
|
-
const groupMap = new Map();
|
|
702
|
-
for (const sw of structuredWarnings) {
|
|
703
|
-
const cat = sw.category ?? "validation";
|
|
704
|
-
const existing = groupMap.get(cat);
|
|
705
|
-
if (existing) {
|
|
706
|
-
existing.count++;
|
|
707
|
-
if (existing.samples.length < 2) {
|
|
708
|
-
existing.samples.push(sw.message);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
else {
|
|
712
|
-
groupMap.set(cat, { count: 1, samples: [sw.message] });
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
aggregatedWarnings = [...groupMap.entries()].map(([category, { count, samples }]) => ({
|
|
716
|
-
category,
|
|
717
|
-
count,
|
|
718
|
-
samples
|
|
719
|
-
}));
|
|
720
|
-
outputWarnings = [];
|
|
721
|
-
outputStructuredWarnings = undefined;
|
|
722
|
-
}
|
|
723
|
-
// Compute confidence score
|
|
724
|
-
const remapFailureCount = provenance?.remapFailures ?? 0;
|
|
725
|
-
const memberSummary = summarizeResolvedMembers(resolvedMembers);
|
|
726
|
-
const confidenceBreakdown = healthReport
|
|
727
|
-
? computeConfidenceBreakdown(healthReport, provenance, remapFailureCount, memberSummary.membersSkipped)
|
|
728
|
-
: undefined;
|
|
729
|
-
const confidenceScore = confidenceBreakdown?.score;
|
|
730
|
-
const total = parsed.injections.length + parsed.shadows.length + parsed.accessors.length;
|
|
731
|
-
const summary = {
|
|
732
|
-
injections: parsed.injections.length,
|
|
733
|
-
shadows: parsed.shadows.length,
|
|
734
|
-
accessors: parsed.accessors.length,
|
|
735
|
-
total,
|
|
736
|
-
...memberSummary,
|
|
737
|
-
errors: errorCount,
|
|
738
|
-
warnings: warningCount,
|
|
739
|
-
definiteErrors,
|
|
740
|
-
uncertainErrors,
|
|
741
|
-
resolutionErrors,
|
|
742
|
-
parseWarnings: parseWarningCount
|
|
743
|
-
};
|
|
744
|
-
const validationStatus = computeValidationStatus(summary);
|
|
745
|
-
const quickSummary = buildQuickSummary(validationStatus, summary, { provenance, healthReport });
|
|
746
|
-
return {
|
|
747
|
-
className: parsed.className,
|
|
748
|
-
targets: targetNames,
|
|
749
|
-
priority: parsed.priority,
|
|
750
|
-
valid: definiteErrors === 0,
|
|
751
|
-
validationStatus,
|
|
752
|
-
issues,
|
|
753
|
-
summary,
|
|
754
|
-
provenance,
|
|
755
|
-
warnings: outputWarnings,
|
|
756
|
-
structuredWarnings: outputStructuredWarnings,
|
|
757
|
-
aggregatedWarnings,
|
|
758
|
-
resolvedMembers: resolvedMembers.length > 0 ? resolvedMembers : undefined,
|
|
759
|
-
toolHealth: healthReport,
|
|
760
|
-
confidenceScore,
|
|
761
|
-
confidenceBreakdown,
|
|
762
|
-
quickSummary
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
/* ------------------------------------------------------------------ */
|
|
766
|
-
/* Access Widener validation */
|
|
767
|
-
/* ------------------------------------------------------------------ */
|
|
768
|
-
export function validateParsedAccessWidener(parsed, membersByClass, warnings, options) {
|
|
769
|
-
warnings.push(...parsed.parseWarnings);
|
|
770
|
-
const validatedEntries = [];
|
|
771
|
-
let validCount = 0;
|
|
772
|
-
let invalidCount = 0;
|
|
773
|
-
for (const entry of parsed.entries) {
|
|
774
|
-
const ownerFqn = entry.target.replace(/\//g, ".");
|
|
775
|
-
if (entry.targetKind === "class") {
|
|
776
|
-
const members = membersByClass.get(ownerFqn);
|
|
777
|
-
if (members) {
|
|
778
|
-
const runtimeAccess = accessLevelFromFlags(members.classAccessFlags);
|
|
779
|
-
validatedEntries.push({
|
|
780
|
-
...entry,
|
|
781
|
-
valid: true,
|
|
782
|
-
...(options?.includeRuntimeEvidence
|
|
783
|
-
? {
|
|
784
|
-
resolvedInRuntime: true,
|
|
785
|
-
...(runtimeAccess
|
|
786
|
-
? { resolvedRuntimeAccess: runtimeAccess }
|
|
787
|
-
: {})
|
|
788
|
-
}
|
|
789
|
-
: {})
|
|
790
|
-
});
|
|
791
|
-
validCount++;
|
|
792
|
-
}
|
|
793
|
-
else {
|
|
794
|
-
validatedEntries.push({
|
|
795
|
-
...entry,
|
|
796
|
-
valid: false,
|
|
797
|
-
issue: `Class "${ownerFqn}" not found in game jar.`,
|
|
798
|
-
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
799
|
-
});
|
|
800
|
-
invalidCount++;
|
|
801
|
-
}
|
|
802
|
-
continue;
|
|
803
|
-
}
|
|
804
|
-
// method or field
|
|
805
|
-
const members = membersByClass.get(ownerFqn);
|
|
806
|
-
if (!members) {
|
|
807
|
-
validatedEntries.push({
|
|
808
|
-
...entry,
|
|
809
|
-
valid: false,
|
|
810
|
-
issue: `Owner class "${ownerFqn}" not found in game jar.`,
|
|
811
|
-
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
812
|
-
});
|
|
813
|
-
invalidCount++;
|
|
814
|
-
continue;
|
|
815
|
-
}
|
|
816
|
-
if (entry.targetKind === "method") {
|
|
817
|
-
const methodNames = allMethodNames(members);
|
|
818
|
-
const matchedMember = members.methods.find((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor)) ?? members.constructors.find((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
|
|
819
|
-
const found = matchedMember != null;
|
|
820
|
-
if (found) {
|
|
821
|
-
const runtimeMember = matchedMember;
|
|
822
|
-
const runtimeAccess = accessLevelFromFlags(runtimeMember.accessFlags);
|
|
823
|
-
validatedEntries.push({
|
|
824
|
-
...entry,
|
|
825
|
-
valid: true,
|
|
826
|
-
...(options?.includeRuntimeEvidence
|
|
827
|
-
? {
|
|
828
|
-
resolvedInRuntime: true,
|
|
829
|
-
...(runtimeAccess
|
|
830
|
-
? { resolvedRuntimeAccess: runtimeAccess }
|
|
831
|
-
: {}),
|
|
832
|
-
...(runtimeMember.jvmDescriptor
|
|
833
|
-
? { resolvedRuntimeJvmDescriptor: runtimeMember.jvmDescriptor }
|
|
834
|
-
: {}),
|
|
835
|
-
...(runtimeMember.javaSignature
|
|
836
|
-
? { resolvedRuntimeJavaSignature: runtimeMember.javaSignature }
|
|
837
|
-
: {})
|
|
838
|
-
}
|
|
839
|
-
: {})
|
|
840
|
-
});
|
|
841
|
-
validCount++;
|
|
842
|
-
}
|
|
843
|
-
else {
|
|
844
|
-
const suggestions = entry.name ? suggestSimilar(entry.name, methodNames) : [];
|
|
845
|
-
validatedEntries.push({
|
|
846
|
-
...entry,
|
|
847
|
-
valid: false,
|
|
848
|
-
issue: `Method "${entry.name}" not found in class "${ownerFqn}".`,
|
|
849
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
850
|
-
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
851
|
-
});
|
|
852
|
-
invalidCount++;
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
else {
|
|
856
|
-
// field
|
|
857
|
-
const fieldNames = allFieldNames(members);
|
|
858
|
-
const matchedMember = members.fields.find((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
|
|
859
|
-
const found = matchedMember != null;
|
|
860
|
-
if (found) {
|
|
861
|
-
const runtimeMember = matchedMember;
|
|
862
|
-
const runtimeAccess = accessLevelFromFlags(runtimeMember.accessFlags);
|
|
863
|
-
validatedEntries.push({
|
|
864
|
-
...entry,
|
|
865
|
-
valid: true,
|
|
866
|
-
...(options?.includeRuntimeEvidence
|
|
867
|
-
? {
|
|
868
|
-
resolvedInRuntime: true,
|
|
869
|
-
...(runtimeAccess
|
|
870
|
-
? { resolvedRuntimeAccess: runtimeAccess }
|
|
871
|
-
: {}),
|
|
872
|
-
...(runtimeMember.jvmDescriptor
|
|
873
|
-
? { resolvedRuntimeJvmDescriptor: runtimeMember.jvmDescriptor }
|
|
874
|
-
: {}),
|
|
875
|
-
...(runtimeMember.javaSignature
|
|
876
|
-
? { resolvedRuntimeJavaSignature: runtimeMember.javaSignature }
|
|
877
|
-
: {})
|
|
878
|
-
}
|
|
879
|
-
: {})
|
|
880
|
-
});
|
|
881
|
-
validCount++;
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
const suggestions = entry.name ? suggestSimilar(entry.name, fieldNames) : [];
|
|
885
|
-
validatedEntries.push({
|
|
886
|
-
...entry,
|
|
887
|
-
valid: false,
|
|
888
|
-
issue: `Field "${entry.name}" not found in class "${ownerFqn}".`,
|
|
889
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
890
|
-
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
891
|
-
});
|
|
892
|
-
invalidCount++;
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
return {
|
|
897
|
-
headerVersion: parsed.headerVersion,
|
|
898
|
-
namespace: parsed.namespace,
|
|
899
|
-
valid: invalidCount === 0,
|
|
900
|
-
entries: validatedEntries,
|
|
901
|
-
summary: {
|
|
902
|
-
total: parsed.entries.length,
|
|
903
|
-
valid: validCount,
|
|
904
|
-
invalid: invalidCount
|
|
905
|
-
},
|
|
906
|
-
warnings
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
export function validateParsedAccessTransformer(parsed, membersByClass, warnings, options) {
|
|
910
|
-
warnings.push(...parsed.parseWarnings);
|
|
911
|
-
const validatedEntries = [];
|
|
912
|
-
let validCount = 0;
|
|
913
|
-
let invalidCount = 0;
|
|
914
|
-
for (const entry of parsed.entries) {
|
|
915
|
-
const ownerFqn = entry.owner;
|
|
916
|
-
const members = membersByClass.get(ownerFqn);
|
|
917
|
-
if (entry.targetKind === "class") {
|
|
918
|
-
if (!members) {
|
|
919
|
-
validatedEntries.push({
|
|
920
|
-
...entry,
|
|
921
|
-
valid: false,
|
|
922
|
-
issue: `Class "${ownerFqn}" not found in runtime jar.`,
|
|
923
|
-
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
924
|
-
});
|
|
925
|
-
invalidCount++;
|
|
926
|
-
continue;
|
|
927
|
-
}
|
|
928
|
-
const runtimeAccess = accessLevelFromFlags(members.classAccessFlags);
|
|
929
|
-
validatedEntries.push({
|
|
930
|
-
...entry,
|
|
931
|
-
valid: true,
|
|
932
|
-
...(options?.includeRuntimeEvidence
|
|
933
|
-
? {
|
|
934
|
-
resolvedInRuntime: true,
|
|
935
|
-
...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {})
|
|
936
|
-
}
|
|
937
|
-
: {})
|
|
938
|
-
});
|
|
939
|
-
validCount++;
|
|
940
|
-
continue;
|
|
941
|
-
}
|
|
942
|
-
if (!members) {
|
|
943
|
-
validatedEntries.push({
|
|
944
|
-
...entry,
|
|
945
|
-
valid: false,
|
|
946
|
-
issue: `Owner class "${ownerFqn}" not found in runtime jar.`,
|
|
947
|
-
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
948
|
-
});
|
|
949
|
-
invalidCount++;
|
|
950
|
-
continue;
|
|
951
|
-
}
|
|
952
|
-
if (entry.targetKind === "field") {
|
|
953
|
-
const fieldNames = allFieldNames(members);
|
|
954
|
-
const matchedField = members.fields.find((member) => member.name === entry.name);
|
|
955
|
-
if (!matchedField) {
|
|
956
|
-
const suggestions = entry.name ? suggestSimilar(entry.name, fieldNames) : [];
|
|
957
|
-
validatedEntries.push({
|
|
958
|
-
...entry,
|
|
959
|
-
valid: false,
|
|
960
|
-
issue: `Field "${entry.name}" not found in class "${ownerFqn}".`,
|
|
961
|
-
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
962
|
-
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
963
|
-
});
|
|
964
|
-
invalidCount++;
|
|
965
|
-
continue;
|
|
966
|
-
}
|
|
967
|
-
const runtimeAccess = accessLevelFromFlags(matchedField.accessFlags);
|
|
968
|
-
validatedEntries.push({
|
|
969
|
-
...entry,
|
|
970
|
-
valid: true,
|
|
971
|
-
...(options?.includeRuntimeEvidence
|
|
972
|
-
? {
|
|
973
|
-
resolvedInRuntime: true,
|
|
974
|
-
...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {}),
|
|
975
|
-
...(matchedField.jvmDescriptor ? { resolvedRuntimeJvmDescriptor: matchedField.jvmDescriptor } : {}),
|
|
976
|
-
...(matchedField.javaSignature ? { resolvedRuntimeJavaSignature: matchedField.javaSignature } : {})
|
|
977
|
-
}
|
|
978
|
-
: {})
|
|
979
|
-
});
|
|
980
|
-
validCount++;
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
|
-
const methodNames = allMethodNames(members);
|
|
984
|
-
const matchedMethod = members.methods.find((member) => member.name === entry.name && member.jvmDescriptor === entry.descriptor) ?? members.constructors.find((member) => member.name === entry.name && member.jvmDescriptor === entry.descriptor);
|
|
985
|
-
if (!matchedMethod) {
|
|
986
|
-
const suggestions = entry.name ? suggestSimilar(entry.name, methodNames) : [];
|
|
987
|
-
validatedEntries.push({
|
|
988
|
-
...entry,
|
|
989
|
-
valid: false,
|
|
990
|
-
issue: `Method "${entry.name}" not found in class "${ownerFqn}".`,
|
|
991
|
-
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
992
|
-
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
993
|
-
});
|
|
994
|
-
invalidCount++;
|
|
995
|
-
continue;
|
|
996
|
-
}
|
|
997
|
-
const runtimeAccess = accessLevelFromFlags(matchedMethod.accessFlags);
|
|
998
|
-
validatedEntries.push({
|
|
999
|
-
...entry,
|
|
1000
|
-
valid: true,
|
|
1001
|
-
...(options?.includeRuntimeEvidence
|
|
1002
|
-
? {
|
|
1003
|
-
resolvedInRuntime: true,
|
|
1004
|
-
...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {}),
|
|
1005
|
-
...(matchedMethod.jvmDescriptor ? { resolvedRuntimeJvmDescriptor: matchedMethod.jvmDescriptor } : {}),
|
|
1006
|
-
...(matchedMethod.javaSignature ? { resolvedRuntimeJavaSignature: matchedMethod.javaSignature } : {})
|
|
1007
|
-
}
|
|
1008
|
-
: {})
|
|
1009
|
-
});
|
|
1010
|
-
validCount++;
|
|
1011
|
-
}
|
|
1012
|
-
return {
|
|
1013
|
-
valid: invalidCount === 0,
|
|
1014
|
-
entries: validatedEntries,
|
|
1015
|
-
summary: {
|
|
1016
|
-
total: parsed.entries.length,
|
|
1017
|
-
valid: validCount,
|
|
1018
|
-
invalid: invalidCount
|
|
1019
|
-
},
|
|
1020
|
-
warnings
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
6
|
+
export { loadMixinStageBudgets } from "./mixin/types.js";
|
|
7
|
+
export { accessLevelFromFlags, buildQuickSummary, computeConfidenceBreakdown, computeFalsePositiveRisk, computeValidationStatus, extractMethodDescriptor, extractMethodName, levenshteinDistance, refreshMixinValidationOutcome, suggestSimilar, summarizeResolvedMembers } from "./mixin/helpers.js";
|
|
8
|
+
export { validateAccessor, validateInjection, validateShadow } from "./mixin/annotation-validators.js";
|
|
9
|
+
export { validateParsedMixin } from "./mixin/parsed-validator.js";
|
|
10
|
+
export { validateParsedAccessTransformer, validateParsedAccessWidener } from "./mixin/access-validators.js";
|
|
1023
11
|
//# sourceMappingURL=mixin-validator.js.map
|