vscode-json-languageservice 4.1.9 → 4.2.0-next.2

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 CHANGED
@@ -1,4 +1,10 @@
1
1
 
2
+
3
+ 4.2.0 /
4
+ ================
5
+ * new API `LanguageService.getLanguageStatus`
6
+ * support for $ref with $id
7
+
2
8
  4.1.6 / 2021-07-16
3
9
  ================
4
10
  * Replace minimatch with glob-to-regexp
@@ -1,4 +1,4 @@
1
- import { Thenable, ASTNode, Color, ColorInformation, ColorPresentation, LanguageServiceParams, LanguageSettings, DocumentLanguageSettings, FoldingRange, JSONSchema, SelectionRange, FoldingRangesContext, DocumentSymbolsContext, ColorInformationContext as DocumentColorsContext, TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema } from './jsonLanguageTypes';
1
+ import { Thenable, ASTNode, Color, ColorInformation, ColorPresentation, LanguageServiceParams, LanguageSettings, DocumentLanguageSettings, FoldingRange, JSONSchema, SelectionRange, FoldingRangesContext, DocumentSymbolsContext, ColorInformationContext as DocumentColorsContext, TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema, JSONLanguageStatus } from './jsonLanguageTypes';
2
2
  import { DocumentLink } from 'vscode-languageserver-types';
3
3
  export declare type JSONDocument = {
4
4
  root: ASTNode | undefined;
@@ -12,6 +12,7 @@ export interface LanguageService {
12
12
  newJSONDocument(rootNode: ASTNode, syntaxDiagnostics?: Diagnostic[]): JSONDocument;
13
13
  resetSchema(uri: string): boolean;
14
14
  getMatchingSchemas(document: TextDocument, jsonDocument: JSONDocument, schema?: JSONSchema): Thenable<MatchingSchema[]>;
15
+ getLanguageStatus(document: TextDocument, jsonDocument: JSONDocument): JSONLanguageStatus;
15
16
  doResolve(item: CompletionItem): Thenable<CompletionItem>;
16
17
  doComplete(document: TextDocument, position: Position, doc: JSONDocument): Thenable<CompletionList | null>;
17
18
  findDocumentSymbols(document: TextDocument, doc: JSONDocument, context?: DocumentSymbolsContext): SymbolInformation[];
@@ -35,6 +35,7 @@ export function getLanguageService(params) {
35
35
  },
36
36
  resetSchema: function (uri) { return jsonSchemaService.onResourceChange(uri); },
37
37
  doValidation: jsonValidation.doValidation.bind(jsonValidation),
38
+ getLanguageStatus: jsonValidation.getLanguageStatus.bind(jsonValidation),
38
39
  parseJSONDocument: function (document) { return parseJSON(document, { collectComments: true }); },
39
40
  newJSONDocument: function (root, diagnostics) { return newJSONDocument(root, diagnostics); },
40
41
  getMatchingSchemas: jsonSchemaService.getMatchingSchemas.bind(jsonSchemaService),
@@ -74,6 +74,9 @@ export interface MatchingSchema {
74
74
  node: ASTNode;
75
75
  schema: JSONSchema;
76
76
  }
77
+ export interface JSONLanguageStatus {
78
+ schemas: string[];
79
+ }
77
80
  export interface LanguageSettings {
78
81
  /**
79
82
  * If set, the validator will return syntax and semantic errors.
@@ -28,7 +28,10 @@ var formats = {
28
28
  'date-time': { errorMessage: localize('dateTimeFormatWarning', 'String is not a RFC3339 date-time.'), pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$/i },
29
29
  'date': { errorMessage: localize('dateFormatWarning', 'String is not a RFC3339 date.'), pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/i },
30
30
  'time': { errorMessage: localize('timeFormatWarning', 'String is not a RFC3339 time.'), pattern: /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$/i },
31
- 'email': { errorMessage: localize('emailFormatWarning', 'String is not an e-mail address.'), pattern: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ }
31
+ 'email': { errorMessage: localize('emailFormatWarning', 'String is not an e-mail address.'), pattern: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}))$/ },
32
+ 'hostname': { errorMessage: localize('hostnameFormatWarning', 'String is not a hostname.'), pattern: /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i },
33
+ 'ipv4': { errorMessage: localize('ipv4FormatWarning', 'String is not an IPv4 address.'), pattern: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/ },
34
+ 'ipv6': { errorMessage: localize('ipv6FormatWarning', 'String is not an IPv6 address.'), pattern: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i },
32
35
  };
33
36
  var ASTNodeImpl = /** @class */ (function () {
34
37
  function ASTNodeImpl(parent, offset, length) {
@@ -617,7 +620,7 @@ function validate(n, schema, validationResult, matchingSchemas) {
617
620
  }
618
621
  if (isString(schema.pattern)) {
619
622
  var regex = extendedRegExp(schema.pattern);
620
- if (!regex.test(node.value)) {
623
+ if (!(regex === null || regex === void 0 ? void 0 : regex.test(node.value))) {
621
624
  validationResult.problems.push({
622
625
  location: { offset: node.offset, length: node.length },
623
626
  message: schema.patternErrorMessage || schema.errorMessage || localize('patternWarning', 'String does not match the pattern of "{0}".', schema.pattern)
@@ -655,6 +658,9 @@ function validate(n, schema, validationResult, matchingSchemas) {
655
658
  case 'date':
656
659
  case 'time':
657
660
  case 'email':
661
+ case 'hostname':
662
+ case 'ipv4':
663
+ case 'ipv6':
658
664
  var format = formats[schema.format];
659
665
  if (!node.value || !format.pattern.exec(node.value)) {
660
666
  validationResult.problems.push({
@@ -811,7 +817,7 @@ function validate(n, schema, validationResult, matchingSchemas) {
811
817
  var regex = extendedRegExp(propertyPattern);
812
818
  for (var _h = 0, _j = unprocessedProperties.slice(0); _h < _j.length; _h++) {
813
819
  var propertyName = _j[_h];
814
- if (regex.test(propertyName)) {
820
+ if (regex === null || regex === void 0 ? void 0 : regex.test(propertyName)) {
815
821
  propertyProcessed(propertyName);
816
822
  var child = seenKeys[propertyName];
817
823
  if (child) {
@@ -440,7 +440,7 @@ var JSONCompletion = /** @class */ (function () {
440
440
  for (var _a = 0, _b = Object.keys(s.schema.patternProperties); _a < _b.length; _a++) {
441
441
  var pattern = _b[_a];
442
442
  var regex = extendedRegExp(pattern);
443
- if (regex.test(parentKey)) {
443
+ if (regex === null || regex === void 0 ? void 0 : regex.test(parentKey)) {
444
444
  propertyMatched = true;
445
445
  var propertySchema = s.schema.patternProperties[pattern];
446
446
  this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
@@ -55,17 +55,18 @@ var FilePatternAssociation = /** @class */ (function () {
55
55
  return FilePatternAssociation;
56
56
  }());
57
57
  var SchemaHandle = /** @class */ (function () {
58
- function SchemaHandle(service, url, unresolvedSchemaContent) {
58
+ function SchemaHandle(service, uri, unresolvedSchemaContent) {
59
59
  this.service = service;
60
- this.url = url;
61
- this.dependencies = {};
60
+ this.uri = uri;
61
+ this.dependencies = new Set();
62
+ this.anchors = new Map();
62
63
  if (unresolvedSchemaContent) {
63
64
  this.unresolvedSchema = this.service.promise.resolve(new UnresolvedSchema(unresolvedSchemaContent));
64
65
  }
65
66
  }
66
67
  SchemaHandle.prototype.getUnresolvedSchema = function () {
67
68
  if (!this.unresolvedSchema) {
68
- this.unresolvedSchema = this.service.loadSchema(this.url);
69
+ this.unresolvedSchema = this.service.loadSchema(this.uri);
69
70
  }
70
71
  return this.unresolvedSchema;
71
72
  };
@@ -73,15 +74,18 @@ var SchemaHandle = /** @class */ (function () {
73
74
  var _this = this;
74
75
  if (!this.resolvedSchema) {
75
76
  this.resolvedSchema = this.getUnresolvedSchema().then(function (unresolved) {
76
- return _this.service.resolveSchemaContent(unresolved, _this.url, _this.dependencies);
77
+ return _this.service.resolveSchemaContent(unresolved, _this);
77
78
  });
78
79
  }
79
80
  return this.resolvedSchema;
80
81
  };
81
82
  SchemaHandle.prototype.clearSchema = function () {
83
+ var hasChanges = !!this.unresolvedSchema;
82
84
  this.resolvedSchema = undefined;
83
85
  this.unresolvedSchema = undefined;
84
- this.dependencies = {};
86
+ this.dependencies.clear();
87
+ this.anchors.clear();
88
+ return hasChanges;
85
89
  };
86
90
  return SchemaHandle;
87
91
  }());
@@ -119,7 +123,7 @@ var ResolvedSchema = /** @class */ (function () {
119
123
  for (var _i = 0, _a = Object.keys(schema.patternProperties); _i < _a.length; _i++) {
120
124
  var pattern = _a[_i];
121
125
  var regex = Strings.extendedRegExp(pattern);
122
- if (regex.test(next)) {
126
+ if (regex === null || regex === void 0 ? void 0 : regex.test(next)) {
123
127
  return this.getSectionRecursive(path, schema.patternProperties[pattern]);
124
128
  }
125
129
  }
@@ -185,13 +189,14 @@ var JSONSchemaService = /** @class */ (function () {
185
189
  var curr = toWalk.pop();
186
190
  for (var i = 0; i < all.length; i++) {
187
191
  var handle = all[i];
188
- if (handle && (handle.url === curr || handle.dependencies[curr])) {
189
- if (handle.url !== curr) {
190
- toWalk.push(handle.url);
192
+ if (handle && (handle.uri === curr || handle.dependencies.has(curr))) {
193
+ if (handle.uri !== curr) {
194
+ toWalk.push(handle.uri);
195
+ }
196
+ if (handle.clearSchema()) {
197
+ hasChanges = true;
191
198
  }
192
- handle.clearSchema();
193
199
  all[i] = undefined;
194
- hasChanges = true;
195
200
  }
196
201
  }
197
202
  }
@@ -287,7 +292,7 @@ var JSONSchemaService = /** @class */ (function () {
287
292
  return new UnresolvedSchema({}, [localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': {1}.', toDisplayString(url), errorMessage)]);
288
293
  });
289
294
  };
290
- JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, schemaURL, dependencies) {
295
+ JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, handle) {
291
296
  var _this = this;
292
297
  var resolveErrors = schemaToResolve.errors.slice(0);
293
298
  var schema = schemaToResolve.schema;
@@ -299,6 +304,9 @@ var JSONSchemaService = /** @class */ (function () {
299
304
  else if (id === 'https://json-schema.org/draft/2019-09/schema') {
300
305
  resolveErrors.push(localize('json.schema.draft201909.notsupported', "Draft 2019-09 schemas are not yet fully supported."));
301
306
  }
307
+ else if (id === 'https://json-schema.org/draft/2020-12/schema') {
308
+ resolveErrors.push(localize('json.schema.draft202012.notsupported', "Draft 2020-12 schemas are not yet fully supported."));
309
+ }
302
310
  }
303
311
  var contextService = this.contextService;
304
312
  var findSection = function (schema, path) {
@@ -316,42 +324,90 @@ var JSONSchemaService = /** @class */ (function () {
316
324
  });
317
325
  return current;
318
326
  };
319
- var merge = function (target, sourceRoot, sourceURI, refSegment) {
327
+ var merge = function (target, section) {
328
+ for (var key in section) {
329
+ if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
330
+ target[key] = section[key];
331
+ }
332
+ }
333
+ };
334
+ var mergeByJsonPointer = function (target, sourceRoot, sourceURI, refSegment) {
320
335
  var path = refSegment ? decodeURIComponent(refSegment) : undefined;
321
336
  var section = findSection(sourceRoot, path);
322
337
  if (section) {
323
- for (var key in section) {
324
- if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
325
- target[key] = section[key];
326
- }
327
- }
338
+ merge(target, section);
328
339
  }
329
340
  else {
330
341
  resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in \'{1}\' can not be resolved.', path, sourceURI));
331
342
  }
332
343
  };
333
- var resolveExternalLink = function (node, uri, refSegment, parentSchemaURL, parentSchemaDependencies) {
344
+ var isSubSchemaRef = function (refSegment) {
345
+ // Check if the first character is not '/' to determine whether it's a sub schema reference or a JSON Pointer
346
+ return !!refSegment && refSegment.charAt(0) !== '/';
347
+ };
348
+ var reconstructRefURI = function (uri, fragment, separator) {
349
+ if (separator === void 0) { separator = '#'; }
350
+ return normalizeId("" + uri + separator + fragment);
351
+ };
352
+ // To find which $refs point to which $ids we keep two maps:
353
+ // pendingSubSchemas '$id' we expect to encounter (if they exist)
354
+ // handle.anchors for the ones we have encountered
355
+ var pendingSubSchemas = new Map();
356
+ var tryMergeSubSchema = function (target, id, handle) {
357
+ // Get the full URI for the current schema to avoid matching schema1#hello and schema2#hello to the same
358
+ // reference by accident
359
+ var fullId = reconstructRefURI(handle.uri, id);
360
+ var resolved = handle.anchors.get(fullId);
361
+ if (resolved) {
362
+ merge(target, resolved);
363
+ return true; // return success
364
+ }
365
+ // This subschema has not been resolved yet
366
+ // Remember the target to merge later once resolved
367
+ var pending = pendingSubSchemas.get(fullId);
368
+ if (!pending) {
369
+ pending = [];
370
+ pendingSubSchemas.set(fullId, pending);
371
+ }
372
+ pending.push(target);
373
+ return false; // return failure - merge didn't occur
374
+ };
375
+ var resolveExternalLink = function (node, uri, refSegment, parentHandle) {
334
376
  if (contextService && !/^[A-Za-z][A-Za-z0-9+\-.+]*:\/\/.*/.test(uri)) {
335
- uri = contextService.resolveRelativePath(uri, parentSchemaURL);
377
+ uri = contextService.resolveRelativePath(uri, parentHandle.uri);
336
378
  }
337
379
  uri = normalizeId(uri);
338
380
  var referencedHandle = _this.getOrAddSchemaHandle(uri);
339
381
  return referencedHandle.getUnresolvedSchema().then(function (unresolvedSchema) {
340
- parentSchemaDependencies[uri] = true;
382
+ parentHandle.dependencies.add(uri);
341
383
  if (unresolvedSchema.errors.length) {
342
384
  var loc = refSegment ? uri + '#' + refSegment : uri;
343
385
  resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
344
386
  }
345
- merge(node, unresolvedSchema.schema, uri, refSegment);
346
- return resolveRefs(node, unresolvedSchema.schema, uri, referencedHandle.dependencies);
387
+ // A placeholder promise that might execute later a ref resolution for the newly resolved schema
388
+ var externalLinkPromise = Promise.resolve(true);
389
+ if (refSegment === undefined || !isSubSchemaRef(refSegment)) {
390
+ // This is not a sub schema, merge the regular way
391
+ mergeByJsonPointer(node, unresolvedSchema.schema, uri, refSegment);
392
+ }
393
+ else {
394
+ // This is a reference to a subschema
395
+ if (!tryMergeSubSchema(node, refSegment, referencedHandle)) {
396
+ // We weren't able to merge currently so we'll try to resolve this schema first to obtain subschemas
397
+ // that could be missed
398
+ // to improve: it would be enough to find the nodes, no need to resolve the full schema
399
+ externalLinkPromise = resolveRefs(unresolvedSchema.schema, unresolvedSchema.schema, referencedHandle);
400
+ }
401
+ }
402
+ return externalLinkPromise.then(function () { return resolveRefs(node, unresolvedSchema.schema, referencedHandle); });
347
403
  });
348
404
  };
349
- var resolveRefs = function (node, parentSchema, parentSchemaURL, parentSchemaDependencies) {
405
+ var resolveRefs = function (node, parentSchema, parentHandle) {
350
406
  if (!node || typeof node !== 'object') {
351
407
  return Promise.resolve(null);
352
408
  }
353
409
  var toWalk = [node];
354
- var seen = [];
410
+ var seen = new Set();
355
411
  var openPromises = [];
356
412
  var collectEntries = function () {
357
413
  var entries = [];
@@ -401,19 +457,27 @@ var JSONSchemaService = /** @class */ (function () {
401
457
  }
402
458
  };
403
459
  var handleRef = function (next) {
404
- var seenRefs = [];
460
+ var seenRefs = new Set();
405
461
  while (next.$ref) {
406
462
  var ref = next.$ref;
407
463
  var segments = ref.split('#', 2);
408
464
  delete next.$ref;
409
465
  if (segments[0].length > 0) {
410
- openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentSchemaURL, parentSchemaDependencies));
466
+ // This is a reference to an external schema
467
+ openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentHandle));
411
468
  return;
412
469
  }
413
470
  else {
414
- if (seenRefs.indexOf(ref) === -1) {
415
- merge(next, parentSchema, parentSchemaURL, segments[1]); // can set next.$ref again, use seenRefs to avoid circle
416
- seenRefs.push(ref);
471
+ // This is a reference inside the current schema
472
+ if (!seenRefs.has(ref)) {
473
+ var id = segments[1];
474
+ if (id !== undefined && isSubSchemaRef(id)) { // A $ref to a sub-schema with an $id (i.e #hello)
475
+ tryMergeSubSchema(next, id, handle);
476
+ }
477
+ else { // A $ref to a JSON Pointer (i.e #/definitions/foo)
478
+ mergeByJsonPointer(next, parentSchema, parentHandle.uri, id); // can set next.$ref again, use seenRefs to avoid circle
479
+ }
480
+ seenRefs.add(ref);
417
481
  }
418
482
  }
419
483
  }
@@ -421,39 +485,70 @@ var JSONSchemaService = /** @class */ (function () {
421
485
  collectMapEntries(next.definitions, next.properties, next.patternProperties, next.dependencies);
422
486
  collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.items);
423
487
  };
488
+ var handleId = function (next) {
489
+ // TODO figure out should loops be preventse
490
+ var id = next.$id || next.id;
491
+ if (typeof id === 'string' && id.charAt(0) === '#') {
492
+ delete next.$id;
493
+ delete next.id;
494
+ // Use a blank separator, as the $id already has the '#'
495
+ var fullId = reconstructRefURI(parentHandle.uri, id, '');
496
+ var resolved = parentHandle.anchors.get(fullId);
497
+ if (!resolved) {
498
+ // it's resolved now
499
+ parentHandle.anchors.set(fullId, next);
500
+ }
501
+ else if (resolved !== next) {
502
+ // Duplicate may occur in recursive $refs, but as long as they are the same object
503
+ // it's ok, otherwise report and error
504
+ resolveErrors.push(localize('json.schema.duplicateid', 'Duplicate id declaration: \'{0}\'', id));
505
+ }
506
+ // Resolve all pending requests and cleanup the queue list
507
+ var pending = pendingSubSchemas.get(fullId);
508
+ if (pending) {
509
+ for (var _i = 0, pending_1 = pending; _i < pending_1.length; _i++) {
510
+ var target = pending_1[_i];
511
+ merge(target, next);
512
+ }
513
+ pendingSubSchemas.delete(fullId);
514
+ }
515
+ }
516
+ };
424
517
  while (toWalk.length) {
425
518
  var next = toWalk.pop();
426
- if (seen.indexOf(next) >= 0) {
519
+ if (seen.has(next)) {
427
520
  continue;
428
521
  }
429
- seen.push(next);
522
+ seen.add(next);
523
+ handleId(next);
430
524
  handleRef(next);
431
525
  }
432
526
  return _this.promise.all(openPromises);
433
527
  };
434
- return resolveRefs(schema, schema, schemaURL, dependencies).then(function (_) { return new ResolvedSchema(schema, resolveErrors); });
528
+ return resolveRefs(schema, schema, handle).then(function (_) {
529
+ for (var unresolvedSubschemaId in pendingSubSchemas) {
530
+ resolveErrors.push(localize('json.schema.idnotfound', 'Subschema with id \'{0}\' was not found', unresolvedSubschemaId));
531
+ }
532
+ return new ResolvedSchema(schema, resolveErrors);
533
+ });
435
534
  };
436
- JSONSchemaService.prototype.getSchemaForResource = function (resource, document) {
437
- // first use $schema if present
438
- if (document && document.root && document.root.type === 'object') {
439
- var schemaProperties = document.root.properties.filter(function (p) { return (p.keyNode.value === '$schema') && p.valueNode && p.valueNode.type === 'string'; });
440
- if (schemaProperties.length > 0) {
441
- var valueNode = schemaProperties[0].valueNode;
442
- if (valueNode && valueNode.type === 'string') {
443
- var schemeId = Parser.getNodeValue(valueNode);
444
- if (schemeId && Strings.startsWith(schemeId, '.') && this.contextService) {
445
- schemeId = this.contextService.resolveRelativePath(schemeId, resource);
446
- }
447
- if (schemeId) {
448
- var id = normalizeId(schemeId);
449
- return this.getOrAddSchemaHandle(id).getResolvedSchema();
535
+ JSONSchemaService.prototype.getSchemaFromProperty = function (resource, document) {
536
+ var _a, _b;
537
+ if (((_a = document.root) === null || _a === void 0 ? void 0 : _a.type) === 'object') {
538
+ for (var _i = 0, _c = document.root.properties; _i < _c.length; _i++) {
539
+ var p = _c[_i];
540
+ if (p.keyNode.value === '$schema' && ((_b = p.valueNode) === null || _b === void 0 ? void 0 : _b.type) === 'string') {
541
+ var schemaId = p.valueNode.value;
542
+ if (this.contextService && !/^\w[\w\d+.-]*:/.test(schemaId)) { // has scheme
543
+ schemaId = this.contextService.resolveRelativePath(schemaId, resource);
450
544
  }
545
+ return schemaId;
451
546
  }
452
547
  }
453
548
  }
454
- if (this.cachedSchemaForResource && this.cachedSchemaForResource.resource === resource) {
455
- return this.cachedSchemaForResource.resolvedSchema;
456
- }
549
+ return undefined;
550
+ };
551
+ JSONSchemaService.prototype.getAssociatedSchemas = function (resource) {
457
552
  var seen = Object.create(null);
458
553
  var schemas = [];
459
554
  var normalizedResource = normalizeResourceForMatching(resource);
@@ -469,6 +564,28 @@ var JSONSchemaService = /** @class */ (function () {
469
564
  }
470
565
  }
471
566
  }
567
+ return schemas;
568
+ };
569
+ JSONSchemaService.prototype.getSchemaURIsForResource = function (resource, document) {
570
+ var schemeId = document && this.getSchemaFromProperty(resource, document);
571
+ if (schemeId) {
572
+ return [schemeId];
573
+ }
574
+ return this.getAssociatedSchemas(resource);
575
+ };
576
+ JSONSchemaService.prototype.getSchemaForResource = function (resource, document) {
577
+ if (document) {
578
+ // first use $schema if present
579
+ var schemeId = this.getSchemaFromProperty(resource, document);
580
+ if (schemeId) {
581
+ var id = normalizeId(schemeId);
582
+ return this.getOrAddSchemaHandle(id).getResolvedSchema();
583
+ }
584
+ }
585
+ if (this.cachedSchemaForResource && this.cachedSchemaForResource.resource === resource) {
586
+ return this.cachedSchemaForResource.resolvedSchema;
587
+ }
588
+ var schemas = this.getAssociatedSchemas(resource);
472
589
  var resolvedSchema = schemas.length > 0 ? this.createCombinedSchema(resource, schemas).getResolvedSchema() : this.promise.resolve(undefined);
473
590
  this.cachedSchemaForResource = { resource: resource, resolvedSchema: resolvedSchema };
474
591
  return resolvedSchema;
@@ -488,7 +605,8 @@ var JSONSchemaService = /** @class */ (function () {
488
605
  JSONSchemaService.prototype.getMatchingSchemas = function (document, jsonDocument, schema) {
489
606
  if (schema) {
490
607
  var id = schema.id || ('schemaservice://untitled/matchingSchemas/' + idCounter++);
491
- return this.resolveSchemaContent(new UnresolvedSchema(schema), id, {}).then(function (resolvedSchema) {
608
+ var handle = this.addSchemaHandle(id, schema);
609
+ return handle.getResolvedSchema().then(function (resolvedSchema) {
492
610
  return jsonDocument.getMatchingSchemas(resolvedSchema.schema).filter(function (s) { return !s.inverted; });
493
611
  });
494
612
  }
@@ -2,7 +2,6 @@
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Licensed under the MIT License. See License.txt in the project root for license information.
4
4
  *--------------------------------------------------------------------------------------------*/
5
- import { UnresolvedSchema } from './jsonSchemaService';
6
5
  import { ErrorCode, Diagnostic, DiagnosticSeverity, Range } from '../jsonLanguageTypes';
7
6
  import * as nls from 'vscode-nls';
8
7
  import { isBoolean } from '../utils/objects';
@@ -86,7 +85,8 @@ var JSONValidation = /** @class */ (function () {
86
85
  };
87
86
  if (schema) {
88
87
  var id = schema.id || ('schemaservice://untitled/' + idCounter++);
89
- return this.jsonSchemaService.resolveSchemaContent(new UnresolvedSchema(schema), id, {}).then(function (resolvedSchema) {
88
+ var handle = this.jsonSchemaService.registerExternalSchema(id, [], schema);
89
+ return handle.getResolvedSchema().then(function (resolvedSchema) {
90
90
  return getDiagnostics(resolvedSchema);
91
91
  });
92
92
  }
@@ -94,6 +94,9 @@ var JSONValidation = /** @class */ (function () {
94
94
  return getDiagnostics(schema);
95
95
  });
96
96
  };
97
+ JSONValidation.prototype.getLanguageStatus = function (textDocument, jsonDocument) {
98
+ return { schemas: this.jsonSchemaService.getSchemaURIsForResource(textDocument.uri, jsonDocument) };
99
+ };
97
100
  return JSONValidation;
98
101
  }());
99
102
  export { JSONValidation };
@@ -43,10 +43,22 @@ export function repeat(value, count) {
43
43
  return s;
44
44
  }
45
45
  export function extendedRegExp(pattern) {
46
+ var flags = '';
46
47
  if (startsWith(pattern, '(?i)')) {
47
- return new RegExp(pattern.substring(4), 'iu');
48
+ pattern = pattern.substring(4);
49
+ flags = 'i';
48
50
  }
49
- else {
50
- return new RegExp(pattern, 'u');
51
+ try {
52
+ return new RegExp(pattern, flags + 'u');
53
+ }
54
+ catch (e) {
55
+ // could be an exception due to the 'u ' flag
56
+ try {
57
+ return new RegExp(pattern, flags);
58
+ }
59
+ catch (e) {
60
+ // invalid pattern
61
+ return undefined;
62
+ }
51
63
  }
52
64
  }
@@ -1,4 +1,4 @@
1
- import { Thenable, ASTNode, Color, ColorInformation, ColorPresentation, LanguageServiceParams, LanguageSettings, DocumentLanguageSettings, FoldingRange, JSONSchema, SelectionRange, FoldingRangesContext, DocumentSymbolsContext, ColorInformationContext as DocumentColorsContext, TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema } from './jsonLanguageTypes';
1
+ import { Thenable, ASTNode, Color, ColorInformation, ColorPresentation, LanguageServiceParams, LanguageSettings, DocumentLanguageSettings, FoldingRange, JSONSchema, SelectionRange, FoldingRangesContext, DocumentSymbolsContext, ColorInformationContext as DocumentColorsContext, TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, DocumentSymbol, DefinitionLink, MatchingSchema, JSONLanguageStatus } from './jsonLanguageTypes';
2
2
  import { DocumentLink } from 'vscode-languageserver-types';
3
3
  export declare type JSONDocument = {
4
4
  root: ASTNode | undefined;
@@ -12,6 +12,7 @@ export interface LanguageService {
12
12
  newJSONDocument(rootNode: ASTNode, syntaxDiagnostics?: Diagnostic[]): JSONDocument;
13
13
  resetSchema(uri: string): boolean;
14
14
  getMatchingSchemas(document: TextDocument, jsonDocument: JSONDocument, schema?: JSONSchema): Thenable<MatchingSchema[]>;
15
+ getLanguageStatus(document: TextDocument, jsonDocument: JSONDocument): JSONLanguageStatus;
15
16
  doResolve(item: CompletionItem): Thenable<CompletionItem>;
16
17
  doComplete(document: TextDocument, position: Position, doc: JSONDocument): Thenable<CompletionList | null>;
17
18
  findDocumentSymbols(document: TextDocument, doc: JSONDocument, context?: DocumentSymbolsContext): SymbolInformation[];
@@ -57,6 +57,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
57
57
  },
58
58
  resetSchema: function (uri) { return jsonSchemaService.onResourceChange(uri); },
59
59
  doValidation: jsonValidation.doValidation.bind(jsonValidation),
60
+ getLanguageStatus: jsonValidation.getLanguageStatus.bind(jsonValidation),
60
61
  parseJSONDocument: function (document) { return jsonParser_1.parse(document, { collectComments: true }); },
61
62
  newJSONDocument: function (root, diagnostics) { return jsonParser_1.newJSONDocument(root, diagnostics); },
62
63
  getMatchingSchemas: jsonSchemaService.getMatchingSchemas.bind(jsonSchemaService),
@@ -74,6 +74,9 @@ export interface MatchingSchema {
74
74
  node: ASTNode;
75
75
  schema: JSONSchema;
76
76
  }
77
+ export interface JSONLanguageStatus {
78
+ schemas: string[];
79
+ }
77
80
  export interface LanguageSettings {
78
81
  /**
79
82
  * If set, the validator will return syntax and semantic errors.
@@ -40,7 +40,10 @@ var __extends = (this && this.__extends) || (function () {
40
40
  'date-time': { errorMessage: localize('dateTimeFormatWarning', 'String is not a RFC3339 date-time.'), pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$/i },
41
41
  'date': { errorMessage: localize('dateFormatWarning', 'String is not a RFC3339 date.'), pattern: /^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/i },
42
42
  'time': { errorMessage: localize('timeFormatWarning', 'String is not a RFC3339 time.'), pattern: /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(Z|(\+|-)([01][0-9]|2[0-3]):([0-5][0-9]))$/i },
43
- 'email': { errorMessage: localize('emailFormatWarning', 'String is not an e-mail address.'), pattern: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ }
43
+ 'email': { errorMessage: localize('emailFormatWarning', 'String is not an e-mail address.'), pattern: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}))$/ },
44
+ 'hostname': { errorMessage: localize('hostnameFormatWarning', 'String is not a hostname.'), pattern: /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i },
45
+ 'ipv4': { errorMessage: localize('ipv4FormatWarning', 'String is not an IPv4 address.'), pattern: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/ },
46
+ 'ipv6': { errorMessage: localize('ipv6FormatWarning', 'String is not an IPv6 address.'), pattern: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i },
44
47
  };
45
48
  var ASTNodeImpl = /** @class */ (function () {
46
49
  function ASTNodeImpl(parent, offset, length) {
@@ -634,7 +637,7 @@ var __extends = (this && this.__extends) || (function () {
634
637
  }
635
638
  if (objects_1.isString(schema.pattern)) {
636
639
  var regex = strings_1.extendedRegExp(schema.pattern);
637
- if (!regex.test(node.value)) {
640
+ if (!(regex === null || regex === void 0 ? void 0 : regex.test(node.value))) {
638
641
  validationResult.problems.push({
639
642
  location: { offset: node.offset, length: node.length },
640
643
  message: schema.patternErrorMessage || schema.errorMessage || localize('patternWarning', 'String does not match the pattern of "{0}".', schema.pattern)
@@ -672,6 +675,9 @@ var __extends = (this && this.__extends) || (function () {
672
675
  case 'date':
673
676
  case 'time':
674
677
  case 'email':
678
+ case 'hostname':
679
+ case 'ipv4':
680
+ case 'ipv6':
675
681
  var format = formats[schema.format];
676
682
  if (!node.value || !format.pattern.exec(node.value)) {
677
683
  validationResult.problems.push({
@@ -828,7 +834,7 @@ var __extends = (this && this.__extends) || (function () {
828
834
  var regex = strings_1.extendedRegExp(propertyPattern);
829
835
  for (var _h = 0, _j = unprocessedProperties.slice(0); _h < _j.length; _h++) {
830
836
  var propertyName = _j[_h];
831
- if (regex.test(propertyName)) {
837
+ if (regex === null || regex === void 0 ? void 0 : regex.test(propertyName)) {
832
838
  propertyProcessed(propertyName);
833
839
  var child = seenKeys[propertyName];
834
840
  if (child) {
@@ -452,7 +452,7 @@
452
452
  for (var _a = 0, _b = Object.keys(s.schema.patternProperties); _a < _b.length; _a++) {
453
453
  var pattern = _b[_a];
454
454
  var regex = strings_1.extendedRegExp(pattern);
455
- if (regex.test(parentKey)) {
455
+ if (regex === null || regex === void 0 ? void 0 : regex.test(parentKey)) {
456
456
  propertyMatched = true;
457
457
  var propertySchema = s.schema.patternProperties[pattern];
458
458
  this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types);
@@ -67,17 +67,18 @@
67
67
  return FilePatternAssociation;
68
68
  }());
69
69
  var SchemaHandle = /** @class */ (function () {
70
- function SchemaHandle(service, url, unresolvedSchemaContent) {
70
+ function SchemaHandle(service, uri, unresolvedSchemaContent) {
71
71
  this.service = service;
72
- this.url = url;
73
- this.dependencies = {};
72
+ this.uri = uri;
73
+ this.dependencies = new Set();
74
+ this.anchors = new Map();
74
75
  if (unresolvedSchemaContent) {
75
76
  this.unresolvedSchema = this.service.promise.resolve(new UnresolvedSchema(unresolvedSchemaContent));
76
77
  }
77
78
  }
78
79
  SchemaHandle.prototype.getUnresolvedSchema = function () {
79
80
  if (!this.unresolvedSchema) {
80
- this.unresolvedSchema = this.service.loadSchema(this.url);
81
+ this.unresolvedSchema = this.service.loadSchema(this.uri);
81
82
  }
82
83
  return this.unresolvedSchema;
83
84
  };
@@ -85,15 +86,18 @@
85
86
  var _this = this;
86
87
  if (!this.resolvedSchema) {
87
88
  this.resolvedSchema = this.getUnresolvedSchema().then(function (unresolved) {
88
- return _this.service.resolveSchemaContent(unresolved, _this.url, _this.dependencies);
89
+ return _this.service.resolveSchemaContent(unresolved, _this);
89
90
  });
90
91
  }
91
92
  return this.resolvedSchema;
92
93
  };
93
94
  SchemaHandle.prototype.clearSchema = function () {
95
+ var hasChanges = !!this.unresolvedSchema;
94
96
  this.resolvedSchema = undefined;
95
97
  this.unresolvedSchema = undefined;
96
- this.dependencies = {};
98
+ this.dependencies.clear();
99
+ this.anchors.clear();
100
+ return hasChanges;
97
101
  };
98
102
  return SchemaHandle;
99
103
  }());
@@ -131,7 +135,7 @@
131
135
  for (var _i = 0, _a = Object.keys(schema.patternProperties); _i < _a.length; _i++) {
132
136
  var pattern = _a[_i];
133
137
  var regex = Strings.extendedRegExp(pattern);
134
- if (regex.test(next)) {
138
+ if (regex === null || regex === void 0 ? void 0 : regex.test(next)) {
135
139
  return this.getSectionRecursive(path, schema.patternProperties[pattern]);
136
140
  }
137
141
  }
@@ -197,13 +201,14 @@
197
201
  var curr = toWalk.pop();
198
202
  for (var i = 0; i < all.length; i++) {
199
203
  var handle = all[i];
200
- if (handle && (handle.url === curr || handle.dependencies[curr])) {
201
- if (handle.url !== curr) {
202
- toWalk.push(handle.url);
204
+ if (handle && (handle.uri === curr || handle.dependencies.has(curr))) {
205
+ if (handle.uri !== curr) {
206
+ toWalk.push(handle.uri);
207
+ }
208
+ if (handle.clearSchema()) {
209
+ hasChanges = true;
203
210
  }
204
- handle.clearSchema();
205
211
  all[i] = undefined;
206
- hasChanges = true;
207
212
  }
208
213
  }
209
214
  }
@@ -299,7 +304,7 @@
299
304
  return new UnresolvedSchema({}, [localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': {1}.', toDisplayString(url), errorMessage)]);
300
305
  });
301
306
  };
302
- JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, schemaURL, dependencies) {
307
+ JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, handle) {
303
308
  var _this = this;
304
309
  var resolveErrors = schemaToResolve.errors.slice(0);
305
310
  var schema = schemaToResolve.schema;
@@ -311,6 +316,9 @@
311
316
  else if (id === 'https://json-schema.org/draft/2019-09/schema') {
312
317
  resolveErrors.push(localize('json.schema.draft201909.notsupported', "Draft 2019-09 schemas are not yet fully supported."));
313
318
  }
319
+ else if (id === 'https://json-schema.org/draft/2020-12/schema') {
320
+ resolveErrors.push(localize('json.schema.draft202012.notsupported', "Draft 2020-12 schemas are not yet fully supported."));
321
+ }
314
322
  }
315
323
  var contextService = this.contextService;
316
324
  var findSection = function (schema, path) {
@@ -328,42 +336,90 @@
328
336
  });
329
337
  return current;
330
338
  };
331
- var merge = function (target, sourceRoot, sourceURI, refSegment) {
339
+ var merge = function (target, section) {
340
+ for (var key in section) {
341
+ if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
342
+ target[key] = section[key];
343
+ }
344
+ }
345
+ };
346
+ var mergeByJsonPointer = function (target, sourceRoot, sourceURI, refSegment) {
332
347
  var path = refSegment ? decodeURIComponent(refSegment) : undefined;
333
348
  var section = findSection(sourceRoot, path);
334
349
  if (section) {
335
- for (var key in section) {
336
- if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
337
- target[key] = section[key];
338
- }
339
- }
350
+ merge(target, section);
340
351
  }
341
352
  else {
342
353
  resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in \'{1}\' can not be resolved.', path, sourceURI));
343
354
  }
344
355
  };
345
- var resolveExternalLink = function (node, uri, refSegment, parentSchemaURL, parentSchemaDependencies) {
356
+ var isSubSchemaRef = function (refSegment) {
357
+ // Check if the first character is not '/' to determine whether it's a sub schema reference or a JSON Pointer
358
+ return !!refSegment && refSegment.charAt(0) !== '/';
359
+ };
360
+ var reconstructRefURI = function (uri, fragment, separator) {
361
+ if (separator === void 0) { separator = '#'; }
362
+ return normalizeId("" + uri + separator + fragment);
363
+ };
364
+ // To find which $refs point to which $ids we keep two maps:
365
+ // pendingSubSchemas '$id' we expect to encounter (if they exist)
366
+ // handle.anchors for the ones we have encountered
367
+ var pendingSubSchemas = new Map();
368
+ var tryMergeSubSchema = function (target, id, handle) {
369
+ // Get the full URI for the current schema to avoid matching schema1#hello and schema2#hello to the same
370
+ // reference by accident
371
+ var fullId = reconstructRefURI(handle.uri, id);
372
+ var resolved = handle.anchors.get(fullId);
373
+ if (resolved) {
374
+ merge(target, resolved);
375
+ return true; // return success
376
+ }
377
+ // This subschema has not been resolved yet
378
+ // Remember the target to merge later once resolved
379
+ var pending = pendingSubSchemas.get(fullId);
380
+ if (!pending) {
381
+ pending = [];
382
+ pendingSubSchemas.set(fullId, pending);
383
+ }
384
+ pending.push(target);
385
+ return false; // return failure - merge didn't occur
386
+ };
387
+ var resolveExternalLink = function (node, uri, refSegment, parentHandle) {
346
388
  if (contextService && !/^[A-Za-z][A-Za-z0-9+\-.+]*:\/\/.*/.test(uri)) {
347
- uri = contextService.resolveRelativePath(uri, parentSchemaURL);
389
+ uri = contextService.resolveRelativePath(uri, parentHandle.uri);
348
390
  }
349
391
  uri = normalizeId(uri);
350
392
  var referencedHandle = _this.getOrAddSchemaHandle(uri);
351
393
  return referencedHandle.getUnresolvedSchema().then(function (unresolvedSchema) {
352
- parentSchemaDependencies[uri] = true;
394
+ parentHandle.dependencies.add(uri);
353
395
  if (unresolvedSchema.errors.length) {
354
396
  var loc = refSegment ? uri + '#' + refSegment : uri;
355
397
  resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
356
398
  }
357
- merge(node, unresolvedSchema.schema, uri, refSegment);
358
- return resolveRefs(node, unresolvedSchema.schema, uri, referencedHandle.dependencies);
399
+ // A placeholder promise that might execute later a ref resolution for the newly resolved schema
400
+ var externalLinkPromise = Promise.resolve(true);
401
+ if (refSegment === undefined || !isSubSchemaRef(refSegment)) {
402
+ // This is not a sub schema, merge the regular way
403
+ mergeByJsonPointer(node, unresolvedSchema.schema, uri, refSegment);
404
+ }
405
+ else {
406
+ // This is a reference to a subschema
407
+ if (!tryMergeSubSchema(node, refSegment, referencedHandle)) {
408
+ // We weren't able to merge currently so we'll try to resolve this schema first to obtain subschemas
409
+ // that could be missed
410
+ // to improve: it would be enough to find the nodes, no need to resolve the full schema
411
+ externalLinkPromise = resolveRefs(unresolvedSchema.schema, unresolvedSchema.schema, referencedHandle);
412
+ }
413
+ }
414
+ return externalLinkPromise.then(function () { return resolveRefs(node, unresolvedSchema.schema, referencedHandle); });
359
415
  });
360
416
  };
361
- var resolveRefs = function (node, parentSchema, parentSchemaURL, parentSchemaDependencies) {
417
+ var resolveRefs = function (node, parentSchema, parentHandle) {
362
418
  if (!node || typeof node !== 'object') {
363
419
  return Promise.resolve(null);
364
420
  }
365
421
  var toWalk = [node];
366
- var seen = [];
422
+ var seen = new Set();
367
423
  var openPromises = [];
368
424
  var collectEntries = function () {
369
425
  var entries = [];
@@ -413,19 +469,27 @@
413
469
  }
414
470
  };
415
471
  var handleRef = function (next) {
416
- var seenRefs = [];
472
+ var seenRefs = new Set();
417
473
  while (next.$ref) {
418
474
  var ref = next.$ref;
419
475
  var segments = ref.split('#', 2);
420
476
  delete next.$ref;
421
477
  if (segments[0].length > 0) {
422
- openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentSchemaURL, parentSchemaDependencies));
478
+ // This is a reference to an external schema
479
+ openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentHandle));
423
480
  return;
424
481
  }
425
482
  else {
426
- if (seenRefs.indexOf(ref) === -1) {
427
- merge(next, parentSchema, parentSchemaURL, segments[1]); // can set next.$ref again, use seenRefs to avoid circle
428
- seenRefs.push(ref);
483
+ // This is a reference inside the current schema
484
+ if (!seenRefs.has(ref)) {
485
+ var id = segments[1];
486
+ if (id !== undefined && isSubSchemaRef(id)) { // A $ref to a sub-schema with an $id (i.e #hello)
487
+ tryMergeSubSchema(next, id, handle);
488
+ }
489
+ else { // A $ref to a JSON Pointer (i.e #/definitions/foo)
490
+ mergeByJsonPointer(next, parentSchema, parentHandle.uri, id); // can set next.$ref again, use seenRefs to avoid circle
491
+ }
492
+ seenRefs.add(ref);
429
493
  }
430
494
  }
431
495
  }
@@ -433,39 +497,70 @@
433
497
  collectMapEntries(next.definitions, next.properties, next.patternProperties, next.dependencies);
434
498
  collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.items);
435
499
  };
500
+ var handleId = function (next) {
501
+ // TODO figure out should loops be preventse
502
+ var id = next.$id || next.id;
503
+ if (typeof id === 'string' && id.charAt(0) === '#') {
504
+ delete next.$id;
505
+ delete next.id;
506
+ // Use a blank separator, as the $id already has the '#'
507
+ var fullId = reconstructRefURI(parentHandle.uri, id, '');
508
+ var resolved = parentHandle.anchors.get(fullId);
509
+ if (!resolved) {
510
+ // it's resolved now
511
+ parentHandle.anchors.set(fullId, next);
512
+ }
513
+ else if (resolved !== next) {
514
+ // Duplicate may occur in recursive $refs, but as long as they are the same object
515
+ // it's ok, otherwise report and error
516
+ resolveErrors.push(localize('json.schema.duplicateid', 'Duplicate id declaration: \'{0}\'', id));
517
+ }
518
+ // Resolve all pending requests and cleanup the queue list
519
+ var pending = pendingSubSchemas.get(fullId);
520
+ if (pending) {
521
+ for (var _i = 0, pending_1 = pending; _i < pending_1.length; _i++) {
522
+ var target = pending_1[_i];
523
+ merge(target, next);
524
+ }
525
+ pendingSubSchemas.delete(fullId);
526
+ }
527
+ }
528
+ };
436
529
  while (toWalk.length) {
437
530
  var next = toWalk.pop();
438
- if (seen.indexOf(next) >= 0) {
531
+ if (seen.has(next)) {
439
532
  continue;
440
533
  }
441
- seen.push(next);
534
+ seen.add(next);
535
+ handleId(next);
442
536
  handleRef(next);
443
537
  }
444
538
  return _this.promise.all(openPromises);
445
539
  };
446
- return resolveRefs(schema, schema, schemaURL, dependencies).then(function (_) { return new ResolvedSchema(schema, resolveErrors); });
540
+ return resolveRefs(schema, schema, handle).then(function (_) {
541
+ for (var unresolvedSubschemaId in pendingSubSchemas) {
542
+ resolveErrors.push(localize('json.schema.idnotfound', 'Subschema with id \'{0}\' was not found', unresolvedSubschemaId));
543
+ }
544
+ return new ResolvedSchema(schema, resolveErrors);
545
+ });
447
546
  };
448
- JSONSchemaService.prototype.getSchemaForResource = function (resource, document) {
449
- // first use $schema if present
450
- if (document && document.root && document.root.type === 'object') {
451
- var schemaProperties = document.root.properties.filter(function (p) { return (p.keyNode.value === '$schema') && p.valueNode && p.valueNode.type === 'string'; });
452
- if (schemaProperties.length > 0) {
453
- var valueNode = schemaProperties[0].valueNode;
454
- if (valueNode && valueNode.type === 'string') {
455
- var schemeId = Parser.getNodeValue(valueNode);
456
- if (schemeId && Strings.startsWith(schemeId, '.') && this.contextService) {
457
- schemeId = this.contextService.resolveRelativePath(schemeId, resource);
458
- }
459
- if (schemeId) {
460
- var id = normalizeId(schemeId);
461
- return this.getOrAddSchemaHandle(id).getResolvedSchema();
547
+ JSONSchemaService.prototype.getSchemaFromProperty = function (resource, document) {
548
+ var _a, _b;
549
+ if (((_a = document.root) === null || _a === void 0 ? void 0 : _a.type) === 'object') {
550
+ for (var _i = 0, _c = document.root.properties; _i < _c.length; _i++) {
551
+ var p = _c[_i];
552
+ if (p.keyNode.value === '$schema' && ((_b = p.valueNode) === null || _b === void 0 ? void 0 : _b.type) === 'string') {
553
+ var schemaId = p.valueNode.value;
554
+ if (this.contextService && !/^\w[\w\d+.-]*:/.test(schemaId)) { // has scheme
555
+ schemaId = this.contextService.resolveRelativePath(schemaId, resource);
462
556
  }
557
+ return schemaId;
463
558
  }
464
559
  }
465
560
  }
466
- if (this.cachedSchemaForResource && this.cachedSchemaForResource.resource === resource) {
467
- return this.cachedSchemaForResource.resolvedSchema;
468
- }
561
+ return undefined;
562
+ };
563
+ JSONSchemaService.prototype.getAssociatedSchemas = function (resource) {
469
564
  var seen = Object.create(null);
470
565
  var schemas = [];
471
566
  var normalizedResource = normalizeResourceForMatching(resource);
@@ -481,6 +576,28 @@
481
576
  }
482
577
  }
483
578
  }
579
+ return schemas;
580
+ };
581
+ JSONSchemaService.prototype.getSchemaURIsForResource = function (resource, document) {
582
+ var schemeId = document && this.getSchemaFromProperty(resource, document);
583
+ if (schemeId) {
584
+ return [schemeId];
585
+ }
586
+ return this.getAssociatedSchemas(resource);
587
+ };
588
+ JSONSchemaService.prototype.getSchemaForResource = function (resource, document) {
589
+ if (document) {
590
+ // first use $schema if present
591
+ var schemeId = this.getSchemaFromProperty(resource, document);
592
+ if (schemeId) {
593
+ var id = normalizeId(schemeId);
594
+ return this.getOrAddSchemaHandle(id).getResolvedSchema();
595
+ }
596
+ }
597
+ if (this.cachedSchemaForResource && this.cachedSchemaForResource.resource === resource) {
598
+ return this.cachedSchemaForResource.resolvedSchema;
599
+ }
600
+ var schemas = this.getAssociatedSchemas(resource);
484
601
  var resolvedSchema = schemas.length > 0 ? this.createCombinedSchema(resource, schemas).getResolvedSchema() : this.promise.resolve(undefined);
485
602
  this.cachedSchemaForResource = { resource: resource, resolvedSchema: resolvedSchema };
486
603
  return resolvedSchema;
@@ -500,7 +617,8 @@
500
617
  JSONSchemaService.prototype.getMatchingSchemas = function (document, jsonDocument, schema) {
501
618
  if (schema) {
502
619
  var id = schema.id || ('schemaservice://untitled/matchingSchemas/' + idCounter++);
503
- return this.resolveSchemaContent(new UnresolvedSchema(schema), id, {}).then(function (resolvedSchema) {
620
+ var handle = this.addSchemaHandle(id, schema);
621
+ return handle.getResolvedSchema().then(function (resolvedSchema) {
504
622
  return jsonDocument.getMatchingSchemas(resolvedSchema.schema).filter(function (s) { return !s.inverted; });
505
623
  });
506
624
  }
@@ -8,13 +8,12 @@
8
8
  if (v !== undefined) module.exports = v;
9
9
  }
10
10
  else if (typeof define === "function" && define.amd) {
11
- define(["require", "exports", "./jsonSchemaService", "../jsonLanguageTypes", "vscode-nls", "../utils/objects"], factory);
11
+ define(["require", "exports", "../jsonLanguageTypes", "vscode-nls", "../utils/objects"], factory);
12
12
  }
13
13
  })(function (require, exports) {
14
14
  "use strict";
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.JSONValidation = void 0;
17
- var jsonSchemaService_1 = require("./jsonSchemaService");
18
17
  var jsonLanguageTypes_1 = require("../jsonLanguageTypes");
19
18
  var nls = require("vscode-nls");
20
19
  var objects_1 = require("../utils/objects");
@@ -98,7 +97,8 @@
98
97
  };
99
98
  if (schema) {
100
99
  var id = schema.id || ('schemaservice://untitled/' + idCounter++);
101
- return this.jsonSchemaService.resolveSchemaContent(new jsonSchemaService_1.UnresolvedSchema(schema), id, {}).then(function (resolvedSchema) {
100
+ var handle = this.jsonSchemaService.registerExternalSchema(id, [], schema);
101
+ return handle.getResolvedSchema().then(function (resolvedSchema) {
102
102
  return getDiagnostics(resolvedSchema);
103
103
  });
104
104
  }
@@ -106,6 +106,9 @@
106
106
  return getDiagnostics(schema);
107
107
  });
108
108
  };
109
+ JSONValidation.prototype.getLanguageStatus = function (textDocument, jsonDocument) {
110
+ return { schemas: this.jsonSchemaService.getSchemaURIsForResource(textDocument.uri, jsonDocument) };
111
+ };
109
112
  return JSONValidation;
110
113
  }());
111
114
  exports.JSONValidation = JSONValidation;
@@ -59,11 +59,23 @@
59
59
  }
60
60
  exports.repeat = repeat;
61
61
  function extendedRegExp(pattern) {
62
+ var flags = '';
62
63
  if (startsWith(pattern, '(?i)')) {
63
- return new RegExp(pattern.substring(4), 'iu');
64
+ pattern = pattern.substring(4);
65
+ flags = 'i';
64
66
  }
65
- else {
66
- return new RegExp(pattern, 'u');
67
+ try {
68
+ return new RegExp(pattern, flags + 'u');
69
+ }
70
+ catch (e) {
71
+ // could be an exception due to the 'u ' flag
72
+ try {
73
+ return new RegExp(pattern, flags);
74
+ }
75
+ catch (e) {
76
+ // invalid pattern
77
+ return undefined;
78
+ }
67
79
  }
68
80
  }
69
81
  exports.extendedRegExp = extendedRegExp;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vscode-json-languageservice",
3
- "version": "4.1.9",
3
+ "version": "4.2.0-next.2",
4
4
  "description": "Language service for JSON",
5
5
  "main": "./lib/umd/jsonLanguageService.js",
6
6
  "typings": "./lib/umd/jsonLanguageService",
@@ -14,9 +14,6 @@
14
14
  "bugs": {
15
15
  "url": "https://github.com/Microsoft/vscode-json-languageservice"
16
16
  },
17
- "engines": {
18
- "npm": ">=7.0.0"
19
- },
20
17
  "devDependencies": {
21
18
  "@types/mocha": "^9.0.0",
22
19
  "@types/node": "^10.12.21",