@adhisang/minecraft-modding-mcp 7.0.0 → 7.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +4 -3
  3. package/dist/access-transformer-parser.d.ts +8 -0
  4. package/dist/access-transformer-parser.js +8 -1
  5. package/dist/access-widener-parser.d.ts +17 -0
  6. package/dist/access-widener-parser.js +12 -1
  7. package/dist/cache-registry.js +19 -6
  8. package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
  9. package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
  10. package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
  11. package/dist/entry-tools/batch-class-members-service.js +20 -6
  12. package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
  13. package/dist/entry-tools/batch-class-source-service.js +10 -0
  14. package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
  15. package/dist/entry-tools/compare-minecraft-service.js +65 -4
  16. package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
  17. package/dist/entry-tools/inspect-minecraft/handlers/versions.js +15 -9
  18. package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
  19. package/dist/entry-tools/manage-cache-service.d.ts +2 -2
  20. package/dist/entry-tools/manage-cache-service.js +10 -14
  21. package/dist/entry-tools/validate-project/cases/access-transformer.js +22 -3
  22. package/dist/entry-tools/validate-project/cases/access-widener.js +31 -4
  23. package/dist/entry-tools/validate-project/cases/mixin.js +11 -3
  24. package/dist/entry-tools/validate-project/cases/project-summary.js +94 -16
  25. package/dist/entry-tools/validate-project-service.d.ts +2 -2
  26. package/dist/entry-tools/verify-mixin-target-service.js +19 -8
  27. package/dist/index.js +38 -15
  28. package/dist/java-process.d.ts +1 -0
  29. package/dist/java-process.js +14 -0
  30. package/dist/mapping/lookup.js +16 -1
  31. package/dist/mapping-service.d.ts +14 -0
  32. package/dist/mapping-service.js +35 -15
  33. package/dist/minecraft-explorer-service.js +70 -8
  34. package/dist/mixin/access-validators.js +38 -2
  35. package/dist/mixin/annotation-validators.js +137 -43
  36. package/dist/mixin/parsed-validator.js +21 -7
  37. package/dist/mixin-parser.d.ts +52 -0
  38. package/dist/mixin-parser.js +709 -130
  39. package/dist/mod-decompile-service.js +11 -1
  40. package/dist/mod-remap-service.js +6 -6
  41. package/dist/nbt/java-nbt-codec.js +7 -1
  42. package/dist/source/access-validate.js +10 -0
  43. package/dist/source/artifact-resolver.d.ts +27 -3
  44. package/dist/source/artifact-resolver.js +235 -30
  45. package/dist/source/class-source/members-builder.d.ts +7 -0
  46. package/dist/source/class-source/members-builder.js +4 -1
  47. package/dist/source/class-source.d.ts +9 -2
  48. package/dist/source/class-source.js +186 -27
  49. package/dist/source/indexer.js +69 -1
  50. package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
  51. package/dist/source/lifecycle/mapping-helpers.js +29 -3
  52. package/dist/source/lifecycle/runtime-check.d.ts +25 -0
  53. package/dist/source/lifecycle/runtime-check.js +68 -39
  54. package/dist/source/nested-jars.d.ts +15 -1
  55. package/dist/source/nested-jars.js +14 -5
  56. package/dist/source/search.d.ts +10 -2
  57. package/dist/source/search.js +60 -13
  58. package/dist/source/symbol-resolver.js +88 -0
  59. package/dist/source/validate-mixin/pipeline/mapping-health.js +20 -1
  60. package/dist/source/validate-mixin/pipeline/target-lookup.js +16 -7
  61. package/dist/source/validate-mixin.d.ts +5 -0
  62. package/dist/source/validate-mixin.js +136 -21
  63. package/dist/source/workspace-target.js +75 -7
  64. package/dist/source-jar-reader.d.ts +48 -1
  65. package/dist/source-jar-reader.js +93 -3
  66. package/dist/source-resolver.d.ts +7 -0
  67. package/dist/source-resolver.js +22 -14
  68. package/dist/source-service.d.ts +5 -0
  69. package/dist/source-service.js +7 -0
  70. package/dist/stdio-supervisor.d.ts +35 -1
  71. package/dist/stdio-supervisor.js +77 -2
  72. package/dist/storage/db.d.ts +62 -2
  73. package/dist/storage/db.js +181 -20
  74. package/dist/storage/files-repo.d.ts +7 -0
  75. package/dist/storage/files-repo.js +17 -4
  76. package/dist/storage/sqlite.d.ts +31 -1
  77. package/dist/storage/sqlite.js +125 -16
  78. package/dist/tool-contract-manifest.js +2 -2
  79. package/dist/tool-execution-gate.js +2 -1
  80. package/dist/tool-guidance.js +4 -1
  81. package/dist/tool-schemas.d.ts +64 -52
  82. package/dist/tool-schemas.js +9 -7
  83. package/dist/types.d.ts +9 -0
  84. package/dist/v1-parity-schemas.js +36 -2
  85. package/dist/version-diff-service.d.ts +23 -0
  86. package/dist/version-diff-service.js +101 -0
  87. package/dist/version-service.d.ts +14 -0
  88. package/dist/version-service.js +52 -3
  89. package/dist/workspace-context-cache.d.ts +25 -0
  90. package/dist/workspace-context-cache.js +52 -2
  91. package/dist/workspace-mapping-service.d.ts +8 -0
  92. package/dist/workspace-mapping-service.js +151 -21
  93. package/docs/README-ja.md +3 -1
  94. package/docs/tool-reference.md +69 -22
  95. package/package.json +1 -1
@@ -1,19 +1,70 @@
1
1
  import { allFieldNames, allMethodNames, computeFalsePositiveRisk, extractMethodDescriptor, extractMethodName, suggestSimilar } from "./helpers.js";
2
+ // Mixin regex target selector: "/pattern/" or "owner=/…/ name=/…/ desc=/…/".
3
+ const REGEX_SELECTOR_RE = /^(?:(?:owner|name|desc)\s*=\s*)?\//;
4
+ // Trailing target-selector quantifier. For the brace form, group 1 is the minimum,
5
+ // group 2 the comma (open or explicit maximum) and group 3 the maximum.
6
+ const QUANTIFIER_RE = /(?:\*|\+|\{(\d*)(?:(,)(\d*))?\})$/;
2
7
  export function validateInjection(inj, targetMembers, targetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport) {
3
8
  for (const targetName of targetNames) {
4
9
  const members = targetMembers.get(targetName);
5
10
  if (!members)
6
11
  continue;
7
12
  const methodNames = allMethodNames(members);
13
+ if (REGEX_SELECTOR_RE.test(inj.method.trim())) {
14
+ // Regex selectors ("/pattern/", "name=/pattern/ desc=/pattern/") match by pattern,
15
+ // which the name lookup below cannot evaluate; unverifiable.
16
+ resolvedMembers.push({
17
+ annotation: `@${inj.annotation}`,
18
+ name: inj.method,
19
+ line: inj.line,
20
+ status: "skipped"
21
+ });
22
+ continue;
23
+ }
24
+ // Mixin strips all whitespace from a selector before parsing it ("tick{1, 3}").
25
+ const selector = inj.method.replace(/\s+/g, "");
8
26
  // Strip owner prefix and JVM descriptor from the method reference
9
- const rawMethodName = extractMethodName(inj.method);
10
- // Strip a trailing target-selector quantifier (e.g. "tick*", "tick+", "tick{2,3}")
11
- const quantifier = /(?:\*|\+|\{\d+(?:,\d+)?\})$/.exec(rawMethodName)?.[0];
27
+ const rawMethodName = extractMethodName(selector);
28
+ // Strip a trailing target-selector quantifier (e.g. "tick*", "tick+", "tick{2,3}").
29
+ // As in Mixin, the quantifier only bounds how many members the EXACT name (and
30
+ // descriptor, when given) must match; it never turns the name into a prefix glob.
31
+ const quantifierMatch = QUANTIFIER_RE.exec(rawMethodName);
32
+ const quantifier = quantifierMatch?.[0];
12
33
  const methodName = quantifier
13
34
  ? rawMethodName.slice(0, rawMethodName.length - quantifier.length)
14
35
  : rawMethodName;
15
- if (quantifier && methodName === "") {
16
- // Bare wildcard selector (e.g. method = "*") matches everything; unverifiable.
36
+ let minMatches = quantifier === "+" ? 1 : 0;
37
+ if (quantifierMatch && quantifier !== "*" && quantifier !== "+") {
38
+ minMatches = quantifierMatch[1] ? Number.parseInt(quantifierMatch[1], 10) : 0;
39
+ const maxMatches = quantifierMatch[2] === undefined
40
+ ? minMatches
41
+ : quantifierMatch[3]
42
+ ? Number.parseInt(quantifierMatch[3], 10)
43
+ : Number.POSITIVE_INFINITY;
44
+ // Mixin clamps a max below the min up to the min ("{3,1}" acts as "{3}"), so only a
45
+ // quantifier whose clamped max is 0 ("{}", "{0}") allows no match; Mixin rejects it.
46
+ if (Math.max(minMatches, maxMatches) === 0) {
47
+ issues.push({
48
+ severity: "warning",
49
+ kind: "method-not-found",
50
+ annotation: `@${inj.annotation}`,
51
+ target: `${targetName}#${inj.method}`,
52
+ message: `Selector "${rawMethodName}" has a malformed quantifier "${quantifier}" (it allows no match); Mixin rejects it.`,
53
+ line: inj.line,
54
+ confidence: "likely"
55
+ });
56
+ resolvedMembers.push({
57
+ annotation: `@${inj.annotation}`,
58
+ name: rawMethodName,
59
+ line: inj.line,
60
+ status: "not-found"
61
+ });
62
+ continue;
63
+ }
64
+ }
65
+ const anyName = quantifier !== undefined && methodName === "";
66
+ if (anyName && minMatches === 0 && !extractMethodDescriptor(selector)) {
67
+ // A bare "*" matches any method, or none at all; there is nothing to check.
17
68
  resolvedMembers.push({
18
69
  annotation: `@${inj.annotation}`,
19
70
  name: rawMethodName,
@@ -22,12 +73,12 @@ export function validateInjection(inj, targetMembers, targetNames, issues, resol
22
73
  });
23
74
  continue;
24
75
  }
25
- const matched = (quantifier === "*" || quantifier === "+")
26
- ? methodNames.some((n) => n.startsWith(methodName))
27
- : methodNames.includes(methodName);
76
+ // An empty name with a quantifier ("*(I)V", "+", "{2}") matches any method name; its
77
+ // descriptor and count still apply below.
78
+ const matched = anyName || methodNames.includes(methodName);
28
79
  if (!matched) {
29
80
  const suggestions = suggestSimilar(methodName, methodNames);
30
- const descriptor = extractMethodDescriptor(inj.method);
81
+ const descriptor = extractMethodDescriptor(selector);
31
82
  const descriptorHint = descriptor ? ` (descriptor: ${descriptor})` : "";
32
83
  // Determine if this is a remap artifact or signature unavailability
33
84
  const isRemapFailed = remapFailedMembers?.get(targetName)?.has(methodName);
@@ -64,31 +115,39 @@ export function validateInjection(inj, targetMembers, targetNames, issues, resol
64
115
  // Name matched. If the reference carries a JVM descriptor, require an
65
116
  // overload to match it; otherwise a wrong-signature target (e.g. tick(D)V
66
117
  // when only tick(I)V exists) is silently reported as resolved.
67
- const refDescriptor = quantifier ? undefined : extractMethodDescriptor(inj.method);
68
- const sameNameMembers = [...members.constructors, ...members.methods].filter((member) => member.name === methodName);
69
- if (refDescriptor &&
70
- sameNameMembers.length > 0 &&
71
- !sameNameMembers.some((member) => member.jvmDescriptor === refDescriptor)) {
72
- const available = [...new Set(sameNameMembers.map((member) => member.jvmDescriptor))];
73
- const isRemapFailed = remapFailedMembers?.get(targetName)?.has(methodName);
74
- const isSigFailed = signatureFailedTargets?.has(targetName);
75
- // Only a healthy mapping lets us trust that the user-written descriptor is
76
- // byte-equal to the runtime descriptor; otherwise downgrade to a warning.
77
- const mappingDegraded = Boolean(isRemapFailed) || Boolean(isSigFailed) || healthReport?.overallHealthy === false;
78
- const resolutionPath = isRemapFailed
79
- ? "member-remap-failed"
80
- : isSigFailed
81
- ? "source-signature-unavailable"
82
- : undefined;
83
- const issueConfidence = mappingDegraded
84
- ? "uncertain"
85
- : confidence ?? "definite";
118
+ const refDescriptor = extractMethodDescriptor(selector);
119
+ const sameNameMembers = anyName
120
+ ? members.methods
121
+ : [...members.constructors, ...members.methods].filter((member) => member.name === methodName);
122
+ const displayName = anyName ? rawMethodName : methodName;
123
+ const matchCount = refDescriptor
124
+ ? sameNameMembers.filter((member) => member.jvmDescriptor === refDescriptor).length
125
+ : sameNameMembers.length;
126
+ const isRemapFailed = remapFailedMembers?.get(targetName)?.has(methodName);
127
+ const isSigFailed = signatureFailedTargets?.has(targetName);
128
+ // Only a healthy mapping lets us trust that the user-written descriptor (and the
129
+ // overload count) is byte-equal to the runtime one; otherwise downgrade to a warning.
130
+ const mappingDegraded = Boolean(isRemapFailed) || Boolean(isSigFailed) || healthReport?.overallHealthy === false;
131
+ const resolutionPath = isRemapFailed
132
+ ? "member-remap-failed"
133
+ : isSigFailed
134
+ ? "source-signature-unavailable"
135
+ : undefined;
136
+ const issueConfidence = mappingDegraded
137
+ ? "uncertain"
138
+ : confidence ?? "definite";
139
+ if (refDescriptor && matchCount === 0 && (anyName || sameNameMembers.length > 0)) {
140
+ const available = anyName
141
+ ? []
142
+ : [...new Set(sameNameMembers.map((member) => member.jvmDescriptor))];
86
143
  issues.push({
87
144
  severity: mappingDegraded ? "warning" : "error",
88
145
  kind: "descriptor-mismatch",
89
146
  annotation: `@${inj.annotation}`,
90
147
  target: `${targetName}#${inj.method}`,
91
- message: `Method "${methodName}" exists in "${targetName}" but no overload matches descriptor ${refDescriptor} (available: ${available.join(", ")}).${mappingDegraded ? " (mapping degraded; may be a remap artifact)" : ""}`,
148
+ message: `${anyName
149
+ ? `No method in "${targetName}" matches descriptor ${refDescriptor}.`
150
+ : `Method "${methodName}" exists in "${targetName}" but no overload matches descriptor ${refDescriptor} (available: ${available.join(", ")}).`}${mappingDegraded ? " (mapping degraded; may be a remap artifact)" : ""}`,
92
151
  suggestions: available.length > 0 ? available : undefined,
93
152
  line: inj.line,
94
153
  confidence: issueConfidence,
@@ -100,7 +159,31 @@ export function validateInjection(inj, targetMembers, targetNames, issues, resol
100
159
  });
101
160
  resolvedMembers.push({
102
161
  annotation: `@${inj.annotation}`,
103
- name: methodName,
162
+ name: displayName,
163
+ line: inj.line,
164
+ status: "not-found"
165
+ });
166
+ }
167
+ else if (matchCount < minMatches) {
168
+ // "+" and "{n}"/"{n,}"/"{n,m}" demand at least n matching members. A shortfall stays
169
+ // uncertain: if a remap ran, a member whose name could not be remapped may not be counted.
170
+ issues.push({
171
+ severity: "warning",
172
+ kind: "method-not-found",
173
+ annotation: `@${inj.annotation}`,
174
+ target: `${targetName}#${inj.method}`,
175
+ message: `Selector "${rawMethodName}" requires at least ${minMatches} matching method(s)${anyName ? "" : ` named "${methodName}"`} in target class "${targetName}" but found ${matchCount}.${mappingDegraded ? " (mapping degraded; may be a remap artifact)" : ""}`,
176
+ line: inj.line,
177
+ confidence: "uncertain",
178
+ confidenceReason: mappingDegraded
179
+ ? `Member remap/signature degraded; the overload count may be a tooling artifact.`
180
+ : `Counted from the listed members; a member whose name could not be remapped, if any, is not counted.`,
181
+ resolutionPath,
182
+ falsePositiveRisk: computeFalsePositiveRisk(healthReport, resolutionPath, "uncertain")
183
+ });
184
+ resolvedMembers.push({
185
+ annotation: `@${inj.annotation}`,
186
+ name: displayName,
104
187
  line: inj.line,
105
188
  status: "not-found"
106
189
  });
@@ -108,9 +191,9 @@ export function validateInjection(inj, targetMembers, targetNames, issues, resol
108
191
  else {
109
192
  resolvedMembers.push({
110
193
  annotation: `@${inj.annotation}`,
111
- name: methodName,
194
+ name: displayName,
112
195
  line: inj.line,
113
- resolvedTo: `${targetName}#${methodName}`,
196
+ resolvedTo: `${targetName}#${displayName}`,
114
197
  status: "resolved"
115
198
  });
116
199
  }
@@ -118,11 +201,20 @@ export function validateInjection(inj, targetMembers, targetNames, issues, resol
118
201
  }
119
202
  }
120
203
  export function validateShadow(shadow, targetMembers, targetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport) {
204
+ // A @Shadow method declared as `<prefix>name` refers to `name` in the target; a
205
+ // @Shadow field's targetName is its declared name unchanged, since fields are not
206
+ // prefixed. `aliases` adds fallback names. Messages keep the declared name so they
207
+ // stay recognisable in the mixin source.
208
+ const effectiveName = shadow.targetName ?? shadow.name;
209
+ const candidateNames = [effectiveName, ...(shadow.aliases ?? [])];
210
+ const prefixHint = effectiveName !== shadow.name
211
+ ? ` (resolved to "${effectiveName}" after @Shadow prefix removal)`
212
+ : "";
121
213
  for (const targetName of targetNames) {
122
214
  const members = targetMembers.get(targetName);
123
215
  if (!members)
124
216
  continue;
125
- const isRemapFailed = remapFailedMembers?.get(targetName)?.has(shadow.name);
217
+ const isRemapFailed = remapFailedMembers?.get(targetName)?.has(effectiveName);
126
218
  const isSigFailed = signatureFailedTargets?.has(targetName);
127
219
  const issueConfidence = isRemapFailed ? "uncertain" : confidence;
128
220
  const issueConfidenceReason = isRemapFailed
@@ -134,14 +226,15 @@ export function validateShadow(shadow, targetMembers, targetNames, issues, resol
134
226
  const memberDegraded = isRemapFailed && healthReport?.memberRemapAvailable === false;
135
227
  if (shadow.kind === "field") {
136
228
  const fieldNames = allFieldNames(members);
137
- if (!fieldNames.includes(shadow.name)) {
138
- const suggestions = suggestSimilar(shadow.name, fieldNames);
229
+ const matchedName = candidateNames.find((candidate) => fieldNames.includes(candidate));
230
+ if (matchedName === undefined) {
231
+ const suggestions = suggestSimilar(effectiveName, fieldNames);
139
232
  issues.push({
140
233
  severity: memberDegraded ? "warning" : "error",
141
234
  kind: "field-not-found",
142
235
  annotation: "@Shadow",
143
- target: `${targetName}#${shadow.name}`,
144
- message: `Field "${shadow.name}" not found in target class "${targetName}" (${fieldNames.length} field(s) available).${memberDegraded ? " (infrastructure degraded; may be false positive)" : ""}`,
236
+ target: `${targetName}#${effectiveName}`,
237
+ message: `Field "${shadow.name}" not found in target class "${targetName}" (${fieldNames.length} field(s) available).${prefixHint}${memberDegraded ? " (infrastructure degraded; may be false positive)" : ""}`,
145
238
  suggestions: suggestions.length > 0 ? suggestions : undefined,
146
239
  line: shadow.line,
147
240
  confidence: issueConfidence,
@@ -152,19 +245,20 @@ export function validateShadow(shadow, targetMembers, targetNames, issues, resol
152
245
  resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, status: "not-found" });
153
246
  }
154
247
  else {
155
- resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, resolvedTo: `${targetName}#${shadow.name}`, status: "resolved" });
248
+ resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, resolvedTo: `${targetName}#${matchedName}`, status: "resolved" });
156
249
  }
157
250
  }
158
251
  else {
159
252
  const methodNames = allMethodNames(members);
160
- if (!methodNames.includes(shadow.name)) {
161
- const suggestions = suggestSimilar(shadow.name, methodNames);
253
+ const matchedName = candidateNames.find((candidate) => methodNames.includes(candidate));
254
+ if (matchedName === undefined) {
255
+ const suggestions = suggestSimilar(effectiveName, methodNames);
162
256
  issues.push({
163
257
  severity: memberDegraded ? "warning" : "error",
164
258
  kind: "method-not-found",
165
259
  annotation: "@Shadow",
166
- target: `${targetName}#${shadow.name}`,
167
- message: `Method "${shadow.name}" not found in target class "${targetName}" (${methodNames.length} method(s) available).${memberDegraded ? " (infrastructure degraded; may be false positive)" : ""}`,
260
+ target: `${targetName}#${effectiveName}`,
261
+ message: `Method "${shadow.name}" not found in target class "${targetName}" (${methodNames.length} method(s) available).${prefixHint}${memberDegraded ? " (infrastructure degraded; may be false positive)" : ""}`,
168
262
  suggestions: suggestions.length > 0 ? suggestions : undefined,
169
263
  line: shadow.line,
170
264
  confidence: issueConfidence,
@@ -175,7 +269,7 @@ export function validateShadow(shadow, targetMembers, targetNames, issues, resol
175
269
  resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, status: "not-found" });
176
270
  }
177
271
  else {
178
- resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, resolvedTo: `${targetName}#${shadow.name}`, status: "resolved" });
272
+ resolvedMembers.push({ annotation: "@Shadow", name: shadow.name, line: shadow.line, resolvedTo: `${targetName}#${matchedName}`, status: "resolved" });
179
273
  }
180
274
  }
181
275
  }
@@ -4,6 +4,20 @@ import { validateAccessor, validateInjection, validateShadow } from "./annotatio
4
4
  export function validateParsedMixin(parsed, targetMembers, warnings, provenance, confidence, mappingFailedTargets, explain, remapFailedMembers, signatureFailedTargets, suggestedCallContext, warningMode, healthReport, symbolExistsButSignatureFailed, deferredBudgetTargets) {
5
5
  const issues = [];
6
6
  const targetNames = parsed.targets.map((t) => t.className);
7
+ // A member is checked only against the targets of the @Mixin class that owns it; a member
8
+ // without an owner is checked against every target.
9
+ const ownerTargets = (parsed.mixins ?? []).map((mixin) => new Set(mixin.targets.map((t) => t.className)));
10
+ const targetsOf = (member, names) => {
11
+ const owned = member.mixinIndex === undefined ? undefined : ownerTargets[member.mixinIndex];
12
+ return owned ? names.filter((name) => owned.has(name)) : names;
13
+ };
14
+ // The members that a failed target leaves unvalidated: those whose owner targets it.
15
+ const membersOf = (targetName) => ({
16
+ ...parsed,
17
+ injections: parsed.injections.filter((member) => targetsOf(member, [targetName]).length > 0),
18
+ shadows: parsed.shadows.filter((member) => targetsOf(member, [targetName]).length > 0),
19
+ accessors: parsed.accessors.filter((member) => targetsOf(member, [targetName]).length > 0)
20
+ });
7
21
  const confidenceReason = confidence === "uncertain"
8
22
  ? `Mapping fallback: requested "${provenance?.requestedMapping}" but applied "${provenance?.mappingApplied}".`
9
23
  : confidence === "likely"
@@ -27,7 +41,7 @@ export function validateParsedMixin(parsed, targetMembers, warnings, provenance,
27
41
  resolutionPath: "target-mapping-failed",
28
42
  falsePositiveRisk: healthReport?.overallHealthy === false ? "high" : "medium"
29
43
  });
30
- addSkippedMembers(parsed, resolvedMembers);
44
+ addSkippedMembers(membersOf(target.className), resolvedMembers);
31
45
  }
32
46
  else if (symbolExistsButSignatureFailed?.has(target.className)) {
33
47
  // Symbol exists in mapping graph but getSignature failed — tool limitation, not code issue
@@ -44,12 +58,12 @@ export function validateParsedMixin(parsed, targetMembers, warnings, provenance,
44
58
  issueOrigin: "tool_issue",
45
59
  falsePositiveRisk: "high"
46
60
  });
47
- addSkippedMembers(parsed, resolvedMembers);
61
+ addSkippedMembers(membersOf(target.className), resolvedMembers);
48
62
  }
49
63
  else if (deferredBudgetTargets?.has(target.className)) {
50
64
  // Deferred by stage budget; the targetOutcomes entry already records
51
65
  // this, so skip member validation rather than emit a not-found error.
52
- addSkippedMembers(parsed, resolvedMembers);
66
+ addSkippedMembers(membersOf(target.className), resolvedMembers);
53
67
  }
54
68
  else if (signatureFailedTargets?.has(target.className)) {
55
69
  issues.push({
@@ -65,7 +79,7 @@ export function validateParsedMixin(parsed, targetMembers, warnings, provenance,
65
79
  issueOrigin: "tool_issue",
66
80
  falsePositiveRisk: "high"
67
81
  });
68
- addSkippedMembers(parsed, resolvedMembers);
82
+ addSkippedMembers(membersOf(target.className), resolvedMembers);
69
83
  }
70
84
  else {
71
85
  issues.push({
@@ -85,13 +99,13 @@ export function validateParsedMixin(parsed, targetMembers, warnings, provenance,
85
99
  // Only validate members against targets that were resolved
86
100
  const resolvedTargetNames = targetNames.filter((t) => targetMembers.has(t));
87
101
  for (const inj of parsed.injections) {
88
- validateInjection(inj, targetMembers, resolvedTargetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
102
+ validateInjection(inj, targetMembers, targetsOf(inj, resolvedTargetNames), issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
89
103
  }
90
104
  for (const shadow of parsed.shadows) {
91
- validateShadow(shadow, targetMembers, resolvedTargetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
105
+ validateShadow(shadow, targetMembers, targetsOf(shadow, resolvedTargetNames), issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
92
106
  }
93
107
  for (const accessor of parsed.accessors) {
94
- validateAccessor(accessor, targetMembers, resolvedTargetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
108
+ validateAccessor(accessor, targetMembers, targetsOf(accessor, resolvedTargetNames), issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport);
95
109
  }
96
110
  // Add parse warnings — escalate @Accessor/@Invoker/@Shadow parse failures to issues
97
111
  for (const pw of parsed.parseWarnings) {
@@ -5,26 +5,71 @@
5
5
  export type ParsedMixinTarget = {
6
6
  className: string;
7
7
  };
8
+ /**
9
+ * One `@Mixin`-annotated class in the file. A file can hold several (nested or top-level),
10
+ * and each member is validated only against the targets of the class that owns it.
11
+ */
12
+ export type ParsedMixinClass = {
13
+ /** Simple name of the annotated class. */
14
+ className: string;
15
+ /** Targets named by this class's own `@Mixin` annotation. */
16
+ targets: ParsedMixinTarget[];
17
+ /** 1-based line of the `@Mixin` annotation. */
18
+ startLine: number;
19
+ /** 1-based line of the brace that closes the class body. */
20
+ endLine: number;
21
+ /**
22
+ * Simple names from the file's top-level type down to this class (`["Outer", "Inner"]`, the
23
+ * nesting a mixins-config entry `Outer$Inner` names). Absent for a class declared inside a
24
+ * method, initializer or anonymous class body, which no such entry can name.
25
+ */
26
+ classPath?: string[];
27
+ /** This class's own `@Mixin(priority = ...)`, when given. */
28
+ priority?: number;
29
+ };
8
30
  export type ParsedInjection = {
9
31
  annotation: string;
10
32
  method: string;
11
33
  line: number;
34
+ /**
35
+ * Index into `ParsedMixin.mixins` of the innermost `@Mixin` class enclosing this member
36
+ * (a member of a nested non-mixin class belongs to the nearest enclosing `@Mixin` class).
37
+ * Absent when no `@Mixin` class encloses it; such a member is checked against every target.
38
+ */
39
+ mixinIndex?: number;
12
40
  };
13
41
  export type ParsedShadow = {
14
42
  kind: "field" | "method";
15
43
  name: string;
44
+ /**
45
+ * The member looked up in the target: for a method, the declared name with the @Shadow
46
+ * prefix removed; for a field, the declared name unchanged (fields are not prefixed).
47
+ */
48
+ targetName?: string;
49
+ /** Alternative target names from `@Shadow(aliases = {...})`. */
50
+ aliases?: string[];
16
51
  descriptor?: string;
17
52
  line: number;
53
+ /** Owning `@Mixin` class; see `ParsedInjection.mixinIndex`. */
54
+ mixinIndex?: number;
18
55
  };
19
56
  export type ParsedAccessor = {
20
57
  annotation: "Accessor" | "Invoker";
21
58
  name: string;
22
59
  targetName: string;
23
60
  line: number;
61
+ /** Owning `@Mixin` class; see `ParsedInjection.mixinIndex`. */
62
+ mixinIndex?: number;
24
63
  };
25
64
  export type ParsedMixin = {
26
65
  className: string;
66
+ /** Every target named by any `@Mixin` annotation in the file, first occurrence first. */
27
67
  targets: ParsedMixinTarget[];
68
+ /**
69
+ * Each `@Mixin` class whose body could be located, in source order. Absent on a parse
70
+ * result built without ownership, whose members are then checked against every target.
71
+ */
72
+ mixins?: ParsedMixinClass[];
28
73
  priority?: number;
29
74
  imports: Map<string, string>;
30
75
  injections: ParsedInjection[];
@@ -33,3 +78,10 @@ export type ParsedMixin = {
33
78
  parseWarnings: string[];
34
79
  };
35
80
  export declare function parseMixinSource(source: string): ParsedMixin;
81
+ /**
82
+ * `parsed` narrowed to the nested `@Mixin` class at `classPath` (see `ParsedMixinClass.classPath`;
83
+ * `["Outer", "Inner"]` for a config entry `Outer$Inner`): its own targets and priority, and only
84
+ * the members and line-numbered parse warnings it owns. Undefined when no `@Mixin` class sits at
85
+ * that path.
86
+ */
87
+ export declare function selectNestedMixin(parsed: ParsedMixin, classPath: readonly string[]): ParsedMixin | undefined;