@adhisang/minecraft-modding-mcp 1.1.0 → 1.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.
@@ -5,21 +5,29 @@
5
5
  /* ------------------------------------------------------------------ */
6
6
  /* Regex patterns */
7
7
  /* ------------------------------------------------------------------ */
8
- const CLASS_DECL_RE = /(?:public\s+)?(?:abstract\s+)?class\s+(\w+)/;
8
+ const CLASS_DECL_RE = /(?:public\s+)?(?:abstract\s+)?(?:class|interface)\s+(\w+)/;
9
+ // import statements for FQCN resolution
10
+ const IMPORT_RE = /^\s*import\s+([\w.]+)\s*;/;
9
11
  // @Mixin(Foo.class) or @Mixin({Foo.class, Bar.class}) or @Mixin(value = Foo.class)
10
12
  // Also handles @Mixin(value = {Foo.class, Bar.class}, priority = 900)
11
13
  const MIXIN_ANNOTATION_START_RE = /^\s*@Mixin\s*\(/;
12
14
  const MIXIN_TARGET_RE = /(\w[\w.]*?)\.class/g;
13
15
  const MIXIN_PRIORITY_RE = /priority\s*=\s*(\d+)/;
16
+ // String-form targets: @Mixin(targets = "pkg.Class") or @Mixin(targets = {"pkg.A", "pkg.B"})
17
+ const MIXIN_TARGETS_STRING_RE = /targets\s*=\s*(?:\{([^}]+)\}|"([^"]+)")/;
18
+ const MIXIN_TARGETS_STRING_ITEM_RE = /"([^"]+)"/g;
14
19
  // Injection annotations: @Inject, @Redirect, @ModifyArg, @ModifyVariable, @ModifyConstant, @ModifyExpressionValue
15
- const INJECTION_ANNOTATION_RE = /^\s*@(Inject|Redirect|ModifyArg|ModifyVariable|ModifyConstant|ModifyExpressionValue)\s*\(/;
20
+ // Also MixinExtras: @WrapOperation, @WrapWithCondition, @ModifyReturnValue
21
+ const INJECTION_ANNOTATION_RE = /^\s*@(Inject|Redirect|ModifyArg|ModifyVariable|ModifyConstant|ModifyExpressionValue|WrapOperation|WrapWithCondition|ModifyReturnValue)\s*\(/;
16
22
  const METHOD_ATTR_RE = /method\s*=\s*"([^"]+)"/;
23
+ const METHOD_ATTR_ARRAY_RE = /method\s*=\s*\{([^}]+)\}/;
24
+ const METHOD_ATTR_ITEM_RE = /"([^"]+)"/g;
17
25
  // @Shadow field / method
18
26
  const SHADOW_ANNOTATION_RE = /^\s*@Shadow\b/;
19
- const FIELD_DECL_RE = /(?:private|protected|public)?\s*(?:static\s+)?(?:final\s+)?(\w[\w<>,\s]*?)\s+(\w+)\s*[;=]/;
20
- const METHOD_DECL_RE = /(?:private|protected|public)?\s*(?:static\s+)?(?:abstract\s+)?(?:native\s+)?(\w[\w<>,\s]*?)\s+(\w+)\s*\(/;
27
+ const FIELD_DECL_RE = /(?:private|protected|public)?\s*(?:static\s+)?(?:final\s+)?(?:volatile\s+)?([\w.][\w<>,.\s?\[\]]*?)\s+([\w$]+)\s*[;=]/;
28
+ const METHOD_DECL_RE = /(?:private|protected|public)?\s*(?:default\s+)?(?:static\s+)?(?:synchronized\s+)?(?:abstract\s+)?(?:native\s+)?(?:<[\w<>,.\s?&\[\]]+>\s+)?([\w.][\w<>,.\s?\[\]]*?)\s+([\w$]+)\s*\(/;
21
29
  // @Accessor / @Invoker
22
- const ACCESSOR_ANNOTATION_RE = /^\s*@(Accessor|Invoker)\s*(?:\(\s*"([^"]+)"\s*\))?\s*$/;
30
+ const ACCESSOR_ANNOTATION_RE = /^\s*@(Accessor|Invoker)\s*(?:\(\s*\)|\(\s*(?:value\s*=\s*)?"([^"]+)"\s*(?:,\s*\w+\s*=\s*(?:\w+|"[^"]*")\s*)*\))?(?:\s|$)/;
23
31
  const ACCESSOR_ANNOTATION_START_RE = /^\s*@(Accessor|Invoker)\s*\(/;
24
32
  const ACCESSOR_EXPLICIT_RE = /"([^"]+)"/;
25
33
  // Naming conventions for accessor/invoker target inference
@@ -47,6 +55,57 @@ function collectMultilineAnnotation(lines, startIndex) {
47
55
  }
48
56
  return { text, endIndex: lines.length - 1 };
49
57
  }
58
+ /**
59
+ * Skip past annotations (including multi-line ones) starting at `startIndex`.
60
+ * Returns the index of the first non-annotation line.
61
+ * Lines with inline annotation + declaration (e.g. `@Nullable private int x;`)
62
+ * are treated as declaration lines (not skipped).
63
+ */
64
+ function skipAnnotations(lines, startIndex) {
65
+ let idx = startIndex;
66
+ while (idx < lines.length && /^\s*@/.test(lines[idx])) {
67
+ if (lines[idx].includes("(")) {
68
+ // Check if parentheses are unbalanced (multi-line annotation) — always skip
69
+ let depth = 0;
70
+ for (const ch of lines[idx]) {
71
+ if (ch === "(")
72
+ depth++;
73
+ if (ch === ")")
74
+ depth--;
75
+ }
76
+ if (depth > 0) {
77
+ // Multi-line annotation — skip to its end
78
+ const { endIndex } = collectMultilineAnnotation(lines, idx);
79
+ idx = endIndex + 1;
80
+ continue;
81
+ }
82
+ // Single-line annotation with parens: check if declaration text remains
83
+ const stripped = stripInlineAnnotations(lines[idx]);
84
+ if (stripped === "") {
85
+ idx++;
86
+ }
87
+ else {
88
+ break; // declaration with inline annotation
89
+ }
90
+ }
91
+ else {
92
+ // Simple annotation like `@Final` — check for trailing declaration
93
+ const stripped = lines[idx].replace(/^\s*@[\w$.]+\s*/, "").trim();
94
+ if (stripped === "") {
95
+ idx++;
96
+ }
97
+ else {
98
+ break; // e.g. `@Deprecated public abstract void foo();`
99
+ }
100
+ }
101
+ }
102
+ return idx;
103
+ }
104
+ /** Strip inline annotations (e.g. `@Final @Nullable`) from a declaration line. */
105
+ const INLINE_ANNOTATION_RE = /\s*@[\w$.]+(?:\([^)]*\))?\s*/g;
106
+ function stripInlineAnnotations(line) {
107
+ return line.replace(INLINE_ANNOTATION_RE, " ").trim();
108
+ }
50
109
  function inferAccessorTarget(methodName) {
51
110
  const getterMatch = GETTER_PREFIX_RE.exec(methodName);
52
111
  if (getterMatch) {
@@ -72,8 +131,21 @@ export function parseMixinSource(source) {
72
131
  const injections = [];
73
132
  const shadows = [];
74
133
  const accessors = [];
134
+ const imports = new Map();
75
135
  let className = "";
76
136
  let priority;
137
+ // --- Pass 0: extract imports ---
138
+ for (const line of lines) {
139
+ const importMatch = IMPORT_RE.exec(line);
140
+ if (importMatch) {
141
+ const fqcn = importMatch[1];
142
+ // Skip wildcard imports (e.g. import java.util.*)
143
+ if (!fqcn.endsWith("*")) {
144
+ const simpleName = fqcn.substring(fqcn.lastIndexOf(".") + 1);
145
+ imports.set(simpleName, fqcn);
146
+ }
147
+ }
148
+ }
77
149
  // --- Pass 1: find @Mixin annotation and class name ---
78
150
  let i = 0;
79
151
  while (i < lines.length) {
@@ -84,6 +156,24 @@ export function parseMixinSource(source) {
84
156
  while ((match = MIXIN_TARGET_RE.exec(mixinText)) !== null) {
85
157
  targets.push({ className: match[1] });
86
158
  }
159
+ // Fallback: parse targets = "..." or targets = {"a", "b"} string form
160
+ if (targets.length === 0) {
161
+ const targetsStringMatch = MIXIN_TARGETS_STRING_RE.exec(mixinText);
162
+ if (targetsStringMatch) {
163
+ const arrayContent = targetsStringMatch[1]; // {..."..."...} content
164
+ const singleTarget = targetsStringMatch[2]; // single "..." content
165
+ if (arrayContent) {
166
+ MIXIN_TARGETS_STRING_ITEM_RE.lastIndex = 0;
167
+ let itemMatch;
168
+ while ((itemMatch = MIXIN_TARGETS_STRING_ITEM_RE.exec(arrayContent)) !== null) {
169
+ targets.push({ className: itemMatch[1] });
170
+ }
171
+ }
172
+ else if (singleTarget) {
173
+ targets.push({ className: singleTarget });
174
+ }
175
+ }
176
+ }
87
177
  const priorityMatch = MIXIN_PRIORITY_RE.exec(mixinText);
88
178
  if (priorityMatch) {
89
179
  priority = parseInt(priorityMatch[1], 10);
@@ -115,21 +205,50 @@ export function parseMixinSource(source) {
115
205
  injections.push({ annotation, method: methodMatch[1], line: lineNum });
116
206
  }
117
207
  else {
118
- parseWarnings.push(`Line ${lineNum}: @${annotation} missing method attribute.`);
208
+ // Try array form: method = {"m1", "m2"}
209
+ const arrayMatch = METHOD_ATTR_ARRAY_RE.exec(fullAnnotation);
210
+ if (arrayMatch) {
211
+ const inner = arrayMatch[1];
212
+ METHOD_ATTR_ITEM_RE.lastIndex = 0;
213
+ let itemMatch;
214
+ let found = false;
215
+ while ((itemMatch = METHOD_ATTR_ITEM_RE.exec(inner)) !== null) {
216
+ injections.push({ annotation, method: itemMatch[1], line: lineNum });
217
+ found = true;
218
+ }
219
+ if (!found) {
220
+ parseWarnings.push(`Line ${lineNum}: @${annotation} method array is empty.`);
221
+ }
222
+ }
223
+ else {
224
+ parseWarnings.push(`Line ${lineNum}: @${annotation} missing method attribute.`);
225
+ }
119
226
  }
120
227
  i = endIndex + 1;
121
228
  continue;
122
229
  }
123
230
  // --- @Shadow ---
124
231
  if (SHADOW_ANNOTATION_RE.test(line)) {
232
+ // Try same-line declaration first (e.g. `@Shadow @Final private int x;`)
233
+ const inlineDecl = stripInlineAnnotations(line);
234
+ const inlineMethodMatch = inlineDecl.includes("(") ? METHOD_DECL_RE.exec(inlineDecl) : null;
235
+ const inlineFieldMatch = !inlineMethodMatch ? FIELD_DECL_RE.exec(inlineDecl) : null;
236
+ if (inlineMethodMatch) {
237
+ shadows.push({ kind: "method", name: inlineMethodMatch[2], line: lineNum });
238
+ i++;
239
+ continue;
240
+ }
241
+ else if (inlineFieldMatch) {
242
+ shadows.push({ kind: "field", name: inlineFieldMatch[2], line: lineNum });
243
+ i++;
244
+ continue;
245
+ }
125
246
  // Advance past @Shadow line to find the declaration
126
247
  let declLine = i + 1;
127
- // Skip additional annotations between @Shadow and declaration
128
- while (declLine < lines.length && /^\s*@/.test(lines[declLine])) {
129
- declLine++;
130
- }
248
+ // Skip additional annotations between @Shadow and declaration (including multi-line)
249
+ declLine = skipAnnotations(lines, declLine);
131
250
  if (declLine < lines.length) {
132
- const declText = lines[declLine];
251
+ const declText = stripInlineAnnotations(lines[declLine]);
133
252
  const methodDeclMatch = METHOD_DECL_RE.exec(declText);
134
253
  const fieldDeclMatch = FIELD_DECL_RE.exec(declText);
135
254
  // Method if it has parentheses
@@ -160,13 +279,11 @@ export function parseMixinSource(source) {
160
279
  }
161
280
  i = endIndex;
162
281
  }
163
- // Find the method declaration following the annotation
282
+ // Find the method declaration following the annotation (skip multi-line annotations)
164
283
  let methodLine = i + 1;
165
- while (methodLine < lines.length && /^\s*@/.test(lines[methodLine])) {
166
- methodLine++;
167
- }
284
+ methodLine = skipAnnotations(lines, methodLine);
168
285
  if (methodLine < lines.length) {
169
- const methodDeclMatch = METHOD_DECL_RE.exec(lines[methodLine]);
286
+ const methodDeclMatch = METHOD_DECL_RE.exec(stripInlineAnnotations(lines[methodLine]));
170
287
  if (methodDeclMatch) {
171
288
  const methodName = methodDeclMatch[2];
172
289
  const targetName = explicitTarget ?? inferAccessorTarget(methodName);
@@ -185,6 +302,7 @@ export function parseMixinSource(source) {
185
302
  className,
186
303
  targets,
187
304
  priority,
305
+ imports,
188
306
  injections,
189
307
  shadows,
190
308
  accessors,
@@ -5,14 +5,37 @@
5
5
  import type { SignatureMember } from "./minecraft-explorer-service.js";
6
6
  import type { ParsedMixin } from "./mixin-parser.js";
7
7
  import type { ParsedAccessWidener, AccessWidenerEntry } from "./access-widener-parser.js";
8
+ import type { SourceMapping } from "./types.js";
9
+ export type MappingHealthReport = {
10
+ jarAvailable: boolean;
11
+ jarPath: string;
12
+ mojangMappingsAvailable: boolean;
13
+ tinyMappingsAvailable: boolean;
14
+ memberRemapAvailable: boolean;
15
+ overallHealthy: boolean;
16
+ degradations: string[];
17
+ };
18
+ export type IssueConfidence = "definite" | "likely" | "uncertain";
19
+ export type ResolutionPath = "member-remap-failed" | "target-mapping-failed" | "target-class-missing" | "source-signature-unavailable";
8
20
  export type ValidationIssue = {
9
21
  severity: "error" | "warning";
10
- kind: "target-not-found" | "method-not-found" | "field-not-found" | "descriptor-mismatch" | "access-mismatch" | "unknown-annotation";
22
+ kind: "target-not-found" | "target-mapping-failed" | "method-not-found" | "field-not-found" | "descriptor-mismatch" | "access-mismatch" | "unknown-annotation";
11
23
  annotation: string;
12
24
  target: string;
13
25
  message: string;
14
26
  suggestions?: string[];
15
27
  line?: number;
28
+ confidence?: IssueConfidence;
29
+ confidenceReason?: string;
30
+ category?: IssueCategory;
31
+ resolutionPath?: ResolutionPath;
32
+ explanation?: string;
33
+ suggestedCall?: {
34
+ tool: string;
35
+ params: Record<string, unknown>;
36
+ };
37
+ falsePositiveRisk?: "high" | "medium" | "low";
38
+ issueOrigin?: "code_issue" | "tool_issue" | "parser_limitation";
16
39
  };
17
40
  export type ValidationSummary = {
18
41
  injections: number;
@@ -21,6 +44,52 @@ export type ValidationSummary = {
21
44
  total: number;
22
45
  errors: number;
23
46
  warnings: number;
47
+ definiteErrors: number;
48
+ uncertainErrors: number;
49
+ resolutionErrors: number;
50
+ parseWarnings: number;
51
+ };
52
+ export type MixinValidationProvenance = {
53
+ version: string;
54
+ jarPath: string;
55
+ requestedMapping: SourceMapping;
56
+ mappingApplied: SourceMapping;
57
+ resolutionNotes?: string[];
58
+ jarType?: "vanilla-client" | "merged" | "unknown";
59
+ mappingChain?: string[];
60
+ remapFailures?: number;
61
+ mappingAutoDetected?: boolean;
62
+ scopeFallback?: {
63
+ requested: string;
64
+ applied: string;
65
+ reason: string;
66
+ };
67
+ resolutionTrace?: Array<{
68
+ target: string;
69
+ step: "mapping" | "signature" | "remap" | "fallback-check";
70
+ input: string;
71
+ output: string;
72
+ success: boolean;
73
+ detail?: string;
74
+ }>;
75
+ };
76
+ export type IssueCategory = "mapping" | "configuration" | "validation" | "resolution" | "parse";
77
+ export type StructuredWarning = {
78
+ severity: "info" | "warning";
79
+ message: string;
80
+ category?: IssueCategory;
81
+ };
82
+ export type ResolvedMember = {
83
+ annotation: string;
84
+ name: string;
85
+ line?: number;
86
+ resolvedTo?: string;
87
+ status: "resolved" | "not-found" | "skipped";
88
+ };
89
+ export type AggregatedWarningGroup = {
90
+ category: IssueCategory;
91
+ count: number;
92
+ samples: string[];
24
93
  };
25
94
  export type MixinValidationResult = {
26
95
  className: string;
@@ -29,7 +98,15 @@ export type MixinValidationResult = {
29
98
  valid: boolean;
30
99
  issues: ValidationIssue[];
31
100
  summary: ValidationSummary;
101
+ unfilteredSummary?: ValidationSummary;
102
+ provenance?: MixinValidationProvenance;
32
103
  warnings: string[];
104
+ structuredWarnings?: StructuredWarning[];
105
+ aggregatedWarnings?: AggregatedWarningGroup[];
106
+ resolvedMembers?: ResolvedMember[];
107
+ toolHealth?: MappingHealthReport;
108
+ confidenceScore?: number;
109
+ quickSummary?: string;
33
110
  };
34
111
  export type ResolvedTargetMembers = {
35
112
  className: string;
@@ -55,5 +132,19 @@ export type AccessWidenerValidationResult = {
55
132
  };
56
133
  export declare function levenshteinDistance(a: string, b: string): number;
57
134
  export declare function suggestSimilar(name: string, candidates: string[], maxDistance?: number, maxResults?: number): string[];
58
- export declare function validateParsedMixin(parsed: ParsedMixin, targetMembers: Map<string, ResolvedTargetMembers>, warnings: string[]): MixinValidationResult;
135
+ export declare function extractMethodName(ref: string): string;
136
+ /**
137
+ * Extract the JVM descriptor portion from a method reference, if present.
138
+ *
139
+ * Examples:
140
+ * "playerTouch(Lnet/minecraft/world/entity/player/Player;)V" → "(Lnet/minecraft/world/entity/player/Player;)V"
141
+ * "tick" → undefined
142
+ */
143
+ export declare function extractMethodDescriptor(ref: string): string | undefined;
144
+ export declare function validateParsedMixin(parsed: ParsedMixin, targetMembers: Map<string, ResolvedTargetMembers>, warnings: string[], provenance?: MixinValidationProvenance, confidence?: IssueConfidence, mappingFailedTargets?: Set<string>, explain?: boolean, remapFailedMembers?: Map<string, Set<string>>, signatureFailedTargets?: Set<string>, suggestedCallContext?: {
145
+ scope?: string;
146
+ sourcePriority?: string;
147
+ projectPath?: string;
148
+ mapping?: string;
149
+ }, warningMode?: "full" | "aggregated", healthReport?: MappingHealthReport, symbolExistsButSignatureFailed?: Set<string>): MixinValidationResult;
59
150
  export declare function validateParsedAccessWidener(parsed: ParsedAccessWidener, membersByClass: Map<string, ResolvedTargetMembers>, warnings: string[]): AccessWidenerValidationResult;