vscode-json-languageservice 5.0.0 → 5.1.1
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/CHANGELOG.md +10 -2
- package/lib/esm/jsonContributions.d.ts +17 -17
- package/lib/esm/jsonContributions.js +1 -1
- package/lib/esm/jsonLanguageService.d.ts +29 -29
- package/lib/esm/jsonLanguageService.js +66 -66
- package/lib/esm/jsonLanguageTypes.d.ts +292 -279
- package/lib/esm/jsonLanguageTypes.js +55 -46
- package/lib/esm/jsonSchema.d.ts +89 -89
- package/lib/esm/jsonSchema.js +1 -1
- package/lib/esm/parser/jsonParser.js +1236 -1214
- package/lib/esm/services/configuration.js +528 -528
- package/lib/esm/services/jsonCompletion.js +924 -918
- package/lib/esm/services/jsonDocumentSymbols.js +267 -267
- package/lib/esm/services/jsonFolding.js +120 -120
- package/lib/esm/services/jsonHover.js +109 -109
- package/lib/esm/services/jsonLinks.js +72 -72
- package/lib/esm/services/jsonSchemaService.js +593 -586
- package/lib/esm/services/jsonSelectionRanges.js +61 -61
- package/lib/esm/services/jsonValidation.js +151 -151
- package/lib/esm/utils/colors.js +68 -68
- package/lib/esm/utils/glob.js +124 -124
- package/lib/esm/utils/json.js +42 -42
- package/lib/esm/utils/objects.js +68 -68
- package/lib/esm/utils/strings.js +79 -64
- package/lib/umd/jsonContributions.d.ts +17 -17
- package/lib/umd/jsonContributions.js +12 -12
- package/lib/umd/jsonLanguageService.d.ts +29 -29
- package/lib/umd/jsonLanguageService.js +94 -90
- package/lib/umd/jsonLanguageTypes.d.ts +292 -279
- package/lib/umd/jsonLanguageTypes.js +103 -93
- package/lib/umd/jsonSchema.d.ts +89 -89
- package/lib/umd/jsonSchema.js +12 -12
- package/lib/umd/parser/jsonParser.js +1265 -1243
- package/lib/umd/services/configuration.js +541 -541
- package/lib/umd/services/jsonCompletion.js +938 -932
- package/lib/umd/services/jsonDocumentSymbols.js +281 -281
- package/lib/umd/services/jsonFolding.js +134 -134
- package/lib/umd/services/jsonHover.js +123 -123
- package/lib/umd/services/jsonLinks.js +86 -86
- package/lib/umd/services/jsonSchemaService.js +609 -602
- package/lib/umd/services/jsonSelectionRanges.js +75 -75
- package/lib/umd/services/jsonValidation.js +165 -165
- package/lib/umd/utils/colors.js +84 -84
- package/lib/umd/utils/glob.js +138 -138
- package/lib/umd/utils/json.js +56 -56
- package/lib/umd/utils/objects.js +87 -87
- package/lib/umd/utils/strings.js +98 -82
- package/package.json +11 -10
|
@@ -1,586 +1,593 @@
|
|
|
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
|
-
import * as Json from 'jsonc-parser';
|
|
6
|
-
import { URI } from 'vscode-uri';
|
|
7
|
-
import * as Strings from '../utils/strings';
|
|
8
|
-
import * as Parser from '../parser/jsonParser';
|
|
9
|
-
import * as nls from 'vscode-nls';
|
|
10
|
-
import { createRegex } from '../utils/glob';
|
|
11
|
-
import { isObject, isString } from '../utils/objects';
|
|
12
|
-
const localize = nls.loadMessageBundle();
|
|
13
|
-
const BANG = '!';
|
|
14
|
-
const PATH_SEP = '/';
|
|
15
|
-
class FilePatternAssociation {
|
|
16
|
-
constructor(pattern, uris) {
|
|
17
|
-
this.globWrappers = [];
|
|
18
|
-
try {
|
|
19
|
-
for (let patternString of pattern) {
|
|
20
|
-
const include = patternString[0] !== BANG;
|
|
21
|
-
if (!include) {
|
|
22
|
-
patternString = patternString.substring(1);
|
|
23
|
-
}
|
|
24
|
-
if (patternString.length > 0) {
|
|
25
|
-
if (patternString[0] === PATH_SEP) {
|
|
26
|
-
patternString = patternString.substring(1);
|
|
27
|
-
}
|
|
28
|
-
this.globWrappers.push({
|
|
29
|
-
regexp: createRegex('**/' + patternString, { extended: true, globstar: true }),
|
|
30
|
-
include: include,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
;
|
|
35
|
-
this.uris = uris;
|
|
36
|
-
}
|
|
37
|
-
catch (e) {
|
|
38
|
-
this.globWrappers.length = 0;
|
|
39
|
-
this.uris = [];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
matchesPattern(fileName) {
|
|
43
|
-
let match = false;
|
|
44
|
-
for (const { regexp, include } of this.globWrappers) {
|
|
45
|
-
if (regexp.test(fileName)) {
|
|
46
|
-
match = include;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return match;
|
|
50
|
-
}
|
|
51
|
-
getURIs() {
|
|
52
|
-
return this.uris;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
class SchemaHandle {
|
|
56
|
-
constructor(service, uri, unresolvedSchemaContent) {
|
|
57
|
-
this.service = service;
|
|
58
|
-
this.uri = uri;
|
|
59
|
-
this.dependencies = new Set();
|
|
60
|
-
this.anchors = undefined;
|
|
61
|
-
if (unresolvedSchemaContent) {
|
|
62
|
-
this.unresolvedSchema = this.service.promise.resolve(new UnresolvedSchema(unresolvedSchemaContent));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
getUnresolvedSchema() {
|
|
66
|
-
if (!this.unresolvedSchema) {
|
|
67
|
-
this.unresolvedSchema = this.service.loadSchema(this.uri);
|
|
68
|
-
}
|
|
69
|
-
return this.unresolvedSchema;
|
|
70
|
-
}
|
|
71
|
-
getResolvedSchema() {
|
|
72
|
-
if (!this.resolvedSchema) {
|
|
73
|
-
this.resolvedSchema = this.getUnresolvedSchema().then(unresolved => {
|
|
74
|
-
return this.service.resolveSchemaContent(unresolved, this);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
return this.resolvedSchema;
|
|
78
|
-
}
|
|
79
|
-
clearSchema() {
|
|
80
|
-
const hasChanges = !!this.unresolvedSchema;
|
|
81
|
-
this.resolvedSchema = undefined;
|
|
82
|
-
this.unresolvedSchema = undefined;
|
|
83
|
-
this.dependencies.clear();
|
|
84
|
-
this.anchors = undefined;
|
|
85
|
-
return hasChanges;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
export class UnresolvedSchema {
|
|
89
|
-
constructor(schema, errors = []) {
|
|
90
|
-
this.schema = schema;
|
|
91
|
-
this.errors = errors;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
export class ResolvedSchema {
|
|
95
|
-
constructor(schema, errors = [], warnings = [], schemaDraft) {
|
|
96
|
-
this.schema = schema;
|
|
97
|
-
this.errors = errors;
|
|
98
|
-
this.warnings = warnings;
|
|
99
|
-
this.schemaDraft = schemaDraft;
|
|
100
|
-
}
|
|
101
|
-
getSection(path) {
|
|
102
|
-
const schemaRef = this.getSectionRecursive(path, this.schema);
|
|
103
|
-
if (schemaRef) {
|
|
104
|
-
return Parser.asSchema(schemaRef);
|
|
105
|
-
}
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
getSectionRecursive(path, schema) {
|
|
109
|
-
if (!schema || typeof schema === 'boolean' || path.length === 0) {
|
|
110
|
-
return schema;
|
|
111
|
-
}
|
|
112
|
-
const next = path.shift();
|
|
113
|
-
if (schema.properties && typeof schema.properties[next]) {
|
|
114
|
-
return this.getSectionRecursive(path, schema.properties[next]);
|
|
115
|
-
}
|
|
116
|
-
else if (schema.patternProperties) {
|
|
117
|
-
for (const pattern of Object.keys(schema.patternProperties)) {
|
|
118
|
-
const regex = Strings.extendedRegExp(pattern);
|
|
119
|
-
if (regex?.test(next)) {
|
|
120
|
-
return this.getSectionRecursive(path, schema.patternProperties[pattern]);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
else if (typeof schema.additionalProperties === 'object') {
|
|
125
|
-
return this.getSectionRecursive(path, schema.additionalProperties);
|
|
126
|
-
}
|
|
127
|
-
else if (next.match('[0-9]+')) {
|
|
128
|
-
if (Array.isArray(schema.items)) {
|
|
129
|
-
const index = parseInt(next, 10);
|
|
130
|
-
if (!isNaN(index) && schema.items[index]) {
|
|
131
|
-
return this.getSectionRecursive(path, schema.items[index]);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
else if (schema.items) {
|
|
135
|
-
return this.getSectionRecursive(path, schema.items);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return undefined;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
export class JSONSchemaService {
|
|
142
|
-
constructor(requestService, contextService, promiseConstructor) {
|
|
143
|
-
this.contextService = contextService;
|
|
144
|
-
this.requestService = requestService;
|
|
145
|
-
this.promiseConstructor = promiseConstructor || Promise;
|
|
146
|
-
this.callOnDispose = [];
|
|
147
|
-
this.contributionSchemas = {};
|
|
148
|
-
this.contributionAssociations = [];
|
|
149
|
-
this.schemasById = {};
|
|
150
|
-
this.filePatternAssociations = [];
|
|
151
|
-
this.registeredSchemasIds = {};
|
|
152
|
-
}
|
|
153
|
-
getRegisteredSchemaIds(filter) {
|
|
154
|
-
return Object.keys(this.registeredSchemasIds).filter(id => {
|
|
155
|
-
const scheme = URI.parse(id).scheme;
|
|
156
|
-
return scheme !== 'schemaservice' && (!filter || filter(scheme));
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
get promise() {
|
|
160
|
-
return this.promiseConstructor;
|
|
161
|
-
}
|
|
162
|
-
dispose() {
|
|
163
|
-
while (this.callOnDispose.length > 0) {
|
|
164
|
-
this.callOnDispose.pop()();
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
onResourceChange(uri) {
|
|
168
|
-
// always clear this local cache when a resource changes
|
|
169
|
-
this.cachedSchemaForResource = undefined;
|
|
170
|
-
let hasChanges = false;
|
|
171
|
-
uri = normalizeId(uri);
|
|
172
|
-
const toWalk = [uri];
|
|
173
|
-
const all = Object.keys(this.schemasById).map(key => this.schemasById[key]);
|
|
174
|
-
while (toWalk.length) {
|
|
175
|
-
const curr = toWalk.pop();
|
|
176
|
-
for (let i = 0; i < all.length; i++) {
|
|
177
|
-
const handle = all[i];
|
|
178
|
-
if (handle && (handle.uri === curr || handle.dependencies.has(curr))) {
|
|
179
|
-
if (handle.uri !== curr) {
|
|
180
|
-
toWalk.push(handle.uri);
|
|
181
|
-
}
|
|
182
|
-
if (handle.clearSchema()) {
|
|
183
|
-
hasChanges = true;
|
|
184
|
-
}
|
|
185
|
-
all[i] = undefined;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return hasChanges;
|
|
190
|
-
}
|
|
191
|
-
setSchemaContributions(schemaContributions) {
|
|
192
|
-
if (schemaContributions.schemas) {
|
|
193
|
-
const schemas = schemaContributions.schemas;
|
|
194
|
-
for (const id in schemas) {
|
|
195
|
-
const normalizedId = normalizeId(id);
|
|
196
|
-
this.contributionSchemas[normalizedId] = this.addSchemaHandle(normalizedId, schemas[id]);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (Array.isArray(schemaContributions.schemaAssociations)) {
|
|
200
|
-
const schemaAssociations = schemaContributions.schemaAssociations;
|
|
201
|
-
for (let schemaAssociation of schemaAssociations) {
|
|
202
|
-
const uris = schemaAssociation.uris.map(normalizeId);
|
|
203
|
-
const association = this.addFilePatternAssociation(schemaAssociation.pattern, uris);
|
|
204
|
-
this.contributionAssociations.push(association);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
addSchemaHandle(id, unresolvedSchemaContent) {
|
|
209
|
-
const schemaHandle = new SchemaHandle(this, id, unresolvedSchemaContent);
|
|
210
|
-
this.schemasById[id] = schemaHandle;
|
|
211
|
-
return schemaHandle;
|
|
212
|
-
}
|
|
213
|
-
getOrAddSchemaHandle(id, unresolvedSchemaContent) {
|
|
214
|
-
return this.schemasById[id] || this.addSchemaHandle(id, unresolvedSchemaContent);
|
|
215
|
-
}
|
|
216
|
-
addFilePatternAssociation(pattern, uris) {
|
|
217
|
-
const fpa = new FilePatternAssociation(pattern, uris);
|
|
218
|
-
this.filePatternAssociations.push(fpa);
|
|
219
|
-
return fpa;
|
|
220
|
-
}
|
|
221
|
-
registerExternalSchema(uri, filePatterns, unresolvedSchemaContent) {
|
|
222
|
-
const id = normalizeId(uri);
|
|
223
|
-
this.registeredSchemasIds[id] = true;
|
|
224
|
-
this.cachedSchemaForResource = undefined;
|
|
225
|
-
if (filePatterns) {
|
|
226
|
-
this.addFilePatternAssociation(filePatterns, [id]);
|
|
227
|
-
}
|
|
228
|
-
return unresolvedSchemaContent ? this.addSchemaHandle(id, unresolvedSchemaContent) : this.getOrAddSchemaHandle(id);
|
|
229
|
-
}
|
|
230
|
-
clearExternalSchemas() {
|
|
231
|
-
this.schemasById = {};
|
|
232
|
-
this.filePatternAssociations = [];
|
|
233
|
-
this.registeredSchemasIds = {};
|
|
234
|
-
this.cachedSchemaForResource = undefined;
|
|
235
|
-
for (const id in this.contributionSchemas) {
|
|
236
|
-
this.schemasById[id] = this.contributionSchemas[id];
|
|
237
|
-
this.registeredSchemasIds[id] = true;
|
|
238
|
-
}
|
|
239
|
-
for (const contributionAssociation of this.contributionAssociations) {
|
|
240
|
-
this.filePatternAssociations.push(contributionAssociation);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
getResolvedSchema(schemaId) {
|
|
244
|
-
const id = normalizeId(schemaId);
|
|
245
|
-
const schemaHandle = this.schemasById[id];
|
|
246
|
-
if (schemaHandle) {
|
|
247
|
-
return schemaHandle.getResolvedSchema();
|
|
248
|
-
}
|
|
249
|
-
return this.promise.resolve(undefined);
|
|
250
|
-
}
|
|
251
|
-
loadSchema(url) {
|
|
252
|
-
if (!this.requestService) {
|
|
253
|
-
const errorMessage = localize('json.schema.norequestservice', 'Unable to load schema from \'{0}\'. No schema request service available', toDisplayString(url));
|
|
254
|
-
return this.promise.resolve(new UnresolvedSchema({}, [errorMessage]));
|
|
255
|
-
}
|
|
256
|
-
return this.requestService(url).then(content => {
|
|
257
|
-
if (!content) {
|
|
258
|
-
const errorMessage = localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': No content.', toDisplayString(url));
|
|
259
|
-
return new UnresolvedSchema({}, [errorMessage]);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
};
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
}
|
|
408
|
-
return
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const
|
|
424
|
-
for (const
|
|
425
|
-
if (isObject(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
return
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
return this.
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
return
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
+
import * as Json from 'jsonc-parser';
|
|
6
|
+
import { URI } from 'vscode-uri';
|
|
7
|
+
import * as Strings from '../utils/strings';
|
|
8
|
+
import * as Parser from '../parser/jsonParser';
|
|
9
|
+
import * as nls from 'vscode-nls';
|
|
10
|
+
import { createRegex } from '../utils/glob';
|
|
11
|
+
import { isObject, isString } from '../utils/objects';
|
|
12
|
+
const localize = nls.loadMessageBundle();
|
|
13
|
+
const BANG = '!';
|
|
14
|
+
const PATH_SEP = '/';
|
|
15
|
+
class FilePatternAssociation {
|
|
16
|
+
constructor(pattern, uris) {
|
|
17
|
+
this.globWrappers = [];
|
|
18
|
+
try {
|
|
19
|
+
for (let patternString of pattern) {
|
|
20
|
+
const include = patternString[0] !== BANG;
|
|
21
|
+
if (!include) {
|
|
22
|
+
patternString = patternString.substring(1);
|
|
23
|
+
}
|
|
24
|
+
if (patternString.length > 0) {
|
|
25
|
+
if (patternString[0] === PATH_SEP) {
|
|
26
|
+
patternString = patternString.substring(1);
|
|
27
|
+
}
|
|
28
|
+
this.globWrappers.push({
|
|
29
|
+
regexp: createRegex('**/' + patternString, { extended: true, globstar: true }),
|
|
30
|
+
include: include,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
;
|
|
35
|
+
this.uris = uris;
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
this.globWrappers.length = 0;
|
|
39
|
+
this.uris = [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
matchesPattern(fileName) {
|
|
43
|
+
let match = false;
|
|
44
|
+
for (const { regexp, include } of this.globWrappers) {
|
|
45
|
+
if (regexp.test(fileName)) {
|
|
46
|
+
match = include;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return match;
|
|
50
|
+
}
|
|
51
|
+
getURIs() {
|
|
52
|
+
return this.uris;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
class SchemaHandle {
|
|
56
|
+
constructor(service, uri, unresolvedSchemaContent) {
|
|
57
|
+
this.service = service;
|
|
58
|
+
this.uri = uri;
|
|
59
|
+
this.dependencies = new Set();
|
|
60
|
+
this.anchors = undefined;
|
|
61
|
+
if (unresolvedSchemaContent) {
|
|
62
|
+
this.unresolvedSchema = this.service.promise.resolve(new UnresolvedSchema(unresolvedSchemaContent));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
getUnresolvedSchema() {
|
|
66
|
+
if (!this.unresolvedSchema) {
|
|
67
|
+
this.unresolvedSchema = this.service.loadSchema(this.uri);
|
|
68
|
+
}
|
|
69
|
+
return this.unresolvedSchema;
|
|
70
|
+
}
|
|
71
|
+
getResolvedSchema() {
|
|
72
|
+
if (!this.resolvedSchema) {
|
|
73
|
+
this.resolvedSchema = this.getUnresolvedSchema().then(unresolved => {
|
|
74
|
+
return this.service.resolveSchemaContent(unresolved, this);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return this.resolvedSchema;
|
|
78
|
+
}
|
|
79
|
+
clearSchema() {
|
|
80
|
+
const hasChanges = !!this.unresolvedSchema;
|
|
81
|
+
this.resolvedSchema = undefined;
|
|
82
|
+
this.unresolvedSchema = undefined;
|
|
83
|
+
this.dependencies.clear();
|
|
84
|
+
this.anchors = undefined;
|
|
85
|
+
return hasChanges;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export class UnresolvedSchema {
|
|
89
|
+
constructor(schema, errors = []) {
|
|
90
|
+
this.schema = schema;
|
|
91
|
+
this.errors = errors;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export class ResolvedSchema {
|
|
95
|
+
constructor(schema, errors = [], warnings = [], schemaDraft) {
|
|
96
|
+
this.schema = schema;
|
|
97
|
+
this.errors = errors;
|
|
98
|
+
this.warnings = warnings;
|
|
99
|
+
this.schemaDraft = schemaDraft;
|
|
100
|
+
}
|
|
101
|
+
getSection(path) {
|
|
102
|
+
const schemaRef = this.getSectionRecursive(path, this.schema);
|
|
103
|
+
if (schemaRef) {
|
|
104
|
+
return Parser.asSchema(schemaRef);
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
getSectionRecursive(path, schema) {
|
|
109
|
+
if (!schema || typeof schema === 'boolean' || path.length === 0) {
|
|
110
|
+
return schema;
|
|
111
|
+
}
|
|
112
|
+
const next = path.shift();
|
|
113
|
+
if (schema.properties && typeof schema.properties[next]) {
|
|
114
|
+
return this.getSectionRecursive(path, schema.properties[next]);
|
|
115
|
+
}
|
|
116
|
+
else if (schema.patternProperties) {
|
|
117
|
+
for (const pattern of Object.keys(schema.patternProperties)) {
|
|
118
|
+
const regex = Strings.extendedRegExp(pattern);
|
|
119
|
+
if (regex?.test(next)) {
|
|
120
|
+
return this.getSectionRecursive(path, schema.patternProperties[pattern]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else if (typeof schema.additionalProperties === 'object') {
|
|
125
|
+
return this.getSectionRecursive(path, schema.additionalProperties);
|
|
126
|
+
}
|
|
127
|
+
else if (next.match('[0-9]+')) {
|
|
128
|
+
if (Array.isArray(schema.items)) {
|
|
129
|
+
const index = parseInt(next, 10);
|
|
130
|
+
if (!isNaN(index) && schema.items[index]) {
|
|
131
|
+
return this.getSectionRecursive(path, schema.items[index]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (schema.items) {
|
|
135
|
+
return this.getSectionRecursive(path, schema.items);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export class JSONSchemaService {
|
|
142
|
+
constructor(requestService, contextService, promiseConstructor) {
|
|
143
|
+
this.contextService = contextService;
|
|
144
|
+
this.requestService = requestService;
|
|
145
|
+
this.promiseConstructor = promiseConstructor || Promise;
|
|
146
|
+
this.callOnDispose = [];
|
|
147
|
+
this.contributionSchemas = {};
|
|
148
|
+
this.contributionAssociations = [];
|
|
149
|
+
this.schemasById = {};
|
|
150
|
+
this.filePatternAssociations = [];
|
|
151
|
+
this.registeredSchemasIds = {};
|
|
152
|
+
}
|
|
153
|
+
getRegisteredSchemaIds(filter) {
|
|
154
|
+
return Object.keys(this.registeredSchemasIds).filter(id => {
|
|
155
|
+
const scheme = URI.parse(id).scheme;
|
|
156
|
+
return scheme !== 'schemaservice' && (!filter || filter(scheme));
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
get promise() {
|
|
160
|
+
return this.promiseConstructor;
|
|
161
|
+
}
|
|
162
|
+
dispose() {
|
|
163
|
+
while (this.callOnDispose.length > 0) {
|
|
164
|
+
this.callOnDispose.pop()();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
onResourceChange(uri) {
|
|
168
|
+
// always clear this local cache when a resource changes
|
|
169
|
+
this.cachedSchemaForResource = undefined;
|
|
170
|
+
let hasChanges = false;
|
|
171
|
+
uri = normalizeId(uri);
|
|
172
|
+
const toWalk = [uri];
|
|
173
|
+
const all = Object.keys(this.schemasById).map(key => this.schemasById[key]);
|
|
174
|
+
while (toWalk.length) {
|
|
175
|
+
const curr = toWalk.pop();
|
|
176
|
+
for (let i = 0; i < all.length; i++) {
|
|
177
|
+
const handle = all[i];
|
|
178
|
+
if (handle && (handle.uri === curr || handle.dependencies.has(curr))) {
|
|
179
|
+
if (handle.uri !== curr) {
|
|
180
|
+
toWalk.push(handle.uri);
|
|
181
|
+
}
|
|
182
|
+
if (handle.clearSchema()) {
|
|
183
|
+
hasChanges = true;
|
|
184
|
+
}
|
|
185
|
+
all[i] = undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return hasChanges;
|
|
190
|
+
}
|
|
191
|
+
setSchemaContributions(schemaContributions) {
|
|
192
|
+
if (schemaContributions.schemas) {
|
|
193
|
+
const schemas = schemaContributions.schemas;
|
|
194
|
+
for (const id in schemas) {
|
|
195
|
+
const normalizedId = normalizeId(id);
|
|
196
|
+
this.contributionSchemas[normalizedId] = this.addSchemaHandle(normalizedId, schemas[id]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (Array.isArray(schemaContributions.schemaAssociations)) {
|
|
200
|
+
const schemaAssociations = schemaContributions.schemaAssociations;
|
|
201
|
+
for (let schemaAssociation of schemaAssociations) {
|
|
202
|
+
const uris = schemaAssociation.uris.map(normalizeId);
|
|
203
|
+
const association = this.addFilePatternAssociation(schemaAssociation.pattern, uris);
|
|
204
|
+
this.contributionAssociations.push(association);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
addSchemaHandle(id, unresolvedSchemaContent) {
|
|
209
|
+
const schemaHandle = new SchemaHandle(this, id, unresolvedSchemaContent);
|
|
210
|
+
this.schemasById[id] = schemaHandle;
|
|
211
|
+
return schemaHandle;
|
|
212
|
+
}
|
|
213
|
+
getOrAddSchemaHandle(id, unresolvedSchemaContent) {
|
|
214
|
+
return this.schemasById[id] || this.addSchemaHandle(id, unresolvedSchemaContent);
|
|
215
|
+
}
|
|
216
|
+
addFilePatternAssociation(pattern, uris) {
|
|
217
|
+
const fpa = new FilePatternAssociation(pattern, uris);
|
|
218
|
+
this.filePatternAssociations.push(fpa);
|
|
219
|
+
return fpa;
|
|
220
|
+
}
|
|
221
|
+
registerExternalSchema(uri, filePatterns, unresolvedSchemaContent) {
|
|
222
|
+
const id = normalizeId(uri);
|
|
223
|
+
this.registeredSchemasIds[id] = true;
|
|
224
|
+
this.cachedSchemaForResource = undefined;
|
|
225
|
+
if (filePatterns) {
|
|
226
|
+
this.addFilePatternAssociation(filePatterns, [id]);
|
|
227
|
+
}
|
|
228
|
+
return unresolvedSchemaContent ? this.addSchemaHandle(id, unresolvedSchemaContent) : this.getOrAddSchemaHandle(id);
|
|
229
|
+
}
|
|
230
|
+
clearExternalSchemas() {
|
|
231
|
+
this.schemasById = {};
|
|
232
|
+
this.filePatternAssociations = [];
|
|
233
|
+
this.registeredSchemasIds = {};
|
|
234
|
+
this.cachedSchemaForResource = undefined;
|
|
235
|
+
for (const id in this.contributionSchemas) {
|
|
236
|
+
this.schemasById[id] = this.contributionSchemas[id];
|
|
237
|
+
this.registeredSchemasIds[id] = true;
|
|
238
|
+
}
|
|
239
|
+
for (const contributionAssociation of this.contributionAssociations) {
|
|
240
|
+
this.filePatternAssociations.push(contributionAssociation);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
getResolvedSchema(schemaId) {
|
|
244
|
+
const id = normalizeId(schemaId);
|
|
245
|
+
const schemaHandle = this.schemasById[id];
|
|
246
|
+
if (schemaHandle) {
|
|
247
|
+
return schemaHandle.getResolvedSchema();
|
|
248
|
+
}
|
|
249
|
+
return this.promise.resolve(undefined);
|
|
250
|
+
}
|
|
251
|
+
loadSchema(url) {
|
|
252
|
+
if (!this.requestService) {
|
|
253
|
+
const errorMessage = localize('json.schema.norequestservice', 'Unable to load schema from \'{0}\'. No schema request service available', toDisplayString(url));
|
|
254
|
+
return this.promise.resolve(new UnresolvedSchema({}, [errorMessage]));
|
|
255
|
+
}
|
|
256
|
+
return this.requestService(url).then(content => {
|
|
257
|
+
if (!content) {
|
|
258
|
+
const errorMessage = localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': No content.', toDisplayString(url));
|
|
259
|
+
return new UnresolvedSchema({}, [errorMessage]);
|
|
260
|
+
}
|
|
261
|
+
const errors = [];
|
|
262
|
+
if (content.charCodeAt(0) === 65279) {
|
|
263
|
+
errors.push(localize('json.schema.encodingWithBOM', 'Problem reading content from \'{0}\': UTF-8 with BOM detected, only UTF 8 is allowed.', toDisplayString(url)));
|
|
264
|
+
content = content.trimStart();
|
|
265
|
+
}
|
|
266
|
+
let schemaContent = {};
|
|
267
|
+
const jsonErrors = [];
|
|
268
|
+
schemaContent = Json.parse(content, jsonErrors);
|
|
269
|
+
if (jsonErrors.length) {
|
|
270
|
+
errors.push(localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': Parse error at offset {1}.', toDisplayString(url), jsonErrors[0].offset));
|
|
271
|
+
}
|
|
272
|
+
return new UnresolvedSchema(schemaContent, errors);
|
|
273
|
+
}, (error) => {
|
|
274
|
+
let errorMessage = error.toString();
|
|
275
|
+
const errorSplit = error.toString().split('Error: ');
|
|
276
|
+
if (errorSplit.length > 1) {
|
|
277
|
+
// more concise error message, URL and context are attached by caller anyways
|
|
278
|
+
errorMessage = errorSplit[1];
|
|
279
|
+
}
|
|
280
|
+
if (Strings.endsWith(errorMessage, '.')) {
|
|
281
|
+
errorMessage = errorMessage.substr(0, errorMessage.length - 1);
|
|
282
|
+
}
|
|
283
|
+
return new UnresolvedSchema({}, [localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': {1}.', toDisplayString(url), errorMessage)]);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
resolveSchemaContent(schemaToResolve, handle) {
|
|
287
|
+
const resolveErrors = schemaToResolve.errors.slice(0);
|
|
288
|
+
const schema = schemaToResolve.schema;
|
|
289
|
+
let schemaDraft = schema.$schema ? normalizeId(schema.$schema) : undefined;
|
|
290
|
+
if (schemaDraft === 'http://json-schema.org/draft-03/schema') {
|
|
291
|
+
return this.promise.resolve(new ResolvedSchema({}, [localize('json.schema.draft03.notsupported', "Draft-03 schemas are not supported.")], [], schemaDraft));
|
|
292
|
+
}
|
|
293
|
+
let usesUnsupportedFeatures = new Set();
|
|
294
|
+
const contextService = this.contextService;
|
|
295
|
+
const findSectionByJSONPointer = (schema, path) => {
|
|
296
|
+
path = decodeURIComponent(path);
|
|
297
|
+
let current = schema;
|
|
298
|
+
if (path[0] === '/') {
|
|
299
|
+
path = path.substring(1);
|
|
300
|
+
}
|
|
301
|
+
path.split('/').some((part) => {
|
|
302
|
+
part = part.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
303
|
+
current = current[part];
|
|
304
|
+
return !current;
|
|
305
|
+
});
|
|
306
|
+
return current;
|
|
307
|
+
};
|
|
308
|
+
const findSchemaById = (schema, handle, id) => {
|
|
309
|
+
if (!handle.anchors) {
|
|
310
|
+
handle.anchors = collectAnchors(schema);
|
|
311
|
+
}
|
|
312
|
+
return handle.anchors.get(id);
|
|
313
|
+
};
|
|
314
|
+
const merge = (target, section) => {
|
|
315
|
+
for (const key in section) {
|
|
316
|
+
if (section.hasOwnProperty(key) && key !== 'id' && key !== '$id') {
|
|
317
|
+
target[key] = section[key];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
const mergeRef = (target, sourceRoot, sourceHandle, refSegment) => {
|
|
322
|
+
let section;
|
|
323
|
+
if (refSegment === undefined || refSegment.length === 0) {
|
|
324
|
+
section = sourceRoot;
|
|
325
|
+
}
|
|
326
|
+
else if (refSegment.charAt(0) === '/') {
|
|
327
|
+
// A $ref to a JSON Pointer (i.e #/definitions/foo)
|
|
328
|
+
section = findSectionByJSONPointer(sourceRoot, refSegment);
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
// A $ref to a sub-schema with an $id (i.e #hello)
|
|
332
|
+
section = findSchemaById(sourceRoot, sourceHandle, refSegment);
|
|
333
|
+
}
|
|
334
|
+
if (section) {
|
|
335
|
+
merge(target, section);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
resolveErrors.push(localize('json.schema.invalidid', '$ref \'{0}\' in \'{1}\' can not be resolved.', refSegment, sourceHandle.uri));
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
const resolveExternalLink = (node, uri, refSegment, parentHandle) => {
|
|
342
|
+
if (contextService && !/^[A-Za-z][A-Za-z0-9+\-.+]*:\/\/.*/.test(uri)) {
|
|
343
|
+
uri = contextService.resolveRelativePath(uri, parentHandle.uri);
|
|
344
|
+
}
|
|
345
|
+
uri = normalizeId(uri);
|
|
346
|
+
const referencedHandle = this.getOrAddSchemaHandle(uri);
|
|
347
|
+
return referencedHandle.getUnresolvedSchema().then(unresolvedSchema => {
|
|
348
|
+
parentHandle.dependencies.add(uri);
|
|
349
|
+
if (unresolvedSchema.errors.length) {
|
|
350
|
+
const loc = refSegment ? uri + '#' + refSegment : uri;
|
|
351
|
+
resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
|
|
352
|
+
}
|
|
353
|
+
mergeRef(node, unresolvedSchema.schema, referencedHandle, refSegment);
|
|
354
|
+
return resolveRefs(node, unresolvedSchema.schema, referencedHandle);
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
const resolveRefs = (node, parentSchema, parentHandle) => {
|
|
358
|
+
const openPromises = [];
|
|
359
|
+
this.traverseNodes(node, next => {
|
|
360
|
+
const seenRefs = new Set();
|
|
361
|
+
while (next.$ref) {
|
|
362
|
+
const ref = next.$ref;
|
|
363
|
+
const segments = ref.split('#', 2);
|
|
364
|
+
delete next.$ref;
|
|
365
|
+
if (segments[0].length > 0) {
|
|
366
|
+
// This is a reference to an external schema
|
|
367
|
+
openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentHandle));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
// This is a reference inside the current schema
|
|
372
|
+
if (!seenRefs.has(ref)) {
|
|
373
|
+
const id = segments[1];
|
|
374
|
+
mergeRef(next, parentSchema, parentHandle, id);
|
|
375
|
+
seenRefs.add(ref);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (next.$recursiveRef) {
|
|
380
|
+
usesUnsupportedFeatures.add('$recursiveRef');
|
|
381
|
+
}
|
|
382
|
+
if (next.$dynamicRef) {
|
|
383
|
+
usesUnsupportedFeatures.add('$dynamicRef');
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return this.promise.all(openPromises);
|
|
387
|
+
};
|
|
388
|
+
const collectAnchors = (root) => {
|
|
389
|
+
const result = new Map();
|
|
390
|
+
this.traverseNodes(root, next => {
|
|
391
|
+
const id = next.$id || next.id;
|
|
392
|
+
const anchor = isString(id) && id.charAt(0) === '#' ? id.substring(1) : next.$anchor;
|
|
393
|
+
if (anchor) {
|
|
394
|
+
if (result.has(anchor)) {
|
|
395
|
+
resolveErrors.push(localize('json.schema.duplicateid', 'Duplicate anchor declaration: \'{0}\'', anchor));
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
result.set(anchor, next);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (next.$recursiveAnchor) {
|
|
402
|
+
usesUnsupportedFeatures.add('$recursiveAnchor');
|
|
403
|
+
}
|
|
404
|
+
if (next.$dynamicAnchor) {
|
|
405
|
+
usesUnsupportedFeatures.add('$dynamicAnchor');
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
return result;
|
|
409
|
+
};
|
|
410
|
+
return resolveRefs(schema, schema, handle).then(_ => {
|
|
411
|
+
let resolveWarnings = [];
|
|
412
|
+
if (usesUnsupportedFeatures.size) {
|
|
413
|
+
resolveWarnings.push(localize('json.schema.warnings', 'The schema uses meta-schema features ({0}) that are not yet supported by the validator.', Array.from(usesUnsupportedFeatures.keys()).join(', ')));
|
|
414
|
+
}
|
|
415
|
+
return new ResolvedSchema(schema, resolveErrors, resolveWarnings, schemaDraft);
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
traverseNodes(root, handle) {
|
|
419
|
+
if (!root || typeof root !== 'object') {
|
|
420
|
+
return Promise.resolve(null);
|
|
421
|
+
}
|
|
422
|
+
const seen = new Set();
|
|
423
|
+
const collectEntries = (...entries) => {
|
|
424
|
+
for (const entry of entries) {
|
|
425
|
+
if (isObject(entry)) {
|
|
426
|
+
toWalk.push(entry);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const collectMapEntries = (...maps) => {
|
|
431
|
+
for (const map of maps) {
|
|
432
|
+
if (isObject(map)) {
|
|
433
|
+
for (const k in map) {
|
|
434
|
+
const key = k;
|
|
435
|
+
const entry = map[key];
|
|
436
|
+
if (isObject(entry)) {
|
|
437
|
+
toWalk.push(entry);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const collectArrayEntries = (...arrays) => {
|
|
444
|
+
for (const array of arrays) {
|
|
445
|
+
if (Array.isArray(array)) {
|
|
446
|
+
for (const entry of array) {
|
|
447
|
+
if (isObject(entry)) {
|
|
448
|
+
toWalk.push(entry);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
const collectEntryOrArrayEntries = (items) => {
|
|
455
|
+
if (Array.isArray(items)) {
|
|
456
|
+
for (const entry of items) {
|
|
457
|
+
if (isObject(entry)) {
|
|
458
|
+
toWalk.push(entry);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
else if (isObject(items)) {
|
|
463
|
+
toWalk.push(items);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const toWalk = [root];
|
|
467
|
+
let next = toWalk.pop();
|
|
468
|
+
while (next) {
|
|
469
|
+
if (!seen.has(next)) {
|
|
470
|
+
seen.add(next);
|
|
471
|
+
handle(next);
|
|
472
|
+
collectEntries(next.additionalItems, next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else, next.unevaluatedItems, next.unevaluatedProperties);
|
|
473
|
+
collectMapEntries(next.definitions, next.$defs, next.properties, next.patternProperties, next.dependencies, next.dependentSchemas);
|
|
474
|
+
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.prefixItems);
|
|
475
|
+
collectEntryOrArrayEntries(next.items);
|
|
476
|
+
}
|
|
477
|
+
next = toWalk.pop();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
;
|
|
481
|
+
getSchemaFromProperty(resource, document) {
|
|
482
|
+
if (document.root?.type === 'object') {
|
|
483
|
+
for (const p of document.root.properties) {
|
|
484
|
+
if (p.keyNode.value === '$schema' && p.valueNode?.type === 'string') {
|
|
485
|
+
let schemaId = p.valueNode.value;
|
|
486
|
+
if (this.contextService && !/^\w[\w\d+.-]*:/.test(schemaId)) { // has scheme
|
|
487
|
+
schemaId = this.contextService.resolveRelativePath(schemaId, resource);
|
|
488
|
+
}
|
|
489
|
+
return schemaId;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return undefined;
|
|
494
|
+
}
|
|
495
|
+
getAssociatedSchemas(resource) {
|
|
496
|
+
const seen = Object.create(null);
|
|
497
|
+
const schemas = [];
|
|
498
|
+
const normalizedResource = normalizeResourceForMatching(resource);
|
|
499
|
+
for (const entry of this.filePatternAssociations) {
|
|
500
|
+
if (entry.matchesPattern(normalizedResource)) {
|
|
501
|
+
for (const schemaId of entry.getURIs()) {
|
|
502
|
+
if (!seen[schemaId]) {
|
|
503
|
+
schemas.push(schemaId);
|
|
504
|
+
seen[schemaId] = true;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return schemas;
|
|
510
|
+
}
|
|
511
|
+
getSchemaURIsForResource(resource, document) {
|
|
512
|
+
let schemeId = document && this.getSchemaFromProperty(resource, document);
|
|
513
|
+
if (schemeId) {
|
|
514
|
+
return [schemeId];
|
|
515
|
+
}
|
|
516
|
+
return this.getAssociatedSchemas(resource);
|
|
517
|
+
}
|
|
518
|
+
getSchemaForResource(resource, document) {
|
|
519
|
+
if (document) {
|
|
520
|
+
// first use $schema if present
|
|
521
|
+
let schemeId = this.getSchemaFromProperty(resource, document);
|
|
522
|
+
if (schemeId) {
|
|
523
|
+
const id = normalizeId(schemeId);
|
|
524
|
+
return this.getOrAddSchemaHandle(id).getResolvedSchema();
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (this.cachedSchemaForResource && this.cachedSchemaForResource.resource === resource) {
|
|
528
|
+
return this.cachedSchemaForResource.resolvedSchema;
|
|
529
|
+
}
|
|
530
|
+
const schemas = this.getAssociatedSchemas(resource);
|
|
531
|
+
const resolvedSchema = schemas.length > 0 ? this.createCombinedSchema(resource, schemas).getResolvedSchema() : this.promise.resolve(undefined);
|
|
532
|
+
this.cachedSchemaForResource = { resource, resolvedSchema };
|
|
533
|
+
return resolvedSchema;
|
|
534
|
+
}
|
|
535
|
+
createCombinedSchema(resource, schemaIds) {
|
|
536
|
+
if (schemaIds.length === 1) {
|
|
537
|
+
return this.getOrAddSchemaHandle(schemaIds[0]);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
const combinedSchemaId = 'schemaservice://combinedSchema/' + encodeURIComponent(resource);
|
|
541
|
+
const combinedSchema = {
|
|
542
|
+
allOf: schemaIds.map(schemaId => ({ $ref: schemaId }))
|
|
543
|
+
};
|
|
544
|
+
return this.addSchemaHandle(combinedSchemaId, combinedSchema);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
getMatchingSchemas(document, jsonDocument, schema) {
|
|
548
|
+
if (schema) {
|
|
549
|
+
const id = schema.id || ('schemaservice://untitled/matchingSchemas/' + idCounter++);
|
|
550
|
+
const handle = this.addSchemaHandle(id, schema);
|
|
551
|
+
return handle.getResolvedSchema().then(resolvedSchema => {
|
|
552
|
+
return jsonDocument.getMatchingSchemas(resolvedSchema.schema).filter(s => !s.inverted);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
return this.getSchemaForResource(document.uri, jsonDocument).then(schema => {
|
|
556
|
+
if (schema) {
|
|
557
|
+
return jsonDocument.getMatchingSchemas(schema.schema).filter(s => !s.inverted);
|
|
558
|
+
}
|
|
559
|
+
return [];
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
let idCounter = 0;
|
|
564
|
+
function normalizeId(id) {
|
|
565
|
+
// remove trailing '#', normalize drive capitalization
|
|
566
|
+
try {
|
|
567
|
+
return URI.parse(id).toString(true);
|
|
568
|
+
}
|
|
569
|
+
catch (e) {
|
|
570
|
+
return id;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function normalizeResourceForMatching(resource) {
|
|
574
|
+
// remove queries and fragments, normalize drive capitalization
|
|
575
|
+
try {
|
|
576
|
+
return URI.parse(resource).with({ fragment: null, query: null }).toString(true);
|
|
577
|
+
}
|
|
578
|
+
catch (e) {
|
|
579
|
+
return resource;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function toDisplayString(url) {
|
|
583
|
+
try {
|
|
584
|
+
const uri = URI.parse(url);
|
|
585
|
+
if (uri.scheme === 'file') {
|
|
586
|
+
return uri.fsPath;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
catch (e) {
|
|
590
|
+
// ignore
|
|
591
|
+
}
|
|
592
|
+
return url;
|
|
593
|
+
}
|