vscode-json-languageservice 4.2.0-next.1 → 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
@@ -3,6 +3,7 @@
3
3
  4.2.0 /
4
4
  ================
5
5
  * new API `LanguageService.getLanguageStatus`
6
+ * support for $ref with $id
6
7
 
7
8
  4.1.6 / 2021-07-16
8
9
  ================
@@ -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) {
@@ -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({
@@ -59,6 +59,7 @@ var SchemaHandle = /** @class */ (function () {
59
59
  this.service = service;
60
60
  this.uri = uri;
61
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
  }
@@ -73,7 +74,7 @@ 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.uri, _this.dependencies);
77
+ return _this.service.resolveSchemaContent(unresolved, _this);
77
78
  });
78
79
  }
79
80
  return this.resolvedSchema;
@@ -83,6 +84,7 @@ var SchemaHandle = /** @class */ (function () {
83
84
  this.resolvedSchema = undefined;
84
85
  this.unresolvedSchema = undefined;
85
86
  this.dependencies.clear();
87
+ this.anchors.clear();
86
88
  return hasChanges;
87
89
  };
88
90
  return SchemaHandle;
@@ -290,7 +292,7 @@ var JSONSchemaService = /** @class */ (function () {
290
292
  return new UnresolvedSchema({}, [localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': {1}.', toDisplayString(url), errorMessage)]);
291
293
  });
292
294
  };
293
- JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, schemaURL, dependencies) {
295
+ JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, handle) {
294
296
  var _this = this;
295
297
  var resolveErrors = schemaToResolve.errors.slice(0);
296
298
  var schema = schemaToResolve.schema;
@@ -322,37 +324,85 @@ var JSONSchemaService = /** @class */ (function () {
322
324
  });
323
325
  return current;
324
326
  };
325
- 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) {
326
335
  var path = refSegment ? decodeURIComponent(refSegment) : undefined;
327
336
  var section = findSection(sourceRoot, path);
328
337
  if (section) {
329
- for (var key in section) {
330
- if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
331
- target[key] = section[key];
332
- }
333
- }
338
+ merge(target, section);
334
339
  }
335
340
  else {
336
341
  resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in \'{1}\' can not be resolved.', path, sourceURI));
337
342
  }
338
343
  };
339
- 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) {
340
376
  if (contextService && !/^[A-Za-z][A-Za-z0-9+\-.+]*:\/\/.*/.test(uri)) {
341
- uri = contextService.resolveRelativePath(uri, parentSchemaURL);
377
+ uri = contextService.resolveRelativePath(uri, parentHandle.uri);
342
378
  }
343
379
  uri = normalizeId(uri);
344
380
  var referencedHandle = _this.getOrAddSchemaHandle(uri);
345
381
  return referencedHandle.getUnresolvedSchema().then(function (unresolvedSchema) {
346
- parentSchemaDependencies.add(uri);
382
+ parentHandle.dependencies.add(uri);
347
383
  if (unresolvedSchema.errors.length) {
348
384
  var loc = refSegment ? uri + '#' + refSegment : uri;
349
385
  resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
350
386
  }
351
- merge(node, unresolvedSchema.schema, uri, refSegment);
352
- 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); });
353
403
  });
354
404
  };
355
- var resolveRefs = function (node, parentSchema, parentSchemaURL, parentSchemaDependencies) {
405
+ var resolveRefs = function (node, parentSchema, parentHandle) {
356
406
  if (!node || typeof node !== 'object') {
357
407
  return Promise.resolve(null);
358
408
  }
@@ -413,12 +463,20 @@ var JSONSchemaService = /** @class */ (function () {
413
463
  var segments = ref.split('#', 2);
414
464
  delete next.$ref;
415
465
  if (segments[0].length > 0) {
416
- 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));
417
468
  return;
418
469
  }
419
470
  else {
471
+ // This is a reference inside the current schema
420
472
  if (!seenRefs.has(ref)) {
421
- merge(next, parentSchema, parentSchemaURL, segments[1]); // can set next.$ref again, use seenRefs to avoid circle
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
+ }
422
480
  seenRefs.add(ref);
423
481
  }
424
482
  }
@@ -427,17 +485,52 @@ var JSONSchemaService = /** @class */ (function () {
427
485
  collectMapEntries(next.definitions, next.properties, next.patternProperties, next.dependencies);
428
486
  collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.items);
429
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
+ };
430
517
  while (toWalk.length) {
431
518
  var next = toWalk.pop();
432
519
  if (seen.has(next)) {
433
520
  continue;
434
521
  }
435
522
  seen.add(next);
523
+ handleId(next);
436
524
  handleRef(next);
437
525
  }
438
526
  return _this.promise.all(openPromises);
439
527
  };
440
- 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
+ });
441
534
  };
442
535
  JSONSchemaService.prototype.getSchemaFromProperty = function (resource, document) {
443
536
  var _a, _b;
@@ -512,7 +605,8 @@ var JSONSchemaService = /** @class */ (function () {
512
605
  JSONSchemaService.prototype.getMatchingSchemas = function (document, jsonDocument, schema) {
513
606
  if (schema) {
514
607
  var id = schema.id || ('schemaservice://untitled/matchingSchemas/' + idCounter++);
515
- return this.resolveSchemaContent(new UnresolvedSchema(schema), id, new Set()).then(function (resolvedSchema) {
608
+ var handle = this.addSchemaHandle(id, schema);
609
+ return handle.getResolvedSchema().then(function (resolvedSchema) {
516
610
  return jsonDocument.getMatchingSchemas(resolvedSchema.schema).filter(function (s) { return !s.inverted; });
517
611
  });
518
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, new Set()).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
  }
@@ -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) {
@@ -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({
@@ -71,6 +71,7 @@
71
71
  this.service = service;
72
72
  this.uri = uri;
73
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
  }
@@ -85,7 +86,7 @@
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.uri, _this.dependencies);
89
+ return _this.service.resolveSchemaContent(unresolved, _this);
89
90
  });
90
91
  }
91
92
  return this.resolvedSchema;
@@ -95,6 +96,7 @@
95
96
  this.resolvedSchema = undefined;
96
97
  this.unresolvedSchema = undefined;
97
98
  this.dependencies.clear();
99
+ this.anchors.clear();
98
100
  return hasChanges;
99
101
  };
100
102
  return SchemaHandle;
@@ -302,7 +304,7 @@
302
304
  return new UnresolvedSchema({}, [localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': {1}.', toDisplayString(url), errorMessage)]);
303
305
  });
304
306
  };
305
- JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, schemaURL, dependencies) {
307
+ JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, handle) {
306
308
  var _this = this;
307
309
  var resolveErrors = schemaToResolve.errors.slice(0);
308
310
  var schema = schemaToResolve.schema;
@@ -334,37 +336,85 @@
334
336
  });
335
337
  return current;
336
338
  };
337
- 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) {
338
347
  var path = refSegment ? decodeURIComponent(refSegment) : undefined;
339
348
  var section = findSection(sourceRoot, path);
340
349
  if (section) {
341
- for (var key in section) {
342
- if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
343
- target[key] = section[key];
344
- }
345
- }
350
+ merge(target, section);
346
351
  }
347
352
  else {
348
353
  resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in \'{1}\' can not be resolved.', path, sourceURI));
349
354
  }
350
355
  };
351
- 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) {
352
388
  if (contextService && !/^[A-Za-z][A-Za-z0-9+\-.+]*:\/\/.*/.test(uri)) {
353
- uri = contextService.resolveRelativePath(uri, parentSchemaURL);
389
+ uri = contextService.resolveRelativePath(uri, parentHandle.uri);
354
390
  }
355
391
  uri = normalizeId(uri);
356
392
  var referencedHandle = _this.getOrAddSchemaHandle(uri);
357
393
  return referencedHandle.getUnresolvedSchema().then(function (unresolvedSchema) {
358
- parentSchemaDependencies.add(uri);
394
+ parentHandle.dependencies.add(uri);
359
395
  if (unresolvedSchema.errors.length) {
360
396
  var loc = refSegment ? uri + '#' + refSegment : uri;
361
397
  resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
362
398
  }
363
- merge(node, unresolvedSchema.schema, uri, refSegment);
364
- 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); });
365
415
  });
366
416
  };
367
- var resolveRefs = function (node, parentSchema, parentSchemaURL, parentSchemaDependencies) {
417
+ var resolveRefs = function (node, parentSchema, parentHandle) {
368
418
  if (!node || typeof node !== 'object') {
369
419
  return Promise.resolve(null);
370
420
  }
@@ -425,12 +475,20 @@
425
475
  var segments = ref.split('#', 2);
426
476
  delete next.$ref;
427
477
  if (segments[0].length > 0) {
428
- 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));
429
480
  return;
430
481
  }
431
482
  else {
483
+ // This is a reference inside the current schema
432
484
  if (!seenRefs.has(ref)) {
433
- merge(next, parentSchema, parentSchemaURL, segments[1]); // can set next.$ref again, use seenRefs to avoid circle
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
+ }
434
492
  seenRefs.add(ref);
435
493
  }
436
494
  }
@@ -439,17 +497,52 @@
439
497
  collectMapEntries(next.definitions, next.properties, next.patternProperties, next.dependencies);
440
498
  collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.items);
441
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
+ };
442
529
  while (toWalk.length) {
443
530
  var next = toWalk.pop();
444
531
  if (seen.has(next)) {
445
532
  continue;
446
533
  }
447
534
  seen.add(next);
535
+ handleId(next);
448
536
  handleRef(next);
449
537
  }
450
538
  return _this.promise.all(openPromises);
451
539
  };
452
- 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
+ });
453
546
  };
454
547
  JSONSchemaService.prototype.getSchemaFromProperty = function (resource, document) {
455
548
  var _a, _b;
@@ -524,7 +617,8 @@
524
617
  JSONSchemaService.prototype.getMatchingSchemas = function (document, jsonDocument, schema) {
525
618
  if (schema) {
526
619
  var id = schema.id || ('schemaservice://untitled/matchingSchemas/' + idCounter++);
527
- return this.resolveSchemaContent(new UnresolvedSchema(schema), id, new Set()).then(function (resolvedSchema) {
620
+ var handle = this.addSchemaHandle(id, schema);
621
+ return handle.getResolvedSchema().then(function (resolvedSchema) {
528
622
  return jsonDocument.getMatchingSchemas(resolvedSchema.schema).filter(function (s) { return !s.inverted; });
529
623
  });
530
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, new Set()).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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vscode-json-languageservice",
3
- "version": "4.2.0-next.1",
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",