@spyglassmc/mcdoc 0.3.8 → 0.3.9

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.
@@ -0,0 +1,914 @@
1
+ import { Range, Source } from '@spyglassmc/core';
2
+ import { localize } from '@spyglassmc/locales';
3
+ import { TypeDefSymbolData } from '../../binder/index.js';
4
+ import { McdocType, NumericRange } from '../../type/index.js';
5
+ import { handleAttributes } from '../attribute/index.js';
6
+ import { McdocCheckerContext } from './context.js';
7
+ import { condenseAndPropagate } from './error.js';
8
+ export * from './context.js';
9
+ export * from './error.js';
10
+ export function reference(node, path, ctx) {
11
+ typeDefinition(node, { kind: 'reference', path }, ctx);
12
+ }
13
+ export function dispatcher(node, registry, index, ctx) {
14
+ const parallelIndices = typeof index === 'string'
15
+ ? [{ kind: 'static', value: index }]
16
+ : Array.isArray(index)
17
+ ? index
18
+ : [index];
19
+ typeDefinition(node, { kind: 'dispatcher', registry, parallelIndices }, ctx);
20
+ }
21
+ export function isAssignable(assignValue, typeDef, ctx, isEquivalent) {
22
+ if (assignValue.kind === 'literal' && typeDef.kind === 'literal'
23
+ && assignValue.value.kind === typeDef.value.kind
24
+ && !assignValue.attributes && !typeDef.attributes) {
25
+ return assignValue.value.value === typeDef.value.value;
26
+ }
27
+ let ans = true;
28
+ const newCtx = McdocCheckerContext.create(ctx, {
29
+ isEquivalent,
30
+ getChildren: (_, d) => {
31
+ switch (d.kind) {
32
+ case 'list':
33
+ const vals = getPossibleTypes(d.item);
34
+ return [vals.map(v => ({ originalNode: v, inferredType: v }))];
35
+ case 'byte_array':
36
+ return [[{ originalNode: { kind: 'byte' }, inferredType: { kind: 'byte' } }]];
37
+ case 'int_array':
38
+ return [[{ originalNode: { kind: 'int' }, inferredType: { kind: 'int' } }]];
39
+ case 'long_array':
40
+ return [[{ originalNode: { kind: 'long' }, inferredType: { kind: 'long' } }]];
41
+ case 'struct':
42
+ return d.fields.map(f => {
43
+ const vals = getPossibleTypes(f.type);
44
+ return {
45
+ attributes: f.attributes,
46
+ key: { originalNode: f.key, inferredType: f.key },
47
+ possibleValues: vals.map(v => ({ originalNode: v, inferredType: v })),
48
+ };
49
+ });
50
+ case 'tuple':
51
+ return d.items.map(f => {
52
+ const vals = getPossibleTypes(f);
53
+ return vals.map(v => ({ originalNode: v, inferredType: v }));
54
+ });
55
+ default:
56
+ return [];
57
+ }
58
+ },
59
+ reportError: () => {
60
+ ans = false;
61
+ },
62
+ });
63
+ const node = {
64
+ parent: undefined,
65
+ runtimeKey: undefined,
66
+ possibleValues: [],
67
+ };
68
+ node.possibleValues = getPossibleTypes(typeDef).map(v => ({
69
+ entryNode: node,
70
+ node: { originalNode: v, inferredType: v },
71
+ children: [],
72
+ definitionsByParent: [],
73
+ }));
74
+ // TODO add bail option to allow checking logic to bail on first error
75
+ typeDefinition(getPossibleTypes(assignValue).map(v => ({ originalNode: v, inferredType: v })), typeDef, newCtx);
76
+ return ans;
77
+ }
78
+ export function typeDefinition(runtimeValues, typeDef, ctx) {
79
+ const rootNode = {
80
+ parent: undefined,
81
+ runtimeKey: undefined,
82
+ possibleValues: [],
83
+ };
84
+ rootNode.possibleValues = runtimeValues.map(n => ({
85
+ node: n,
86
+ entryNode: rootNode,
87
+ definitionsByParent: [],
88
+ children: [],
89
+ }));
90
+ for (const value of rootNode.possibleValues) {
91
+ const simplifiedRoot = simplify(typeDef, { ctx, node: value });
92
+ const validRootDefinitions = simplifiedRoot.kind === 'union'
93
+ ? simplifiedRoot.members
94
+ : [simplifiedRoot];
95
+ value.definitionsByParent = [{
96
+ parents: [],
97
+ keyDefinition: undefined,
98
+ runtimeNode: value,
99
+ originalTypeDef: typeDef,
100
+ condensedErrors: [],
101
+ validDefinitions: [],
102
+ }];
103
+ value.definitionsByParent[0].validDefinitions = validRootDefinitions.map(d => ({
104
+ groupNode: value.definitionsByParent[0],
105
+ typeDef: d,
106
+ children: [],
107
+ }));
108
+ }
109
+ const nodeQueue = [rootNode];
110
+ while (nodeQueue.length !== 0) {
111
+ const node = nodeQueue.shift();
112
+ for (const value of node.possibleValues) {
113
+ const inferredSimplified = simplify(value.node.inferredType, { ctx, node: value });
114
+ const children = ctx.getChildren(value.node.originalNode, inferredSimplified);
115
+ const childNodes = children.map(c => {
116
+ const ans = {
117
+ parent: value,
118
+ runtimeKey: !Array.isArray(c) ? c.key : undefined,
119
+ possibleValues: [],
120
+ };
121
+ ans.possibleValues = (Array.isArray(c) ? c : c.possibleValues).map(v => ({
122
+ entryNode: ans,
123
+ node: v,
124
+ definitionsByParent: [],
125
+ condensedErrors: [],
126
+ children: [],
127
+ }));
128
+ return ans;
129
+ });
130
+ for (const definitionGroup of value.definitionsByParent) {
131
+ const definitionErrors = [];
132
+ if (definitionGroup.validDefinitions.length === 0) {
133
+ // nothing can be assigned to an empty union
134
+ definitionGroup.condensedErrors = [[{
135
+ kind: 'type_mismatch',
136
+ node: value.node,
137
+ expected: [],
138
+ }]];
139
+ }
140
+ for (const def of definitionGroup.validDefinitions) {
141
+ const { errors, childDefinitions } = checkShallowly(value.node, inferredSimplified, children, def.typeDef, ctx);
142
+ definitionErrors.push({ definition: def, errors });
143
+ for (let i = 0; i < childDefinitions.length; i++) {
144
+ const childDef = childDefinitions[i];
145
+ if (!childDef) {
146
+ continue;
147
+ }
148
+ const child = childNodes[i];
149
+ const existingDef = child.possibleValues.length > 0
150
+ ? child.possibleValues[0].definitionsByParent
151
+ .find(d => (d.keyDefinition === undefined || childDef.keyType === undefined
152
+ ? d.keyDefinition === undefined
153
+ : McdocType.equals(d.keyDefinition, childDef.keyType))
154
+ && McdocType.equals(d.originalTypeDef, childDef.type))
155
+ : undefined;
156
+ for (const childValue of child.possibleValues) {
157
+ if (existingDef) {
158
+ existingDef.parents.push(def);
159
+ def.children.push(existingDef);
160
+ continue;
161
+ }
162
+ // TODO We need some sort of map / local cache which keeps track of the original
163
+ // non-simplified types and see if they have been compared yet. This is needed
164
+ // for structures that are cyclic, to essentially bail out once we are comparing
165
+ // the same types again and just collect the errors of the lower depth.
166
+ // This will currently lead to a stack overflow error when e.g. comparing two
167
+ // text component definitions
168
+ const simplified = simplify(childDef.type, { ctx, node: childValue });
169
+ const childDefinitionGroup = {
170
+ parents: [def],
171
+ runtimeNode: childValue,
172
+ keyDefinition: childDef.keyType,
173
+ originalTypeDef: childDef.type,
174
+ validDefinitions: [],
175
+ condensedErrors: [],
176
+ desc: childDef.desc,
177
+ };
178
+ childDefinitionGroup.validDefinitions =
179
+ (simplified.kind === 'union' ? simplified.members : [simplified])
180
+ .map(d => ({
181
+ groupNode: childDefinitionGroup,
182
+ typeDef: d,
183
+ children: [],
184
+ }));
185
+ childValue.definitionsByParent.push(childDefinitionGroup);
186
+ def.children.push(childDefinitionGroup);
187
+ }
188
+ }
189
+ }
190
+ condenseAndPropagate(definitionGroup, definitionErrors);
191
+ }
192
+ value.children = childNodes;
193
+ nodeQueue.push(...childNodes);
194
+ }
195
+ }
196
+ if (ctx.attachTypeInfo) {
197
+ for (const node of rootNode.possibleValues) {
198
+ attachTypeInfo(node, ctx);
199
+ }
200
+ }
201
+ for (const error of rootNode.possibleValues
202
+ .flatMap(v => v.definitionsByParent)
203
+ .flatMap(d => d.condensedErrors)
204
+ .flat()) {
205
+ if (error) {
206
+ ctx.reportError(error);
207
+ }
208
+ }
209
+ }
210
+ function attachTypeInfo(node, ctx) {
211
+ const definitions = node.definitionsByParent.flatMap(d => d.validDefinitions);
212
+ if (definitions.length === 1) {
213
+ const { typeDef, groupNode } = definitions[0];
214
+ ctx.attachTypeInfo?.(node.node.originalNode, typeDef, groupNode.desc);
215
+ handleStringAttachers(node.node, typeDef, ctx);
216
+ if (node.entryNode.runtimeKey && groupNode.keyDefinition) {
217
+ ctx.attachTypeInfo?.(node.entryNode.runtimeKey.originalNode, groupNode.keyDefinition, groupNode.desc);
218
+ handleStringAttachers(node.entryNode.runtimeKey, groupNode.keyDefinition, ctx);
219
+ }
220
+ }
221
+ else if (definitions.length > 1) {
222
+ ctx.attachTypeInfo?.(node.node.originalNode, {
223
+ kind: 'union',
224
+ members: definitions.map(d => d.typeDef),
225
+ });
226
+ if (node.entryNode.runtimeKey) {
227
+ ctx.attachTypeInfo?.(node.entryNode.runtimeKey.originalNode, {
228
+ kind: 'union',
229
+ members: node.definitionsByParent
230
+ .map(d => d.keyDefinition)
231
+ .filter((d) => d !== undefined),
232
+ });
233
+ }
234
+ // when there are multiple valid definitions, we don't run any string parsers.
235
+ }
236
+ for (const child of node.children.flatMap(c => c.possibleValues)) {
237
+ attachTypeInfo(child, ctx);
238
+ }
239
+ }
240
+ function handleStringAttachers(runtimeValue, typeDef, ctx) {
241
+ const { stringAttacher } = ctx;
242
+ if (!stringAttacher) {
243
+ return;
244
+ }
245
+ handleAttributes(typeDef.attributes, ctx, (handler, config) => {
246
+ const parser = handler.stringParser?.(config, typeDef, ctx);
247
+ if (!parser) {
248
+ return;
249
+ }
250
+ stringAttacher(runtimeValue.originalNode, (node) => {
251
+ const src = new Source(node.value, node.valueMap);
252
+ const start = src.cursor;
253
+ const child = parser(src, ctx);
254
+ if (!child) {
255
+ ctx.err.report(localize('expected', localize('mcdoc.runtime.checker.value')), Range.create(start, src.skipRemaining()));
256
+ return;
257
+ }
258
+ else if (src.canRead()) {
259
+ ctx.err.report(localize('mcdoc.runtime.checker.trailing'), Range.create(src.cursor, src.skipRemaining()));
260
+ }
261
+ node.children = [child];
262
+ });
263
+ });
264
+ }
265
+ function checkShallowly(runtimeNode, simplifiedInferred, children, typeDef, ctx) {
266
+ const typeDefValueType = getValueType(typeDef);
267
+ const runtimeValueType = getValueType(simplifiedInferred);
268
+ const childDefinitions = Array(children.length)
269
+ .fill(undefined);
270
+ if ((typeDef.kind !== 'any' && typeDef.kind !== 'unsafe'
271
+ && simplifiedInferred.kind !== 'unsafe'
272
+ && runtimeValueType.kind !== typeDefValueType.kind
273
+ && !ctx.isEquivalent(runtimeValueType, typeDefValueType))) {
274
+ return {
275
+ childDefinitions,
276
+ errors: [{ kind: 'type_mismatch', node: runtimeNode, expected: [typeDef] }],
277
+ };
278
+ }
279
+ const errors = [];
280
+ let assignable = true;
281
+ handleAttributes(typeDef.attributes, ctx, (handler, config) => {
282
+ if (handler.checkInferred?.(config, simplifiedInferred, ctx) === false) {
283
+ assignable = false;
284
+ }
285
+ });
286
+ if (!assignable) {
287
+ errors.push({ kind: 'internal', node: runtimeNode });
288
+ }
289
+ if ((typeDef.kind === 'literal'
290
+ && (simplifiedInferred.kind !== 'literal'
291
+ || typeDef.value.value !== simplifiedInferred.value.value))
292
+ // TODO handle enum field attributes
293
+ || (typeDef.kind === 'enum'
294
+ && (simplifiedInferred.kind !== 'literal'
295
+ || !typeDef.values.some(v => v.value === simplifiedInferred.value.value)))) {
296
+ return {
297
+ childDefinitions,
298
+ errors: [{ kind: 'type_mismatch', node: runtimeNode, expected: [typeDef] }],
299
+ };
300
+ }
301
+ switch (typeDef.kind) {
302
+ case 'any':
303
+ case 'unsafe':
304
+ break;
305
+ case 'byte':
306
+ case 'short':
307
+ case 'int':
308
+ case 'long':
309
+ case 'float':
310
+ case 'double':
311
+ if (typeDef.valueRange
312
+ && simplifiedInferred.kind === 'literal'
313
+ && typeof simplifiedInferred.value.value === 'number'
314
+ && !NumericRange.isInRange(typeDef.valueRange, simplifiedInferred.value.value)) {
315
+ errors.push({
316
+ kind: 'number_out_of_range',
317
+ node: runtimeNode,
318
+ ranges: [typeDef.valueRange],
319
+ });
320
+ }
321
+ break;
322
+ case 'string':
323
+ if (typeDef.lengthRange
324
+ && simplifiedInferred.kind === 'literal'
325
+ && simplifiedInferred.value.kind === 'string'
326
+ && !NumericRange.isInRange(typeDef.lengthRange, simplifiedInferred.value.value.length)) {
327
+ errors.push({
328
+ kind: 'invalid_string_length',
329
+ node: runtimeNode,
330
+ ranges: [typeDef.lengthRange],
331
+ });
332
+ }
333
+ break;
334
+ case 'struct': {
335
+ const literalKvps = new Map();
336
+ const otherKvps = [];
337
+ for (let i = 0; i < children.length; i++) {
338
+ const child = children[i];
339
+ if (Array.isArray(child)) {
340
+ continue;
341
+ }
342
+ if (child.key.inferredType.kind === 'literal'
343
+ && child.key.inferredType.value.kind === 'string') {
344
+ const existing = literalKvps.get(child.key.inferredType.value.value);
345
+ if (existing) {
346
+ // duplicate key
347
+ existing.values.push({ pair: child, index: i });
348
+ }
349
+ else {
350
+ literalKvps.set(child.key.inferredType.value.value, {
351
+ values: [{ pair: child, index: i }],
352
+ definition: undefined,
353
+ });
354
+ }
355
+ }
356
+ else {
357
+ otherKvps.push({ value: child, index: i });
358
+ }
359
+ }
360
+ for (const pair of typeDef.fields) {
361
+ const otherKvpMatches = [];
362
+ let foundMatch = false;
363
+ if (pair.key.kind === 'literal' && pair.key.value.kind === 'string') {
364
+ const runtimeChild = literalKvps.get(pair.key.value.value);
365
+ if (runtimeChild) {
366
+ foundMatch = true;
367
+ runtimeChild.definition = { keyType: pair.key, type: pair.type, desc: pair.desc };
368
+ }
369
+ }
370
+ if (!foundMatch) {
371
+ for (let i = 0; i < otherKvps.length; i++) {
372
+ const kvp = otherKvps[i];
373
+ if (isAssignable(kvp.value.key.inferredType, pair.key, ctx, ctx.isEquivalent)) {
374
+ foundMatch = true;
375
+ otherKvps.splice(i, 1);
376
+ otherKvpMatches.push(kvp.index);
377
+ i--;
378
+ }
379
+ }
380
+ for (const kvp of literalKvps.entries()) {
381
+ if (!kvp[1].definition
382
+ && isAssignable({ kind: 'literal', value: { kind: 'string', value: kvp[0] } }, pair.key, ctx, ctx.isEquivalent)) {
383
+ foundMatch = true;
384
+ kvp[1].definition = { keyType: pair.key, type: pair.type, desc: pair.desc };
385
+ }
386
+ }
387
+ }
388
+ for (const match of otherKvpMatches) {
389
+ childDefinitions[match] = { keyType: pair.key, type: pair.type, desc: pair.desc };
390
+ }
391
+ if (!foundMatch
392
+ && !ctx.allowMissingKeys
393
+ && pair.key.kind === 'literal'
394
+ && pair.key.value.kind === 'string'
395
+ && pair.optional !== true) {
396
+ errors.push({ kind: 'missing_key', node: runtimeNode, keys: [pair.key.value.value] });
397
+ }
398
+ }
399
+ for (const kvp of literalKvps.values()) {
400
+ for (const value of kvp.values) {
401
+ childDefinitions[value.index] = kvp.definition;
402
+ if (kvp.values.length > 1) {
403
+ errors.push({ kind: 'duplicate_key', node: value.pair.key });
404
+ }
405
+ }
406
+ }
407
+ for (let i = 0; i < children.length; i++) {
408
+ const childDef = childDefinitions[i];
409
+ const child = children[i];
410
+ if (childDef === undefined) {
411
+ if (Array.isArray(child)) {
412
+ // This should never happen
413
+ errors.push(...child.map(v => ({
414
+ kind: 'expected_key_value_pair',
415
+ node: v,
416
+ })));
417
+ }
418
+ else {
419
+ errors.push({ kind: 'unknown_key', node: child.key });
420
+ }
421
+ }
422
+ }
423
+ break;
424
+ }
425
+ case 'list':
426
+ case 'byte_array':
427
+ case 'int_array':
428
+ case 'long_array': {
429
+ let itemType;
430
+ switch (typeDef.kind) {
431
+ case 'list':
432
+ itemType = typeDef.item;
433
+ break;
434
+ case 'byte_array':
435
+ itemType = { kind: 'byte', valueRange: typeDef.valueRange };
436
+ break;
437
+ case 'int_array':
438
+ itemType = { kind: 'int', valueRange: typeDef.valueRange };
439
+ break;
440
+ case 'long_array':
441
+ itemType = { kind: 'long', valueRange: typeDef.valueRange };
442
+ break;
443
+ }
444
+ for (let i = 0; i < childDefinitions.length; i++) {
445
+ childDefinitions[i] = { type: itemType };
446
+ }
447
+ if (typeDef.lengthRange && !NumericRange.isInRange(typeDef.lengthRange, children.length)) {
448
+ errors.push({
449
+ kind: 'invalid_collection_length',
450
+ node: runtimeNode,
451
+ ranges: [typeDef.lengthRange],
452
+ });
453
+ }
454
+ break;
455
+ }
456
+ case 'tuple': {
457
+ for (let i = 0; i < children.length; i++) {
458
+ const child = children[i];
459
+ if (i < typeDef.items.length) {
460
+ childDefinitions[i] = { type: typeDef.items[i] };
461
+ }
462
+ else {
463
+ // This really should always be an array, just to handle this gracefully
464
+ const values = Array.isArray(child) ? child : [...child.possibleValues, child.key];
465
+ errors.push(...values.map(v => ({
466
+ kind: 'unknown_tuple_element',
467
+ node: v,
468
+ })));
469
+ }
470
+ }
471
+ if (typeDef.items.length > children.length) {
472
+ errors.push({
473
+ kind: 'invalid_collection_length',
474
+ node: runtimeNode,
475
+ ranges: [{ kind: 0b00, max: typeDef.items.length, min: typeDef.items.length }],
476
+ });
477
+ }
478
+ break;
479
+ }
480
+ }
481
+ return { childDefinitions, errors };
482
+ }
483
+ export function getPossibleTypes(typeDef) {
484
+ return typeDef.kind === 'union' ? typeDef.members.flatMap(m => getPossibleTypes(m)) : [typeDef];
485
+ }
486
+ export function simplify(typeDef, context) {
487
+ function wrap(typeDef) {
488
+ if (!typeDef.attributes?.length) {
489
+ return typeDef;
490
+ }
491
+ handleAttributes(typeDef.attributes, context.ctx, (handler, config) => {
492
+ if (handler.mapType) {
493
+ typeDef = handler.mapType(config, typeDef, context.ctx);
494
+ }
495
+ });
496
+ return typeDef;
497
+ }
498
+ switch (typeDef.kind) {
499
+ case 'reference':
500
+ return wrap(simplifyReference(typeDef, context));
501
+ case 'dispatcher':
502
+ return wrap(simplifyDispatcher(typeDef, context));
503
+ case 'indexed':
504
+ return wrap(simplifyIndexed(typeDef, context));
505
+ case 'union':
506
+ return wrap(simplifyUnion(typeDef, context));
507
+ case 'struct':
508
+ return wrap(simplifyStruct(typeDef, context));
509
+ case 'list':
510
+ return wrap(simplifyList(typeDef, context));
511
+ case 'tuple':
512
+ return wrap(simplifyTuple(typeDef, context));
513
+ case 'enum':
514
+ return wrap(simplifyEnum(typeDef, context));
515
+ case 'concrete':
516
+ return wrap(simplifyConcrete(typeDef, context));
517
+ case 'template':
518
+ return wrap(simplifyTemplate(typeDef, context));
519
+ case 'mapped':
520
+ return wrap(simplifyMapped(typeDef, context));
521
+ default:
522
+ return wrap(typeDef);
523
+ }
524
+ }
525
+ function simplifyReference(typeDef, context) {
526
+ if (!typeDef.path) {
527
+ // TODO when does this happen?
528
+ context.ctx.logger.warn(`Tried to access empty reference`);
529
+ return { kind: 'union', members: [] };
530
+ }
531
+ const mapped = context.typeMapping?.[typeDef.path];
532
+ if (mapped) {
533
+ return mapped;
534
+ }
535
+ // TODO Probably need to keep original symbol around in some way to support "go to definition"
536
+ const symbol = context.ctx.symbols.query(context.ctx.doc, 'mcdoc', typeDef.path);
537
+ const def = symbol.getData(TypeDefSymbolData.is)?.typeDef;
538
+ if (!def) {
539
+ context.ctx.logger.warn(`Tried to access unknown reference ${typeDef.path}`);
540
+ return { kind: 'union', members: [] };
541
+ }
542
+ const simplifiedDef = simplify(def, context);
543
+ if (typeDef.attributes?.length) {
544
+ return {
545
+ ...simplifiedDef,
546
+ attributes: [...typeDef.attributes, ...simplifiedDef.attributes ?? []],
547
+ };
548
+ }
549
+ return simplifiedDef;
550
+ }
551
+ function simplifyDispatcher(typeDef, context) {
552
+ const dispatcher = context.ctx.symbols.query(context.ctx.doc, 'mcdoc/dispatcher', typeDef.registry).symbol
553
+ ?.members;
554
+ if (!dispatcher) {
555
+ context.ctx.logger.warn(`Tried to access unknown dispatcher ${typeDef.registry}`);
556
+ return { kind: 'union', members: [] };
557
+ }
558
+ return resolveIndices(typeDef.parallelIndices, dispatcher, context);
559
+ }
560
+ function simplifyIndexed(typeDef, context) {
561
+ const child = simplify(typeDef.child, {
562
+ ...context,
563
+ typeArgs: [],
564
+ structFields: undefined,
565
+ });
566
+ if (child.kind !== 'struct') {
567
+ context.ctx.logger.warn(`Tried to index un-indexable type ${child.kind}`);
568
+ return { kind: 'union', members: [] };
569
+ }
570
+ const symbolMap = {};
571
+ for (const field of child.fields) {
572
+ if (field.key.kind === 'literal' && field.key.value.kind === 'string') {
573
+ symbolMap[field.key.value.value] = {
574
+ data: {
575
+ typeDef: field.type,
576
+ },
577
+ };
578
+ }
579
+ }
580
+ return resolveIndices(typeDef.parallelIndices, symbolMap, context);
581
+ }
582
+ function resolveIndices(parallelIndices, symbolMap, context) {
583
+ let values = [];
584
+ let unkownTypeDef = false;
585
+ function getUnknownTypeDef() {
586
+ if (unkownTypeDef === false) {
587
+ const data = symbolMap['%unknown']?.data;
588
+ unkownTypeDef = TypeDefSymbolData.is(data) ? data.typeDef : undefined;
589
+ }
590
+ return unkownTypeDef;
591
+ }
592
+ for (const index of parallelIndices) {
593
+ let lookup = [];
594
+ if (index.kind === 'static') {
595
+ if (index.value === '%fallback') {
596
+ values = Object.values(symbolMap)
597
+ .map(e => e.data)
598
+ .filter(TypeDefSymbolData.is)
599
+ .map(f => f.typeDef);
600
+ break;
601
+ }
602
+ if (index.value.startsWith('minecraft:')) {
603
+ lookup.push(index.value.substring(10));
604
+ }
605
+ else {
606
+ lookup.push(index.value);
607
+ }
608
+ }
609
+ else {
610
+ let possibilities = context.isMember
611
+ ? [{ value: context.node, key: context.node.entryNode.runtimeKey }]
612
+ : [{
613
+ value: context.node.entryNode.parent,
614
+ key: context.node.entryNode.runtimeKey,
615
+ }];
616
+ for (const entry of index.accessor) {
617
+ if (typeof entry !== 'string' && entry.keyword === 'parent') {
618
+ possibilities = possibilities.map(n => ({
619
+ value: n.value?.entryNode.parent,
620
+ key: n.value?.entryNode.runtimeKey,
621
+ }));
622
+ }
623
+ else if (typeof entry !== 'string' && entry.keyword === 'key') {
624
+ possibilities = possibilities.map(p => ({
625
+ value: p.key
626
+ ? { node: p.key, entryNode: { parent: p.value, runtimeKey: p.key } }
627
+ : undefined,
628
+ key: undefined,
629
+ }));
630
+ break;
631
+ }
632
+ else if (typeof entry === 'string') {
633
+ const newPossibilities = [];
634
+ for (const node of possibilities) {
635
+ const possibleChildren = node.value
636
+ ? context.ctx.getChildren(node.value.node.originalNode, simplify(node.value.node.inferredType, { ...context, node: node.value })).filter(child => {
637
+ if (!Array.isArray(child)) {
638
+ return child.key.inferredType.kind === 'literal'
639
+ && child.key.inferredType.value.kind === 'string'
640
+ && child.key.inferredType.value.value === entry;
641
+ }
642
+ // TODO if it's a list, consider all list items.
643
+ // This should probably work recursively if we have a list of lists.
644
+ return false;
645
+ }) // We don't consider arrays yet, see above.
646
+ .flatMap(c => c.possibleValues.map(v => ({
647
+ value: {
648
+ node: v,
649
+ entryNode: { parent: node.value, runtimeKey: c.key },
650
+ },
651
+ key: undefined,
652
+ })))
653
+ : [{ value: undefined, key: undefined }];
654
+ newPossibilities.push(...possibleChildren);
655
+ }
656
+ possibilities = newPossibilities;
657
+ }
658
+ else {
659
+ lookup.push('%none');
660
+ break;
661
+ }
662
+ }
663
+ for (const value of possibilities.map(p => p.value?.node)) {
664
+ if (value?.inferredType.kind === 'literal' && value.inferredType.value.kind === 'string') {
665
+ const ans = value.inferredType.value.value;
666
+ if (ans.startsWith('minecraft:')) {
667
+ lookup.push(ans.substring(10));
668
+ }
669
+ else {
670
+ lookup.push(ans);
671
+ }
672
+ }
673
+ else {
674
+ lookup.push('%none');
675
+ }
676
+ }
677
+ }
678
+ if (lookup.length === 0) {
679
+ lookup = ['%none'];
680
+ }
681
+ const currentValues = lookup.map(v => {
682
+ const data = symbolMap[v]?.data;
683
+ return TypeDefSymbolData.is(data) ? data.typeDef : getUnknownTypeDef();
684
+ });
685
+ if (currentValues.includes(undefined)) {
686
+ // fallback case
687
+ return { kind: 'any' };
688
+ }
689
+ else {
690
+ values.push(...currentValues.map(v => v));
691
+ }
692
+ }
693
+ if (values.length === 1) {
694
+ // avoid overhead from union
695
+ return simplify(values[0], context);
696
+ }
697
+ return simplifyUnion({ kind: 'union', members: values }, context);
698
+ }
699
+ function simplifyUnion(typeDef, context) {
700
+ const filterCanonical = context.ctx.requireCanonical
701
+ && typeDef.members.some(m => m.attributes?.some(a => a.name === 'canonical'));
702
+ const validMembers = typeDef.members
703
+ .filter(member => {
704
+ if (filterCanonical && !member.attributes?.some(a => a.name === 'canonical')) {
705
+ return false;
706
+ }
707
+ let keep = true;
708
+ handleAttributes(member.attributes, context.ctx, (handler, config) => {
709
+ if (!keep || !handler.filterElement) {
710
+ return;
711
+ }
712
+ if (!handler.filterElement(config, context.ctx)) {
713
+ keep = false;
714
+ }
715
+ });
716
+ return keep;
717
+ });
718
+ if (validMembers.length === 1) {
719
+ // Avoid needing to manually add struct fields to parent if we are in a spread operation
720
+ return simplify(validMembers[0], context);
721
+ }
722
+ const members = [];
723
+ for (const member of validMembers) {
724
+ const simplified = simplify(member, {
725
+ ...context,
726
+ structFields: undefined,
727
+ });
728
+ if (simplified.kind === 'union') {
729
+ members.push(...simplified.members);
730
+ }
731
+ else {
732
+ members.push(simplified);
733
+ }
734
+ if (context.structFields && members.length > 1) {
735
+ return { kind: 'union', members: [] };
736
+ }
737
+ }
738
+ if (members.length === 1) {
739
+ // This should basically never happen, only when a union member resolves to an empty union.
740
+ // Apply struct fields to parent if we are inside a spread operation.
741
+ if (members[0].kind === 'struct' && context.structFields) {
742
+ for (const field of members[0].fields) {
743
+ if (field.key.kind === 'literal' && field.key.value.kind === 'string') {
744
+ context.structFields.literalFields.set(field.key.value.value, field);
745
+ }
746
+ else {
747
+ context.structFields.complexFields.push(field);
748
+ }
749
+ }
750
+ }
751
+ return members[0];
752
+ }
753
+ return { ...typeDef, kind: 'union', members };
754
+ }
755
+ function simplifyStruct(typeDef, context) {
756
+ const literalFields = context.structFields?.literalFields
757
+ ?? new Map();
758
+ let complexFields = context.structFields?.complexFields ?? [];
759
+ function addField(key, field) {
760
+ handleAttributes(field.attributes, context.ctx, (handler, config) => {
761
+ if (handler.mapField) {
762
+ field = handler.mapField(config, field, context.ctx);
763
+ }
764
+ });
765
+ if (typeof key === 'string') {
766
+ literalFields.set(key, field);
767
+ }
768
+ else if (key.kind === 'literal' && key.value.kind === 'string' && !key.attributes?.length) {
769
+ literalFields.set(key.value.value, field);
770
+ }
771
+ else if (key.kind === 'union') {
772
+ key.members.forEach(m => addField(m, { ...field, optional: true }));
773
+ }
774
+ else {
775
+ // Only keep fields where the new key is not assignable to an existing field
776
+ complexFields = complexFields.filter(other => !McdocType.equals(key, typeof other.key === 'string'
777
+ ? { kind: 'literal', value: { kind: 'string', value: other.key } }
778
+ : other.key));
779
+ complexFields.push({ ...field, key });
780
+ }
781
+ }
782
+ for (const field of typeDef.fields) {
783
+ let keep = true;
784
+ handleAttributes(field.attributes, context.ctx, (handler, config) => {
785
+ if (keep && handler.filterElement?.(config, context.ctx) === false) {
786
+ keep = false;
787
+ }
788
+ });
789
+ if (!keep) {
790
+ continue;
791
+ }
792
+ if (field.kind === 'pair') {
793
+ // Don't simplify the value here. We need to have the correct `node` and `parents`, which we
794
+ // cannot deterministically find for non-string keys.
795
+ // Instead, this method will be called by every struct child by the outer checking method.
796
+ const structKey = typeof field.key === 'string'
797
+ ? field.key
798
+ : simplify(field.key, { ...context, isMember: true, typeArgs: [] });
799
+ const mappedField = context.typeMapping
800
+ ? {
801
+ ...field,
802
+ type: {
803
+ kind: 'mapped',
804
+ child: field.type,
805
+ mapping: context.typeMapping,
806
+ },
807
+ }
808
+ : field;
809
+ addField(structKey, mappedField);
810
+ }
811
+ else {
812
+ const simplifiedSpread = simplify(field.type, {
813
+ ...context,
814
+ isMember: true,
815
+ typeArgs: [],
816
+ structFields: { literalFields, complexFields },
817
+ });
818
+ // Recursive calls above will already modify literal and complex fields passed by the
819
+ // context, so no need to handle them here.
820
+ // In case we are spreading sth that resolves to `any`, allow any other additional field.
821
+ if (simplifiedSpread.kind === 'any') {
822
+ addField({ kind: 'any' }, { kind: 'pair', key: { kind: 'any' }, type: { kind: 'any' } });
823
+ }
824
+ }
825
+ }
826
+ if (context.structFields) {
827
+ // In this case we are spreading a struct and the fields have been added to the parent map
828
+ // from context now.
829
+ return { kind: 'struct', fields: [] };
830
+ }
831
+ // Literal fields may still be assignable to complex fields,
832
+ // however this is currently not seen as an issue
833
+ return {
834
+ kind: 'struct',
835
+ fields: [
836
+ ...complexFields,
837
+ ...[...literalFields.entries()].map(([key, field]) => ({
838
+ ...field,
839
+ key: { kind: 'literal', value: { kind: 'string', value: key } },
840
+ })),
841
+ ],
842
+ };
843
+ }
844
+ function simplifyList(typeDef, context) {
845
+ if (!context.typeMapping) {
846
+ return typeDef;
847
+ }
848
+ return {
849
+ ...typeDef,
850
+ item: { kind: 'mapped', child: typeDef.item, mapping: context.typeMapping },
851
+ };
852
+ }
853
+ function simplifyTuple(typeDef, context) {
854
+ if (!context.typeMapping) {
855
+ return typeDef;
856
+ }
857
+ return {
858
+ ...typeDef,
859
+ items: typeDef.items.map(item => ({
860
+ kind: 'mapped',
861
+ child: item,
862
+ mapping: context.typeMapping,
863
+ })),
864
+ };
865
+ }
866
+ function simplifyEnum(typeDef, context) {
867
+ const filteredValues = typeDef.values.filter(value => {
868
+ let keep = true;
869
+ handleAttributes(value.attributes, context.ctx, (handler, config) => {
870
+ if (!keep || !handler.filterElement) {
871
+ return;
872
+ }
873
+ if (!handler.filterElement(config, context.ctx)) {
874
+ keep = false;
875
+ }
876
+ });
877
+ return keep;
878
+ });
879
+ return { ...typeDef, enumKind: typeDef.enumKind ?? 'int', values: filteredValues };
880
+ }
881
+ function simplifyConcrete(typeDef, context) {
882
+ const simplifiedArgs = typeDef.typeArgs.map(arg => simplify(arg, context));
883
+ return simplify(typeDef.child, { ...context, typeArgs: simplifiedArgs });
884
+ }
885
+ function simplifyTemplate(typeDef, context) {
886
+ if (context.typeArgs?.length !== typeDef.typeParams.length) {
887
+ context.ctx.logger.warn(`Expected ${typeDef.typeParams.length} mcdoc type args for ${McdocType.toString(typeDef.child)}, but got ${context.typeArgs?.length ?? 0}`);
888
+ }
889
+ const mapping = Object.fromEntries(typeDef.typeParams.map((param, i) => {
890
+ const arg = context.typeArgs?.[i] ?? { kind: 'union', members: [] };
891
+ return [param.path, arg];
892
+ }));
893
+ return simplify(typeDef.child, { ...context, typeArgs: [], typeMapping: mapping });
894
+ }
895
+ function simplifyMapped(typeDef, context) {
896
+ // Mapped types that were created in simplify are always simplified
897
+ // types already, in which case this will be a cheap operation, but
898
+ // this is necessary for type safety
899
+ const simplifiedMapping = Object.fromEntries(Object.entries(typeDef.mapping).map(([path, param]) => {
900
+ return [path, simplify(param, context)];
901
+ }));
902
+ return simplify(typeDef.child, { ...context, typeMapping: simplifiedMapping });
903
+ }
904
+ function getValueType(type) {
905
+ switch (type.kind) {
906
+ case 'literal':
907
+ return { kind: type.value.kind };
908
+ case 'enum':
909
+ return { kind: type.enumKind };
910
+ default:
911
+ return type;
912
+ }
913
+ }
914
+ //# sourceMappingURL=index.js.map