@spyglassmc/mcdoc 0.3.10 → 0.3.12

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.
@@ -2,11 +2,13 @@ import type { BinderContext, MetaRegistry } from '@spyglassmc/core';
2
2
  import { AsyncBinder } from '@spyglassmc/core';
3
3
  import type { AdditionalContext } from '../common.js';
4
4
  import type { ModuleNode } from '../node/index.js';
5
+ import type { SimplifiedMcdocType } from '../runtime/checker/index.js';
5
6
  import type { McdocType } from '../type/index.js';
6
7
  interface McdocBinderContext extends BinderContext, AdditionalContext {
7
8
  }
8
9
  export interface TypeDefSymbolData {
9
10
  typeDef: McdocType;
11
+ simplifiedTypeDef?: SimplifiedMcdocType;
10
12
  }
11
13
  export declare namespace TypeDefSymbolData {
12
14
  function is(data: unknown): data is TypeDefSymbolData;
@@ -1,10 +1,12 @@
1
1
  import * as core from '@spyglassmc/core';
2
+ import { localeQuote, localize } from '@spyglassmc/locales';
2
3
  import { registerAttribute, validator } from './index.js';
3
4
  const idValidator = validator.alternatives(validator.map(validator.string, v => ({ registry: v })), validator.tree({
4
5
  registry: validator.string,
5
6
  tags: validator.optional(validator.options('allowed', 'implicit', 'required')),
6
7
  definition: validator.optional(validator.boolean),
7
8
  prefix: validator.optional(validator.options('!')),
9
+ empty: validator.optional(validator.options('allowed')),
8
10
  }), () => ({}));
9
11
  function getResourceLocationOptions({ registry, tags, definition }, requireCanonical, ctx, typeDef) {
10
12
  if (!registry) {
@@ -45,6 +47,10 @@ function getResourceLocationOptions({ registry, tags, definition }, requireCanon
45
47
  ctx.logger.warn(`[mcdoc id] Unhandled registry ${registry}`);
46
48
  return undefined;
47
49
  }
50
+ const integerValidator = validator.alternatives(validator.tree({
51
+ min: validator.optional(validator.number),
52
+ max: validator.optional(validator.number),
53
+ }), () => ({}));
48
54
  export function registerBuiltinAttributes(meta) {
49
55
  registerAttribute(meta, 'canonical', () => undefined, {
50
56
  // Has hardcoded behavior in the runtime checker
@@ -108,6 +114,11 @@ export function registerBuiltinAttributes(meta) {
108
114
  }
109
115
  const resourceLocation = core.resourceLocation(options);
110
116
  return (src, ctx) => {
117
+ if (config.empty && !src.canRead()) {
118
+ return core.string({
119
+ unquotable: { blockList: new Set(), allowEmpty: true },
120
+ })(src, ctx);
121
+ }
111
122
  if (config.prefix) {
112
123
  return core.prefixed({ prefix: config.prefix, child: resourceLocation })(src, ctx);
113
124
  }
@@ -126,5 +137,70 @@ export function registerBuiltinAttributes(meta) {
126
137
  return resourceLocation;
127
138
  },
128
139
  });
140
+ registerAttribute(meta, 'integer', integerValidator, {
141
+ stringParser: (config) => {
142
+ return core.integer({ pattern: /^-?\d+$/, min: config.min, max: config.max });
143
+ },
144
+ });
145
+ registerAttribute(meta, 'color', validator.string, {
146
+ checkInferred: (config, inferred, ctx) => {
147
+ if (config === 'hex_rgb' && inferred.kind === 'literal' && inferred.value.kind === 'string') {
148
+ return inferred.value.value.startsWith('#');
149
+ }
150
+ return true;
151
+ },
152
+ checker: (config, inferred) => {
153
+ return (node, ctx) => {
154
+ switch (config) {
155
+ case 'named':
156
+ if (inferred.kind !== 'literal' || inferred.value.kind !== 'string') {
157
+ return;
158
+ }
159
+ node.color = core.Color.fromNamed(inferred.value.value);
160
+ return;
161
+ case 'hex_rgb':
162
+ if (inferred.kind !== 'literal' || inferred.value.kind !== 'string') {
163
+ return;
164
+ }
165
+ let range = node.range;
166
+ if (core.StringBaseNode.is(node) && node.quote) {
167
+ range = core.Range.translate(range, 1, -1);
168
+ }
169
+ if (!inferred.value.value.startsWith('#')) {
170
+ ctx.err.report(localize('expected', localeQuote('#')), range, 2 /* core.ErrorSeverity.Warning */);
171
+ return;
172
+ }
173
+ node.color = {
174
+ value: core.Color.fromHexRGB(inferred.value.value),
175
+ format: [core.ColorFormat.HexRGB],
176
+ range,
177
+ };
178
+ return;
179
+ case 'composite_rgb':
180
+ if (inferred.kind !== 'literal' || typeof inferred.value.value !== 'number') {
181
+ return;
182
+ }
183
+ node.color = {
184
+ value: core.Color.fromCompositeRGB(inferred.value.value),
185
+ format: [core.ColorFormat.CompositeRGB],
186
+ range: node.range,
187
+ };
188
+ return;
189
+ case 'composite_argb':
190
+ if (inferred.kind !== 'literal' || typeof inferred.value.value !== 'number') {
191
+ return;
192
+ }
193
+ node.color = {
194
+ value: core.Color.fromCompositeARGB(inferred.value.value),
195
+ format: [core.ColorFormat.CompositeARGB],
196
+ range: node.range,
197
+ };
198
+ return;
199
+ default:
200
+ return;
201
+ }
202
+ };
203
+ },
204
+ });
129
205
  }
130
206
  //# sourceMappingURL=builtin.js.map
@@ -1,5 +1,5 @@
1
1
  import * as core from '@spyglassmc/core';
2
- import type { Attribute, StructTypePairField } from '../../type/index.js';
2
+ import type { Attribute, McdocType, StructTypePairField, UnionType } from '../../type/index.js';
3
3
  import type { McdocCheckerContext, SimplifiedMcdocType, SimplifiedMcdocTypeNoUnion } from '../checker/index.js';
4
4
  import type { McdocCompleterContext } from '../completer/index.js';
5
5
  import type { McdocAttributeValidator } from './validator.js';
@@ -10,7 +10,9 @@ export interface McdocAttribute<C = unknown> {
10
10
  mapField?: <T>(config: C, field: StructTypePairField, ctx: McdocCheckerContext<T>) => StructTypePairField;
11
11
  filterElement?: (config: C, ctx: core.ContextBase) => boolean;
12
12
  stringParser?: <T>(config: C, typeDef: SimplifiedMcdocTypeNoUnion, ctx: McdocCheckerContext<T>) => core.InfallibleParser<core.AstNode | undefined> | undefined;
13
+ checker?: <T>(config: C, inferred: Exclude<McdocType, UnionType>, ctx: McdocCheckerContext<T>) => core.SyncChecker<core.AstNode> | undefined;
13
14
  stringMocker?: (config: C, typeDef: core.DeepReadonly<SimplifiedMcdocTypeNoUnion>, ctx: McdocCompleterContext) => core.AstNode | undefined;
15
+ numericCompleter?: (config: C, ctx: McdocCompleterContext) => core.CompletionItem[];
14
16
  }
15
17
  export declare function registerAttribute<C extends core.Returnable>(meta: core.MetaRegistry, name: string, validator: McdocAttributeValidator<C>, attribute: McdocAttribute<C>): void;
16
18
  interface AttributeInfo {
@@ -14,6 +14,7 @@ export interface RuntimePair<T> {
14
14
  }
15
15
  export type NodeEquivalenceChecker = (inferredNode: Exclude<SimplifiedMcdocTypeNoUnion, LiteralType | EnumType>, definition: Exclude<SimplifiedMcdocTypeNoUnion, LiteralType | EnumType>) => boolean;
16
16
  export type TypeInfoAttacher<T> = (node: T, definition: SimplifiedMcdocType, description?: string) => void;
17
+ export type NodeAttacher<T> = (node: T, attacher: (node: core.AstNode) => void) => void;
17
18
  export type StringAttacher<T> = (node: T, attacher: (node: core.StringBaseNode) => void) => void;
18
19
  export type ChildrenGetter<T> = (node: T, simplified: SimplifiedMcdocTypeNoUnion) => RuntimeUnion<T>[];
19
20
  export type ErrorReporter<T> = (error: McdocRuntimeError<T>) => void;
@@ -24,6 +25,7 @@ export interface McdocCheckerContext<T> extends core.CheckerContext {
24
25
  getChildren: ChildrenGetter<T>;
25
26
  reportError: ErrorReporter<T>;
26
27
  attachTypeInfo?: TypeInfoAttacher<T>;
28
+ nodeAttacher?: NodeAttacher<T>;
27
29
  stringAttacher?: StringAttacher<T>;
28
30
  }
29
31
  type McdocCheckerContextOptions<T> = Partial<McdocCheckerContext<T>>;
@@ -9,6 +9,7 @@ export var McdocCheckerContext;
9
9
  getChildren: options.getChildren ?? (() => []),
10
10
  reportError: options.reportError ?? (() => { }),
11
11
  attachTypeInfo: options.attachTypeInfo,
12
+ nodeAttacher: options.nodeAttacher,
12
13
  stringAttacher: options.stringAttacher,
13
14
  };
14
15
  }
@@ -202,7 +202,7 @@ function condense(validDefinitions, is, equals, combineAlternatives) {
202
202
  }, []);
203
203
  const distinctErrors = distinctErrorsPerNode.flatMap(e => e.errors);
204
204
  const commonErrors = distinctErrors
205
- .filter(e => e.definitions.length == validDefinitions.length)
205
+ .filter(e => e.definitions.length === validDefinitions.length)
206
206
  .map(e => e.error);
207
207
  const definitionsWithUncommonErrors = distinctErrors
208
208
  .filter(e => e.definitions.length < validDefinitions.length)
@@ -342,7 +342,7 @@ export function getDefaultErrorReporter(ctx, getErrorRange) {
342
342
  localizedText = localize(`mcdoc.runtime.checker.${defaultTranslationKey}`);
343
343
  break;
344
344
  case 'internal':
345
- break;
345
+ return;
346
346
  default:
347
347
  localizedText = localize(defaultTranslationKey);
348
348
  }
@@ -65,16 +65,16 @@ export interface SimplifyValueNode<T> {
65
65
  export interface SimplifyContext<T> {
66
66
  node: SimplifyValueNode<T>;
67
67
  ctx: McdocCheckerContext<T>;
68
- structFields?: {
69
- literalFields: Map<string, StructTypePairField>;
70
- complexFields: SimplifiedStructTypePairField[];
71
- };
72
68
  isMember?: boolean;
73
69
  typeArgs?: SimplifiedMcdocType[];
74
70
  typeMapping?: {
75
71
  [path: string]: SimplifiedMcdocType;
76
72
  };
77
73
  }
78
- export declare function simplify<T>(typeDef: Exclude<McdocType, UnionType>, context: SimplifyContext<T>): SimplifiedMcdocTypeNoUnion;
79
- export declare function simplify<T>(typeDef: McdocType, context: SimplifyContext<T>): SimplifiedMcdocType;
74
+ export interface SimplifyResult<T extends SimplifiedMcdocType> {
75
+ typeDef: T;
76
+ dynamicData?: boolean;
77
+ }
78
+ export declare function simplify<T>(typeDef: Exclude<McdocType, UnionType>, context: SimplifyContext<T>): SimplifyResult<SimplifiedMcdocTypeNoUnion>;
79
+ export declare function simplify<T>(typeDef: McdocType, context: SimplifyContext<T>): SimplifyResult<SimplifiedMcdocType>;
80
80
  //# sourceMappingURL=index.d.ts.map
@@ -88,7 +88,7 @@ export function typeDefinition(runtimeValues, typeDef, ctx) {
88
88
  children: [],
89
89
  }));
90
90
  for (const value of rootNode.possibleValues) {
91
- const simplifiedRoot = simplify(typeDef, { ctx, node: value });
91
+ const simplifiedRoot = simplify(typeDef, { ctx, node: value }).typeDef;
92
92
  const validRootDefinitions = simplifiedRoot.kind === 'union'
93
93
  ? simplifiedRoot.members
94
94
  : [simplifiedRoot];
@@ -110,7 +110,7 @@ export function typeDefinition(runtimeValues, typeDef, ctx) {
110
110
  while (nodeQueue.length !== 0) {
111
111
  const node = nodeQueue.shift();
112
112
  for (const value of node.possibleValues) {
113
- const inferredSimplified = simplify(value.node.inferredType, { ctx, node: value });
113
+ const inferredSimplified = simplify(value.node.inferredType, { ctx, node: value }).typeDef;
114
114
  const children = ctx.getChildren(value.node.originalNode, inferredSimplified);
115
115
  const childNodes = children.map(c => {
116
116
  const ans = {
@@ -165,7 +165,7 @@ export function typeDefinition(runtimeValues, typeDef, ctx) {
165
165
  // the same types again and just collect the errors of the lower depth.
166
166
  // This will currently lead to a stack overflow error when e.g. comparing two
167
167
  // text component definitions
168
- const simplified = simplify(childDef.type, { ctx, node: childValue });
168
+ const simplified = simplify(childDef.type, { ctx, node: childValue }).typeDef;
169
169
  const childDefinitionGroup = {
170
170
  parents: [def],
171
171
  runtimeNode: childValue,
@@ -212,10 +212,10 @@ function attachTypeInfo(node, ctx) {
212
212
  if (definitions.length === 1) {
213
213
  const { typeDef, groupNode } = definitions[0];
214
214
  ctx.attachTypeInfo?.(node.node.originalNode, typeDef, groupNode.desc);
215
- handleStringAttachers(node.node, typeDef, ctx);
215
+ handleNodeAttachers(node.node, typeDef, ctx);
216
216
  if (node.entryNode.runtimeKey && groupNode.keyDefinition) {
217
217
  ctx.attachTypeInfo?.(node.entryNode.runtimeKey.originalNode, groupNode.keyDefinition, groupNode.desc);
218
- handleStringAttachers(node.entryNode.runtimeKey, groupNode.keyDefinition, ctx);
218
+ handleNodeAttachers(node.entryNode.runtimeKey, groupNode.keyDefinition, ctx);
219
219
  }
220
220
  }
221
221
  else if (definitions.length > 1) {
@@ -237,40 +237,46 @@ function attachTypeInfo(node, ctx) {
237
237
  attachTypeInfo(child, ctx);
238
238
  }
239
239
  }
240
- function handleStringAttachers(runtimeValue, typeDef, ctx) {
241
- const { stringAttacher } = ctx;
242
- if (!stringAttacher) {
240
+ function handleNodeAttachers(runtimeValue, typeDef, ctx) {
241
+ const { nodeAttacher, stringAttacher } = ctx;
242
+ if (!nodeAttacher && !stringAttacher) {
243
243
  return;
244
244
  }
245
245
  handleAttributes(typeDef.attributes, ctx, (handler, config) => {
246
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
- });
247
+ if (parser && stringAttacher) {
248
+ stringAttacher(runtimeValue.originalNode, (node) => {
249
+ const src = new Source(node.value, node.valueMap);
250
+ const start = src.cursor;
251
+ const child = parser(src, ctx);
252
+ if (!child) {
253
+ ctx.err.report(localize('expected', localize('mcdoc.runtime.checker.value')), Range.create(start, src.skipRemaining()));
254
+ return;
255
+ }
256
+ else if (src.canRead()) {
257
+ ctx.err.report(localize('mcdoc.runtime.checker.trailing'), Range.create(src.cursor, src.skipRemaining()));
258
+ }
259
+ node.children = [child];
260
+ });
261
+ }
262
+ const checker = handler.checker?.(config, runtimeValue.inferredType, ctx);
263
+ if (checker && nodeAttacher) {
264
+ nodeAttacher(runtimeValue.originalNode, (node) => {
265
+ checker(node, ctx);
266
+ });
267
+ }
263
268
  });
264
269
  }
265
270
  function checkShallowly(runtimeNode, simplifiedInferred, children, typeDef, ctx) {
266
- const typeDefValueType = getValueType(typeDef);
267
- const runtimeValueType = getValueType(simplifiedInferred);
268
271
  const childDefinitions = Array(children.length)
269
272
  .fill(undefined);
270
- if ((typeDef.kind !== 'any' && typeDef.kind !== 'unsafe'
271
- && simplifiedInferred.kind !== 'unsafe'
272
- && runtimeValueType.kind !== typeDefValueType.kind
273
- && !ctx.isEquivalent(runtimeValueType, typeDefValueType))) {
273
+ if (typeDef.kind === 'any' || typeDef.kind === 'unsafe' || simplifiedInferred.kind === 'unsafe') {
274
+ return { childDefinitions, errors: [] };
275
+ }
276
+ const typeDefValueType = getValueType(typeDef);
277
+ const runtimeValueType = getValueType(simplifiedInferred);
278
+ if (runtimeValueType.kind !== typeDefValueType.kind
279
+ && !ctx.isEquivalent(runtimeValueType, typeDefValueType)) {
274
280
  return {
275
281
  childDefinitions,
276
282
  errors: [{ kind: 'type_mismatch', node: runtimeNode, expected: [typeDef] }],
@@ -299,9 +305,6 @@ function checkShallowly(runtimeNode, simplifiedInferred, children, typeDef, ctx)
299
305
  };
300
306
  }
301
307
  switch (typeDef.kind) {
302
- case 'any':
303
- case 'unsafe':
304
- break;
305
308
  case 'byte':
306
309
  case 'short':
307
310
  case 'int':
@@ -357,6 +360,7 @@ function checkShallowly(runtimeNode, simplifiedInferred, children, typeDef, ctx)
357
360
  otherKvps.push({ value: child, index: i });
358
361
  }
359
362
  }
363
+ const missingKeys = new Set();
360
364
  for (const pair of typeDef.fields) {
361
365
  const otherKvpMatches = [];
362
366
  let foundMatch = false;
@@ -368,17 +372,14 @@ function checkShallowly(runtimeNode, simplifiedInferred, children, typeDef, ctx)
368
372
  }
369
373
  }
370
374
  if (!foundMatch) {
371
- for (let i = 0; i < otherKvps.length; i++) {
372
- const kvp = otherKvps[i];
375
+ for (const kvp of otherKvps) {
373
376
  if (isAssignable(kvp.value.key.inferredType, pair.key, ctx, ctx.isEquivalent)) {
374
377
  foundMatch = true;
375
- otherKvps.splice(i, 1);
376
378
  otherKvpMatches.push(kvp.index);
377
- i--;
378
379
  }
379
380
  }
380
381
  for (const kvp of literalKvps.entries()) {
381
- if (!kvp[1].definition
382
+ if ((!kvp[1].definition || kvp[1].definition.keyType?.kind !== 'literal')
382
383
  && isAssignable({ kind: 'literal', value: { kind: 'string', value: kvp[0] } }, pair.key, ctx, ctx.isEquivalent)) {
383
384
  foundMatch = true;
384
385
  kvp[1].definition = { keyType: pair.key, type: pair.type, desc: pair.desc };
@@ -393,9 +394,14 @@ function checkShallowly(runtimeNode, simplifiedInferred, children, typeDef, ctx)
393
394
  && pair.key.kind === 'literal'
394
395
  && pair.key.value.kind === 'string'
395
396
  && pair.optional !== true) {
396
- errors.push({ kind: 'missing_key', node: runtimeNode, keys: [pair.key.value.value] });
397
+ missingKeys.add(pair.key.value.value);
397
398
  }
398
399
  }
400
+ errors.push(...Array.from(missingKeys).map(key => ({
401
+ kind: 'missing_key',
402
+ node: runtimeNode,
403
+ keys: [key],
404
+ })));
399
405
  for (const kvp of literalKvps.values()) {
400
406
  for (const value of kvp.values) {
401
407
  childDefinitions[value.index] = kvp.definition;
@@ -484,16 +490,16 @@ export function getPossibleTypes(typeDef) {
484
490
  return typeDef.kind === 'union' ? typeDef.members.flatMap(m => getPossibleTypes(m)) : [typeDef];
485
491
  }
486
492
  export function simplify(typeDef, context) {
487
- function wrap(typeDef) {
488
- if (!typeDef.attributes?.length) {
489
- return typeDef;
493
+ function wrap(result) {
494
+ if (!result.typeDef.attributes?.length) {
495
+ return result;
490
496
  }
491
497
  handleAttributes(typeDef.attributes, context.ctx, (handler, config) => {
492
498
  if (handler.mapType) {
493
- typeDef = handler.mapType(config, typeDef, context.ctx);
499
+ result.typeDef = handler.mapType(config, result.typeDef, context.ctx);
494
500
  }
495
501
  });
496
- return typeDef;
502
+ return result;
497
503
  }
498
504
  switch (typeDef.kind) {
499
505
  case 'reference':
@@ -519,53 +525,67 @@ export function simplify(typeDef, context) {
519
525
  case 'mapped':
520
526
  return wrap(simplifyMapped(typeDef, context));
521
527
  default:
522
- return wrap(typeDef);
528
+ return wrap({ typeDef });
523
529
  }
524
530
  }
525
531
  function simplifyReference(typeDef, context) {
526
532
  if (!typeDef.path) {
527
533
  // TODO when does this happen?
528
534
  context.ctx.logger.warn(`Tried to access empty reference`);
529
- return { kind: 'union', members: [] };
535
+ return { typeDef: { kind: 'union', members: [] } };
530
536
  }
531
537
  const mapped = context.typeMapping?.[typeDef.path];
532
538
  if (mapped) {
533
- return mapped;
539
+ return { typeDef: mapped, dynamicData: true };
534
540
  }
535
541
  // TODO Probably need to keep original symbol around in some way to support "go to definition"
536
542
  const symbol = context.ctx.symbols.query(context.ctx.doc, 'mcdoc', typeDef.path);
537
- const def = symbol.getData(TypeDefSymbolData.is)?.typeDef;
538
- if (!def) {
543
+ const data = symbol.getData(TypeDefSymbolData.is);
544
+ if (!data?.typeDef) {
539
545
  context.ctx.logger.warn(`Tried to access unknown reference ${typeDef.path}`);
540
- return { kind: 'union', members: [] };
546
+ return { typeDef: { kind: 'union', members: [] } };
541
547
  }
542
- const simplifiedDef = simplify(def, context);
548
+ if (data.simplifiedTypeDef) {
549
+ return { typeDef: data.simplifiedTypeDef };
550
+ }
551
+ const simplifiedResult = simplify(data.typeDef, context);
543
552
  if (typeDef.attributes?.length) {
544
- return {
545
- ...simplifiedDef,
546
- attributes: [...typeDef.attributes, ...simplifiedDef.attributes ?? []],
553
+ simplifiedResult.typeDef = {
554
+ ...simplifiedResult.typeDef,
555
+ attributes: [...typeDef.attributes, ...simplifiedResult.typeDef.attributes ?? []],
547
556
  };
548
557
  }
549
- return simplifiedDef;
558
+ if (!simplifiedResult.dynamicData) {
559
+ symbol.amend({
560
+ data: {
561
+ data: {
562
+ ...data,
563
+ simplifiedTypeDef: simplifiedResult.typeDef,
564
+ },
565
+ },
566
+ });
567
+ }
568
+ return simplifiedResult;
550
569
  }
551
570
  function simplifyDispatcher(typeDef, context) {
552
- const dispatcher = context.ctx.symbols.query(context.ctx.doc, 'mcdoc/dispatcher', typeDef.registry).symbol
553
- ?.members;
571
+ const dispatcherQuery = context.ctx.symbols.query(context.ctx.doc, 'mcdoc/dispatcher', typeDef.registry);
572
+ const dispatcher = dispatcherQuery.symbol?.members;
554
573
  if (!dispatcher) {
555
574
  context.ctx.logger.warn(`Tried to access unknown dispatcher ${typeDef.registry}`);
556
- return { kind: 'union', members: [] };
575
+ return { typeDef: { kind: 'union', members: [] } };
557
576
  }
558
- return resolveIndices(typeDef.parallelIndices, dispatcher, context);
577
+ const result = resolveIndices(typeDef.parallelIndices, dispatcher, dispatcherQuery, context);
578
+ return result;
559
579
  }
560
580
  function simplifyIndexed(typeDef, context) {
561
- const child = simplify(typeDef.child, {
581
+ const childResult = simplify(typeDef.child, {
562
582
  ...context,
563
583
  typeArgs: [],
564
- structFields: undefined,
565
584
  });
585
+ const child = childResult.typeDef;
566
586
  if (child.kind !== 'struct') {
567
587
  context.ctx.logger.warn(`Tried to index un-indexable type ${child.kind}`);
568
- return { kind: 'union', members: [] };
588
+ return { typeDef: { kind: 'union', members: [] }, dynamicData: childResult.dynamicData };
569
589
  }
570
590
  const symbolMap = {};
571
591
  for (const field of child.fields) {
@@ -577,15 +597,44 @@ function simplifyIndexed(typeDef, context) {
577
597
  };
578
598
  }
579
599
  }
580
- return resolveIndices(typeDef.parallelIndices, symbolMap, context);
600
+ const simplified = resolveIndices(typeDef.parallelIndices, symbolMap, undefined, context);
601
+ return { ...simplified, dynamicData: childResult.dynamicData ?? simplified.dynamicData };
581
602
  }
582
- function resolveIndices(parallelIndices, symbolMap, context) {
603
+ function resolveIndices(parallelIndices, symbolMap, symbolQuery, context) {
604
+ let dynamicData = false;
583
605
  let values = [];
606
+ function pushValue(key, data) {
607
+ if (data.simplifiedTypeDef) {
608
+ if (data.simplifiedTypeDef.kind === 'union') {
609
+ values.push(...data.simplifiedTypeDef.members);
610
+ }
611
+ else {
612
+ values.push(data.simplifiedTypeDef);
613
+ }
614
+ }
615
+ else {
616
+ const simplifiedResult = simplify(data.typeDef, context);
617
+ if (simplifiedResult.dynamicData) {
618
+ dynamicData = true;
619
+ }
620
+ else if (symbolQuery) {
621
+ symbolQuery.member(key, s => s.amend({
622
+ data: { data: { ...data, simplifiedTypeDef: simplifiedResult.typeDef } },
623
+ }));
624
+ }
625
+ if (simplifiedResult.typeDef.kind === 'union') {
626
+ values.push(...simplifiedResult.typeDef.members);
627
+ }
628
+ else {
629
+ values.push(simplifiedResult.typeDef);
630
+ }
631
+ }
632
+ }
584
633
  let unkownTypeDef = false;
585
634
  function getUnknownTypeDef() {
586
635
  if (unkownTypeDef === false) {
587
636
  const data = symbolMap['%unknown']?.data;
588
- unkownTypeDef = TypeDefSymbolData.is(data) ? data.typeDef : undefined;
637
+ unkownTypeDef = TypeDefSymbolData.is(data) ? data : undefined;
589
638
  }
590
639
  return unkownTypeDef;
591
640
  }
@@ -593,10 +642,12 @@ function resolveIndices(parallelIndices, symbolMap, context) {
593
642
  let lookup = [];
594
643
  if (index.kind === 'static') {
595
644
  if (index.value === '%fallback') {
596
- values = Object.values(symbolMap)
597
- .map(e => e.data)
598
- .filter(TypeDefSymbolData.is)
599
- .map(f => f.typeDef);
645
+ values = [];
646
+ for (const [key, value] of Object.entries(symbolMap)) {
647
+ if (TypeDefSymbolData.is(value.data)) {
648
+ pushValue(key, value.data);
649
+ }
650
+ }
600
651
  break;
601
652
  }
602
653
  if (index.value.startsWith('minecraft:')) {
@@ -607,6 +658,7 @@ function resolveIndices(parallelIndices, symbolMap, context) {
607
658
  }
608
659
  }
609
660
  else {
661
+ dynamicData = true;
610
662
  let possibilities = context.isMember
611
663
  ? [{ value: context.node, key: context.node.entryNode.runtimeKey }]
612
664
  : [{
@@ -633,7 +685,8 @@ function resolveIndices(parallelIndices, symbolMap, context) {
633
685
  const newPossibilities = [];
634
686
  for (const node of possibilities) {
635
687
  const possibleChildren = node.value
636
- ? context.ctx.getChildren(node.value.node.originalNode, simplify(node.value.node.inferredType, { ...context, node: node.value })).filter(child => {
688
+ ? context.ctx.getChildren(node.value.node.originalNode, simplify(node.value.node.inferredType, { ...context, node: node.value })
689
+ .typeDef).filter(child => {
637
690
  if (!Array.isArray(child)) {
638
691
  return child.key.inferredType.kind === 'literal'
639
692
  && child.key.inferredType.value.kind === 'string'
@@ -680,23 +733,26 @@ function resolveIndices(parallelIndices, symbolMap, context) {
680
733
  }
681
734
  const currentValues = lookup.map(v => {
682
735
  const data = symbolMap[v]?.data;
683
- return TypeDefSymbolData.is(data) ? data.typeDef : getUnknownTypeDef();
736
+ return { value: v, data: TypeDefSymbolData.is(data) ? data : getUnknownTypeDef() };
684
737
  });
685
- if (currentValues.includes(undefined)) {
738
+ const missing = currentValues.find(v => !v.data);
739
+ if (missing) {
686
740
  // fallback case
687
- return { kind: 'any' };
741
+ return { typeDef: { kind: 'any' }, dynamicData };
688
742
  }
689
743
  else {
690
- values.push(...currentValues.map(v => v));
744
+ for (const entry of currentValues) {
745
+ pushValue(entry.value, entry.data);
746
+ }
691
747
  }
692
748
  }
693
749
  if (values.length === 1) {
694
- // avoid overhead from union
695
- return simplify(values[0], context);
750
+ return { typeDef: values[0], dynamicData };
696
751
  }
697
- return simplifyUnion({ kind: 'union', members: values }, context);
752
+ return { typeDef: { kind: 'union', members: values }, dynamicData };
698
753
  }
699
754
  function simplifyUnion(typeDef, context) {
755
+ let dynamicData = false;
700
756
  const filterCanonical = context.ctx.requireCanonical
701
757
  && typeDef.members.some(m => m.attributes?.some(a => a.name === 'canonical'));
702
758
  const validMembers = typeDef.members
@@ -716,46 +772,29 @@ function simplifyUnion(typeDef, context) {
716
772
  return keep;
717
773
  });
718
774
  if (validMembers.length === 1) {
719
- // Avoid needing to manually add struct fields to parent if we are in a spread operation
720
775
  return simplify(validMembers[0], context);
721
776
  }
722
777
  const members = [];
723
778
  for (const member of validMembers) {
724
- const simplified = simplify(member, {
725
- ...context,
726
- structFields: undefined,
727
- });
779
+ const { typeDef: simplified, dynamicData: memberDynamic } = simplify(member, context);
780
+ if (memberDynamic) {
781
+ dynamicData = true;
782
+ }
728
783
  if (simplified.kind === 'union') {
729
784
  members.push(...simplified.members);
730
785
  }
731
786
  else {
732
787
  members.push(simplified);
733
788
  }
734
- if (context.structFields && members.length > 1) {
735
- return { kind: 'union', members: [] };
736
- }
737
789
  }
738
790
  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];
791
+ return { typeDef: members[0], dynamicData };
752
792
  }
753
- return { ...typeDef, kind: 'union', members };
793
+ return { typeDef: { kind: 'union', members }, dynamicData };
754
794
  }
755
795
  function simplifyStruct(typeDef, context) {
756
- const literalFields = context.structFields?.literalFields
757
- ?? new Map();
758
- let complexFields = context.structFields?.complexFields ?? [];
796
+ const fields = [];
797
+ let dynamicData = false;
759
798
  function addField(key, field) {
760
799
  handleAttributes(field.attributes, context.ctx, (handler, config) => {
761
800
  if (handler.mapField) {
@@ -763,20 +802,16 @@ function simplifyStruct(typeDef, context) {
763
802
  }
764
803
  });
765
804
  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);
805
+ fields.push({
806
+ ...field,
807
+ key: { kind: 'literal', value: { kind: 'string', value: key } },
808
+ });
770
809
  }
771
810
  else if (key.kind === 'union') {
772
811
  key.members.forEach(m => addField(m, { ...field, optional: true }));
773
812
  }
774
813
  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 });
814
+ fields.push({ ...field, key });
780
815
  }
781
816
  }
782
817
  for (const field of typeDef.fields) {
@@ -790,22 +825,41 @@ function simplifyStruct(typeDef, context) {
790
825
  continue;
791
826
  }
792
827
  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
- ? {
828
+ // Don't simplify the value here, simplify is shallow and needs a runtime value to work
829
+ // properly, so the values should only be simplified once they were assigned to a
830
+ // runtime value.
831
+ let structKey;
832
+ if (typeof field.key === 'string') {
833
+ structKey = field.key;
834
+ }
835
+ else {
836
+ const simplifiedKeyResult = simplify(field.key, {
837
+ ...context,
838
+ isMember: true,
839
+ typeArgs: [],
840
+ });
841
+ if (simplifiedKeyResult.dynamicData) {
842
+ dynamicData = true;
843
+ }
844
+ structKey = simplifiedKeyResult.typeDef;
845
+ }
846
+ let mappedField;
847
+ if (context.typeMapping) {
848
+ mappedField = {
801
849
  ...field,
802
850
  type: {
803
851
  kind: 'mapped',
804
852
  child: field.type,
805
853
  mapping: context.typeMapping,
806
854
  },
807
- }
808
- : field;
855
+ };
856
+ // Don't cache mapped field data
857
+ // TODO find a better way to handle mapped types with caching
858
+ dynamicData = true;
859
+ }
860
+ else {
861
+ mappedField = field;
862
+ }
809
863
  addField(structKey, mappedField);
810
864
  }
811
865
  else {
@@ -813,54 +867,53 @@ function simplifyStruct(typeDef, context) {
813
867
  ...context,
814
868
  isMember: true,
815
869
  typeArgs: [],
816
- structFields: { literalFields, complexFields },
817
870
  });
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' } });
871
+ if (simplifiedSpread.dynamicData) {
872
+ dynamicData = true;
873
+ }
874
+ if (simplifiedSpread.typeDef.kind === 'any') {
875
+ fields.push({ kind: 'pair', key: { kind: 'any' }, type: { kind: 'any' } });
876
+ }
877
+ else if (simplifiedSpread.typeDef.kind === 'struct') {
878
+ fields.push(...simplifiedSpread.typeDef.fields);
823
879
  }
824
880
  }
825
881
  }
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
882
  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
- ],
883
+ typeDef: { kind: 'struct', fields },
884
+ dynamicData,
842
885
  };
843
886
  }
844
887
  function simplifyList(typeDef, context) {
845
888
  if (!context.typeMapping) {
846
- return typeDef;
889
+ return { typeDef };
847
890
  }
848
891
  return {
849
- ...typeDef,
850
- item: { kind: 'mapped', child: typeDef.item, mapping: context.typeMapping },
892
+ typeDef: {
893
+ ...typeDef,
894
+ item: { kind: 'mapped', child: typeDef.item, mapping: context.typeMapping },
895
+ },
896
+ // Don't cache mapped field data
897
+ // TODO find a better way to handle mapped types with caching
898
+ dynamicData: true,
851
899
  };
852
900
  }
853
901
  function simplifyTuple(typeDef, context) {
854
902
  if (!context.typeMapping) {
855
- return typeDef;
903
+ return { typeDef };
856
904
  }
857
905
  return {
858
- ...typeDef,
859
- items: typeDef.items.map(item => ({
860
- kind: 'mapped',
861
- child: item,
862
- mapping: context.typeMapping,
863
- })),
906
+ typeDef: {
907
+ ...typeDef,
908
+ items: typeDef.items.map(item => ({
909
+ kind: 'mapped',
910
+ child: item,
911
+ mapping: context.typeMapping,
912
+ })),
913
+ },
914
+ // Don't cache mapped field data
915
+ // TODO find a better way to handle mapped types with caching
916
+ dynamicData: true,
864
917
  };
865
918
  }
866
919
  function simplifyEnum(typeDef, context) {
@@ -876,11 +929,19 @@ function simplifyEnum(typeDef, context) {
876
929
  });
877
930
  return keep;
878
931
  });
879
- return { ...typeDef, enumKind: typeDef.enumKind ?? 'int', values: filteredValues };
932
+ return { typeDef: { ...typeDef, enumKind: typeDef.enumKind ?? 'int', values: filteredValues } };
880
933
  }
881
934
  function simplifyConcrete(typeDef, context) {
882
- const simplifiedArgs = typeDef.typeArgs.map(arg => simplify(arg, context));
883
- return simplify(typeDef.child, { ...context, typeArgs: simplifiedArgs });
935
+ let dynamicData = false;
936
+ const simplifiedArgs = typeDef.typeArgs.map(arg => {
937
+ const ans = simplify(arg, context);
938
+ if (ans.dynamicData) {
939
+ dynamicData = true;
940
+ }
941
+ return ans.typeDef;
942
+ });
943
+ const result = simplify(typeDef.child, { ...context, typeArgs: simplifiedArgs });
944
+ return { typeDef: result.typeDef, dynamicData: dynamicData || result.dynamicData };
884
945
  }
885
946
  function simplifyTemplate(typeDef, context) {
886
947
  if (context.typeArgs?.length !== typeDef.typeParams.length) {
@@ -893,13 +954,19 @@ function simplifyTemplate(typeDef, context) {
893
954
  return simplify(typeDef.child, { ...context, typeArgs: [], typeMapping: mapping });
894
955
  }
895
956
  function simplifyMapped(typeDef, context) {
957
+ let dynamicData = false;
896
958
  // Mapped types that were created in simplify are always simplified
897
959
  // types already, in which case this will be a cheap operation, but
898
960
  // this is necessary for type safety
899
961
  const simplifiedMapping = Object.fromEntries(Object.entries(typeDef.mapping).map(([path, param]) => {
900
- return [path, simplify(param, context)];
962
+ const ans = simplify(param, context);
963
+ if (ans.dynamicData) {
964
+ dynamicData = true;
965
+ }
966
+ return [path, ans.typeDef];
901
967
  }));
902
- return simplify(typeDef.child, { ...context, typeMapping: simplifiedMapping });
968
+ const ans = simplify(typeDef.child, { ...context, typeMapping: simplifiedMapping });
969
+ return { typeDef: ans.typeDef, dynamicData: dynamicData || ans.dynamicData };
903
970
  }
904
971
  function getValueType(type) {
905
972
  switch (type.kind) {
@@ -12,9 +12,11 @@ export declare function getFields(typeDef: core.DeepReadonly<SimplifiedMcdocType
12
12
  export type SimpleCompletionValue = {
13
13
  value: string;
14
14
  detail?: string;
15
+ labelSuffix?: string;
15
16
  kind?: McdocType['kind'];
16
17
  completionKind?: core.CompletionKind;
17
18
  insertText?: string;
19
+ sortText?: string;
18
20
  };
19
21
  export declare function getValues(typeDef: core.DeepReadonly<McdocType>, ctx: McdocCompleterContext): SimpleCompletionValue[];
20
22
  //# sourceMappingURL=index.d.ts.map
@@ -95,6 +95,13 @@ export function getValues(typeDef, ctx) {
95
95
  detail: v.identifier,
96
96
  kind: typeDef.enumKind ?? 'string',
97
97
  }));
98
+ case 'byte':
99
+ case 'short':
100
+ case 'int':
101
+ case 'long':
102
+ case 'float':
103
+ case 'double':
104
+ return getNumericCompletions(typeDef, ctx);
98
105
  default:
99
106
  return [];
100
107
  }
@@ -110,9 +117,11 @@ function getStringCompletions(typeDef, ctx) {
110
117
  ans.push(...items.map(item => ({
111
118
  value: item.label,
112
119
  kind: 'string',
120
+ labelSuffix: item.labelSuffix,
113
121
  detail: item.detail,
114
122
  completionKind: item.kind,
115
123
  insertText: item.insertText,
124
+ sortText: item.sortText,
116
125
  })));
117
126
  });
118
127
  if (ans.length === 0 && typeDef.kind === 'literal') {
@@ -120,4 +129,23 @@ function getStringCompletions(typeDef, ctx) {
120
129
  }
121
130
  return ans;
122
131
  }
132
+ function getNumericCompletions(typeDef, ctx) {
133
+ const ans = [];
134
+ handleAttributes(typeDef.attributes, ctx, (handler, config) => {
135
+ const items = handler.numericCompleter?.(config, ctx);
136
+ if (!items) {
137
+ return;
138
+ }
139
+ ans.push(...items.map(item => ({
140
+ value: item.label,
141
+ kind: typeDef.kind,
142
+ labelSuffix: item.labelSuffix,
143
+ detail: item.detail,
144
+ completionKind: item.kind,
145
+ insertText: item.insertText,
146
+ sortText: item.sortText,
147
+ })));
148
+ });
149
+ return ans;
150
+ }
123
151
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spyglassmc/mcdoc",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -25,7 +25,7 @@
25
25
  "url": "https://github.com/SpyglassMC/Spyglass/issues"
26
26
  },
27
27
  "dependencies": {
28
- "@spyglassmc/core": "0.4.7",
29
- "@spyglassmc/locales": "0.3.7"
28
+ "@spyglassmc/core": "0.4.9",
29
+ "@spyglassmc/locales": "0.3.8"
30
30
  }
31
31
  }