@spyglassmc/mcdoc 0.3.7 → 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.
- package/lib/binder/index.js +122 -191
- package/lib/index.d.ts +1 -0
- package/lib/index.js +4 -4
- package/lib/node/index.d.ts +9 -4
- package/lib/node/index.js +77 -131
- package/lib/parser/index.d.ts +13 -1
- package/lib/parser/index.js +112 -190
- package/lib/runtime/attribute/builtin.d.ts +3 -0
- package/lib/runtime/attribute/builtin.js +130 -0
- package/lib/runtime/attribute/index.d.ts +22 -0
- package/lib/runtime/attribute/index.js +22 -0
- package/lib/runtime/attribute/validator.d.ts +16 -0
- package/lib/runtime/attribute/validator.js +85 -0
- package/lib/runtime/checker/context.d.ts +34 -0
- package/lib/runtime/checker/context.js +17 -0
- package/lib/runtime/checker/error.d.ts +70 -0
- package/lib/runtime/checker/error.js +352 -0
- package/lib/runtime/checker/index.d.ts +80 -0
- package/lib/runtime/checker/index.js +914 -0
- package/lib/runtime/completer/index.d.ts +20 -0
- package/lib/runtime/completer/index.js +123 -0
- package/lib/runtime/index.d.ts +5 -0
- package/lib/runtime/index.js +5 -0
- package/lib/type/index.d.ts +73 -92
- package/lib/type/index.js +341 -422
- package/lib/uri_processors.js +2 -8
- package/package.json +3 -3
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as core from '@spyglassmc/core';
|
|
2
|
+
import type { Attribute, StructTypePairField } from '../../type/index.js';
|
|
3
|
+
import type { McdocCheckerContext, SimplifiedMcdocType, SimplifiedMcdocTypeNoUnion } from '../checker/index.js';
|
|
4
|
+
import type { McdocCompleterContext } from '../completer/index.js';
|
|
5
|
+
import type { McdocAttributeValidator } from './validator.js';
|
|
6
|
+
export * as validator from './validator.js';
|
|
7
|
+
export interface McdocAttribute<C = unknown> {
|
|
8
|
+
checkInferred?: <T>(config: C, inferred: SimplifiedMcdocTypeNoUnion, ctx: McdocCheckerContext<T>) => boolean;
|
|
9
|
+
mapType?: <T>(config: C, typeDef: SimplifiedMcdocType, ctx: McdocCheckerContext<T>) => SimplifiedMcdocType;
|
|
10
|
+
mapField?: <T>(config: C, field: StructTypePairField, ctx: McdocCheckerContext<T>) => StructTypePairField;
|
|
11
|
+
filterElement?: (config: C, ctx: core.ContextBase) => boolean;
|
|
12
|
+
stringParser?: <T>(config: C, typeDef: SimplifiedMcdocTypeNoUnion, ctx: McdocCheckerContext<T>) => core.InfallibleParser<core.AstNode | undefined> | undefined;
|
|
13
|
+
stringMocker?: (config: C, typeDef: core.DeepReadonly<SimplifiedMcdocTypeNoUnion>, ctx: McdocCompleterContext) => core.AstNode | undefined;
|
|
14
|
+
}
|
|
15
|
+
export declare function registerAttribute<C extends core.Returnable>(meta: core.MetaRegistry, name: string, validator: McdocAttributeValidator<C>, attribute: McdocAttribute<C>): void;
|
|
16
|
+
interface AttributeInfo {
|
|
17
|
+
validator: McdocAttributeValidator<core.Returnable>;
|
|
18
|
+
attribute: McdocAttribute;
|
|
19
|
+
}
|
|
20
|
+
export declare function getAttribute(meta: core.MetaRegistry, name: string): AttributeInfo | undefined;
|
|
21
|
+
export declare function handleAttributes(attributes: core.DeepReadonly<Attribute[]> | undefined, ctx: core.ContextBase, fn: <C>(handler: McdocAttribute<C>, config: C) => void): void;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as core from '@spyglassmc/core';
|
|
2
|
+
export * as validator from './validator.js';
|
|
3
|
+
export function registerAttribute(meta, name, validator, attribute) {
|
|
4
|
+
meta.registerCustom('mcdoc:attribute', name, { validator, attribute });
|
|
5
|
+
}
|
|
6
|
+
export function getAttribute(meta, name) {
|
|
7
|
+
return meta.getCustom('mcdoc:attribute')?.get(name);
|
|
8
|
+
}
|
|
9
|
+
export function handleAttributes(attributes, ctx, fn) {
|
|
10
|
+
for (const { name, value } of attributes ?? []) {
|
|
11
|
+
const handler = getAttribute(ctx.meta, name);
|
|
12
|
+
if (!handler) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
const config = handler.validator(value, ctx);
|
|
16
|
+
if (config === core.Failure) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
fn(handler.attribute, config);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as core from '@spyglassmc/core';
|
|
2
|
+
import type { AttributeValue } from '../../type/index.js';
|
|
3
|
+
export type McdocAttributeValidator<C extends core.Returnable> = (value: core.DeepReadonly<AttributeValue> | undefined, ctx: core.ContextBase) => core.Result<C>;
|
|
4
|
+
export declare const string: McdocAttributeValidator<string>;
|
|
5
|
+
export declare const number: McdocAttributeValidator<number>;
|
|
6
|
+
export declare const boolean: McdocAttributeValidator<boolean>;
|
|
7
|
+
export declare function options<C extends string>(...options: C[]): McdocAttributeValidator<C>;
|
|
8
|
+
export declare function tree<C extends {
|
|
9
|
+
[K in keyof C]: core.Returnable;
|
|
10
|
+
}>(properties: {
|
|
11
|
+
[K in keyof C]: McdocAttributeValidator<C[K]>;
|
|
12
|
+
}): McdocAttributeValidator<C>;
|
|
13
|
+
export declare function optional<C extends core.Returnable>(validator: McdocAttributeValidator<C>): McdocAttributeValidator<C | undefined>;
|
|
14
|
+
export declare function map<C extends core.Returnable, D extends core.Returnable>(validator: McdocAttributeValidator<C>, mapper: (value: C) => D | typeof core.Failure): McdocAttributeValidator<D>;
|
|
15
|
+
export declare function alternatives<C extends core.Returnable>(...validators: McdocAttributeValidator<C>[]): McdocAttributeValidator<C>;
|
|
16
|
+
//# sourceMappingURL=validator.d.ts.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as core from '@spyglassmc/core';
|
|
2
|
+
export const string = (value) => {
|
|
3
|
+
if (value === undefined) {
|
|
4
|
+
return core.Failure;
|
|
5
|
+
}
|
|
6
|
+
if (value.kind === 'literal' && value.value.kind === 'string') {
|
|
7
|
+
return value.value.value;
|
|
8
|
+
}
|
|
9
|
+
if (value.kind === 'reference' && value.path) {
|
|
10
|
+
return value.path.replace(/.*::/, '');
|
|
11
|
+
}
|
|
12
|
+
return core.Failure;
|
|
13
|
+
};
|
|
14
|
+
export const number = (value) => {
|
|
15
|
+
if (value === undefined) {
|
|
16
|
+
return core.Failure;
|
|
17
|
+
}
|
|
18
|
+
if (value.kind === 'literal' && typeof value.value.value === 'number') {
|
|
19
|
+
return value.value.value;
|
|
20
|
+
}
|
|
21
|
+
return core.Failure;
|
|
22
|
+
};
|
|
23
|
+
export const boolean = (value) => {
|
|
24
|
+
if (value === undefined) {
|
|
25
|
+
return core.Failure;
|
|
26
|
+
}
|
|
27
|
+
if (value.kind === 'literal' && value.value.kind === 'boolean') {
|
|
28
|
+
return value.value.value;
|
|
29
|
+
}
|
|
30
|
+
return core.Failure;
|
|
31
|
+
};
|
|
32
|
+
export function options(...options) {
|
|
33
|
+
return (value, ctx) => {
|
|
34
|
+
const stringValue = string(value, ctx);
|
|
35
|
+
if (stringValue === core.Failure) {
|
|
36
|
+
return core.Failure;
|
|
37
|
+
}
|
|
38
|
+
if (options.includes(stringValue)) {
|
|
39
|
+
return stringValue;
|
|
40
|
+
}
|
|
41
|
+
return core.Failure;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function tree(properties) {
|
|
45
|
+
return (value, ctx) => {
|
|
46
|
+
if (value?.kind !== 'tree') {
|
|
47
|
+
return core.Failure;
|
|
48
|
+
}
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const key in properties) {
|
|
51
|
+
const validator = properties[key];
|
|
52
|
+
const propValue = value.values[key];
|
|
53
|
+
const property = validator(propValue, ctx);
|
|
54
|
+
if (property === core.Failure) {
|
|
55
|
+
return core.Failure;
|
|
56
|
+
}
|
|
57
|
+
result[key] = property;
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function optional(validator) {
|
|
63
|
+
return (value, ctx) => {
|
|
64
|
+
const config = validator(value, ctx);
|
|
65
|
+
return config === core.Failure ? undefined : config;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function map(validator, mapper) {
|
|
69
|
+
return (value, ctx) => {
|
|
70
|
+
const config = validator(value, ctx);
|
|
71
|
+
return config === core.Failure ? core.Failure : mapper(config);
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function alternatives(...validators) {
|
|
75
|
+
return (value, ctx) => {
|
|
76
|
+
for (const validator of validators) {
|
|
77
|
+
const result = validator(value, ctx);
|
|
78
|
+
if (result !== core.Failure) {
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return core.Failure;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type * as core from '@spyglassmc/core';
|
|
2
|
+
import type { Attribute, EnumType, LiteralType, McdocType, UnionType } from '../../type/index.js';
|
|
3
|
+
import type { McdocRuntimeError } from './error.js';
|
|
4
|
+
import type { SimplifiedMcdocType, SimplifiedMcdocTypeNoUnion } from './index.js';
|
|
5
|
+
export type RuntimeUnion<T> = RuntimeNode<T>[] | RuntimePair<T>;
|
|
6
|
+
export interface RuntimeNode<T> {
|
|
7
|
+
originalNode: T;
|
|
8
|
+
inferredType: Exclude<McdocType, UnionType>;
|
|
9
|
+
}
|
|
10
|
+
export interface RuntimePair<T> {
|
|
11
|
+
attributes?: Attribute[];
|
|
12
|
+
key: RuntimeNode<T>;
|
|
13
|
+
possibleValues: RuntimeNode<T>[];
|
|
14
|
+
}
|
|
15
|
+
export type NodeEquivalenceChecker = (inferredNode: Exclude<SimplifiedMcdocTypeNoUnion, LiteralType | EnumType>, definition: Exclude<SimplifiedMcdocTypeNoUnion, LiteralType | EnumType>) => boolean;
|
|
16
|
+
export type TypeInfoAttacher<T> = (node: T, definition: SimplifiedMcdocType, description?: string) => void;
|
|
17
|
+
export type StringAttacher<T> = (node: T, attacher: (node: core.StringBaseNode) => void) => void;
|
|
18
|
+
export type ChildrenGetter<T> = (node: T, simplified: SimplifiedMcdocTypeNoUnion) => RuntimeUnion<T>[];
|
|
19
|
+
export type ErrorReporter<T> = (error: McdocRuntimeError<T>) => void;
|
|
20
|
+
export interface McdocCheckerContext<T> extends core.CheckerContext {
|
|
21
|
+
allowMissingKeys: boolean;
|
|
22
|
+
requireCanonical: boolean;
|
|
23
|
+
isEquivalent: NodeEquivalenceChecker;
|
|
24
|
+
getChildren: ChildrenGetter<T>;
|
|
25
|
+
reportError: ErrorReporter<T>;
|
|
26
|
+
attachTypeInfo?: TypeInfoAttacher<T>;
|
|
27
|
+
stringAttacher?: StringAttacher<T>;
|
|
28
|
+
}
|
|
29
|
+
type McdocCheckerContextOptions<T> = Partial<McdocCheckerContext<T>>;
|
|
30
|
+
export declare namespace McdocCheckerContext {
|
|
31
|
+
function create<T>(ctx: core.CheckerContext, options: McdocCheckerContextOptions<T>): McdocCheckerContext<T>;
|
|
32
|
+
}
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export var McdocCheckerContext;
|
|
2
|
+
(function (McdocCheckerContext) {
|
|
3
|
+
function create(ctx, options) {
|
|
4
|
+
return {
|
|
5
|
+
...ctx,
|
|
6
|
+
allowMissingKeys: options.allowMissingKeys ?? false,
|
|
7
|
+
requireCanonical: options.requireCanonical ?? false,
|
|
8
|
+
isEquivalent: options.isEquivalent ?? (() => false),
|
|
9
|
+
getChildren: options.getChildren ?? (() => []),
|
|
10
|
+
reportError: options.reportError ?? (() => { }),
|
|
11
|
+
attachTypeInfo: options.attachTypeInfo,
|
|
12
|
+
stringAttacher: options.stringAttacher,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
McdocCheckerContext.create = create;
|
|
16
|
+
})(McdocCheckerContext || (McdocCheckerContext = {}));
|
|
17
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { AstNode, CheckerContext, Range } from '@spyglassmc/core';
|
|
2
|
+
import { NumericRange } from '../../type/index.js';
|
|
3
|
+
import type { CheckerTreeDefinitionGroupNode, CheckerTreeDefinitionNode, ErrorReporter, RuntimeNode, SimplifiedMcdocTypeNoUnion } from './index.js';
|
|
4
|
+
export type McdocRuntimeError<T> = SimpleError<T> | UnknownKeyError<T> | RangeError<T> | TypeMismatchError<T> | MissingKeyError<T>;
|
|
5
|
+
export interface McdocRuntimeBaseError<T> {
|
|
6
|
+
node: RuntimeNode<T>;
|
|
7
|
+
/**
|
|
8
|
+
* This is set when this error may not need to be fixed if another error of the same kind is
|
|
9
|
+
* fixed instead. This contains a list of nodes with conflicting
|
|
10
|
+
*/
|
|
11
|
+
nodesWithConflictingErrors?: RuntimeNode<T>[];
|
|
12
|
+
}
|
|
13
|
+
export interface SimpleError<T> extends McdocRuntimeBaseError<T> {
|
|
14
|
+
kind: 'duplicate_key' | 'unknown_key' | 'expected_key_value_pair' | 'unknown_tuple_element' | 'internal';
|
|
15
|
+
}
|
|
16
|
+
export declare namespace SimpleError {
|
|
17
|
+
function is<T>(error: McdocRuntimeError<T> | undefined): error is SimpleError<T>;
|
|
18
|
+
}
|
|
19
|
+
export interface UnknownKeyError<T> extends McdocRuntimeBaseError<T> {
|
|
20
|
+
kind: 'unknown_key';
|
|
21
|
+
}
|
|
22
|
+
export declare namespace UnknownKeyError {
|
|
23
|
+
function is<T>(error: McdocRuntimeError<T> | undefined): error is UnknownKeyError<T>;
|
|
24
|
+
}
|
|
25
|
+
export interface RangeError<T> extends McdocRuntimeBaseError<T> {
|
|
26
|
+
kind: 'invalid_collection_length' | 'invalid_string_length' | 'number_out_of_range';
|
|
27
|
+
/**
|
|
28
|
+
* A list of multiple mean the number (or length) has to be within one of these ranges. This is
|
|
29
|
+
* a result of merging errors from two conflicting definitions for the same value.
|
|
30
|
+
*/
|
|
31
|
+
ranges: NumericRange[];
|
|
32
|
+
}
|
|
33
|
+
export declare namespace RangeError {
|
|
34
|
+
function is<T>(error: McdocRuntimeError<T> | undefined): error is RangeError<T>;
|
|
35
|
+
}
|
|
36
|
+
export interface MissingKeyError<T> extends McdocRuntimeBaseError<T> {
|
|
37
|
+
kind: 'missing_key';
|
|
38
|
+
/**
|
|
39
|
+
* A list of multiple are an alternative, and at least on e needs to be fixed, not neccessarily
|
|
40
|
+
* all of them.
|
|
41
|
+
*
|
|
42
|
+
* In case there are multiple non-conflicting missing keys, multiple errors will be reported on
|
|
43
|
+
* the same node instead.
|
|
44
|
+
*
|
|
45
|
+
* If this has a length > 1, there is at least one error that needs to be fixed, and at least one
|
|
46
|
+
* error that does not neccassarily need to be fixed.
|
|
47
|
+
*/
|
|
48
|
+
keys: string[];
|
|
49
|
+
}
|
|
50
|
+
export declare namespace MissingKeyError {
|
|
51
|
+
function is<T>(error: McdocRuntimeError<T> | undefined): error is MissingKeyError<T>;
|
|
52
|
+
}
|
|
53
|
+
export interface TypeMismatchError<T> extends McdocRuntimeBaseError<T> {
|
|
54
|
+
kind: 'type_mismatch';
|
|
55
|
+
/**
|
|
56
|
+
* These are all valid definitions. The node needs to only fullfill one of them.
|
|
57
|
+
*/
|
|
58
|
+
expected: SimplifiedMcdocTypeNoUnion[];
|
|
59
|
+
}
|
|
60
|
+
export declare namespace TypeMismatchError {
|
|
61
|
+
function is<T>(error: McdocRuntimeError<T> | undefined): error is TypeMismatchError<T>;
|
|
62
|
+
}
|
|
63
|
+
export interface ErrorCondensingDefinition<T> {
|
|
64
|
+
definition: CheckerTreeDefinitionNode<T>;
|
|
65
|
+
errors: McdocRuntimeError<T>[];
|
|
66
|
+
}
|
|
67
|
+
export declare function condenseAndPropagate<T>(definitionGroup: CheckerTreeDefinitionGroupNode<T>, definitionErrors: ErrorCondensingDefinition<T>[]): void;
|
|
68
|
+
export declare function getDefaultErrorRange<T extends AstNode>(node: RuntimeNode<T>, error: McdocRuntimeError<T>['kind']): Range;
|
|
69
|
+
export declare function getDefaultErrorReporter<T>(ctx: CheckerContext, getErrorRange: (node: RuntimeNode<T>, error: McdocRuntimeError<T>['kind']) => Range): ErrorReporter<T>;
|
|
70
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { ResourceLocation } from '@spyglassmc/core';
|
|
2
|
+
import { arrayToMessage, localeQuote, localize } from '@spyglassmc/locales';
|
|
3
|
+
import { RangeKind } from '../../node/index.js';
|
|
4
|
+
import { McdocType, NumericRange } from '../../type/index.js';
|
|
5
|
+
export var SimpleError;
|
|
6
|
+
(function (SimpleError) {
|
|
7
|
+
function is(error) {
|
|
8
|
+
return error?.kind === 'duplicate_key'
|
|
9
|
+
|| error?.kind === 'unknown_key'
|
|
10
|
+
|| error?.kind === 'expected_key_value_pair'
|
|
11
|
+
|| error?.kind === 'unknown_tuple_element'
|
|
12
|
+
|| error?.kind === 'internal';
|
|
13
|
+
}
|
|
14
|
+
SimpleError.is = is;
|
|
15
|
+
})(SimpleError || (SimpleError = {}));
|
|
16
|
+
export var UnknownKeyError;
|
|
17
|
+
(function (UnknownKeyError) {
|
|
18
|
+
function is(error) {
|
|
19
|
+
return error?.kind === 'unknown_key';
|
|
20
|
+
}
|
|
21
|
+
UnknownKeyError.is = is;
|
|
22
|
+
})(UnknownKeyError || (UnknownKeyError = {}));
|
|
23
|
+
export var RangeError;
|
|
24
|
+
(function (RangeError) {
|
|
25
|
+
function is(error) {
|
|
26
|
+
return error?.kind === 'invalid_collection_length'
|
|
27
|
+
|| error?.kind === 'invalid_string_length'
|
|
28
|
+
|| error?.kind === 'number_out_of_range';
|
|
29
|
+
}
|
|
30
|
+
RangeError.is = is;
|
|
31
|
+
})(RangeError || (RangeError = {}));
|
|
32
|
+
export var MissingKeyError;
|
|
33
|
+
(function (MissingKeyError) {
|
|
34
|
+
function is(error) {
|
|
35
|
+
return error?.kind === 'missing_key';
|
|
36
|
+
}
|
|
37
|
+
MissingKeyError.is = is;
|
|
38
|
+
})(MissingKeyError || (MissingKeyError = {}));
|
|
39
|
+
export var TypeMismatchError;
|
|
40
|
+
(function (TypeMismatchError) {
|
|
41
|
+
function is(error) {
|
|
42
|
+
return error?.kind === 'type_mismatch';
|
|
43
|
+
}
|
|
44
|
+
TypeMismatchError.is = is;
|
|
45
|
+
})(TypeMismatchError || (TypeMismatchError = {}));
|
|
46
|
+
export function condenseAndPropagate(definitionGroup, definitionErrors) {
|
|
47
|
+
const queue = [{ node: definitionGroup, errorsOnLayer: definitionErrors, depth: 0 }];
|
|
48
|
+
while (queue.length) {
|
|
49
|
+
const { node, errorsOnLayer, depth } = queue.shift();
|
|
50
|
+
const stillValidDefinitions = [];
|
|
51
|
+
const { definitions, condensedErrors } = condenseErrorsAndFilterSiblings(errorsOnLayer);
|
|
52
|
+
stillValidDefinitions.push(...definitions);
|
|
53
|
+
node.condensedErrors.push(condensedErrors);
|
|
54
|
+
if (node.validDefinitions.length !== stillValidDefinitions.length) {
|
|
55
|
+
filterChildDefinitions(node.validDefinitions.filter(d => !stillValidDefinitions.includes(d)), node.runtimeNode.children);
|
|
56
|
+
node.validDefinitions = stillValidDefinitions;
|
|
57
|
+
}
|
|
58
|
+
const parents = node.parents
|
|
59
|
+
.filter(parent => {
|
|
60
|
+
const lastDefWithNode = parent.groupNode.validDefinitions
|
|
61
|
+
.findLast(d => d.children.includes(node));
|
|
62
|
+
if (lastDefWithNode !== parent) {
|
|
63
|
+
// Wait for last definition that leads to this parent
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
const lastChild = parent.groupNode.validDefinitions
|
|
67
|
+
.flatMap(d => d.children)
|
|
68
|
+
.findLast(v => {
|
|
69
|
+
if (v.condensedErrors.length > depth) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
let children = [v];
|
|
73
|
+
for (let i = 0; i < depth; i++) {
|
|
74
|
+
children = children.flatMap(v => v.validDefinitions).flatMap(v => v.children);
|
|
75
|
+
}
|
|
76
|
+
return children.length > 0;
|
|
77
|
+
});
|
|
78
|
+
if (lastChild !== node) {
|
|
79
|
+
// Wait for all siblings to be evaluated first
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
})
|
|
84
|
+
.map(parent => ({
|
|
85
|
+
node: parent.groupNode,
|
|
86
|
+
depth: depth + 1,
|
|
87
|
+
errorsOnLayer: parent.groupNode.validDefinitions
|
|
88
|
+
.flatMap(d => ({
|
|
89
|
+
definition: d,
|
|
90
|
+
errors: d.children
|
|
91
|
+
.flatMap(c => c.condensedErrors.length > depth
|
|
92
|
+
? c.condensedErrors[depth]
|
|
93
|
+
: []),
|
|
94
|
+
})),
|
|
95
|
+
}));
|
|
96
|
+
queue.push(...parents);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function filterChildDefinitions(removedDefs, children) {
|
|
100
|
+
for (const child of children) {
|
|
101
|
+
for (const childValue of child.possibleValues) {
|
|
102
|
+
const removedChildDefs = [];
|
|
103
|
+
for (let i = 0; i < childValue.definitionsByParent.length; i++) {
|
|
104
|
+
const definitionGroup = childValue.definitionsByParent[i];
|
|
105
|
+
definitionGroup.parents = definitionGroup.parents.filter(p => !removedDefs.includes(p));
|
|
106
|
+
if (definitionGroup.parents.length === 0) {
|
|
107
|
+
removedChildDefs.push(...definitionGroup.validDefinitions);
|
|
108
|
+
childValue.definitionsByParent.splice(i, 1);
|
|
109
|
+
i--;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (removedChildDefs.length > 0) {
|
|
113
|
+
filterChildDefinitions(removedChildDefs, childValue.children);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function condenseErrorsAndFilterSiblings(definitions) {
|
|
119
|
+
if (definitions.length === 0) {
|
|
120
|
+
return { definitions: [], condensedErrors: [] };
|
|
121
|
+
}
|
|
122
|
+
let validDefinitions = definitions;
|
|
123
|
+
const errors = [];
|
|
124
|
+
const typeMismatchResult = condense(validDefinitions, TypeMismatchError.is, (a, b) => a.expected.length === b.expected.length
|
|
125
|
+
&& !a.expected.some(d => !b.expected.some(od => McdocType.equals(d, od))), errors => ({
|
|
126
|
+
kind: 'type_mismatch',
|
|
127
|
+
node: errors[0].node,
|
|
128
|
+
expected: deduplicateGroups(errors.map(e => e.expected), McdocType.equals),
|
|
129
|
+
}));
|
|
130
|
+
validDefinitions = typeMismatchResult.filteredDefinitions;
|
|
131
|
+
errors.push(...typeMismatchResult.condensedErrors);
|
|
132
|
+
for (const kind of [
|
|
133
|
+
'unknown_key',
|
|
134
|
+
'expected_key_value_pair',
|
|
135
|
+
'unknown_tuple_element',
|
|
136
|
+
]) {
|
|
137
|
+
const simpleErrorResult = condense(validDefinitions, (e) => e.kind === kind, _ => true);
|
|
138
|
+
validDefinitions = simpleErrorResult.filteredDefinitions;
|
|
139
|
+
errors.push(...simpleErrorResult.condensedErrors);
|
|
140
|
+
}
|
|
141
|
+
const missingKeyResult = condense(validDefinitions, MissingKeyError.is, (a, b) => !a.keys.some(k => !b.keys.includes(k)), errors => ({
|
|
142
|
+
kind: 'missing_key',
|
|
143
|
+
node: errors[0].node,
|
|
144
|
+
keys: deduplicateGroups(errors.map(e => e.keys)),
|
|
145
|
+
}));
|
|
146
|
+
validDefinitions = missingKeyResult.filteredDefinitions;
|
|
147
|
+
errors.push(...missingKeyResult.condensedErrors);
|
|
148
|
+
for (const kind of [
|
|
149
|
+
'invalid_collection_length',
|
|
150
|
+
'invalid_string_length',
|
|
151
|
+
'number_out_of_range',
|
|
152
|
+
]) {
|
|
153
|
+
const rangeErrorResult = condense(validDefinitions, (e) => e.kind === kind, (a, b) => a.ranges.length === b.ranges.length
|
|
154
|
+
&& !a.ranges.some(r => !b.ranges.some(or => NumericRange.equals(r, or))),
|
|
155
|
+
// TODO merge overlapping ranges better?
|
|
156
|
+
errors => ({
|
|
157
|
+
kind,
|
|
158
|
+
node: errors[0].node,
|
|
159
|
+
ranges: deduplicateGroups(errors.map(e => e.ranges), NumericRange.equals),
|
|
160
|
+
}));
|
|
161
|
+
validDefinitions = rangeErrorResult.filteredDefinitions;
|
|
162
|
+
errors.push(...rangeErrorResult.condensedErrors);
|
|
163
|
+
}
|
|
164
|
+
// No condensing needed for duplicate key. If a key is a duplicate in one definition, it really
|
|
165
|
+
// should have been reported by all of them
|
|
166
|
+
validDefinitions[0].errors.filter(e => e.kind === 'duplicate_key');
|
|
167
|
+
const internalErrorResult = condense(validDefinitions, (e) => e.kind === 'internal', _ => false);
|
|
168
|
+
validDefinitions = internalErrorResult.filteredDefinitions;
|
|
169
|
+
errors.push(...internalErrorResult.condensedErrors);
|
|
170
|
+
return {
|
|
171
|
+
definitions: validDefinitions.map(d => d.definition),
|
|
172
|
+
condensedErrors: errors,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function condense(validDefinitions, is, equals, combineAlternatives) {
|
|
176
|
+
// TODO a lot of O(n^2) in this function, may need optimization
|
|
177
|
+
const errorsOfType = validDefinitions
|
|
178
|
+
.map(def => ({ def, errors: def.errors.filter(is) }));
|
|
179
|
+
const definitionsWithoutError = errorsOfType
|
|
180
|
+
.filter(d => d.errors.length === 0)
|
|
181
|
+
.map(e => e.def);
|
|
182
|
+
if (definitionsWithoutError.length > 0) {
|
|
183
|
+
return { condensedErrors: [], filteredDefinitions: definitionsWithoutError };
|
|
184
|
+
}
|
|
185
|
+
const distinctErrorsPerNode = errorsOfType
|
|
186
|
+
.flatMap(d => d.errors.map(e => ({ definition: d.def, error: e })))
|
|
187
|
+
.reduce((entries, e) => {
|
|
188
|
+
const entry = entries.find(oe => oe.errors[0].error.node === e.error.node);
|
|
189
|
+
if (entry) {
|
|
190
|
+
const error = entry.errors.find(oe => equals(e.error, oe.error));
|
|
191
|
+
if (error) {
|
|
192
|
+
error.definitions.push(e.definition);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
entry.errors.push({ error: e.error, definitions: [e.definition] });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
entries.push({ errors: [{ error: e.error, definitions: [e.definition] }] });
|
|
200
|
+
}
|
|
201
|
+
return entries;
|
|
202
|
+
}, []);
|
|
203
|
+
const distinctErrors = distinctErrorsPerNode.flatMap(e => e.errors);
|
|
204
|
+
const commonErrors = distinctErrors
|
|
205
|
+
.filter(e => e.definitions.length == validDefinitions.length)
|
|
206
|
+
.map(e => e.error);
|
|
207
|
+
const definitionsWithUncommonErrors = distinctErrors
|
|
208
|
+
.filter(e => e.definitions.length < validDefinitions.length)
|
|
209
|
+
.flatMap(e => e.definitions);
|
|
210
|
+
const definitionsWithOnlyCommonErrors = validDefinitions
|
|
211
|
+
.filter(d => !definitionsWithUncommonErrors.includes(d));
|
|
212
|
+
if (definitionsWithOnlyCommonErrors.length > 0) {
|
|
213
|
+
return {
|
|
214
|
+
filteredDefinitions: definitionsWithOnlyCommonErrors,
|
|
215
|
+
condensedErrors: commonErrors,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const combinedErrors = combineAlternatives
|
|
219
|
+
? distinctErrorsPerNode
|
|
220
|
+
.map(e => {
|
|
221
|
+
const uniqueDefinitions = deduplicateGroups(e.errors.map(e => e.definitions), (a, b) => McdocType.equals(a.definition.typeDef, b.definition.typeDef));
|
|
222
|
+
const ans = {
|
|
223
|
+
definitions: uniqueDefinitions,
|
|
224
|
+
error: combineAlternatives(e.errors
|
|
225
|
+
.filter(e => !commonErrors.includes(e.error))
|
|
226
|
+
.map(e => e.error)),
|
|
227
|
+
};
|
|
228
|
+
ans.error.nodesWithConflictingErrors = deduplicateGroups(e.errors.map(e => e.error.nodesWithConflictingErrors ?? []));
|
|
229
|
+
if (ans.error.nodesWithConflictingErrors.length === 0) {
|
|
230
|
+
ans.error.nodesWithConflictingErrors = undefined;
|
|
231
|
+
}
|
|
232
|
+
return ans;
|
|
233
|
+
})
|
|
234
|
+
: distinctErrorsPerNode.flatMap(e => e.errors
|
|
235
|
+
.map(ee => ({ definitions: ee.definitions, error: ee.error })));
|
|
236
|
+
const conflictingErrors = combinedErrors
|
|
237
|
+
.filter(e => e.definitions.length < validDefinitions.length);
|
|
238
|
+
const nodesWithConflictingErrors = conflictingErrors
|
|
239
|
+
.map(e => e.error.node)
|
|
240
|
+
.filter((n, i, arr) => arr.indexOf(n) === i);
|
|
241
|
+
for (const error of conflictingErrors) {
|
|
242
|
+
error.error.nodesWithConflictingErrors = nodesWithConflictingErrors;
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
filteredDefinitions: validDefinitions,
|
|
246
|
+
condensedErrors: [...commonErrors, ...combinedErrors.map(e => e.error)],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Deduplicates groups assuming:
|
|
251
|
+
* - There are no duplicates within a group
|
|
252
|
+
* - A group with 1 element implies there is no duplicated group of length 1 with the same element
|
|
253
|
+
*
|
|
254
|
+
* When calling {@link condense}, if a group stems from an error, this should automatically be the
|
|
255
|
+
* case.
|
|
256
|
+
*/
|
|
257
|
+
function deduplicateGroups(definitionGroups, predicate) {
|
|
258
|
+
const definitions = [];
|
|
259
|
+
for (let i = 0; i < definitionGroups.length; i++) {
|
|
260
|
+
const group = definitionGroups[i];
|
|
261
|
+
if (group.length === 1) {
|
|
262
|
+
definitions.push(group[0]);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
definitions.push(...group
|
|
266
|
+
.filter(v => !definitionGroups.some((og, oi) => (oi > i || og.length === 1)
|
|
267
|
+
&& predicate
|
|
268
|
+
? og.some(ov => predicate(v, ov))
|
|
269
|
+
: og.includes(v))));
|
|
270
|
+
}
|
|
271
|
+
return definitions;
|
|
272
|
+
}
|
|
273
|
+
export function getDefaultErrorRange(node, error) {
|
|
274
|
+
const { range } = node.originalNode;
|
|
275
|
+
if (error === 'missing_key' || error === 'invalid_collection_length') {
|
|
276
|
+
return { start: range.start, end: range.start + 1 };
|
|
277
|
+
}
|
|
278
|
+
return range;
|
|
279
|
+
}
|
|
280
|
+
export function getDefaultErrorReporter(ctx, getErrorRange) {
|
|
281
|
+
return (error) => {
|
|
282
|
+
const defaultTranslationKey = error.kind.replaceAll('_', '-');
|
|
283
|
+
let localizedText;
|
|
284
|
+
let severity = 3 /* ErrorSeverity.Error */;
|
|
285
|
+
switch (error.kind) {
|
|
286
|
+
case 'unknown_tuple_element':
|
|
287
|
+
localizedText = localize('expected', localize('nothing'));
|
|
288
|
+
break;
|
|
289
|
+
case 'unknown_key':
|
|
290
|
+
if (error.nodesWithConflictingErrors) {
|
|
291
|
+
localizedText = localize('invalid-key-combination', arrayToMessage(error.nodesWithConflictingErrors.map(n => McdocType.toString(n.inferredType)), true, 'and'));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
localizedText = localize(defaultTranslationKey, error.node.inferredType.kind === 'literal'
|
|
295
|
+
? localeQuote(error.node.inferredType.value.value.toString())
|
|
296
|
+
: `<${localize(`mcdoc.type.${error.node.inferredType.kind}`)}>`);
|
|
297
|
+
severity = 2 /* ErrorSeverity.Warning */;
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
case 'missing_key':
|
|
301
|
+
if (error.keys.length === 1) {
|
|
302
|
+
localizedText = localize(defaultTranslationKey, localeQuote(error.keys[0]));
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
localizedText = localize('mcdoc.runtime.checker.some-missing-keys', arrayToMessage(error.keys));
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
case 'invalid_collection_length':
|
|
309
|
+
case 'invalid_string_length':
|
|
310
|
+
case 'number_out_of_range':
|
|
311
|
+
const baseKey = error.kind === 'invalid_collection_length'
|
|
312
|
+
? 'mcdoc.runtime.checker.range.collection'
|
|
313
|
+
: error.kind === 'invalid_string_length'
|
|
314
|
+
? 'mcdoc.runtime.checker.range.string'
|
|
315
|
+
: 'mcdoc.runtime.checker.range.number';
|
|
316
|
+
const rangeMessages = error.ranges.map(r => {
|
|
317
|
+
const left = r.min !== undefined
|
|
318
|
+
? localize(RangeKind.isLeftExclusive(r.kind)
|
|
319
|
+
? 'mcdoc.runtime.checker.range.left-exclusive'
|
|
320
|
+
: 'mcdoc.runtime.checker.range.left-inclusive', r.min)
|
|
321
|
+
: undefined;
|
|
322
|
+
const right = r.max !== undefined
|
|
323
|
+
? localize(RangeKind.isLeftExclusive(r.kind)
|
|
324
|
+
? 'mcdoc.runtime.checker.range.right-exclusive'
|
|
325
|
+
: 'mcdoc.runtime.checker.range.right-inclusive', r.max)
|
|
326
|
+
: undefined;
|
|
327
|
+
if (left !== undefined && right !== undefined) {
|
|
328
|
+
return localize('mcdoc.runtime.checker.range.concat', left, right);
|
|
329
|
+
}
|
|
330
|
+
return left ?? right;
|
|
331
|
+
}).filter(r => r !== undefined);
|
|
332
|
+
localizedText = localize('expected', localize(baseKey, arrayToMessage(rangeMessages, false)));
|
|
333
|
+
break;
|
|
334
|
+
case 'type_mismatch':
|
|
335
|
+
localizedText = localize('expected', arrayToMessage(error.expected.map(e => e.kind === 'enum'
|
|
336
|
+
? arrayToMessage(e.values.map(v => ResourceLocation.shorten(v.value.toString())))
|
|
337
|
+
: e.kind === 'literal'
|
|
338
|
+
? localeQuote(e.value.value.toString())
|
|
339
|
+
: localize(`mcdoc.type.${e.kind}`)), false));
|
|
340
|
+
break;
|
|
341
|
+
case 'expected_key_value_pair':
|
|
342
|
+
localizedText = localize(`mcdoc.runtime.checker.${defaultTranslationKey}`);
|
|
343
|
+
break;
|
|
344
|
+
case 'internal':
|
|
345
|
+
break;
|
|
346
|
+
default:
|
|
347
|
+
localizedText = localize(defaultTranslationKey);
|
|
348
|
+
}
|
|
349
|
+
ctx.err.report(localizedText, getErrorRange(error.node, error.kind), severity);
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=error.js.map
|