@spyglassmc/json 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/checker/JsonChecker.d.ts +1 -1
- package/lib/checker/primitives/list.d.ts +1 -1
- package/lib/checker/primitives/list.js +10 -6
- package/lib/checker/primitives/number.js +9 -3
- package/lib/checker/primitives/object.d.ts +5 -5
- package/lib/checker/primitives/object.js +54 -29
- package/lib/checker/primitives/string.js +7 -3
- package/lib/checker/primitives/util.d.ts +1 -1
- package/lib/checker/primitives/util.js +14 -9
- package/lib/colorizer/index.js +2 -2
- package/lib/completer/index.js +31 -24
- package/lib/formatter/index.js +6 -4
- package/lib/node/JsonAstNode.d.ts +5 -5
- package/lib/node/JsonAstNode.js +1 -3
- package/lib/parser/entry.js +24 -6
- package/lib/parser/object.js +7 -1
- package/package.json +3 -3
|
@@ -4,5 +4,5 @@ export interface JsonCheckerContext extends CheckerContext {
|
|
|
4
4
|
context: string;
|
|
5
5
|
depth?: number;
|
|
6
6
|
}
|
|
7
|
-
export
|
|
7
|
+
export type JsonChecker = (node: JsonNode, ctx: JsonCheckerContext) => void;
|
|
8
8
|
//# sourceMappingURL=JsonChecker.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { JsonNode } from '../../node/index.js';
|
|
2
2
|
import type { JsonChecker, JsonCheckerContext } from '../JsonChecker.js';
|
|
3
3
|
export declare function listOf(checker: JsonChecker): JsonChecker;
|
|
4
|
-
|
|
4
|
+
type UniqueListOptions = {
|
|
5
5
|
items?: (node: JsonNode) => [string | undefined, JsonNode];
|
|
6
6
|
report?: (node: JsonNode, ctx: JsonCheckerContext) => unknown;
|
|
7
7
|
};
|
|
@@ -5,26 +5,30 @@ export function listOf(checker) {
|
|
|
5
5
|
return (node, ctx) => {
|
|
6
6
|
node.expectation = [{ type: 'json:array', typedoc: 'Array' }];
|
|
7
7
|
if (!ctx.depth || ctx.depth <= 0) {
|
|
8
|
+
;
|
|
8
9
|
node.expectation[0].items = expectation(checker, ctx);
|
|
9
10
|
}
|
|
10
11
|
if (!JsonArrayNode.is(node)) {
|
|
11
12
|
ctx.err.report(localize('expected', localize('array')), node);
|
|
12
13
|
}
|
|
13
14
|
else {
|
|
14
|
-
node.children
|
|
15
|
-
.
|
|
15
|
+
node.children
|
|
16
|
+
.filter((e) => e.value)
|
|
17
|
+
.forEach((e) => checker(e.value, ctx));
|
|
16
18
|
}
|
|
17
19
|
};
|
|
18
20
|
}
|
|
19
21
|
export function uniqueListOf(checker, options = {}) {
|
|
20
|
-
const getItem =
|
|
21
|
-
|
|
22
|
+
const getItem = options.items ??
|
|
23
|
+
((node) => [JsonStringNode.is(node) ? node.value : undefined, node]);
|
|
24
|
+
const reporter = options.report ??
|
|
25
|
+
((node, ctx) => ctx.err.report(localize('json.checker.item.duplicate'), node, 2 /* ErrorSeverity.Warning */));
|
|
22
26
|
return (node, ctx) => {
|
|
23
27
|
listOf(checker)(node, ctx);
|
|
24
28
|
if (JsonArrayNode.is(node)) {
|
|
25
29
|
const items = new Map();
|
|
26
30
|
const duplicates = new Set();
|
|
27
|
-
node.children.forEach(c => {
|
|
31
|
+
node.children.forEach((c) => {
|
|
28
32
|
if (!c.value)
|
|
29
33
|
return;
|
|
30
34
|
const [value, item] = getItem(c.value);
|
|
@@ -37,7 +41,7 @@ export function uniqueListOf(checker, options = {}) {
|
|
|
37
41
|
}
|
|
38
42
|
items.set(value, item);
|
|
39
43
|
});
|
|
40
|
-
duplicates.forEach(node => reporter(node, ctx));
|
|
44
|
+
duplicates.forEach((node) => reporter(node, ctx));
|
|
41
45
|
}
|
|
42
46
|
};
|
|
43
47
|
}
|
|
@@ -2,12 +2,18 @@ import { localize } from '@spyglassmc/locales';
|
|
|
2
2
|
import { JsonNumberNode } from '../../node/index.js';
|
|
3
3
|
const number = (type) => (min, max) => {
|
|
4
4
|
return (node, ctx) => {
|
|
5
|
-
const typedoc = 'Number' +
|
|
5
|
+
const typedoc = 'Number' +
|
|
6
|
+
(min === undefined && max === undefined
|
|
7
|
+
? ''
|
|
8
|
+
: `(${min ?? '-∞'}, ${max ?? '+∞'})`);
|
|
6
9
|
node.expectation = [{ type: 'json:number', typedoc }];
|
|
7
|
-
if (!JsonNumberNode.is(node) ||
|
|
10
|
+
if (!JsonNumberNode.is(node) ||
|
|
11
|
+
(type === 'integer' && !Number.isInteger(node.value))) {
|
|
8
12
|
ctx.err.report(localize('expected', localize(type)), node);
|
|
9
13
|
}
|
|
10
|
-
else if (min !== undefined &&
|
|
14
|
+
else if (min !== undefined &&
|
|
15
|
+
max !== undefined &&
|
|
16
|
+
(node.value < min || node.value > max)) {
|
|
11
17
|
ctx.err.report(localize('expected', localize('number.between', min, max)), node);
|
|
12
18
|
}
|
|
13
19
|
else if (min !== undefined && node.value < min) {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import type { PairNode } from '@spyglassmc/core';
|
|
2
2
|
import type { JsonNode, JsonStringNode } from '../../node/index.js';
|
|
3
3
|
import type { JsonChecker, JsonCheckerContext } from '../JsonChecker.js';
|
|
4
|
-
|
|
4
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
5
5
|
[key: string]: JsonValue;
|
|
6
6
|
};
|
|
7
|
-
|
|
7
|
+
type ComplexProperty = {
|
|
8
8
|
checker: JsonChecker;
|
|
9
9
|
opt?: boolean;
|
|
10
10
|
def?: JsonValue;
|
|
11
11
|
deprecated?: boolean;
|
|
12
12
|
context?: string;
|
|
13
13
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
type CheckerProperty = JsonChecker | ComplexProperty;
|
|
15
|
+
type CheckerRecord = Record<string, CheckerProperty | undefined>;
|
|
16
|
+
type ObjectCheckerOptions = {
|
|
17
17
|
allowUnknownProperties?: boolean;
|
|
18
18
|
};
|
|
19
19
|
export declare function object(): JsonChecker;
|
|
@@ -10,36 +10,45 @@ export function object(keys, values, options = {}) {
|
|
|
10
10
|
node.expectation = [{ type: 'json:object', typedoc: 'Object' }];
|
|
11
11
|
if (!ctx.depth || ctx.depth <= 0) {
|
|
12
12
|
if (Array.isArray(keys) && values) {
|
|
13
|
-
const fields = keys
|
|
14
|
-
|
|
13
|
+
const fields = keys
|
|
14
|
+
.map((key) => [key, values(key, ctx)])
|
|
15
|
+
.filter(([_, v]) => v !== undefined);
|
|
16
|
+
node.expectation[0].fields = fields
|
|
17
|
+
.map(([key, prop]) => {
|
|
15
18
|
return {
|
|
16
19
|
key,
|
|
17
20
|
value: expectation(isComplex(prop) ? prop.checker : prop, ctx),
|
|
18
|
-
...isComplex(prop) && (prop.opt || prop.deprecated)
|
|
19
|
-
|
|
21
|
+
...(isComplex(prop) && (prop.opt || prop.deprecated)
|
|
22
|
+
? { opt: true }
|
|
23
|
+
: {}),
|
|
24
|
+
...(isComplex(prop) && prop.deprecated
|
|
25
|
+
? { deprecated: true }
|
|
26
|
+
: {}),
|
|
20
27
|
};
|
|
21
28
|
});
|
|
22
29
|
}
|
|
23
30
|
else if (typeof keys === 'function' && values) {
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
;
|
|
32
|
+
node.expectation[0].keys = expectation(keys, ctx)?.filter(JsonStringExpectation.is);
|
|
26
33
|
}
|
|
27
34
|
}
|
|
28
35
|
if (!JsonObjectNode.is(node)) {
|
|
29
36
|
ctx.err.report(localize('expected', localize('object')), node);
|
|
30
37
|
}
|
|
31
38
|
else if (Array.isArray(keys) && values) {
|
|
32
|
-
const givenKeys = node.children.map(n => n.key?.value);
|
|
33
|
-
keys.forEach(k => {
|
|
39
|
+
const givenKeys = node.children.map((n) => n.key?.value);
|
|
40
|
+
keys.forEach((k) => {
|
|
34
41
|
const value = values(k, ctx);
|
|
35
|
-
if (!value || isComplex(value) && (value.opt || value.deprecated)) {
|
|
42
|
+
if (!value || (isComplex(value) && (value.opt || value.deprecated))) {
|
|
36
43
|
return;
|
|
37
44
|
}
|
|
38
45
|
if (!givenKeys.includes(k)) {
|
|
39
46
|
ctx.err.report(localize('json.checker.property.missing', localeQuote(k)), Range.create(node.range.start, node.range.start + 1));
|
|
40
47
|
}
|
|
41
48
|
});
|
|
42
|
-
node.children
|
|
49
|
+
node.children
|
|
50
|
+
.filter((p) => p.key)
|
|
51
|
+
.forEach((prop) => {
|
|
43
52
|
const key = prop.key.value;
|
|
44
53
|
const value = values(key, ctx);
|
|
45
54
|
if (!value || !keys.includes(key)) {
|
|
@@ -51,9 +60,12 @@ export function object(keys, values, options = {}) {
|
|
|
51
60
|
if (isComplex(value) && value.deprecated) {
|
|
52
61
|
ctx.err.report(localize('json.checker.property.deprecated', localeQuote(key)), prop.key, 0 /* ErrorSeverity.Hint */, { deprecated: true });
|
|
53
62
|
}
|
|
54
|
-
const context = ctx.context +
|
|
63
|
+
const context = ctx.context +
|
|
64
|
+
(isComplex(value) && value.context ? `.${value.context}` : '');
|
|
55
65
|
const doc = localize(`json.doc.${context}`);
|
|
56
|
-
const propNode = prop.value !== undefined
|
|
66
|
+
const propNode = prop.value !== undefined
|
|
67
|
+
? prop.value
|
|
68
|
+
: { type: 'json:null', range: Range.create(0) };
|
|
57
69
|
const checker = isComplex(value) ? value.checker : value;
|
|
58
70
|
try {
|
|
59
71
|
checker(propNode, { ...ctx, context: `${context}.${key}` });
|
|
@@ -63,12 +75,19 @@ export function object(keys, values, options = {}) {
|
|
|
63
75
|
ctx.logger.error(`Checking "${key}" at ${pos.line}:${pos.character} in "${ctx.doc.uri}"`, e);
|
|
64
76
|
}
|
|
65
77
|
const defaultValue = isComplex(value) ? value.def : undefined;
|
|
66
|
-
const typedoc = propNode.expectation
|
|
67
|
-
|
|
78
|
+
const typedoc = propNode.expectation
|
|
79
|
+
?.map((e) => e.typedoc)
|
|
80
|
+
.join(' | ');
|
|
81
|
+
prop.key.hover =
|
|
82
|
+
`\`\`\`typescript\n${context}.${key}: ${typedoc}\n\`\`\`${doc || defaultValue !== undefined ? '\n******\n ' : ''}${doc}${defaultValue !== undefined
|
|
83
|
+
? `\n\`@default\` ${JSON.stringify(defaultValue)}`
|
|
84
|
+
: ''}`;
|
|
68
85
|
});
|
|
69
86
|
}
|
|
70
87
|
else if (typeof keys === 'function' && values) {
|
|
71
|
-
node.children
|
|
88
|
+
node.children
|
|
89
|
+
.filter((p) => p.key)
|
|
90
|
+
.forEach((prop) => {
|
|
72
91
|
keys(prop.key, ctx);
|
|
73
92
|
if (prop.value !== undefined) {
|
|
74
93
|
const value = values(prop.key.value, ctx);
|
|
@@ -104,12 +123,15 @@ export function dispatch(arg1, arg2) {
|
|
|
104
123
|
ctx.err.report(localize('expected', localize('object')), node);
|
|
105
124
|
}
|
|
106
125
|
else if (arg2) {
|
|
107
|
-
const dispatcherIndex = node.children.findIndex(p => p.key?.value === arg1);
|
|
126
|
+
const dispatcherIndex = node.children.findIndex((p) => p.key?.value === arg1);
|
|
108
127
|
const dispatcher = node.children[dispatcherIndex];
|
|
109
|
-
const value = dispatcher?.value?.type === 'json:string'
|
|
128
|
+
const value = dispatcher?.value?.type === 'json:string'
|
|
129
|
+
? dispatcher.value.value
|
|
130
|
+
: undefined;
|
|
110
131
|
arg2(value, node.children, ctx)(node, ctx);
|
|
111
132
|
}
|
|
112
133
|
else {
|
|
134
|
+
;
|
|
113
135
|
arg1(node.children, ctx)(node, ctx);
|
|
114
136
|
}
|
|
115
137
|
};
|
|
@@ -122,7 +144,7 @@ export function pick(value, cases) {
|
|
|
122
144
|
if (properties === undefined) {
|
|
123
145
|
return {};
|
|
124
146
|
}
|
|
125
|
-
Object.keys(properties).forEach(key => {
|
|
147
|
+
Object.keys(properties).forEach((key) => {
|
|
126
148
|
const p = properties[key];
|
|
127
149
|
if (p === undefined)
|
|
128
150
|
return;
|
|
@@ -145,30 +167,33 @@ export function when(value, values, properties, notProperties = {}) {
|
|
|
145
167
|
return properties;
|
|
146
168
|
}
|
|
147
169
|
export function extract(value, children) {
|
|
148
|
-
const node = children?.find(p => p.key?.value === value);
|
|
170
|
+
const node = children?.find((p) => p.key?.value === value);
|
|
149
171
|
return node?.value?.type === 'json:string' ? node.value.value : undefined;
|
|
150
172
|
}
|
|
151
173
|
export function extractNested(wrap, value, children) {
|
|
152
|
-
const wrapper = children?.find(p => p.key?.value === wrap);
|
|
174
|
+
const wrapper = children?.find((p) => p.key?.value === wrap);
|
|
153
175
|
if (wrapper?.value?.type !== 'json:object')
|
|
154
176
|
return undefined;
|
|
155
|
-
const node = wrapper.children?.find(p => p.key?.value ===
|
|
177
|
+
const node = wrapper.children?.find((p) => p.key?.value ===
|
|
178
|
+
value);
|
|
156
179
|
return node?.type === 'json:string' ? node.value : undefined;
|
|
157
180
|
}
|
|
158
181
|
export function extractStringArray(value, children) {
|
|
159
|
-
const node = children?.find(p => p.key?.value === value);
|
|
160
|
-
return node?.value?.type === 'json:array' &&
|
|
161
|
-
|
|
182
|
+
const node = children?.find((p) => p.key?.value === value);
|
|
183
|
+
return node?.value?.type === 'json:array' &&
|
|
184
|
+
node.value.children?.every((n) => n.value?.type === 'json:string')
|
|
185
|
+
? node.value.children.map((n) => n.value.value)
|
|
162
186
|
: undefined;
|
|
163
187
|
}
|
|
164
188
|
export function having(node, ctx, cases) {
|
|
165
|
-
const givenKeys = new Set(JsonObjectNode.is(node)
|
|
166
|
-
|
|
167
|
-
const key = Object.keys(cases).find(c => givenKeys.has(c));
|
|
189
|
+
const givenKeys = new Set(JsonObjectNode.is(node) ? node.children.map((n) => n.key?.value) : []);
|
|
190
|
+
const key = Object.keys(cases).find((c) => givenKeys.has(c));
|
|
168
191
|
if (key === undefined) {
|
|
169
192
|
ctx.err.report(localize('json.checker.property.missing', Object.keys(cases)), Range.create(node.range.start, node.range.start + 1));
|
|
170
|
-
return Object.fromEntries(Object.entries(cases)
|
|
171
|
-
|
|
193
|
+
return Object.fromEntries(Object.entries(cases).map(([k, v]) => [
|
|
194
|
+
k,
|
|
195
|
+
opt(typeof v === 'function' ? any() : v[k] ?? any()),
|
|
196
|
+
]));
|
|
172
197
|
}
|
|
173
198
|
const c = cases[key];
|
|
174
199
|
return typeof c === 'function' ? c() : c;
|
|
@@ -14,7 +14,9 @@ export function literal(value) {
|
|
|
14
14
|
}
|
|
15
15
|
export function string(name, parser, checker, expectation) {
|
|
16
16
|
return (node, ctx) => {
|
|
17
|
-
node.expectation = [
|
|
17
|
+
node.expectation = [
|
|
18
|
+
{ type: 'json:string', typedoc: typedoc(name), ...expectation },
|
|
19
|
+
];
|
|
18
20
|
if (!JsonStringNode.is(node)) {
|
|
19
21
|
ctx.err.report(localize('expected', localize('string')), node);
|
|
20
22
|
}
|
|
@@ -40,8 +42,10 @@ function typedoc(id) {
|
|
|
40
42
|
if (typeof id === 'string') {
|
|
41
43
|
return `String("${id}")`;
|
|
42
44
|
}
|
|
43
|
-
return id
|
|
44
|
-
|
|
45
|
+
return (id
|
|
46
|
+
.slice(0, 10)
|
|
47
|
+
.map((e) => `"${e}"`)
|
|
48
|
+
.join(' | ') + (id.length > 10 ? ' | ...' : ''));
|
|
45
49
|
}
|
|
46
50
|
export const simpleString = string();
|
|
47
51
|
//# sourceMappingURL=string.js.map
|
|
@@ -3,7 +3,7 @@ import type { JsonExpectation, JsonNode } from '../../node/index.js';
|
|
|
3
3
|
import type { JsonChecker, JsonCheckerContext } from '../JsonChecker.js';
|
|
4
4
|
export declare function ref(checker: () => JsonChecker): JsonChecker;
|
|
5
5
|
export declare function as(context: string, checker: JsonChecker): JsonChecker;
|
|
6
|
-
export
|
|
6
|
+
export type AttemptResult = {
|
|
7
7
|
totalErrorSpan: number;
|
|
8
8
|
maxSeverity: ErrorSeverity;
|
|
9
9
|
expectation?: JsonExpectation[];
|
|
@@ -21,11 +21,11 @@ export function attempt(checker, node, ctx) {
|
|
|
21
21
|
const tempExpectation = node.expectation;
|
|
22
22
|
StateProxy.undoChanges(node);
|
|
23
23
|
const totalErrorSpan = tempCtx.err.errors
|
|
24
|
-
.map(e => e.range.end - e.range.start)
|
|
24
|
+
.map((e) => e.range.end - e.range.start)
|
|
25
25
|
.reduce((a, b) => a + b, 0);
|
|
26
26
|
return {
|
|
27
27
|
totalErrorSpan,
|
|
28
|
-
maxSeverity: Math.max(...tempCtx.err.errors.map(e => e.severity)),
|
|
28
|
+
maxSeverity: Math.max(...tempCtx.err.errors.map((e) => e.severity)),
|
|
29
29
|
expectation: tempExpectation,
|
|
30
30
|
updateNodeAndCtx: () => {
|
|
31
31
|
ctx.err.absorb(tempCtx.err);
|
|
@@ -40,13 +40,15 @@ export function any(checkers = []) {
|
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
const attempts = checkers
|
|
43
|
-
.map(checker => attempt(checker, node, ctx))
|
|
44
|
-
.sort((a, b) => a.maxSeverity - b.maxSeverity ||
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const allExpectations = attempts
|
|
43
|
+
.map((checker) => attempt(checker, node, ctx))
|
|
44
|
+
.sort((a, b) => a.maxSeverity - b.maxSeverity ||
|
|
45
|
+
a.totalErrorSpan - b.totalErrorSpan);
|
|
46
|
+
const sameTypeAttempts = attempts.filter((a) => a.expectation?.map((e) => e.type).includes(node.type));
|
|
47
|
+
const allExpectations = attempts
|
|
48
|
+
.filter((a) => a.expectation)
|
|
49
|
+
.flatMap((a) => a.expectation);
|
|
48
50
|
if (sameTypeAttempts.length === 0) {
|
|
49
|
-
const allowedTypes = allExpectations.map(e => localize(e.type.slice(5)));
|
|
51
|
+
const allowedTypes = allExpectations.map((e) => localize(e.type.slice(5)));
|
|
50
52
|
ctx.err.report(localize('expected', arrayToMessage(allowedTypes, false)), node);
|
|
51
53
|
}
|
|
52
54
|
else {
|
|
@@ -56,7 +58,10 @@ export function any(checkers = []) {
|
|
|
56
58
|
};
|
|
57
59
|
}
|
|
58
60
|
export function expectation(checker, ctx) {
|
|
59
|
-
const node = StateProxy.create({
|
|
61
|
+
const node = StateProxy.create({
|
|
62
|
+
type: 'json:null',
|
|
63
|
+
range: Range.create(0),
|
|
64
|
+
});
|
|
60
65
|
const tempCtx = {
|
|
61
66
|
...ctx,
|
|
62
67
|
err: new ErrorReporter(),
|
package/lib/colorizer/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as core from '@spyglassmc/core';
|
|
2
2
|
import { ColorToken } from '@spyglassmc/core';
|
|
3
|
-
export const boolean = node => {
|
|
3
|
+
export const boolean = (node) => {
|
|
4
4
|
return [ColorToken.create(node, 'literal')];
|
|
5
5
|
};
|
|
6
|
-
export const null_ = node => {
|
|
6
|
+
export const null_ = (node) => {
|
|
7
7
|
return [ColorToken.create(node, 'literal')];
|
|
8
8
|
};
|
|
9
9
|
export const object = (node, ctx) => {
|
package/lib/completer/index.js
CHANGED
|
@@ -15,8 +15,9 @@ export const entry = (node, ctx) => {
|
|
|
15
15
|
export const object = core.completer.record({
|
|
16
16
|
key: (record, pair, ctx, range, insertValue, insertComma) => {
|
|
17
17
|
if (record.expectation) {
|
|
18
|
-
return unique(record.expectation
|
|
19
|
-
.
|
|
18
|
+
return unique(record.expectation
|
|
19
|
+
.filter(JsonExpectation.isObject)
|
|
20
|
+
.flatMap((e) => objectCompletion(range, record, e, ctx, insertValue, insertComma, pair?.key?.value)));
|
|
20
21
|
}
|
|
21
22
|
return [];
|
|
22
23
|
},
|
|
@@ -25,10 +26,11 @@ export const object = core.completer.record({
|
|
|
25
26
|
return core.completer.dispatch(pair.value, ctx);
|
|
26
27
|
}
|
|
27
28
|
if (record.expectation) {
|
|
28
|
-
return unique(record.expectation
|
|
29
|
-
.filter(
|
|
30
|
-
.
|
|
31
|
-
.
|
|
29
|
+
return unique(record.expectation
|
|
30
|
+
.filter(JsonExpectation.isObject)
|
|
31
|
+
.filter((e) => e.fields)
|
|
32
|
+
.map((e) => e.fields.find((f) => f.key === pair.key?.value))
|
|
33
|
+
.flatMap((f) => valueCompletion(ctx.offset, f.value, ctx)));
|
|
32
34
|
}
|
|
33
35
|
return [];
|
|
34
36
|
},
|
|
@@ -41,37 +43,42 @@ export const array = (node, ctx) => {
|
|
|
41
43
|
if (item?.value) {
|
|
42
44
|
return core.completer.dispatch(item.value, ctx);
|
|
43
45
|
}
|
|
44
|
-
if (node.expectation &&
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
.
|
|
46
|
+
if (node.expectation &&
|
|
47
|
+
Range.contains(Range.translate(node, 1, -1), ctx.offset, true)) {
|
|
48
|
+
return unique(node.expectation
|
|
49
|
+
.filter(JsonExpectation.isArray)
|
|
50
|
+
.filter((e) => e.items)
|
|
51
|
+
.flatMap((e) => valueCompletion(ctx.offset, e.items, ctx)));
|
|
48
52
|
}
|
|
49
53
|
return [];
|
|
50
54
|
};
|
|
51
55
|
export const boolean = (node) => {
|
|
52
|
-
return ['false', 'true'].map(v => simpleCompletion(node, v));
|
|
56
|
+
return ['false', 'true'].map((v) => simpleCompletion(node, v));
|
|
53
57
|
};
|
|
54
58
|
const string = (node, ctx) => {
|
|
55
59
|
if (node.children?.length) {
|
|
56
60
|
return core.completer.string(node, ctx);
|
|
57
61
|
}
|
|
58
62
|
if (node.expectation) {
|
|
59
|
-
return unique(node.expectation
|
|
60
|
-
.
|
|
63
|
+
return unique(node.expectation
|
|
64
|
+
.filter(JsonExpectation.isString)
|
|
65
|
+
.flatMap((e) => stringCompletion(node, e, ctx)));
|
|
61
66
|
}
|
|
62
67
|
return [];
|
|
63
68
|
};
|
|
64
69
|
function objectCompletion(range, node, expectation, ctx, insertValue, insertComma, selectedKey) {
|
|
65
70
|
if (expectation.fields) {
|
|
66
|
-
return expectation
|
|
67
|
-
.filter(f => f.key === selectedKey ||
|
|
68
|
-
.
|
|
71
|
+
return expectation
|
|
72
|
+
.fields.filter((f) => f.key === selectedKey ||
|
|
73
|
+
!node.children.find((p) => f.key === p.key?.value))
|
|
74
|
+
.map((f) => fieldCompletion(range, f, insertValue, insertComma));
|
|
69
75
|
}
|
|
70
76
|
else if (expectation.keys) {
|
|
71
|
-
return expectation.keys.flatMap(e => stringCompletion(range, e, ctx)
|
|
72
|
-
.map(c => ({
|
|
77
|
+
return expectation.keys.flatMap((e) => stringCompletion(range, e, ctx).map((c) => ({
|
|
73
78
|
...c,
|
|
74
|
-
...insertValue
|
|
79
|
+
...(insertValue
|
|
80
|
+
? { insertText: `${c.insertText}: ${insertComma ? ',' : ''}` }
|
|
81
|
+
: {}),
|
|
75
82
|
})));
|
|
76
83
|
}
|
|
77
84
|
return [];
|
|
@@ -80,7 +87,7 @@ function fieldCompletion(range, field, insertValue, insertComma) {
|
|
|
80
87
|
const value = field.value?.[0] ? SIMPLE_SNIPPETS[field.value[0].type] : '';
|
|
81
88
|
return CompletionItem.create(field.key, range, {
|
|
82
89
|
kind: 10 /* CompletionKind.Property */,
|
|
83
|
-
detail: field.value?.map(e => e.typedoc).join(' | '),
|
|
90
|
+
detail: field.value?.map((e) => e.typedoc).join(' | '),
|
|
84
91
|
sortText: `${field.deprecated ? 2 : field.opt ? 1 : 0}${field.key}`,
|
|
85
92
|
deprecated: field.deprecated,
|
|
86
93
|
filterText: `"${field.key}"`,
|
|
@@ -88,7 +95,7 @@ function fieldCompletion(range, field, insertValue, insertComma) {
|
|
|
88
95
|
});
|
|
89
96
|
}
|
|
90
97
|
function valueCompletion(range, expectation, ctx) {
|
|
91
|
-
return unique(expectation.flatMap(e => {
|
|
98
|
+
return unique(expectation.flatMap((e) => {
|
|
92
99
|
switch (e.type) {
|
|
93
100
|
case 'json:object':
|
|
94
101
|
case 'json:array':
|
|
@@ -96,7 +103,7 @@ function valueCompletion(range, expectation, ctx) {
|
|
|
96
103
|
case 'json:string':
|
|
97
104
|
return stringCompletion(ctx.offset, e, ctx);
|
|
98
105
|
case 'json:boolean':
|
|
99
|
-
return ['false', 'true'].map(v => simpleCompletion(range, v));
|
|
106
|
+
return ['false', 'true'].map((v) => simpleCompletion(range, v));
|
|
100
107
|
case 'json:number':
|
|
101
108
|
return [simpleCompletion(range, '0')];
|
|
102
109
|
}
|
|
@@ -104,7 +111,7 @@ function valueCompletion(range, expectation, ctx) {
|
|
|
104
111
|
}
|
|
105
112
|
function stringCompletion(range, expectation, ctx) {
|
|
106
113
|
if (Array.isArray(expectation.pool)) {
|
|
107
|
-
return expectation.pool.map(v => CompletionItem.create(v, range, {
|
|
114
|
+
return expectation.pool.map((v) => CompletionItem.create(v, range, {
|
|
108
115
|
kind: 12 /* CompletionKind.Value */,
|
|
109
116
|
filterText: `"${v}"`,
|
|
110
117
|
insertText: `"${v}"`,
|
|
@@ -121,7 +128,7 @@ function simpleCompletion(range, value) {
|
|
|
121
128
|
function unique(completions) {
|
|
122
129
|
const ans = [];
|
|
123
130
|
const labels = new Set();
|
|
124
|
-
completions.forEach(c => {
|
|
131
|
+
completions.forEach((c) => {
|
|
125
132
|
if (!labels.has(c.label)) {
|
|
126
133
|
labels.add(c.label);
|
|
127
134
|
ans.push(c);
|
package/lib/formatter/index.js
CHANGED
|
@@ -3,8 +3,9 @@ import { indentFormatter } from '@spyglassmc/core';
|
|
|
3
3
|
const array = (node, ctx) => {
|
|
4
4
|
if (node.children.length === 0)
|
|
5
5
|
return '[]';
|
|
6
|
-
const values = node.children.map(child => {
|
|
7
|
-
const value = child.value &&
|
|
6
|
+
const values = node.children.map((child) => {
|
|
7
|
+
const value = child.value &&
|
|
8
|
+
ctx.meta.getFormatter(child.value.type)(child.value, indentFormatter(ctx));
|
|
8
9
|
return `${ctx.indent(1)}${value ?? ''}`;
|
|
9
10
|
});
|
|
10
11
|
return `[\n${values.join(',\n')}\n${ctx.indent()}]`;
|
|
@@ -12,9 +13,10 @@ const array = (node, ctx) => {
|
|
|
12
13
|
const object = (node, ctx) => {
|
|
13
14
|
if (node.children.length === 0)
|
|
14
15
|
return '{}';
|
|
15
|
-
const fields = node.children.map(child => {
|
|
16
|
+
const fields = node.children.map((child) => {
|
|
16
17
|
const key = child.key && core.formatter.string(child.key, ctx);
|
|
17
|
-
const value = child.value &&
|
|
18
|
+
const value = child.value &&
|
|
19
|
+
ctx.meta.getFormatter(child.value.type)(child.value, indentFormatter(ctx));
|
|
18
20
|
return `${ctx.indent(1)}${key ?? ''}: ${value ?? ''}`;
|
|
19
21
|
});
|
|
20
22
|
return `{\n${fields.join(',\n')}\n${ctx.indent()}}`;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DeepReadonly, ItemNode, PairNode } from '@spyglassmc/core';
|
|
2
2
|
import * as core from '@spyglassmc/core';
|
|
3
|
-
export
|
|
4
|
-
export
|
|
3
|
+
export type JsonNode = JsonObjectNode | JsonArrayNode | JsonStringNode | JsonNumberNode | JsonBooleanNode | JsonNullNode;
|
|
4
|
+
export type JsonRelatedNode = JsonNode | JsonPairNode | JsonItemNode;
|
|
5
5
|
export declare namespace JsonNode {
|
|
6
6
|
function is(node: core.AstNode): node is JsonNode;
|
|
7
7
|
function isRelated(node: core.AstNode): node is JsonRelatedNode;
|
|
@@ -9,7 +9,7 @@ export declare namespace JsonNode {
|
|
|
9
9
|
interface JsonBaseAstNode {
|
|
10
10
|
expectation?: JsonExpectation[];
|
|
11
11
|
}
|
|
12
|
-
export
|
|
12
|
+
export type JsonExpectation = JsonObjectExpectation | JsonArrayExpectation | JsonStringExpectation | JsonNumberExpectation | JsonBooleanExpectation;
|
|
13
13
|
export declare namespace JsonExpectation {
|
|
14
14
|
function isArray(e: DeepReadonly<JsonExpectation>): e is DeepReadonly<JsonArrayExpectation>;
|
|
15
15
|
function isObject(e: DeepReadonly<JsonExpectation>): e is DeepReadonly<JsonObjectExpectation>;
|
|
@@ -35,7 +35,7 @@ export declare namespace JsonObjectNode {
|
|
|
35
35
|
function is(obj: object | undefined): obj is JsonObjectNode;
|
|
36
36
|
function mock(range: core.RangeLike): JsonObjectNode;
|
|
37
37
|
}
|
|
38
|
-
export
|
|
38
|
+
export type JsonPairNode = PairNode<JsonStringNode, JsonNode>;
|
|
39
39
|
export declare namespace JsonPairNode {
|
|
40
40
|
function is(obj: object): obj is JsonPairNode;
|
|
41
41
|
}
|
|
@@ -50,7 +50,7 @@ export declare namespace JsonArrayNode {
|
|
|
50
50
|
function is(obj: object | undefined): obj is JsonArrayNode;
|
|
51
51
|
function mock(range: core.RangeLike): JsonArrayNode;
|
|
52
52
|
}
|
|
53
|
-
export
|
|
53
|
+
export type JsonItemNode = ItemNode<JsonNode>;
|
|
54
54
|
export declare namespace JsonItemNode {
|
|
55
55
|
function is(obj: object): obj is JsonItemNode;
|
|
56
56
|
}
|
package/lib/node/JsonAstNode.js
CHANGED
|
@@ -12,9 +12,7 @@ export var JsonNode;
|
|
|
12
12
|
}
|
|
13
13
|
JsonNode.is = is;
|
|
14
14
|
function isRelated(node) {
|
|
15
|
-
return
|
|
16
|
-
JsonPairNode.is(node) ||
|
|
17
|
-
JsonItemNode.is(node));
|
|
15
|
+
return JsonNode.is(node) || JsonPairNode.is(node) || JsonItemNode.is(node);
|
|
18
16
|
}
|
|
19
17
|
JsonNode.isRelated = isRelated;
|
|
20
18
|
})(JsonNode || (JsonNode = {}));
|
package/lib/parser/entry.js
CHANGED
|
@@ -5,15 +5,33 @@ import { null_ } from './null.js';
|
|
|
5
5
|
import { number } from './number.js';
|
|
6
6
|
import { object } from './object.js';
|
|
7
7
|
import { string } from './string.js';
|
|
8
|
-
const LegalNumberStart = new Set([
|
|
8
|
+
const LegalNumberStart = new Set([
|
|
9
|
+
'0',
|
|
10
|
+
'1',
|
|
11
|
+
'2',
|
|
12
|
+
'3',
|
|
13
|
+
'4',
|
|
14
|
+
'5',
|
|
15
|
+
'6',
|
|
16
|
+
'7',
|
|
17
|
+
'8',
|
|
18
|
+
'9',
|
|
19
|
+
'-',
|
|
20
|
+
]);
|
|
9
21
|
export function json(dumpErrors = false) {
|
|
10
22
|
return (src, ctx) => {
|
|
11
23
|
const result = core.select([
|
|
12
|
-
{ predicate: src => src.tryPeek('['), parser: array },
|
|
13
|
-
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
{ predicate: (src) => src.tryPeek('['), parser: array },
|
|
25
|
+
{
|
|
26
|
+
predicate: (src) => src.tryPeek('false') || src.tryPeek('true'),
|
|
27
|
+
parser: boolean,
|
|
28
|
+
},
|
|
29
|
+
{ predicate: (src) => src.tryPeek('null'), parser: null_ },
|
|
30
|
+
{
|
|
31
|
+
predicate: (src) => LegalNumberStart.has(src.peek()),
|
|
32
|
+
parser: number,
|
|
33
|
+
},
|
|
34
|
+
{ predicate: (src) => src.tryPeek('{'), parser: object },
|
|
17
35
|
{ parser: string },
|
|
18
36
|
])(src, ctx);
|
|
19
37
|
if (dumpErrors) {
|
package/lib/parser/object.js
CHANGED
|
@@ -4,7 +4,13 @@ import { string } from './string.js';
|
|
|
4
4
|
export const object = (src, ctx) => {
|
|
5
5
|
return core.setType('json:object', core.record({
|
|
6
6
|
start: '{',
|
|
7
|
-
pair: {
|
|
7
|
+
pair: {
|
|
8
|
+
key: string,
|
|
9
|
+
sep: ':',
|
|
10
|
+
value: entry,
|
|
11
|
+
end: ',',
|
|
12
|
+
trailingEnd: false,
|
|
13
|
+
},
|
|
8
14
|
end: '}',
|
|
9
15
|
}))(src, ctx);
|
|
10
16
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spyglassmc/json",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
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.
|
|
29
|
-
"@spyglassmc/locales": "0.3.
|
|
28
|
+
"@spyglassmc/core": "0.4.2",
|
|
29
|
+
"@spyglassmc/locales": "0.3.2"
|
|
30
30
|
}
|
|
31
31
|
}
|