@spyglassmc/mcdoc 0.3.0 → 0.3.2

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.
@@ -1,14 +1,18 @@
1
- import { AsyncBinder, atArray, Dev, Range, ResourceLocationNode, SymbolUtil, traversePreOrder } from '@spyglassmc/core';
1
+ import { AsyncBinder, atArray, Dev, Range, ResourceLocationNode, SymbolUtil, traversePreOrder, } from '@spyglassmc/core';
2
2
  import { localeQuote, localize } from '@spyglassmc/locales';
3
- import { AttributeNode, AttributeTreeNamedValuesNode, AttributeTreeNode, AttributeTreePosValuesNode, DispatcherTypeNode, DispatchStatementNode, DocCommentsNode, DynamicIndexNode, EnumBlockNode, EnumFieldNode, EnumInjectionNode, EnumNode, FloatRangeNode, IndexBodyNode, InjectionNode, IntRangeNode, ListTypeNode, LiteralNode, LiteralTypeNode, NumericTypeNode, PathNode, PrimitiveArrayTypeNode, ReferenceTypeNode, StaticIndexNode, StringTypeNode, StructBlockNode, StructMapKeyNode, StructNode, StructPairFieldNode, StructSpreadFieldNode, TopLevelNode, TupleTypeNode, TypeAliasNode, TypeBaseNode, TypedNumberNode, TypeParamBlockNode, TypeParamNode, UnionTypeNode, UseStatementNode } from '../node/index.js';
3
+ import { AttributeNode, AttributeTreeNamedValuesNode, AttributeTreeNode, AttributeTreePosValuesNode, DispatcherTypeNode, DispatchStatementNode, DocCommentsNode, DynamicIndexNode, EnumBlockNode, EnumFieldNode, EnumInjectionNode, EnumNode, FloatRangeNode, IndexBodyNode, InjectionNode, IntRangeNode, ListTypeNode, LiteralNode, LiteralTypeNode, NumericTypeNode, PathNode, PrimitiveArrayTypeNode, ReferenceTypeNode, StaticIndexNode, StringTypeNode, StructBlockNode, StructMapKeyNode, StructNode, StructPairFieldNode, StructSpreadFieldNode, TopLevelNode, TupleTypeNode, TypeAliasNode, TypeArgBlockNode, TypeBaseNode, TypedNumberNode, TypeParamBlockNode, TypeParamNode, UnionTypeNode, UseStatementNode, } from '../node/index.js';
4
4
  const ModuleSymbolData = Object.freeze({
5
5
  is(data) {
6
- return !!data && typeof data === 'object' && typeof data.nextAnonymousIndex === 'number';
6
+ return (!!data &&
7
+ typeof data === 'object' &&
8
+ typeof data.nextAnonymousIndex === 'number');
7
9
  },
8
10
  });
9
- const TypeDefSymbolData = Object.freeze({
11
+ export const TypeDefSymbolData = Object.freeze({
10
12
  is(data) {
11
- return !!data && typeof data === 'object' && typeof data.typeDef === 'object';
13
+ return (!!data &&
14
+ typeof data === 'object' &&
15
+ typeof data.typeDef === 'object');
12
16
  },
13
17
  });
14
18
  export const fileModule = AsyncBinder.create(async (node, ctx) => {
@@ -56,7 +60,7 @@ export async function module_(node, ctx) {
56
60
  * Hoist enums, structs, type aliases, and use statements under the module scope.
57
61
  */
58
62
  function hoist(node, ctx) {
59
- traversePreOrder(node, () => true, TopLevelNode.is, child => {
63
+ traversePreOrder(node, () => true, TopLevelNode.is, (child) => {
60
64
  switch (child.type) {
61
65
  case 'mcdoc:enum':
62
66
  hoistEnum(child);
@@ -73,18 +77,27 @@ function hoist(node, ctx) {
73
77
  }
74
78
  });
75
79
  function hoistEnum(node) {
76
- hoistFor('enum', node, EnumNode.destruct, n => ({ typeDef: convertEnum(n, ctx) }));
80
+ hoistFor('enum', node, EnumNode.destruct, (n) => ({
81
+ typeDef: convertEnum(n, ctx),
82
+ }));
77
83
  }
78
84
  function hoistStruct(node) {
79
- hoistFor('struct', node, StructNode.destruct, n => ({ typeDef: convertStruct(n, ctx) }));
85
+ hoistFor('struct', node, StructNode.destruct, (n) => ({
86
+ typeDef: convertStruct(n, ctx),
87
+ }));
80
88
  }
81
89
  function hoistTypeAlias(node) {
82
- hoistFor('type_alias', node, TypeAliasNode.destruct, n => {
83
- const { rhs } = TypeAliasNode.destruct(n);
90
+ hoistFor('type_alias', node, TypeAliasNode.destruct, (n) => {
91
+ const { attributes, rhs, typeParams } = TypeAliasNode.destruct(n);
84
92
  if (!rhs) {
85
93
  return undefined;
86
94
  }
87
- return { typeDef: convertType(rhs, ctx) };
95
+ const ans = { typeDef: convertType(rhs, ctx) };
96
+ if (typeParams) {
97
+ bindTypeParamBlock(node, typeParams, ans, ctx);
98
+ }
99
+ ans.typeDef = attributeType(ans.typeDef, attributes, ctx);
100
+ return ans;
88
101
  });
89
102
  }
90
103
  function hoistUseStatement(node) {
@@ -103,23 +116,34 @@ function hoist(node, ctx) {
103
116
  // they will go to the definition in the imported file.
104
117
  ctx.symbols
105
118
  .query({ doc: ctx.doc, node }, 'mcdoc', `${ctx.moduleIdentifier}::${identifier.value}`)
106
- .ifDeclared(symbol => reportDuplicatedDeclaration(ctx, symbol, identifier))
119
+ .ifDeclared((symbol) => reportDuplicatedDeclaration(ctx, symbol, identifier))
107
120
  .elseEnter({
108
- data: { subcategory: 'use_statement_binding', visibility: 1 /* SymbolVisibility.File */ },
121
+ data: {
122
+ subcategory: 'use_statement_binding',
123
+ visibility: 1 /* SymbolVisibility.File */,
124
+ },
109
125
  usage: { type: 'definition', node: identifier, fullRange: node },
110
126
  });
111
127
  }
112
128
  function hoistFor(subcategory, node, destructor, getData) {
113
- const { docComments, identifier } = destructor(node);
129
+ const { docComments, identifier, keyword } = destructor(node);
114
130
  const name = identifier?.value ?? nextAnonymousIdentifier(node, ctx);
115
131
  ctx.symbols
116
132
  .query({ doc: ctx.doc, node }, 'mcdoc', `${ctx.moduleIdentifier}::${name}`)
117
- .ifDeclared(symbol => reportDuplicatedDeclaration(ctx, symbol, identifier ?? node))
133
+ .ifDeclared((symbol) => reportDuplicatedDeclaration(ctx, symbol, identifier ?? node))
118
134
  .elseEnter({
119
- data: { data: getData(node), desc: DocCommentsNode.asText(docComments), subcategory },
135
+ data: {
136
+ data: getData(node),
137
+ desc: DocCommentsNode.asText(docComments),
138
+ subcategory,
139
+ },
120
140
  // If the current syntax structure is named, then the identifier node is entered as a definition;
121
- // otherwise, an anonymous identifier is generated for the symbol and the whole syntax structure node is entered as a definition.
122
- usage: { type: 'definition', node: identifier ?? node, fullRange: identifier && node },
141
+ // otherwise, an anonymous identifier is generated for the symbol and the keyword node is entered as a definition.
142
+ usage: {
143
+ type: 'definition',
144
+ node: identifier ?? keyword,
145
+ fullRange: identifier && node,
146
+ },
123
147
  });
124
148
  }
125
149
  function nextAnonymousIndex(node, ctx) {
@@ -135,41 +159,83 @@ function hoist(node, ctx) {
135
159
  return `<anonymous ${nextAnonymousIndex(node, ctx)}>`;
136
160
  }
137
161
  }
162
+ /**
163
+ * Bind the type param block of a parent node, and modifies the `data` argument in-place to change its `typeDef` to be of template kind.
164
+ */
165
+ function bindTypeParamBlock(node, typeParams, data, ctx) {
166
+ // Type parameters are added as local symbols on the type alias AST node.
167
+ // Thus we create a new local scope on the type alias statement node first.
168
+ node.locals = Object.create(null);
169
+ // They are also added to the type definition.
170
+ data.typeDef = {
171
+ kind: 'template',
172
+ child: data.typeDef,
173
+ typeParams: [],
174
+ };
175
+ const { params } = TypeParamBlockNode.destruct(typeParams);
176
+ for (const param of params) {
177
+ const { identifier: paramIdentifier } = TypeParamNode.destruct(param);
178
+ if (paramIdentifier.value) {
179
+ // Add the type parameter as a local symbol.
180
+ const paramPath = `${ctx.moduleIdentifier}::${paramIdentifier.value}`;
181
+ ctx.symbols
182
+ .query({ doc: ctx.doc, node }, 'mcdoc', paramPath)
183
+ .ifDeclared((symbol) => reportDuplicatedDeclaration(ctx, symbol, paramIdentifier))
184
+ .elseEnter({
185
+ data: { visibility: 0 /* SymbolVisibility.Block */ },
186
+ usage: {
187
+ type: 'declaration',
188
+ node: paramIdentifier,
189
+ fullRange: param,
190
+ },
191
+ });
192
+ // Also add it to the type definition.
193
+ data.typeDef.typeParams.push({ path: paramPath });
194
+ }
195
+ // if (constraint) {
196
+ // await bindPath(constraint, ctx)
197
+ // }
198
+ }
199
+ }
138
200
  async function bindDispatchStatement(node, ctx) {
139
- const { attributes, location, index, target } = DispatchStatementNode.destruct(node);
201
+ const { attributes, location, index, target, typeParams } = DispatchStatementNode.destruct(node);
140
202
  if (!(location && index && target)) {
141
203
  return;
142
204
  }
143
205
  const locationStr = ResourceLocationNode.toString(location, 'full');
144
- ctx.symbols
145
- .query(ctx.doc, 'mcdoc/dispatcher', locationStr)
146
- .enter({
206
+ ctx.symbols.query(ctx.doc, 'mcdoc/dispatcher', locationStr).enter({
147
207
  usage: { type: 'reference', node: location, fullRange: node },
148
208
  });
149
209
  const { parallelIndices } = IndexBodyNode.destruct(index);
150
- for (const key of parallelIndices) {
151
- if (DynamicIndexNode.is(key)) {
152
- // Ignore dynamic indices in dispatch statements.
153
- continue;
210
+ if (parallelIndices.length) {
211
+ const data = {
212
+ typeDef: convertType(target, ctx),
213
+ };
214
+ if (typeParams) {
215
+ bindTypeParamBlock(node, typeParams, data, ctx);
216
+ }
217
+ data.typeDef = attributeType(data.typeDef, attributes, ctx);
218
+ for (const key of parallelIndices) {
219
+ if (DynamicIndexNode.is(key)) {
220
+ // Ignore dynamic indices in dispatch statements.
221
+ continue;
222
+ }
223
+ ctx.symbols
224
+ .query(ctx.doc, 'mcdoc/dispatcher', locationStr, asString(key))
225
+ .ifDeclared((symbol) => reportDuplicatedDeclaration(ctx, symbol, key, {
226
+ localeString: 'mcdoc.binder.dispatcher-statement.duplicated-key',
227
+ }))
228
+ .elseEnter({
229
+ data: { data },
230
+ usage: { type: 'definition', node: key, fullRange: node },
231
+ });
154
232
  }
155
- ctx.symbols
156
- .query(ctx.doc, 'mcdoc/dispatcher', locationStr, asString(key))
157
- .ifDeclared(symbol => reportDuplicatedDeclaration(ctx, symbol, key, { localeString: 'mcdoc.binder.dispatcher-statement.duplicated-key.related' }))
158
- .elseEnter({
159
- data: {
160
- data: {
161
- attributes: convertAttributes(attributes, ctx),
162
- typeDef: convertType(target, ctx),
163
- },
164
- },
165
- usage: { type: 'definition', node: key, fullRange: node },
166
- });
167
233
  }
168
234
  await bindType(target, ctx);
169
235
  }
170
236
  async function bindType(node, ctx) {
171
237
  if (DispatcherTypeNode.is(node)) {
172
- bindDispatcherType(node, ctx);
238
+ await bindDispatcherType(node, ctx);
173
239
  }
174
240
  else if (EnumNode.is(node)) {
175
241
  bindEnum(node, ctx);
@@ -179,11 +245,8 @@ async function bindType(node, ctx) {
179
245
  await bindType(item, ctx);
180
246
  }
181
247
  else if (ReferenceTypeNode.is(node)) {
182
- const { path, typeParameters } = ReferenceTypeNode.destruct(node);
248
+ const { path } = ReferenceTypeNode.destruct(node);
183
249
  await bindPath(path, ctx);
184
- for (const param of typeParameters) {
185
- await bindType(param, ctx);
186
- }
187
250
  }
188
251
  else if (StructNode.is(node)) {
189
252
  await bindStruct(node, ctx);
@@ -200,10 +263,22 @@ async function bindType(node, ctx) {
200
263
  await bindType(member, ctx);
201
264
  }
202
265
  }
266
+ const { appendixes } = TypeBaseNode.destruct(node);
267
+ for (const appendix of appendixes) {
268
+ if (TypeArgBlockNode.is(appendix)) {
269
+ const { args } = TypeArgBlockNode.destruct(appendix);
270
+ for (const arg of args) {
271
+ await bindType(arg, ctx);
272
+ }
273
+ }
274
+ }
203
275
  }
204
- function bindDispatcherType(node, ctx) {
276
+ async function bindDispatcherType(node, ctx) {
205
277
  const { index, location } = DispatcherTypeNode.destruct(node);
206
278
  const locationStr = ResourceLocationNode.toString(location, 'full');
279
+ ctx.symbols
280
+ .query(ctx.doc, 'mcdoc/dispatcher', locationStr)
281
+ .enter({ usage: { type: 'reference', node: location, fullRange: node } });
207
282
  const { parallelIndices } = IndexBodyNode.destruct(index);
208
283
  for (const key of parallelIndices) {
209
284
  if (DynamicIndexNode.is(key)) {
@@ -226,7 +301,7 @@ async function bindPath(node, ctx) {
226
301
  const referencedModuleFile = pathArrayToString(identifiers);
227
302
  const referencedModuleUri = identifierToUri(referencedModuleFile, ctx);
228
303
  if (!referencedModuleUri) {
229
- ctx.err.report(localize('mcdoc.binder.path.unknown-module', localeQuote(referencedModuleFile)), node);
304
+ ctx.err.report(localize('mcdoc.binder.path.unknown-module', localeQuote(referencedModuleFile)), node, 2 /* ErrorSeverity.Warning */);
230
305
  return;
231
306
  }
232
307
  await ctx.ensureBindingStarted(referencedModuleUri);
@@ -234,18 +309,23 @@ async function bindPath(node, ctx) {
234
309
  ctx.symbols
235
310
  .query({ doc: ctx.doc, node: identNode }, 'mcdoc', pathArrayToString(identifiers))
236
311
  .ifDeclared((_, query) => query.enter({
237
- usage: { type: 'reference', node: identNode, fullRange: node, skipRenaming: LiteralNode.is(identNode) },
312
+ usage: {
313
+ type: 'reference',
314
+ node: identNode,
315
+ fullRange: node,
316
+ skipRenaming: LiteralNode.is(identNode),
317
+ },
238
318
  }))
239
319
  .else(() => {
240
320
  if (indexRight === 0) {
241
- ctx.err.report(localize('mcdoc.binder.path.unknown-identifier', localeQuote(atArray(identifiers, -1)), localeQuote(pathArrayToString(identifiers.slice(0, -1)))), node);
321
+ ctx.err.report(localize('mcdoc.binder.path.unknown-identifier', localeQuote(atArray(identifiers, -1)), localeQuote(pathArrayToString(identifiers.slice(0, -1)))), node, 2 /* ErrorSeverity.Warning */);
242
322
  }
243
323
  });
244
324
  }
245
325
  }
246
326
  function bindEnum(node, ctx) {
247
- const { block, identifier } = EnumNode.destruct(node);
248
- const symbol = identifier?.symbol ?? node.symbol;
327
+ const { block, identifier, keyword } = EnumNode.destruct(node);
328
+ const symbol = identifier?.symbol ?? keyword.symbol;
249
329
  if (symbol?.subcategory !== 'enum') {
250
330
  return;
251
331
  }
@@ -257,9 +337,11 @@ function bindEnumBlock(node, ctx, query, options = {}) {
257
337
  const { fields } = EnumBlockNode.destruct(node);
258
338
  for (const field of fields) {
259
339
  const { identifier } = EnumFieldNode.destruct(field);
260
- query.member(identifier.value, fieldQuery => fieldQuery
261
- .ifDeclared(symbol => reportDuplicatedDeclaration(ctx, symbol, identifier))
262
- .elseEnter({ usage: { type: 'definition', node: identifier, fullRange: field } }));
340
+ query.member(identifier.value, (fieldQuery) => fieldQuery
341
+ .ifDeclared((symbol) => reportDuplicatedDeclaration(ctx, symbol, identifier))
342
+ .elseEnter({
343
+ usage: { type: 'definition', node: identifier, fullRange: field },
344
+ }));
263
345
  }
264
346
  }
265
347
  async function bindInjection(node, ctx) {
@@ -271,8 +353,8 @@ async function bindInjection(node, ctx) {
271
353
  }
272
354
  }
273
355
  async function bindStruct(node, ctx) {
274
- const { block, identifier } = StructNode.destruct(node);
275
- const symbol = identifier?.symbol ?? node.symbol;
356
+ const { block, identifier, keyword } = StructNode.destruct(node);
357
+ const symbol = identifier?.symbol ?? keyword.symbol;
276
358
  if (symbol?.subcategory !== 'struct') {
277
359
  return;
278
360
  }
@@ -286,9 +368,11 @@ async function bindStructBlock(node, ctx, query, options = {}) {
286
368
  if (StructPairFieldNode.is(field)) {
287
369
  const { key, type } = StructPairFieldNode.destruct(field);
288
370
  if (!StructMapKeyNode.is(key)) {
289
- query.member(key.value, fieldQuery => fieldQuery
290
- .ifDeclared(symbol => reportDuplicatedDeclaration(ctx, symbol, key))
291
- .elseEnter({ usage: { type: 'definition', node: key, fullRange: field } }));
371
+ query.member(key.value, (fieldQuery) => fieldQuery
372
+ .ifDeclared((symbol) => reportDuplicatedDeclaration(ctx, symbol, key))
373
+ .elseEnter({
374
+ usage: { type: 'definition', node: key, fullRange: field },
375
+ }));
292
376
  }
293
377
  await bindType(type, ctx);
294
378
  }
@@ -303,39 +387,6 @@ async function bindTypeAlias(node, ctx) {
303
387
  if (!identifier?.value) {
304
388
  return;
305
389
  }
306
- if (typeParams) {
307
- // Type parameters are added as local symbols on the type alias AST node.
308
- node.locals = Object.create(null);
309
- const { params } = TypeParamBlockNode.destruct(typeParams);
310
- const query = ctx.symbols.query({ doc: ctx.doc, node }, 'mcdoc', `${ctx.moduleIdentifier}::${identifier.value}`);
311
- if (query.symbol?.subcategory === 'type_alias') {
312
- // Type parameters are also added to the symbol data.
313
- const oldData = query.symbol.data;
314
- if (!TypeDefSymbolData.is(oldData)) {
315
- throw new Error('Failed to locate the typeDef data associated with a supposedly hoisted type alias symbol');
316
- }
317
- const data = {
318
- ...oldData,
319
- typeParams: [],
320
- };
321
- query.symbol.data = data;
322
- for (const param of params) {
323
- const { identifier: paramIdentifier } = TypeParamNode.destruct(param);
324
- if (paramIdentifier.value) {
325
- // Add the type parameter as a local symbol.
326
- ctx.symbols
327
- .query({ doc: ctx.doc, node }, 'mcdoc', `${ctx.moduleIdentifier}::${paramIdentifier.value}`)
328
- .ifDeclared(symbol => reportDuplicatedDeclaration(ctx, symbol, paramIdentifier))
329
- .elseEnter({ data: { visibility: 0 /* SymbolVisibility.Block */ }, usage: { type: 'declaration', node: paramIdentifier, fullRange: param } });
330
- // Also add it to the symbol data.
331
- data.typeParams.push({ identifier: paramIdentifier.value });
332
- }
333
- // if (constraint) {
334
- // await bindPath(constraint, ctx)
335
- // }
336
- }
337
- }
338
- }
339
390
  if (rhs) {
340
391
  await bindType(rhs, ctx);
341
392
  }
@@ -352,10 +403,12 @@ export function registerMcdocBinders(meta) {
352
403
  }
353
404
  function reportDuplicatedDeclaration(ctx, symbol, range, options = { localeString: 'mcdoc.binder.duplicated-declaration' }) {
354
405
  ctx.err.report(localize(options.localeString, localeQuote(symbol.identifier)), range, 2 /* ErrorSeverity.Warning */, {
355
- related: [{
406
+ related: [
407
+ {
356
408
  location: SymbolUtil.getDeclaredLocation(symbol),
357
409
  message: localize(`${options.localeString}.related`, localeQuote(symbol.identifier)),
358
- }],
410
+ },
411
+ ],
359
412
  });
360
413
  }
361
414
  function* resolvePathByStep(path, ctx, options = {}) {
@@ -381,7 +434,12 @@ function* resolvePathByStep(path, ctx, options = {}) {
381
434
  default:
382
435
  Dev.assertNever(child);
383
436
  }
384
- yield { identifiers, node: child, index: i, indexRight: children.length - 1 - i };
437
+ yield {
438
+ identifiers,
439
+ node: child,
440
+ index: i,
441
+ indexRight: children.length - 1 - i,
442
+ };
385
443
  }
386
444
  }
387
445
  function resolvePath(path, ctx, options = {}) {
@@ -391,43 +449,84 @@ function identifierToUri(module, ctx) {
391
449
  return ctx.symbols.global.mcdoc?.[module]?.definition?.[0]?.uri;
392
450
  }
393
451
  function uriToIdentifier(uri, ctx) {
394
- return Object
395
- .values(ctx.symbols.global.mcdoc ?? {})
396
- .find(symbol => {
397
- return symbol.subcategory === 'module' && symbol.definition?.some(loc => loc.uri === uri);
398
- })
399
- ?.identifier;
452
+ return Object.values(ctx.symbols.global.mcdoc ?? {}).find((symbol) => {
453
+ return (symbol.subcategory === 'module' &&
454
+ symbol.definition?.some((loc) => loc.uri === uri));
455
+ })?.identifier;
400
456
  }
401
457
  function pathArrayToString(path) {
402
458
  return path ? `::${path.join('::')}` : undefined;
403
459
  }
404
460
  function convertType(node, ctx) {
405
461
  switch (node.type) {
406
- case 'mcdoc:enum': return convertEnum(node, ctx);
407
- case 'mcdoc:struct': return convertStruct(node, ctx);
408
- case 'mcdoc:type/any': return convertAny(node, ctx);
409
- case 'mcdoc:type/boolean': return convertBoolean(node, ctx);
410
- case 'mcdoc:type/dispatcher': return convertDispatcher(node, ctx);
411
- case 'mcdoc:type/list': return convertList(node, ctx);
412
- case 'mcdoc:type/literal': return convertLiteral(node, ctx);
413
- case 'mcdoc:type/numeric_type': return convertNumericType(node, ctx);
414
- case 'mcdoc:type/primitive_array': return convertPrimitiveArray(node, ctx);
415
- case 'mcdoc:type/string': return convertString(node, ctx);
416
- case 'mcdoc:type/reference': return convertReference(node, ctx);
417
- case 'mcdoc:type/tuple': return convertTuple(node, ctx);
418
- case 'mcdoc:type/union': return convertUnion(node, ctx);
419
- default: return Dev.assertNever(node);
420
- }
421
- }
422
- function convertBase(node, ctx, options = {}) {
423
- const { attributes, indices } = TypeBaseNode.destruct(node);
424
- return {
425
- attributes: convertAttributes(attributes, ctx),
426
- indices: convertIndexBodies(options.skipFirstIndexBody ? indices.slice(1) : indices, ctx),
427
- };
462
+ case 'mcdoc:enum':
463
+ return convertEnum(node, ctx);
464
+ case 'mcdoc:struct':
465
+ return convertStruct(node, ctx);
466
+ case 'mcdoc:type/any':
467
+ return convertAny(node, ctx);
468
+ case 'mcdoc:type/boolean':
469
+ return convertBoolean(node, ctx);
470
+ case 'mcdoc:type/dispatcher':
471
+ return convertDispatcher(node, ctx);
472
+ case 'mcdoc:type/list':
473
+ return convertList(node, ctx);
474
+ case 'mcdoc:type/literal':
475
+ return convertLiteral(node, ctx);
476
+ case 'mcdoc:type/numeric_type':
477
+ return convertNumericType(node, ctx);
478
+ case 'mcdoc:type/primitive_array':
479
+ return convertPrimitiveArray(node, ctx);
480
+ case 'mcdoc:type/string':
481
+ return convertString(node, ctx);
482
+ case 'mcdoc:type/reference':
483
+ return convertReference(node, ctx);
484
+ case 'mcdoc:type/tuple':
485
+ return convertTuple(node, ctx);
486
+ case 'mcdoc:type/union':
487
+ return convertUnion(node, ctx);
488
+ default:
489
+ return Dev.assertNever(node);
490
+ }
491
+ }
492
+ function wrapType(node, type, ctx, options = {}) {
493
+ const { attributes, appendixes } = TypeBaseNode.destruct(node);
494
+ let ans = type;
495
+ for (const appendix of appendixes) {
496
+ if (IndexBodyNode.is(appendix)) {
497
+ if (options.skipFirstIndexBody) {
498
+ options.skipFirstIndexBody = false;
499
+ continue;
500
+ }
501
+ ans = {
502
+ kind: 'indexed',
503
+ child: ans,
504
+ parallelIndices: convertIndexBody(appendix, ctx),
505
+ };
506
+ }
507
+ else {
508
+ ans = {
509
+ kind: 'concrete',
510
+ child: ans,
511
+ typeArgs: convertTypeArgBlock(appendix, ctx),
512
+ };
513
+ }
514
+ }
515
+ ans = attributeType(ans, attributes, ctx);
516
+ return ans;
517
+ }
518
+ function attributeType(type, attributes, ctx) {
519
+ for (const attribute of attributes) {
520
+ type = {
521
+ kind: 'attributed',
522
+ attribute: convertAttribute(attribute, ctx),
523
+ child: type,
524
+ };
525
+ }
526
+ return type;
428
527
  }
429
528
  function convertAttributes(nodes, ctx) {
430
- return undefineEmptyArray(nodes.map(n => convertAttribute(n, ctx)));
529
+ return undefineEmptyArray(nodes.map((n) => convertAttribute(n, ctx)));
431
530
  }
432
531
  function undefineEmptyArray(array) {
433
532
  return array.length ? array : undefined;
@@ -468,11 +567,11 @@ function convertAttributeTree(node, ctx) {
468
567
  return ans;
469
568
  }
470
569
  function convertIndexBodies(nodes, ctx) {
471
- return undefineEmptyArray(nodes.map(n => convertIndexBody(n, ctx)));
570
+ return undefineEmptyArray(nodes.map((n) => convertIndexBody(n, ctx)));
472
571
  }
473
572
  function convertIndexBody(node, ctx) {
474
573
  const { parallelIndices } = IndexBodyNode.destruct(node);
475
- return parallelIndices.map(n => convertIndex(n, ctx));
574
+ return parallelIndices.map((n) => convertIndex(n, ctx));
476
575
  }
477
576
  function convertIndex(node, ctx) {
478
577
  return StaticIndexNode.is(node)
@@ -492,23 +591,28 @@ function convertDynamicIndex(node, ctx) {
492
591
  accessor: keys.map(asString),
493
592
  };
494
593
  }
594
+ function convertTypeArgBlock(node, ctx) {
595
+ const { args } = TypeArgBlockNode.destruct(node);
596
+ return args.map((a) => convertType(a, ctx));
597
+ }
495
598
  function convertEnum(node, ctx) {
496
599
  const { block, enumKind, identifier } = EnumNode.destruct(node);
497
600
  // Shortcut if the typeDef has been added to the enum symbol.
498
601
  const symbol = identifier?.symbol ?? node.symbol;
499
- if (symbol && TypeDefSymbolData.is(symbol.data) && symbol.data.typeDef.kind === 'enum') {
602
+ if (symbol &&
603
+ TypeDefSymbolData.is(symbol.data) &&
604
+ symbol.data.typeDef.kind === 'enum') {
500
605
  return symbol.data.typeDef;
501
606
  }
502
- return {
503
- ...convertBase(node, ctx),
607
+ return wrapType(node, {
504
608
  kind: 'enum',
505
609
  enumKind,
506
610
  values: convertEnumBlock(block, ctx),
507
- };
611
+ }, ctx);
508
612
  }
509
613
  function convertEnumBlock(node, ctx) {
510
614
  const { fields } = EnumBlockNode.destruct(node);
511
- return fields.map(n => convertEnumField(n, ctx));
615
+ return fields.map((n) => convertEnumField(n, ctx));
512
616
  }
513
617
  function convertEnumField(node, ctx) {
514
618
  const { attributes, identifier, value } = EnumFieldNode.destruct(node);
@@ -529,18 +633,19 @@ function convertStruct(node, ctx) {
529
633
  const { block, identifier } = StructNode.destruct(node);
530
634
  // Shortcut if the typeDef has been added to the struct symbol.
531
635
  const symbol = identifier?.symbol ?? node.symbol;
532
- if (symbol && TypeDefSymbolData.is(symbol.data) && symbol.data.typeDef.kind === 'struct') {
636
+ if (symbol &&
637
+ TypeDefSymbolData.is(symbol.data) &&
638
+ symbol.data.typeDef.kind === 'struct') {
533
639
  return symbol.data.typeDef;
534
640
  }
535
- return {
536
- ...convertBase(node, ctx),
641
+ return wrapType(node, {
537
642
  kind: 'struct',
538
643
  fields: convertStructBlock(block, ctx),
539
- };
644
+ }, ctx);
540
645
  }
541
646
  function convertStructBlock(node, ctx) {
542
647
  const { fields } = StructBlockNode.destruct(node);
543
- return fields.map(n => convertStructField(n, ctx));
648
+ return fields.map((n) => convertStructField(n, ctx));
544
649
  }
545
650
  function convertStructField(node, ctx) {
546
651
  return StructPairFieldNode.is(node)
@@ -575,51 +680,46 @@ function convertStructSpreadField(node, ctx) {
575
680
  };
576
681
  }
577
682
  function convertAny(node, ctx) {
578
- return {
579
- ...convertBase(node, ctx),
683
+ return wrapType(node, {
580
684
  kind: 'any',
581
- };
685
+ }, ctx);
582
686
  }
583
687
  function convertBoolean(node, ctx) {
584
- return {
585
- ...convertBase(node, ctx),
688
+ return wrapType(node, {
586
689
  kind: 'boolean',
587
- };
690
+ }, ctx);
588
691
  }
589
692
  function convertDispatcher(node, ctx) {
590
693
  const { index, location } = DispatcherTypeNode.destruct(node);
591
- return {
592
- ...convertBase(node, ctx, {
593
- skipFirstIndexBody: true,
594
- }),
694
+ return wrapType(node, {
595
695
  kind: 'dispatcher',
596
- index: convertIndexBody(index, ctx),
696
+ parallelIndices: convertIndexBody(index, ctx),
597
697
  registry: ResourceLocationNode.toString(location, 'full'),
598
- };
698
+ }, ctx, { skipFirstIndexBody: true });
599
699
  }
600
700
  function convertList(node, ctx) {
601
701
  const { item, lengthRange } = ListTypeNode.destruct(node);
602
- return {
603
- ...convertBase(node, ctx),
702
+ return wrapType(node, {
604
703
  kind: 'list',
605
704
  item: convertType(item, ctx),
606
705
  lengthRange: convertRange(lengthRange, ctx),
607
- };
706
+ }, ctx);
608
707
  }
609
708
  function convertRange(node, ctx) {
610
709
  if (!node) {
611
710
  return undefined;
612
711
  }
613
- const { kind, min, max } = FloatRangeNode.is(node) ? FloatRangeNode.destruct(node) : IntRangeNode.destruct(node);
712
+ const { kind, min, max } = FloatRangeNode.is(node)
713
+ ? FloatRangeNode.destruct(node)
714
+ : IntRangeNode.destruct(node);
614
715
  return { kind, min: min?.value, max: max?.value };
615
716
  }
616
717
  function convertLiteral(node, ctx) {
617
718
  const { value } = LiteralTypeNode.destruct(node);
618
- return {
619
- ...convertBase(node, ctx),
719
+ return wrapType(node, {
620
720
  kind: 'literal',
621
721
  value: convertLiteralValue(value, ctx),
622
- };
722
+ }, ctx);
623
723
  }
624
724
  function convertLiteralValue(node, ctx) {
625
725
  if (LiteralNode.is(node)) {
@@ -649,53 +749,46 @@ function convertLiteralNumberSuffix(node, ctx) {
649
749
  }
650
750
  function convertNumericType(node, ctx) {
651
751
  const { numericKind, valueRange } = NumericTypeNode.destruct(node);
652
- return {
653
- ...convertBase(node, ctx),
752
+ return wrapType(node, {
654
753
  kind: numericKind.value,
655
754
  valueRange: convertRange(valueRange, ctx),
656
- };
755
+ }, ctx);
657
756
  }
658
757
  function convertPrimitiveArray(node, ctx) {
659
758
  const { arrayKind, lengthRange, valueRange } = PrimitiveArrayTypeNode.destruct(node);
660
- return {
661
- ...convertBase(node, ctx),
759
+ return wrapType(node, {
662
760
  kind: `${arrayKind.value}_array`,
663
761
  lengthRange: convertRange(lengthRange, ctx),
664
762
  valueRange: convertRange(valueRange, ctx),
665
- };
763
+ }, ctx);
666
764
  }
667
765
  function convertString(node, ctx) {
668
766
  const { lengthRange } = StringTypeNode.destruct(node);
669
- return {
670
- ...convertBase(node, ctx),
767
+ return wrapType(node, {
671
768
  kind: 'string',
672
769
  lengthRange: convertRange(lengthRange, ctx),
673
- };
770
+ }, ctx);
674
771
  }
675
772
  function convertReference(node, ctx) {
676
- const { path, typeParameters } = ReferenceTypeNode.destruct(node);
677
- return {
678
- ...convertBase(node, ctx),
773
+ const { path } = ReferenceTypeNode.destruct(node);
774
+ return wrapType(node, {
679
775
  kind: 'reference',
680
776
  path: pathArrayToString(resolvePath(path, ctx)),
681
- typeParameters: undefineEmptyArray(typeParameters.map(n => convertType(n, ctx))),
682
- };
777
+ }, ctx);
683
778
  }
684
779
  function convertTuple(node, ctx) {
685
780
  const { items } = TupleTypeNode.destruct(node);
686
- return {
687
- ...convertBase(node, ctx),
781
+ return wrapType(node, {
688
782
  kind: 'tuple',
689
- items: items.map(n => convertType(n, ctx)),
690
- };
783
+ items: items.map((n) => convertType(n, ctx)),
784
+ }, ctx);
691
785
  }
692
786
  function convertUnion(node, ctx) {
693
787
  const { members } = UnionTypeNode.destruct(node);
694
- return {
695
- ...convertBase(node, ctx),
788
+ return wrapType(node, {
696
789
  kind: 'union',
697
- members: members.map(n => convertType(n, ctx)),
698
- };
790
+ members: members.map((n) => convertType(n, ctx)),
791
+ }, ctx);
699
792
  }
700
793
  function asString(node) {
701
794
  if (ResourceLocationNode.is(node)) {