@spyglassmc/mcdoc 0.3.1 → 0.3.3

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,9 +116,12 @@ 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
  }
@@ -114,12 +130,20 @@ function hoist(node, ctx) {
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
141
  // otherwise, an anonymous identifier is generated for the symbol and the keyword node is entered as a definition.
122
- usage: { type: 'definition', node: identifier ?? keyword, fullRange: identifier && node },
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' }))
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)) {
@@ -234,7 +309,12 @@ 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) {
@@ -257,9 +337,15 @@ 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: {
344
+ type: 'definition',
345
+ node: identifier,
346
+ fullRange: field,
347
+ },
348
+ }));
263
349
  }
264
350
  }
265
351
  async function bindInjection(node, ctx) {
@@ -286,9 +372,11 @@ async function bindStructBlock(node, ctx, query, options = {}) {
286
372
  if (StructPairFieldNode.is(field)) {
287
373
  const { key, type } = StructPairFieldNode.destruct(field);
288
374
  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 } }));
375
+ query.member(key.value, (fieldQuery) => fieldQuery
376
+ .ifDeclared((symbol) => reportDuplicatedDeclaration(ctx, symbol, key))
377
+ .elseEnter({
378
+ usage: { type: 'definition', node: key, fullRange: field },
379
+ }));
292
380
  }
293
381
  await bindType(type, ctx);
294
382
  }
@@ -303,39 +391,6 @@ async function bindTypeAlias(node, ctx) {
303
391
  if (!identifier?.value) {
304
392
  return;
305
393
  }
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
394
  if (rhs) {
340
395
  await bindType(rhs, ctx);
341
396
  }
@@ -352,10 +407,12 @@ export function registerMcdocBinders(meta) {
352
407
  }
353
408
  function reportDuplicatedDeclaration(ctx, symbol, range, options = { localeString: 'mcdoc.binder.duplicated-declaration' }) {
354
409
  ctx.err.report(localize(options.localeString, localeQuote(symbol.identifier)), range, 2 /* ErrorSeverity.Warning */, {
355
- related: [{
410
+ related: [
411
+ {
356
412
  location: SymbolUtil.getDeclaredLocation(symbol),
357
413
  message: localize(`${options.localeString}.related`, localeQuote(symbol.identifier)),
358
- }],
414
+ },
415
+ ],
359
416
  });
360
417
  }
361
418
  function* resolvePathByStep(path, ctx, options = {}) {
@@ -381,7 +438,12 @@ function* resolvePathByStep(path, ctx, options = {}) {
381
438
  default:
382
439
  Dev.assertNever(child);
383
440
  }
384
- yield { identifiers, node: child, index: i, indexRight: children.length - 1 - i };
441
+ yield {
442
+ identifiers,
443
+ node: child,
444
+ index: i,
445
+ indexRight: children.length - 1 - i,
446
+ };
385
447
  }
386
448
  }
387
449
  function resolvePath(path, ctx, options = {}) {
@@ -391,43 +453,84 @@ function identifierToUri(module, ctx) {
391
453
  return ctx.symbols.global.mcdoc?.[module]?.definition?.[0]?.uri;
392
454
  }
393
455
  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;
456
+ return Object.values(ctx.symbols.global.mcdoc ?? {}).find((symbol) => {
457
+ return (symbol.subcategory === 'module' &&
458
+ symbol.definition?.some((loc) => loc.uri === uri));
459
+ })?.identifier;
400
460
  }
401
461
  function pathArrayToString(path) {
402
462
  return path ? `::${path.join('::')}` : undefined;
403
463
  }
404
464
  function convertType(node, ctx) {
405
465
  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
- };
466
+ case 'mcdoc:enum':
467
+ return convertEnum(node, ctx);
468
+ case 'mcdoc:struct':
469
+ return convertStruct(node, ctx);
470
+ case 'mcdoc:type/any':
471
+ return convertAny(node, ctx);
472
+ case 'mcdoc:type/boolean':
473
+ return convertBoolean(node, ctx);
474
+ case 'mcdoc:type/dispatcher':
475
+ return convertDispatcher(node, ctx);
476
+ case 'mcdoc:type/list':
477
+ return convertList(node, ctx);
478
+ case 'mcdoc:type/literal':
479
+ return convertLiteral(node, ctx);
480
+ case 'mcdoc:type/numeric_type':
481
+ return convertNumericType(node, ctx);
482
+ case 'mcdoc:type/primitive_array':
483
+ return convertPrimitiveArray(node, ctx);
484
+ case 'mcdoc:type/string':
485
+ return convertString(node, ctx);
486
+ case 'mcdoc:type/reference':
487
+ return convertReference(node, ctx);
488
+ case 'mcdoc:type/tuple':
489
+ return convertTuple(node, ctx);
490
+ case 'mcdoc:type/union':
491
+ return convertUnion(node, ctx);
492
+ default:
493
+ return Dev.assertNever(node);
494
+ }
495
+ }
496
+ function wrapType(node, type, ctx, options = {}) {
497
+ const { attributes, appendixes } = TypeBaseNode.destruct(node);
498
+ let ans = type;
499
+ for (const appendix of appendixes) {
500
+ if (IndexBodyNode.is(appendix)) {
501
+ if (options.skipFirstIndexBody) {
502
+ options.skipFirstIndexBody = false;
503
+ continue;
504
+ }
505
+ ans = {
506
+ kind: 'indexed',
507
+ child: ans,
508
+ parallelIndices: convertIndexBody(appendix, ctx),
509
+ };
510
+ }
511
+ else {
512
+ ans = {
513
+ kind: 'concrete',
514
+ child: ans,
515
+ typeArgs: convertTypeArgBlock(appendix, ctx),
516
+ };
517
+ }
518
+ }
519
+ ans = attributeType(ans, attributes, ctx);
520
+ return ans;
521
+ }
522
+ function attributeType(type, attributes, ctx) {
523
+ for (const attribute of attributes) {
524
+ type = {
525
+ kind: 'attributed',
526
+ attribute: convertAttribute(attribute, ctx),
527
+ child: type,
528
+ };
529
+ }
530
+ return type;
428
531
  }
429
532
  function convertAttributes(nodes, ctx) {
430
- return undefineEmptyArray(nodes.map(n => convertAttribute(n, ctx)));
533
+ return undefineEmptyArray(nodes.map((n) => convertAttribute(n, ctx)));
431
534
  }
432
535
  function undefineEmptyArray(array) {
433
536
  return array.length ? array : undefined;
@@ -468,11 +571,11 @@ function convertAttributeTree(node, ctx) {
468
571
  return ans;
469
572
  }
470
573
  function convertIndexBodies(nodes, ctx) {
471
- return undefineEmptyArray(nodes.map(n => convertIndexBody(n, ctx)));
574
+ return undefineEmptyArray(nodes.map((n) => convertIndexBody(n, ctx)));
472
575
  }
473
576
  function convertIndexBody(node, ctx) {
474
577
  const { parallelIndices } = IndexBodyNode.destruct(node);
475
- return parallelIndices.map(n => convertIndex(n, ctx));
578
+ return parallelIndices.map((n) => convertIndex(n, ctx));
476
579
  }
477
580
  function convertIndex(node, ctx) {
478
581
  return StaticIndexNode.is(node)
@@ -492,23 +595,28 @@ function convertDynamicIndex(node, ctx) {
492
595
  accessor: keys.map(asString),
493
596
  };
494
597
  }
598
+ function convertTypeArgBlock(node, ctx) {
599
+ const { args } = TypeArgBlockNode.destruct(node);
600
+ return args.map((a) => convertType(a, ctx));
601
+ }
495
602
  function convertEnum(node, ctx) {
496
603
  const { block, enumKind, identifier } = EnumNode.destruct(node);
497
604
  // Shortcut if the typeDef has been added to the enum symbol.
498
605
  const symbol = identifier?.symbol ?? node.symbol;
499
- if (symbol && TypeDefSymbolData.is(symbol.data) && symbol.data.typeDef.kind === 'enum') {
606
+ if (symbol &&
607
+ TypeDefSymbolData.is(symbol.data) &&
608
+ symbol.data.typeDef.kind === 'enum') {
500
609
  return symbol.data.typeDef;
501
610
  }
502
- return {
503
- ...convertBase(node, ctx),
611
+ return wrapType(node, {
504
612
  kind: 'enum',
505
613
  enumKind,
506
614
  values: convertEnumBlock(block, ctx),
507
- };
615
+ }, ctx);
508
616
  }
509
617
  function convertEnumBlock(node, ctx) {
510
618
  const { fields } = EnumBlockNode.destruct(node);
511
- return fields.map(n => convertEnumField(n, ctx));
619
+ return fields.map((n) => convertEnumField(n, ctx));
512
620
  }
513
621
  function convertEnumField(node, ctx) {
514
622
  const { attributes, identifier, value } = EnumFieldNode.destruct(node);
@@ -529,18 +637,19 @@ function convertStruct(node, ctx) {
529
637
  const { block, identifier } = StructNode.destruct(node);
530
638
  // Shortcut if the typeDef has been added to the struct symbol.
531
639
  const symbol = identifier?.symbol ?? node.symbol;
532
- if (symbol && TypeDefSymbolData.is(symbol.data) && symbol.data.typeDef.kind === 'struct') {
640
+ if (symbol &&
641
+ TypeDefSymbolData.is(symbol.data) &&
642
+ symbol.data.typeDef.kind === 'struct') {
533
643
  return symbol.data.typeDef;
534
644
  }
535
- return {
536
- ...convertBase(node, ctx),
645
+ return wrapType(node, {
537
646
  kind: 'struct',
538
647
  fields: convertStructBlock(block, ctx),
539
- };
648
+ }, ctx);
540
649
  }
541
650
  function convertStructBlock(node, ctx) {
542
651
  const { fields } = StructBlockNode.destruct(node);
543
- return fields.map(n => convertStructField(n, ctx));
652
+ return fields.map((n) => convertStructField(n, ctx));
544
653
  }
545
654
  function convertStructField(node, ctx) {
546
655
  return StructPairFieldNode.is(node)
@@ -575,51 +684,46 @@ function convertStructSpreadField(node, ctx) {
575
684
  };
576
685
  }
577
686
  function convertAny(node, ctx) {
578
- return {
579
- ...convertBase(node, ctx),
687
+ return wrapType(node, {
580
688
  kind: 'any',
581
- };
689
+ }, ctx);
582
690
  }
583
691
  function convertBoolean(node, ctx) {
584
- return {
585
- ...convertBase(node, ctx),
692
+ return wrapType(node, {
586
693
  kind: 'boolean',
587
- };
694
+ }, ctx);
588
695
  }
589
696
  function convertDispatcher(node, ctx) {
590
697
  const { index, location } = DispatcherTypeNode.destruct(node);
591
- return {
592
- ...convertBase(node, ctx, {
593
- skipFirstIndexBody: true,
594
- }),
698
+ return wrapType(node, {
595
699
  kind: 'dispatcher',
596
- index: convertIndexBody(index, ctx),
700
+ parallelIndices: convertIndexBody(index, ctx),
597
701
  registry: ResourceLocationNode.toString(location, 'full'),
598
- };
702
+ }, ctx, { skipFirstIndexBody: true });
599
703
  }
600
704
  function convertList(node, ctx) {
601
705
  const { item, lengthRange } = ListTypeNode.destruct(node);
602
- return {
603
- ...convertBase(node, ctx),
706
+ return wrapType(node, {
604
707
  kind: 'list',
605
708
  item: convertType(item, ctx),
606
709
  lengthRange: convertRange(lengthRange, ctx),
607
- };
710
+ }, ctx);
608
711
  }
609
712
  function convertRange(node, ctx) {
610
713
  if (!node) {
611
714
  return undefined;
612
715
  }
613
- const { kind, min, max } = FloatRangeNode.is(node) ? FloatRangeNode.destruct(node) : IntRangeNode.destruct(node);
716
+ const { kind, min, max } = FloatRangeNode.is(node)
717
+ ? FloatRangeNode.destruct(node)
718
+ : IntRangeNode.destruct(node);
614
719
  return { kind, min: min?.value, max: max?.value };
615
720
  }
616
721
  function convertLiteral(node, ctx) {
617
722
  const { value } = LiteralTypeNode.destruct(node);
618
- return {
619
- ...convertBase(node, ctx),
723
+ return wrapType(node, {
620
724
  kind: 'literal',
621
725
  value: convertLiteralValue(value, ctx),
622
- };
726
+ }, ctx);
623
727
  }
624
728
  function convertLiteralValue(node, ctx) {
625
729
  if (LiteralNode.is(node)) {
@@ -649,53 +753,47 @@ function convertLiteralNumberSuffix(node, ctx) {
649
753
  }
650
754
  function convertNumericType(node, ctx) {
651
755
  const { numericKind, valueRange } = NumericTypeNode.destruct(node);
652
- return {
653
- ...convertBase(node, ctx),
756
+ return wrapType(node, {
654
757
  kind: numericKind.value,
655
758
  valueRange: convertRange(valueRange, ctx),
656
- };
759
+ }, ctx);
657
760
  }
658
761
  function convertPrimitiveArray(node, ctx) {
659
- const { arrayKind, lengthRange, valueRange } = PrimitiveArrayTypeNode.destruct(node);
660
- return {
661
- ...convertBase(node, ctx),
762
+ const { arrayKind, lengthRange, valueRange } = PrimitiveArrayTypeNode
763
+ .destruct(node);
764
+ return wrapType(node, {
662
765
  kind: `${arrayKind.value}_array`,
663
766
  lengthRange: convertRange(lengthRange, ctx),
664
767
  valueRange: convertRange(valueRange, ctx),
665
- };
768
+ }, ctx);
666
769
  }
667
770
  function convertString(node, ctx) {
668
771
  const { lengthRange } = StringTypeNode.destruct(node);
669
- return {
670
- ...convertBase(node, ctx),
772
+ return wrapType(node, {
671
773
  kind: 'string',
672
774
  lengthRange: convertRange(lengthRange, ctx),
673
- };
775
+ }, ctx);
674
776
  }
675
777
  function convertReference(node, ctx) {
676
- const { path, typeParameters } = ReferenceTypeNode.destruct(node);
677
- return {
678
- ...convertBase(node, ctx),
778
+ const { path } = ReferenceTypeNode.destruct(node);
779
+ return wrapType(node, {
679
780
  kind: 'reference',
680
781
  path: pathArrayToString(resolvePath(path, ctx)),
681
- typeParameters: undefineEmptyArray(typeParameters.map(n => convertType(n, ctx))),
682
- };
782
+ }, ctx);
683
783
  }
684
784
  function convertTuple(node, ctx) {
685
785
  const { items } = TupleTypeNode.destruct(node);
686
- return {
687
- ...convertBase(node, ctx),
786
+ return wrapType(node, {
688
787
  kind: 'tuple',
689
- items: items.map(n => convertType(n, ctx)),
690
- };
788
+ items: items.map((n) => convertType(n, ctx)),
789
+ }, ctx);
691
790
  }
692
791
  function convertUnion(node, ctx) {
693
792
  const { members } = UnionTypeNode.destruct(node);
694
- return {
695
- ...convertBase(node, ctx),
793
+ return wrapType(node, {
696
794
  kind: 'union',
697
- members: members.map(n => convertType(n, ctx)),
698
- };
795
+ members: members.map((n) => convertType(n, ctx)),
796
+ }, ctx);
699
797
  }
700
798
  function asString(node) {
701
799
  if (ResourceLocationNode.is(node)) {