Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/common/jsonSchema.ts
3291 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
export type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'null' | 'array' | 'object';
7
8
export interface IJSONSchema {
9
id?: string;
10
$id?: string;
11
$schema?: string;
12
type?: JSONSchemaType | JSONSchemaType[];
13
title?: string;
14
default?: any;
15
definitions?: IJSONSchemaMap;
16
description?: string;
17
properties?: IJSONSchemaMap;
18
patternProperties?: IJSONSchemaMap;
19
additionalProperties?: boolean | IJSONSchema;
20
minProperties?: number;
21
maxProperties?: number;
22
dependencies?: IJSONSchemaMap | { [prop: string]: string[] };
23
items?: IJSONSchema | IJSONSchema[];
24
minItems?: number;
25
maxItems?: number;
26
uniqueItems?: boolean;
27
additionalItems?: boolean | IJSONSchema;
28
pattern?: string;
29
minLength?: number;
30
maxLength?: number;
31
minimum?: number;
32
maximum?: number;
33
exclusiveMinimum?: boolean | number;
34
exclusiveMaximum?: boolean | number;
35
multipleOf?: number;
36
required?: string[];
37
$ref?: string;
38
anyOf?: IJSONSchema[];
39
allOf?: IJSONSchema[];
40
oneOf?: IJSONSchema[];
41
not?: IJSONSchema;
42
enum?: any[];
43
format?: string;
44
45
// schema draft 06
46
const?: any;
47
contains?: IJSONSchema;
48
propertyNames?: IJSONSchema;
49
examples?: any[];
50
51
// schema draft 07
52
$comment?: string;
53
if?: IJSONSchema;
54
then?: IJSONSchema;
55
else?: IJSONSchema;
56
57
// schema 2019-09
58
unevaluatedProperties?: boolean | IJSONSchema;
59
unevaluatedItems?: boolean | IJSONSchema;
60
minContains?: number;
61
maxContains?: number;
62
deprecated?: boolean;
63
dependentRequired?: { [prop: string]: string[] };
64
dependentSchemas?: IJSONSchemaMap;
65
$defs?: { [name: string]: IJSONSchema };
66
$anchor?: string;
67
$recursiveRef?: string;
68
$recursiveAnchor?: string;
69
$vocabulary?: any;
70
71
// schema 2020-12
72
prefixItems?: IJSONSchema[];
73
$dynamicRef?: string;
74
$dynamicAnchor?: string;
75
76
// VSCode extensions
77
78
defaultSnippets?: IJSONSchemaSnippet[];
79
errorMessage?: string;
80
patternErrorMessage?: string;
81
deprecationMessage?: string;
82
markdownDeprecationMessage?: string;
83
enumDescriptions?: string[];
84
markdownEnumDescriptions?: string[];
85
markdownDescription?: string;
86
doNotSuggest?: boolean;
87
suggestSortText?: string;
88
allowComments?: boolean;
89
allowTrailingCommas?: boolean;
90
}
91
92
export interface IJSONSchemaMap {
93
[name: string]: IJSONSchema;
94
}
95
96
export interface IJSONSchemaSnippet {
97
label?: string;
98
description?: string;
99
body?: any; // a object that will be JSON stringified
100
bodyText?: string; // an already stringified JSON object that can contain new lines (\n) and tabs (\t)
101
}
102
103
/**
104
* Converts a basic JSON schema to a TypeScript type.
105
*
106
* TODO: only supports basic schemas. Doesn't support all JSON schema features.
107
*/
108
export type SchemaToType<T> = T extends { type: 'string' }
109
? string
110
: T extends { type: 'number' }
111
? number
112
: T extends { type: 'boolean' }
113
? boolean
114
: T extends { type: 'null' }
115
? null
116
// Object
117
: T extends { type: 'object'; properties: infer P }
118
? { [K in keyof P]: SchemaToType<P[K]> }
119
// Array
120
: T extends { type: 'array'; items: infer I }
121
? Array<SchemaToType<I>>
122
// OneOf
123
: T extends { oneOf: infer I }
124
? MapSchemaToType<I>
125
// Fallthrough
126
: never;
127
128
type MapSchemaToType<T> = T extends [infer First, ...infer Rest]
129
? SchemaToType<First> | MapSchemaToType<Rest>
130
: never;
131
132
interface Equals { schemas: IJSONSchema[]; id?: string }
133
134
export function getCompressedContent(schema: IJSONSchema): string {
135
let hasDups = false;
136
137
138
// visit all schema nodes and collect the ones that are equal
139
const equalsByString = new Map<string, Equals>();
140
const nodeToEquals = new Map<IJSONSchema, Equals>();
141
const visitSchemas = (next: IJSONSchema) => {
142
if (schema === next) {
143
return true;
144
}
145
const val = JSON.stringify(next);
146
if (val.length < 30) {
147
// the $ref takes around 25 chars, so we don't save anything
148
return true;
149
}
150
const eq = equalsByString.get(val);
151
if (!eq) {
152
const newEq = { schemas: [next] };
153
equalsByString.set(val, newEq);
154
nodeToEquals.set(next, newEq);
155
return true;
156
}
157
eq.schemas.push(next);
158
nodeToEquals.set(next, eq);
159
hasDups = true;
160
return false;
161
};
162
traverseNodes(schema, visitSchemas);
163
equalsByString.clear();
164
165
if (!hasDups) {
166
return JSON.stringify(schema);
167
}
168
169
let defNodeName = '$defs';
170
while (schema.hasOwnProperty(defNodeName)) {
171
defNodeName += '_';
172
}
173
174
// used to collect all schemas that are later put in `$defs`. The index in the array is the id of the schema.
175
const definitions: IJSONSchema[] = [];
176
177
function stringify(root: IJSONSchema): string {
178
return JSON.stringify(root, (_key: string, value: any) => {
179
if (value !== root) {
180
const eq = nodeToEquals.get(value);
181
if (eq && eq.schemas.length > 1) {
182
if (!eq.id) {
183
eq.id = `_${definitions.length}`;
184
definitions.push(eq.schemas[0]);
185
}
186
return { $ref: `#/${defNodeName}/${eq.id}` };
187
}
188
}
189
return value;
190
});
191
}
192
193
// stringify the schema and replace duplicate subtrees with $ref
194
// this will add new items to the definitions array
195
const str = stringify(schema);
196
197
// now stringify the definitions. Each invication of stringify cann add new items to the definitions array, so the length can grow while we iterate
198
const defStrings: string[] = [];
199
for (let i = 0; i < definitions.length; i++) {
200
defStrings.push(`"_${i}":${stringify(definitions[i])}`);
201
}
202
if (defStrings.length) {
203
return `${str.substring(0, str.length - 1)},"${defNodeName}":{${defStrings.join(',')}}}`;
204
}
205
return str;
206
}
207
208
type IJSONSchemaRef = IJSONSchema | boolean;
209
210
function isObject(thing: unknown): thing is object {
211
return typeof thing === 'object' && thing !== null;
212
}
213
214
/*
215
* Traverse a JSON schema and visit each schema node
216
*/
217
function traverseNodes(root: IJSONSchema, visit: (schema: IJSONSchema) => boolean) {
218
if (!root || typeof root !== 'object') {
219
return;
220
}
221
const collectEntries = (...entries: (IJSONSchemaRef | undefined)[]) => {
222
for (const entry of entries) {
223
if (isObject(entry)) {
224
toWalk.push(entry);
225
}
226
}
227
};
228
const collectMapEntries = (...maps: (IJSONSchemaMap | undefined)[]) => {
229
for (const map of maps) {
230
if (isObject(map)) {
231
for (const key in map) {
232
const entry = map[key];
233
if (isObject(entry)) {
234
toWalk.push(entry);
235
}
236
}
237
}
238
}
239
};
240
const collectArrayEntries = (...arrays: (IJSONSchemaRef[] | undefined)[]) => {
241
for (const array of arrays) {
242
if (Array.isArray(array)) {
243
for (const entry of array) {
244
if (isObject(entry)) {
245
toWalk.push(entry);
246
}
247
}
248
}
249
}
250
};
251
const collectEntryOrArrayEntries = (items: (IJSONSchemaRef[] | IJSONSchemaRef | undefined)) => {
252
if (Array.isArray(items)) {
253
for (const entry of items) {
254
if (isObject(entry)) {
255
toWalk.push(entry);
256
}
257
}
258
} else if (isObject(items)) {
259
toWalk.push(items);
260
}
261
};
262
263
const toWalk: IJSONSchema[] = [root];
264
265
let next = toWalk.pop();
266
while (next) {
267
const visitChildern = visit(next);
268
if (visitChildern) {
269
collectEntries(next.additionalItems, next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else, next.unevaluatedItems, next.unevaluatedProperties);
270
collectMapEntries(next.definitions, next.$defs, next.properties, next.patternProperties, <IJSONSchemaMap>next.dependencies, next.dependentSchemas);
271
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.prefixItems);
272
collectEntryOrArrayEntries(next.items);
273
}
274
next = toWalk.pop();
275
}
276
}
277
278
279