@adhisang/minecraft-modding-mcp 4.2.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +5 -4
- package/dist/access-transformer-parser.d.ts +2 -1
- package/dist/access-transformer-parser.js +10 -1
- package/dist/build-suggested-call.js +31 -10
- package/dist/cache-registry.d.ts +8 -0
- package/dist/cache-registry.js +15 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +4 -0
- package/dist/entry-tools/analyze-mod-service.d.ts +24 -24
- package/dist/entry-tools/analyze-symbol-service.d.ts +57 -22
- package/dist/entry-tools/analyze-symbol-service.js +124 -28
- package/dist/entry-tools/batch-class-members-service.d.ts +3 -1
- package/dist/entry-tools/batch-class-members-service.js +6 -6
- package/dist/entry-tools/batch-class-source-service.d.ts +3 -1
- package/dist/entry-tools/batch-class-source-service.js +5 -6
- package/dist/entry-tools/batch-mappings-service.d.ts +3 -1
- package/dist/entry-tools/batch-mappings-service.js +4 -5
- package/dist/entry-tools/batch-symbol-exists-service.d.ts +3 -1
- package/dist/entry-tools/batch-symbol-exists-service.js +4 -5
- package/dist/entry-tools/compare-minecraft-service.d.ts +10 -10
- package/dist/entry-tools/entry-tool-schema.d.ts +1 -1
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +1 -3
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +2 -2
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +0 -5
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +0 -1
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +0 -1
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +0 -1
- package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +8 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.js +13 -2
- package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +1 -1
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +1 -1
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +90 -89
- package/dist/entry-tools/inspect-minecraft/internal.js +30 -14
- package/dist/entry-tools/inspect-minecraft-service.d.ts +218 -218
- package/dist/entry-tools/inspect-minecraft-service.js +1 -1
- package/dist/entry-tools/manage-cache-service.d.ts +4 -4
- package/dist/entry-tools/response-contract.d.ts +1 -1
- package/dist/entry-tools/response-contract.js +1 -0
- package/dist/entry-tools/validate-project/cases/mixin.js +2 -1
- package/dist/entry-tools/validate-project/cases/project-summary.js +5 -3
- package/dist/entry-tools/validate-project-service.d.ts +42 -36
- package/dist/entry-tools/validate-project-service.js +3 -1
- package/dist/entry-tools/verify-mixin-target-service.d.ts +30 -0
- package/dist/entry-tools/verify-mixin-target-service.js +113 -22
- package/dist/error-mapping.d.ts +51 -0
- package/dist/error-mapping.js +157 -2
- package/dist/index.js +138 -71
- package/dist/mapping/internal-types.d.ts +7 -0
- package/dist/mapping/lookup.d.ts +1 -1
- package/dist/mapping/lookup.js +7 -1
- package/dist/mapping/types.d.ts +6 -0
- package/dist/mapping-service.d.ts +26 -2
- package/dist/mapping-service.js +178 -36
- package/dist/mcp-helpers.d.ts +1 -0
- package/dist/mcp-helpers.js +25 -3
- package/dist/minecraft-explorer-service.js +24 -11
- package/dist/mixin/access-validators.js +33 -1
- package/dist/mixin/annotation-validators.js +53 -7
- package/dist/mixin/parsed-validator.js +2 -2
- package/dist/mixin-parser.js +48 -4
- package/dist/mod-decompile-service.js +2 -1
- package/dist/mod-remap-service.d.ts +20 -1
- package/dist/mod-remap-service.js +95 -40
- package/dist/nbt/java-nbt-codec.js +6 -0
- package/dist/observability.d.ts +6 -0
- package/dist/observability.js +10 -0
- package/dist/page-cursor.d.ts +17 -0
- package/dist/page-cursor.js +0 -0
- package/dist/registry-service.js +25 -1
- package/dist/resources.js +45 -9
- package/dist/response-utils.d.ts +29 -4
- package/dist/response-utils.js +95 -5
- package/dist/source/access-validate.js +12 -2
- package/dist/source/artifact-resolver.js +1 -1
- package/dist/source/class-source/members-builder.d.ts +44 -1
- package/dist/source/class-source/members-builder.js +51 -4
- package/dist/source/class-source/snippet-builder.d.ts +13 -0
- package/dist/source/class-source/snippet-builder.js +48 -4
- package/dist/source/class-source-helpers.js +67 -11
- package/dist/source/class-source.js +97 -20
- package/dist/source/indexer.js +5 -2
- package/dist/source/search.d.ts +1 -1
- package/dist/source/search.js +94 -22
- package/dist/source/validate-mixin.d.ts +2 -1
- package/dist/source/validate-mixin.js +45 -4
- package/dist/source/workspace-target.js +15 -9
- package/dist/source-jar-reader.d.ts +54 -1
- package/dist/source-jar-reader.js +121 -10
- package/dist/source-service.d.ts +28 -7
- package/dist/storage/files-repo.d.ts +12 -0
- package/dist/storage/files-repo.js +24 -0
- package/dist/storage/symbols-repo.d.ts +6 -0
- package/dist/storage/symbols-repo.js +6 -0
- package/dist/symbols/symbol-extractor.js +35 -6
- package/dist/text-truncate.d.ts +7 -0
- package/dist/text-truncate.js +21 -0
- package/dist/tiny-remapper-resolver.js +10 -0
- package/dist/tool-contract-manifest.js +2 -2
- package/dist/tool-execution-gate.js +12 -1
- package/dist/tool-guidance.d.ts +4 -1
- package/dist/tool-guidance.js +37 -12
- package/dist/tool-schemas.d.ts +559 -494
- package/dist/tool-schemas.js +99 -63
- package/dist/types.d.ts +1 -0
- package/dist/version-diff-service.js +5 -0
- package/dist/version-service.d.ts +3 -0
- package/dist/version-service.js +18 -2
- package/dist/warning-details.d.ts +18 -0
- package/dist/warning-details.js +63 -0
- package/docs/README-ja.md +2 -2
- package/docs/examples.md +26 -2
- package/docs/tool-reference.md +22 -17
- package/package.json +1 -1
package/dist/response-utils.d.ts
CHANGED
|
@@ -46,14 +46,29 @@ export declare const TOOL_PRESERVE_PAYLOAD_KEYS: Record<string, ReadonlySet<stri
|
|
|
46
46
|
* payload fields do not leak through as explicit nulls.
|
|
47
47
|
*/
|
|
48
48
|
export declare function compactResponse(obj: Record<string, unknown>, preserveKeys?: ReadonlySet<string>): Record<string, unknown>;
|
|
49
|
-
/**
|
|
50
|
-
|
|
49
|
+
/**
|
|
50
|
+
* resolve-artifact compact: omit debug/diagnostic fields. When `include` is provided,
|
|
51
|
+
* any field protected by a present include group is kept (re-added).
|
|
52
|
+
*/
|
|
53
|
+
export declare function compactArtifactResponse(obj: Record<string, unknown>, include?: ReadonlySet<string>): Record<string, unknown>;
|
|
51
54
|
/** get-class-source compact: drop provenance, artifactContents, qualityFlags. */
|
|
52
55
|
export declare function compactSourceResponse(obj: Record<string, unknown>): Record<string, unknown>;
|
|
53
56
|
/** get-class-members compact: drop provenance, artifactContents, qualityFlags, context. */
|
|
54
57
|
export declare function compactMembersResponse(obj: Record<string, unknown>): Record<string, unknown>;
|
|
55
|
-
/**
|
|
56
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Default diagnostic strip for get-class-source / get-class-members. Drops the
|
|
60
|
+
* three diagnostic fields (provenance, artifactContents, qualityFlags) that the
|
|
61
|
+
* common path never needs, applied unless the caller passes includeProvenance.
|
|
62
|
+
* Unlike compactMembersResponse this KEEPS members' `context` — only compact:true
|
|
63
|
+
* drops context. Reuses SOURCE_COMPACT_OMIT_KEYS (exactly those three keys).
|
|
64
|
+
*/
|
|
65
|
+
export declare function stripSourceDiagnostics(obj: Record<string, unknown>): Record<string, unknown>;
|
|
66
|
+
export declare function stripMembersDiagnostics(obj: Record<string, unknown>): Record<string, unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* Light compact projection: drop the artifactContents summary only. When `include`
|
|
69
|
+
* contains "artifact", the summary is kept (re-added).
|
|
70
|
+
*/
|
|
71
|
+
export declare function compactLightResponse(obj: Record<string, unknown>, include?: ReadonlySet<string>): Record<string, unknown>;
|
|
57
72
|
/**
|
|
58
73
|
* Mapping tool compact: project candidates for size reduction.
|
|
59
74
|
*
|
|
@@ -67,3 +82,13 @@ export declare function compactLightResponse(obj: Record<string, unknown>): Reco
|
|
|
67
82
|
* seeing.
|
|
68
83
|
*/
|
|
69
84
|
export declare function compactMappingResponse(obj: Record<string, unknown>): Record<string, unknown>;
|
|
85
|
+
export type ResponseDetailLevel = "summary" | "standard" | "full";
|
|
86
|
+
/** Expert + batch tools that accept the detail/include response contract. */
|
|
87
|
+
export declare const DETAIL_ENABLED_TOOL_NAMES: Set<string>;
|
|
88
|
+
/** Per-tool default detail level, chosen so default output is byte-identical to the old compact defaults. */
|
|
89
|
+
export declare const DEFAULT_DETAIL_BY_TOOL: Record<string, ResponseDetailLevel>;
|
|
90
|
+
/**
|
|
91
|
+
* Project an expert/batch tool result for the requested detail level + include set.
|
|
92
|
+
* Reuses the existing compact omit-sets; see the block comment above for the mapping.
|
|
93
|
+
*/
|
|
94
|
+
export declare function projectByDetail(tool: string, result: Record<string, unknown>, detail: ResponseDetailLevel, include: ReadonlySet<string>): Record<string, unknown>;
|
package/dist/response-utils.js
CHANGED
|
@@ -126,9 +126,32 @@ const ARTIFACT_COMPACT_OMIT_KEYS = new Set([
|
|
|
126
126
|
"repoUrl",
|
|
127
127
|
"resolvedSourceJarPath"
|
|
128
128
|
]);
|
|
129
|
-
/**
|
|
130
|
-
|
|
131
|
-
|
|
129
|
+
/**
|
|
130
|
+
* include-group => the resolve-artifact fields that group re-adds (un-omits) when present.
|
|
131
|
+
* Used by the detail/include projection so callers can opt specific diagnostics back in.
|
|
132
|
+
*/
|
|
133
|
+
const ARTIFACT_INCLUDE_PROTECTS = {
|
|
134
|
+
provenance: ["provenance"],
|
|
135
|
+
artifact: ["artifactContents"],
|
|
136
|
+
samples: ["sampleEntries"],
|
|
137
|
+
candidates: ["adjacentSourceCandidates"],
|
|
138
|
+
paths: ["binaryJarPath", "resolvedSourceJarPath", "coordinate", "repoUrl"]
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* resolve-artifact compact: omit debug/diagnostic fields. When `include` is provided,
|
|
142
|
+
* any field protected by a present include group is kept (re-added).
|
|
143
|
+
*/
|
|
144
|
+
export function compactArtifactResponse(obj, include) {
|
|
145
|
+
if (!include || include.size === 0) {
|
|
146
|
+
return projectOmitKeys(obj, ARTIFACT_COMPACT_OMIT_KEYS);
|
|
147
|
+
}
|
|
148
|
+
const omit = new Set(ARTIFACT_COMPACT_OMIT_KEYS);
|
|
149
|
+
for (const group of include) {
|
|
150
|
+
for (const protectedKey of ARTIFACT_INCLUDE_PROTECTS[group] ?? []) {
|
|
151
|
+
omit.delete(protectedKey);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return projectOmitKeys(obj, omit);
|
|
132
155
|
}
|
|
133
156
|
/** Fields to omit from get-class-source in compact mode. */
|
|
134
157
|
const SOURCE_COMPACT_OMIT_KEYS = new Set([
|
|
@@ -151,12 +174,31 @@ const MEMBERS_COMPACT_OMIT_KEYS = new Set([
|
|
|
151
174
|
export function compactMembersResponse(obj) {
|
|
152
175
|
return projectOmitKeys(obj, MEMBERS_COMPACT_OMIT_KEYS);
|
|
153
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Default diagnostic strip for get-class-source / get-class-members. Drops the
|
|
179
|
+
* three diagnostic fields (provenance, artifactContents, qualityFlags) that the
|
|
180
|
+
* common path never needs, applied unless the caller passes includeProvenance.
|
|
181
|
+
* Unlike compactMembersResponse this KEEPS members' `context` — only compact:true
|
|
182
|
+
* drops context. Reuses SOURCE_COMPACT_OMIT_KEYS (exactly those three keys).
|
|
183
|
+
*/
|
|
184
|
+
export function stripSourceDiagnostics(obj) {
|
|
185
|
+
return projectOmitKeys(obj, SOURCE_COMPACT_OMIT_KEYS);
|
|
186
|
+
}
|
|
187
|
+
export function stripMembersDiagnostics(obj) {
|
|
188
|
+
return projectOmitKeys(obj, SOURCE_COMPACT_OMIT_KEYS);
|
|
189
|
+
}
|
|
154
190
|
/** Fields to omit from search-class-source / list-artifact-files in compact mode. */
|
|
155
191
|
const LIGHT_COMPACT_OMIT_KEYS = new Set([
|
|
156
192
|
"artifactContents"
|
|
157
193
|
]);
|
|
158
|
-
/**
|
|
159
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Light compact projection: drop the artifactContents summary only. When `include`
|
|
196
|
+
* contains "artifact", the summary is kept (re-added).
|
|
197
|
+
*/
|
|
198
|
+
export function compactLightResponse(obj, include) {
|
|
199
|
+
if (include?.has("artifact")) {
|
|
200
|
+
return obj;
|
|
201
|
+
}
|
|
160
202
|
return projectOmitKeys(obj, LIGHT_COMPACT_OMIT_KEYS);
|
|
161
203
|
}
|
|
162
204
|
/** Max number of unresolved candidates that get full metadata in compact mode. */
|
|
@@ -224,4 +266,52 @@ export function compactMappingResponse(obj) {
|
|
|
224
266
|
}
|
|
225
267
|
return projected;
|
|
226
268
|
}
|
|
269
|
+
/** Expert + batch tools that accept the detail/include response contract. */
|
|
270
|
+
export const DETAIL_ENABLED_TOOL_NAMES = COMPACT_ENABLED_TOOL_NAMES;
|
|
271
|
+
/** Per-tool default detail level, chosen so default output is byte-identical to the old compact defaults. */
|
|
272
|
+
export const DEFAULT_DETAIL_BY_TOOL = {
|
|
273
|
+
"resolve-artifact": "summary",
|
|
274
|
+
"find-mapping": "summary",
|
|
275
|
+
"resolve-method-mapping-exact": "summary",
|
|
276
|
+
"resolve-workspace-symbol": "summary",
|
|
277
|
+
"check-symbol-exists": "summary",
|
|
278
|
+
"get-class-source": "standard",
|
|
279
|
+
"get-class-members": "standard",
|
|
280
|
+
"search-class-source": "standard",
|
|
281
|
+
"list-artifact-files": "standard"
|
|
282
|
+
};
|
|
283
|
+
/** Extra keys omitted from get-class-members at detail=summary (beyond the diagnostic strip). */
|
|
284
|
+
const MEMBERS_SUMMARY_EXTRA_OMIT = new Set(["context"]);
|
|
285
|
+
/**
|
|
286
|
+
* Project an expert/batch tool result for the requested detail level + include set.
|
|
287
|
+
* Reuses the existing compact omit-sets; see the block comment above for the mapping.
|
|
288
|
+
*/
|
|
289
|
+
export function projectByDetail(tool, result, detail, include) {
|
|
290
|
+
let out = result;
|
|
291
|
+
// Phase-4 diagnostic strip for source/members, now detail/include-aware:
|
|
292
|
+
// kept at detail=full or when include opts provenance back in.
|
|
293
|
+
const keepDiagnostics = detail === "full" || include.has("provenance");
|
|
294
|
+
if (!keepDiagnostics) {
|
|
295
|
+
if (tool === "get-class-source")
|
|
296
|
+
out = stripSourceDiagnostics(out);
|
|
297
|
+
if (tool === "get-class-members")
|
|
298
|
+
out = stripMembersDiagnostics(out);
|
|
299
|
+
}
|
|
300
|
+
if (detail === "summary") {
|
|
301
|
+
if (tool === "resolve-artifact") {
|
|
302
|
+
out = compactArtifactResponse(out, include);
|
|
303
|
+
}
|
|
304
|
+
if (COMPACT_MAPPING_TOOL_NAMES.has(tool) && !include.has("candidates")) {
|
|
305
|
+
out = compactMappingResponse(out);
|
|
306
|
+
}
|
|
307
|
+
if (tool === "get-class-members") {
|
|
308
|
+
out = projectOmitKeys(out, MEMBERS_SUMMARY_EXTRA_OMIT);
|
|
309
|
+
}
|
|
310
|
+
if (COMPACT_LIGHT_TOOL_NAMES.has(tool)) {
|
|
311
|
+
out = compactLightResponse(out, include);
|
|
312
|
+
}
|
|
313
|
+
out = compactResponse(out, TOOL_PRESERVE_PAYLOAD_KEYS[tool]);
|
|
314
|
+
}
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
227
317
|
//# sourceMappingURL=response-utils.js.map
|
|
@@ -11,6 +11,11 @@ function normalizeAccessWidenerNamespace(namespace) {
|
|
|
11
11
|
if (normalized === "named") {
|
|
12
12
|
return "yarn";
|
|
13
13
|
}
|
|
14
|
+
// Fabric/Loom call the obfuscated namespace "official"; treat it as obfuscated
|
|
15
|
+
// (matches the tiny-parser alias) instead of silently assuming intermediary.
|
|
16
|
+
if (normalized === "official") {
|
|
17
|
+
return "obfuscated";
|
|
18
|
+
}
|
|
14
19
|
if (normalized === "obfuscated" ||
|
|
15
20
|
normalized === "mojang" ||
|
|
16
21
|
normalized === "intermediary" ||
|
|
@@ -39,8 +44,13 @@ export async function validateAccessWidener(svc, input) {
|
|
|
39
44
|
const headerNamespaceRaw = normalizeOptionalString(parsed.namespace);
|
|
40
45
|
const overrideMapping = input.mapping ? normalizeMapping(input.mapping) : undefined;
|
|
41
46
|
const headerNamespace = normalizeAccessWidenerNamespace(headerNamespaceRaw);
|
|
42
|
-
if (!headerNamespace &&
|
|
43
|
-
|
|
47
|
+
if (!headerNamespace && !overrideMapping) {
|
|
48
|
+
// No usable namespace resolved and no override: surface the intermediary
|
|
49
|
+
// assumption so it is never silent (previously only warned when a header
|
|
50
|
+
// namespace was present but unsupported).
|
|
51
|
+
warnings.push(headerNamespaceRaw
|
|
52
|
+
? `Unsupported access widener namespace "${headerNamespaceRaw}". Assuming intermediary.`
|
|
53
|
+
: `Access widener namespace not declared; assuming intermediary. Pass mapping=… to set it explicitly.`);
|
|
44
54
|
}
|
|
45
55
|
const awNamespace = overrideMapping ?? headerNamespace ?? "intermediary";
|
|
46
56
|
if (overrideMapping && headerNamespace && overrideMapping !== headerNamespace) {
|
|
@@ -1228,7 +1228,7 @@ export async function resolveArtifact(svc, input) {
|
|
|
1228
1228
|
resolved.artifactAlias = artifactAlias;
|
|
1229
1229
|
await svc.ingestIfNeeded(resolved);
|
|
1230
1230
|
let sampleEntries;
|
|
1231
|
-
if (input.
|
|
1231
|
+
if (input.includeSampleEntries && resolved.sourceJarPath) {
|
|
1232
1232
|
try {
|
|
1233
1233
|
const javaEntries = await listJavaEntries(resolved.sourceJarPath);
|
|
1234
1234
|
const MAX_SAMPLE = 10;
|
|
@@ -31,5 +31,48 @@ export type MemberSliceResult = {
|
|
|
31
31
|
methods: SignatureMember[];
|
|
32
32
|
truncated: boolean;
|
|
33
33
|
returnedTotal: number;
|
|
34
|
+
/** Absolute offset to resume from when more members remain after this page. */
|
|
35
|
+
nextOffset?: number;
|
|
34
36
|
};
|
|
35
|
-
export declare function sliceMembersWithLimit(remapped: Omit<RemappedMembers, "counts">, totalCount: number, maxMembers: number, warnings: string[]): MemberSliceResult;
|
|
37
|
+
export declare function sliceMembersWithLimit(remapped: Omit<RemappedMembers, "counts">, totalCount: number, maxMembers: number, warnings: string[], offset?: number): MemberSliceResult;
|
|
38
|
+
/** Slim per-member wire shape: only what an agent reading Java needs. */
|
|
39
|
+
export type WireMember = {
|
|
40
|
+
name: string;
|
|
41
|
+
javaSignature: string;
|
|
42
|
+
/**
|
|
43
|
+
* JVM descriptor. Always present on methods/constructors (overload
|
|
44
|
+
* disambiguation for @At/@Shadow matching); omitted on FIELD members by
|
|
45
|
+
* default (the single type is already in javaSignature) unless
|
|
46
|
+
* keepFieldDescriptors is set.
|
|
47
|
+
*/
|
|
48
|
+
jvmDescriptor?: string;
|
|
49
|
+
/** Present only when members span multiple owners (includeInherited). */
|
|
50
|
+
ownerFqn?: string;
|
|
51
|
+
/** Present only when true. */
|
|
52
|
+
isSynthetic?: boolean;
|
|
53
|
+
};
|
|
54
|
+
export type WireMembersBlock = {
|
|
55
|
+
/** Hoisted owner when every member shares one owner (the common, non-inherited case). */
|
|
56
|
+
ownerFqn?: string;
|
|
57
|
+
constructors: WireMember[];
|
|
58
|
+
fields: WireMember[];
|
|
59
|
+
methods: WireMember[];
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Project the internal six-field SignatureMember arrays to the slim wire shape.
|
|
63
|
+
* `ownerFqn` is hoisted to the block level when all members share a single owner
|
|
64
|
+
* (so it is not repeated per member); when members span multiple owners — the
|
|
65
|
+
* includeInherited case — it stays per member and the block-level field is omitted.
|
|
66
|
+
* `accessFlags` is dropped (javaSignature already encodes the modifiers);
|
|
67
|
+
* `isSynthetic` is emitted only when true. `jvmDescriptor` is always kept on
|
|
68
|
+
* methods/constructors (overload disambiguation) and, on FIELD members, only when
|
|
69
|
+
* keepFieldDescriptors is set (the field's single type is already in javaSignature).
|
|
70
|
+
*
|
|
71
|
+
* The owner anchor is derived from the members themselves (not from the looked-up
|
|
72
|
+
* class name) so it is correct in the requested namespace regardless of remapping.
|
|
73
|
+
*/
|
|
74
|
+
export declare function projectMembersForWire(slice: {
|
|
75
|
+
constructors: SignatureMember[];
|
|
76
|
+
fields: SignatureMember[];
|
|
77
|
+
methods: SignatureMember[];
|
|
78
|
+
}, includeInherited: boolean, keepFieldDescriptors?: boolean): WireMembersBlock;
|
|
@@ -23,13 +23,22 @@ export async function remapAndCountMembers(svc, input) {
|
|
|
23
23
|
};
|
|
24
24
|
return { constructors, fields, methods, counts };
|
|
25
25
|
}
|
|
26
|
-
export function sliceMembersWithLimit(remapped, totalCount, maxMembers, warnings) {
|
|
26
|
+
export function sliceMembersWithLimit(remapped, totalCount, maxMembers, warnings, offset = 0) {
|
|
27
|
+
// Members form a single flat sequence in [constructors, fields, methods]
|
|
28
|
+
// order; offset skips that many from the start before taking maxMembers.
|
|
29
|
+
let toSkip = Math.max(0, offset);
|
|
27
30
|
let remaining = maxMembers;
|
|
28
31
|
const takeWithinLimit = (members) => {
|
|
32
|
+
if (toSkip >= members.length) {
|
|
33
|
+
toSkip -= members.length;
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const afterSkip = toSkip > 0 ? members.slice(toSkip) : members;
|
|
37
|
+
toSkip = 0;
|
|
29
38
|
if (remaining <= 0) {
|
|
30
39
|
return [];
|
|
31
40
|
}
|
|
32
|
-
const slice =
|
|
41
|
+
const slice = afterSkip.slice(0, remaining);
|
|
33
42
|
remaining -= slice.length;
|
|
34
43
|
return slice;
|
|
35
44
|
};
|
|
@@ -37,10 +46,48 @@ export function sliceMembersWithLimit(remapped, totalCount, maxMembers, warnings
|
|
|
37
46
|
const fields = takeWithinLimit(remapped.fields);
|
|
38
47
|
const methods = takeWithinLimit(remapped.methods);
|
|
39
48
|
const returnedTotal = constructors.length + fields.length + methods.length;
|
|
40
|
-
const
|
|
49
|
+
const consumed = Math.min(Math.max(0, offset), totalCount) + returnedTotal;
|
|
50
|
+
const truncated = consumed < totalCount;
|
|
41
51
|
if (truncated) {
|
|
42
52
|
warnings.push(`Member list was truncated to ${returnedTotal} entries (from ${totalCount}).`);
|
|
43
53
|
}
|
|
44
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
constructors,
|
|
56
|
+
fields,
|
|
57
|
+
methods,
|
|
58
|
+
truncated,
|
|
59
|
+
returnedTotal,
|
|
60
|
+
...(truncated ? { nextOffset: consumed } : {})
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Project the internal six-field SignatureMember arrays to the slim wire shape.
|
|
65
|
+
* `ownerFqn` is hoisted to the block level when all members share a single owner
|
|
66
|
+
* (so it is not repeated per member); when members span multiple owners — the
|
|
67
|
+
* includeInherited case — it stays per member and the block-level field is omitted.
|
|
68
|
+
* `accessFlags` is dropped (javaSignature already encodes the modifiers);
|
|
69
|
+
* `isSynthetic` is emitted only when true. `jvmDescriptor` is always kept on
|
|
70
|
+
* methods/constructors (overload disambiguation) and, on FIELD members, only when
|
|
71
|
+
* keepFieldDescriptors is set (the field's single type is already in javaSignature).
|
|
72
|
+
*
|
|
73
|
+
* The owner anchor is derived from the members themselves (not from the looked-up
|
|
74
|
+
* class name) so it is correct in the requested namespace regardless of remapping.
|
|
75
|
+
*/
|
|
76
|
+
export function projectMembersForWire(slice, includeInherited, keepFieldDescriptors = false) {
|
|
77
|
+
const owners = new Set([...slice.constructors, ...slice.fields, ...slice.methods].map((m) => m.ownerFqn));
|
|
78
|
+
const hoistOwner = !includeInherited && owners.size === 1;
|
|
79
|
+
const toWire = (m, keepDescriptor) => ({
|
|
80
|
+
name: m.name,
|
|
81
|
+
javaSignature: m.javaSignature,
|
|
82
|
+
...(keepDescriptor ? { jvmDescriptor: m.jvmDescriptor } : {}),
|
|
83
|
+
...(hoistOwner ? {} : { ownerFqn: m.ownerFqn }),
|
|
84
|
+
...(m.isSynthetic ? { isSynthetic: true } : {})
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
...(hoistOwner ? { ownerFqn: [...owners][0] } : {}),
|
|
88
|
+
constructors: slice.constructors.map((m) => toWire(m, true)),
|
|
89
|
+
fields: slice.fields.map((m) => toWire(m, keepFieldDescriptors)),
|
|
90
|
+
methods: slice.methods.map((m) => toWire(m, true))
|
|
91
|
+
};
|
|
45
92
|
}
|
|
46
93
|
//# sourceMappingURL=members-builder.js.map
|
|
@@ -15,5 +15,18 @@ export type SnippetBuildResult = {
|
|
|
15
15
|
returnedEnd: number;
|
|
16
16
|
truncated: boolean;
|
|
17
17
|
charsTruncated: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* First line the caller has NOT yet fully received, when more source remains.
|
|
20
|
+
* Safe to pass back as `startLine` to continue reading. Undefined when the
|
|
21
|
+
* response already reached the end of the file.
|
|
22
|
+
*/
|
|
23
|
+
nextStartLine?: number;
|
|
24
|
+
/**
|
|
25
|
+
* True when the requested window begins past the end of the file. The returned
|
|
26
|
+
* window is empty (returnedEnd < returnedStart) rather than clamped into the
|
|
27
|
+
* last real line, so paginated readers can detect overshoot instead of silently
|
|
28
|
+
* re-reading the final line.
|
|
29
|
+
*/
|
|
30
|
+
outOfRange?: boolean;
|
|
18
31
|
};
|
|
19
32
|
export declare function buildClassSourceSnippet(input: SnippetBuildInput): SnippetBuildResult;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import * as classSourceHelpers from "../class-source-helpers.js";
|
|
2
|
+
import { sliceToMaxCharsSafe } from "../../text-truncate.js";
|
|
2
3
|
export function buildClassSourceSnippet(input) {
|
|
3
|
-
const
|
|
4
|
+
const rawLines = input.content.split(/\r?\n/);
|
|
5
|
+
// A trailing newline produces a final empty element that is not a real source
|
|
6
|
+
// line. Drop a single one so totalLines and line addressing reflect the actual
|
|
7
|
+
// source (decompiled Java almost always ends with a trailing newline).
|
|
8
|
+
const lines = rawLines.length > 1 && rawLines[rawLines.length - 1] === ""
|
|
9
|
+
? rawLines.slice(0, -1)
|
|
10
|
+
: rawLines;
|
|
4
11
|
const totalLines = lines.length;
|
|
5
12
|
let sourceText;
|
|
6
13
|
let returnedStart;
|
|
@@ -15,7 +22,20 @@ export function buildClassSourceSnippet(input) {
|
|
|
15
22
|
else {
|
|
16
23
|
const requestedStart = input.startLine ?? 1;
|
|
17
24
|
const requestedEnd = input.endLine ?? totalLines;
|
|
18
|
-
|
|
25
|
+
if (requestedStart > totalLines) {
|
|
26
|
+
// The window begins past EOF: return an empty selection instead of clamping
|
|
27
|
+
// into the last real line, so paginated readers can detect the overshoot.
|
|
28
|
+
return {
|
|
29
|
+
sourceText: "",
|
|
30
|
+
totalLines,
|
|
31
|
+
returnedStart: requestedStart,
|
|
32
|
+
returnedEnd: requestedStart - 1,
|
|
33
|
+
truncated: true,
|
|
34
|
+
charsTruncated: false,
|
|
35
|
+
outOfRange: true
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const normalizedStart = Math.max(1, requestedStart);
|
|
19
39
|
const normalizedEnd = Math.min(Math.max(normalizedStart, requestedEnd), Math.max(totalLines, 1));
|
|
20
40
|
let selectedLines = lines.slice(normalizedStart - 1, normalizedEnd);
|
|
21
41
|
const clippedByRange = normalizedStart !== requestedStart || normalizedEnd !== requestedEnd;
|
|
@@ -30,17 +50,41 @@ export function buildClassSourceSnippet(input) {
|
|
|
30
50
|
truncated = clippedByRange || clippedByMax;
|
|
31
51
|
}
|
|
32
52
|
if (input.maxChars != null && sourceText.length > input.maxChars) {
|
|
33
|
-
sourceText = sourceText
|
|
53
|
+
sourceText = sliceToMaxCharsSafe(sourceText, input.maxChars);
|
|
34
54
|
charsTruncated = true;
|
|
35
55
|
truncated = true;
|
|
36
56
|
}
|
|
57
|
+
let nextStartLine;
|
|
58
|
+
// Metadata mode returns a synthesized outline, not a line window into the
|
|
59
|
+
// source, so its returnedStart/returnedEnd are not line-addressable and a
|
|
60
|
+
// char cut must not yield a (bogus) line continuation.
|
|
61
|
+
if (truncated && input.mode !== "metadata") {
|
|
62
|
+
if (charsTruncated) {
|
|
63
|
+
// A mid-line character cut may leave the final returned line partial.
|
|
64
|
+
// Count only the complete (newline-terminated) lines and resume from the
|
|
65
|
+
// first line not fully returned, re-reading any partial line in full.
|
|
66
|
+
// Require forward progress: when the cut lands inside the first returned
|
|
67
|
+
// line (no complete line returned), resuming at the same startLine with
|
|
68
|
+
// the same maxChars would loop, so emit no continuation — the caller must
|
|
69
|
+
// raise maxChars instead.
|
|
70
|
+
const completeLines = (sourceText.match(/\n/g) ?? []).length;
|
|
71
|
+
const resume = returnedStart + completeLines;
|
|
72
|
+
if (resume > returnedStart && resume <= totalLines) {
|
|
73
|
+
nextStartLine = resume;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (returnedEnd < totalLines) {
|
|
77
|
+
nextStartLine = returnedEnd + 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
37
80
|
return {
|
|
38
81
|
sourceText,
|
|
39
82
|
totalLines,
|
|
40
83
|
returnedStart,
|
|
41
84
|
returnedEnd,
|
|
42
85
|
truncated,
|
|
43
|
-
charsTruncated
|
|
86
|
+
charsTruncated,
|
|
87
|
+
...(nextStartLine != null ? { nextStartLine } : {})
|
|
44
88
|
};
|
|
45
89
|
}
|
|
46
90
|
//# sourceMappingURL=snippet-builder.js.map
|
|
@@ -67,17 +67,73 @@ export function extractDecompiledMembers(className, filePath, content) {
|
|
|
67
67
|
}
|
|
68
68
|
return { constructors, fields, methods };
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove everything that must not count toward brace depth — line comments,
|
|
72
|
+
* (possibly multi-line) block comments, string literals, and char literals —
|
|
73
|
+
* in a single stateful pass so that block comments spanning lines and char
|
|
74
|
+
* literals like '{' / '}' never skew brace accounting. Line indices are
|
|
75
|
+
* preserved (one output entry per input line). Text blocks (""" … """) are not
|
|
76
|
+
* special-cased; they are vanishingly rare in decompiled output.
|
|
77
|
+
*/
|
|
78
|
+
function stripBraceNoise(lines) {
|
|
79
|
+
const out = new Array(lines.length);
|
|
80
|
+
let inBlockComment = false;
|
|
81
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
82
|
+
const line = lines[i] ?? "";
|
|
83
|
+
let result = "";
|
|
84
|
+
let j = 0;
|
|
85
|
+
while (j < line.length) {
|
|
86
|
+
const ch = line[j];
|
|
87
|
+
const next = line[j + 1];
|
|
88
|
+
if (inBlockComment) {
|
|
89
|
+
if (ch === "*" && next === "/") {
|
|
90
|
+
inBlockComment = false;
|
|
91
|
+
j += 2;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
j += 1;
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (ch === "/" && next === "/") {
|
|
99
|
+
break; // line comment: ignore the rest of the line
|
|
100
|
+
}
|
|
101
|
+
if (ch === "/" && next === "*") {
|
|
102
|
+
inBlockComment = true;
|
|
103
|
+
j += 2;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (ch === '"' || ch === "'") {
|
|
107
|
+
const quote = ch;
|
|
108
|
+
j += 1;
|
|
109
|
+
while (j < line.length) {
|
|
110
|
+
if (line[j] === "\\") {
|
|
111
|
+
j += 2;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (line[j] === quote) {
|
|
115
|
+
j += 1;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
j += 1;
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
result += ch;
|
|
123
|
+
j += 1;
|
|
124
|
+
}
|
|
125
|
+
out[i] = result;
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
70
129
|
export function computeLineBraceDepths(lines) {
|
|
130
|
+
const stripped = stripBraceNoise(lines);
|
|
71
131
|
const depths = new Array(lines.length).fill(0);
|
|
72
132
|
let depth = 0;
|
|
73
|
-
for (let i = 0; i <
|
|
133
|
+
for (let i = 0; i < stripped.length; i += 1) {
|
|
74
134
|
// Entry depth for this line = depth observed before any brace on it.
|
|
75
135
|
depths[i] = depth;
|
|
76
|
-
const
|
|
77
|
-
.replace(/\/\/.*/g, "")
|
|
78
|
-
.replace(/"(?:\\.|[^"\\])*"/g, "\"\"")
|
|
79
|
-
.replace(/'(?:\\.|[^'\\])*'/g, "''");
|
|
80
|
-
for (const char of stripped) {
|
|
136
|
+
for (const char of stripped[i] ?? "") {
|
|
81
137
|
if (char === "{") {
|
|
82
138
|
depth += 1;
|
|
83
139
|
}
|
|
@@ -98,13 +154,13 @@ export function computeBraceRange(lines, symbols, simpleName) {
|
|
|
98
154
|
return scanBraceRange(lines, classSymbol.line);
|
|
99
155
|
}
|
|
100
156
|
export function scanBraceRange(lines, declarationLine) {
|
|
157
|
+
// Strip from the start of the file so multi-line block-comment state is correct
|
|
158
|
+
// by the time we reach declarationLine; indices stay aligned with `lines`.
|
|
159
|
+
const stripped = stripBraceNoise(lines);
|
|
101
160
|
let depth = 0;
|
|
102
161
|
let started = false;
|
|
103
|
-
for (let i = declarationLine - 1; i <
|
|
104
|
-
const
|
|
105
|
-
.replace(/\/\/.*/g, "")
|
|
106
|
-
.replace(/"(?:\\.|[^"\\])*"/g, "\"\"");
|
|
107
|
-
for (const char of stripped) {
|
|
162
|
+
for (let i = declarationLine - 1; i < stripped.length; i += 1) {
|
|
163
|
+
for (const char of stripped[i] ?? "") {
|
|
108
164
|
if (char === "{") {
|
|
109
165
|
depth += 1;
|
|
110
166
|
started = true;
|