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 *--------------------------------------------------------------------------------------------*/
6 if (typeof module === "object" && typeof module.exports === "object") {
7 var v = factory(require, exports);
8 if (v !== undefined) module.exports = v;
10 else if (typeof define === "function" && define.amd) {
11 define(["require", "exports", "jsonc-parser", "vscode-uri", "../utils/strings", "../parser/jsonParser", "minimatch", "vscode-nls"], factory);
13 })(function (require, exports) {
15 Object.defineProperty(exports, "__esModule", { value: true });
16 exports.JSONSchemaService = exports.ResolvedSchema = exports.UnresolvedSchema = void 0;
17 var Json = require("jsonc-parser");
18 var vscode_uri_1 = require("vscode-uri");
19 var Strings = require("../utils/strings");
20 var Parser = require("../parser/jsonParser");
21 var minimatch_1 = require("minimatch");
22 var nls = require("vscode-nls");
23 var localize = nls.loadMessageBundle();
26 var FilePatternAssociation = /** @class */ (function () {
27 function FilePatternAssociation(pattern, uris) {
28 this.minimatchWrappers = [];
30 for (var _i = 0, pattern_1 = pattern; _i < pattern_1.length; _i++) {
31 var patternString = pattern_1[_i];
32 var include = patternString[0] !== BANG;
34 patternString = patternString.substring(1);
36 if (patternString.length > 0) {
37 if (patternString[0] === PATH_SEP) {
38 patternString = patternString.substring(1);
40 this.minimatchWrappers.push({
41 minimatch: new minimatch_1.Minimatch("**/" + patternString),
50 this.minimatchWrappers.length = 0;
54 FilePatternAssociation.prototype.matchesPattern = function (fileName) {
56 for (var _i = 0, _a = this.minimatchWrappers; _i < _a.length; _i++) {
57 var _b = _a[_i], minimatch = _b.minimatch, include = _b.include;
58 if (minimatch.match(fileName)) {
64 FilePatternAssociation.prototype.getURIs = function () {
67 return FilePatternAssociation;
69 var SchemaHandle = /** @class */ (function () {
70 function SchemaHandle(service, url, unresolvedSchemaContent) {
71 this.service = service;
73 this.dependencies = {};
74 if (unresolvedSchemaContent) {
75 this.unresolvedSchema = this.service.promise.resolve(new UnresolvedSchema(unresolvedSchemaContent));
78 SchemaHandle.prototype.getUnresolvedSchema = function () {
79 if (!this.unresolvedSchema) {
80 this.unresolvedSchema = this.service.loadSchema(this.url);
82 return this.unresolvedSchema;
84 SchemaHandle.prototype.getResolvedSchema = function () {
86 if (!this.resolvedSchema) {
87 this.resolvedSchema = this.getUnresolvedSchema().then(function (unresolved) {
88 return _this.service.resolveSchemaContent(unresolved, _this.url, _this.dependencies);
91 return this.resolvedSchema;
93 SchemaHandle.prototype.clearSchema = function () {
94 this.resolvedSchema = undefined;
95 this.unresolvedSchema = undefined;
96 this.dependencies = {};
100 var UnresolvedSchema = /** @class */ (function () {
101 function UnresolvedSchema(schema, errors) {
102 if (errors === void 0) { errors = []; }
103 this.schema = schema;
104 this.errors = errors;
106 return UnresolvedSchema;
108 exports.UnresolvedSchema = UnresolvedSchema;
109 var ResolvedSchema = /** @class */ (function () {
110 function ResolvedSchema(schema, errors) {
111 if (errors === void 0) { errors = []; }
112 this.schema = schema;
113 this.errors = errors;
115 ResolvedSchema.prototype.getSection = function (path) {
116 var schemaRef = this.getSectionRecursive(path, this.schema);
118 return Parser.asSchema(schemaRef);
122 ResolvedSchema.prototype.getSectionRecursive = function (path, schema) {
123 if (!schema || typeof schema === 'boolean' || path.length === 0) {
126 var next = path.shift();
127 if (schema.properties && typeof schema.properties[next]) {
128 return this.getSectionRecursive(path, schema.properties[next]);
130 else if (schema.patternProperties) {
131 for (var _i = 0, _a = Object.keys(schema.patternProperties); _i < _a.length; _i++) {
132 var pattern = _a[_i];
133 var regex = Strings.extendedRegExp(pattern);
134 if (regex.test(next)) {
135 return this.getSectionRecursive(path, schema.patternProperties[pattern]);
139 else if (typeof schema.additionalProperties === 'object') {
140 return this.getSectionRecursive(path, schema.additionalProperties);
142 else if (next.match('[0-9]+')) {
143 if (Array.isArray(schema.items)) {
144 var index = parseInt(next, 10);
145 if (!isNaN(index) && schema.items[index]) {
146 return this.getSectionRecursive(path, schema.items[index]);
149 else if (schema.items) {
150 return this.getSectionRecursive(path, schema.items);
155 return ResolvedSchema;
157 exports.ResolvedSchema = ResolvedSchema;
158 var JSONSchemaService = /** @class */ (function () {
159 function JSONSchemaService(requestService, contextService, promiseConstructor) {
160 this.contextService = contextService;
161 this.requestService = requestService;
162 this.promiseConstructor = promiseConstructor || Promise;
163 this.callOnDispose = [];
164 this.contributionSchemas = {};
165 this.contributionAssociations = [];
166 this.schemasById = {};
167 this.filePatternAssociations = [];
168 this.registeredSchemasIds = {};
170 JSONSchemaService.prototype.getRegisteredSchemaIds = function (filter) {
171 return Object.keys(this.registeredSchemasIds).filter(function (id) {
172 var scheme = vscode_uri_1.URI.parse(id).scheme;
173 return scheme !== 'schemaservice' && (!filter || filter(scheme));
176 Object.defineProperty(JSONSchemaService.prototype, "promise", {
178 return this.promiseConstructor;
183 JSONSchemaService.prototype.dispose = function () {
184 while (this.callOnDispose.length > 0) {
185 this.callOnDispose.pop()();
188 JSONSchemaService.prototype.onResourceChange = function (uri) {
190 var hasChanges = false;
191 uri = normalizeId(uri);
193 var all = Object.keys(this.schemasById).map(function (key) { return _this.schemasById[key]; });
194 while (toWalk.length) {
195 var curr = toWalk.pop();
196 for (var i = 0; i < all.length; i++) {
198 if (handle && (handle.url === curr || handle.dependencies[curr])) {
199 if (handle.url !== curr) {
200 toWalk.push(handle.url);
202 handle.clearSchema();
210 JSONSchemaService.prototype.setSchemaContributions = function (schemaContributions) {
211 if (schemaContributions.schemas) {
212 var schemas = schemaContributions.schemas;
213 for (var id in schemas) {
214 var normalizedId = normalizeId(id);
215 this.contributionSchemas[normalizedId] = this.addSchemaHandle(normalizedId, schemas[id]);
218 if (Array.isArray(schemaContributions.schemaAssociations)) {
219 var schemaAssociations = schemaContributions.schemaAssociations;
220 for (var _i = 0, schemaAssociations_1 = schemaAssociations; _i < schemaAssociations_1.length; _i++) {
221 var schemaAssociation = schemaAssociations_1[_i];
222 var uris = schemaAssociation.uris.map(normalizeId);
223 var association = this.addFilePatternAssociation(schemaAssociation.pattern, uris);
224 this.contributionAssociations.push(association);
228 JSONSchemaService.prototype.addSchemaHandle = function (id, unresolvedSchemaContent) {
229 var schemaHandle = new SchemaHandle(this, id, unresolvedSchemaContent);
230 this.schemasById[id] = schemaHandle;
233 JSONSchemaService.prototype.getOrAddSchemaHandle = function (id, unresolvedSchemaContent) {
234 return this.schemasById[id] || this.addSchemaHandle(id, unresolvedSchemaContent);
236 JSONSchemaService.prototype.addFilePatternAssociation = function (pattern, uris) {
237 var fpa = new FilePatternAssociation(pattern, uris);
238 this.filePatternAssociations.push(fpa);
241 JSONSchemaService.prototype.registerExternalSchema = function (uri, filePatterns, unresolvedSchemaContent) {
242 var id = normalizeId(uri);
243 this.registeredSchemasIds[id] = true;
244 this.cachedSchemaForResource = undefined;
246 this.addFilePatternAssociation(filePatterns, [uri]);
248 return unresolvedSchemaContent ? this.addSchemaHandle(id, unresolvedSchemaContent) : this.getOrAddSchemaHandle(id);
250 JSONSchemaService.prototype.clearExternalSchemas = function () {
251 this.schemasById = {};
252 this.filePatternAssociations = [];
253 this.registeredSchemasIds = {};
254 this.cachedSchemaForResource = undefined;
255 for (var id in this.contributionSchemas) {
256 this.schemasById[id] = this.contributionSchemas[id];
257 this.registeredSchemasIds[id] = true;
259 for (var _i = 0, _a = this.contributionAssociations; _i < _a.length; _i++) {
260 var contributionAssociation = _a[_i];
261 this.filePatternAssociations.push(contributionAssociation);
264 JSONSchemaService.prototype.getResolvedSchema = function (schemaId) {
265 var id = normalizeId(schemaId);
266 var schemaHandle = this.schemasById[id];
268 return schemaHandle.getResolvedSchema();
270 return this.promise.resolve(undefined);
272 JSONSchemaService.prototype.loadSchema = function (url) {
273 if (!this.requestService) {
274 var errorMessage = localize('json.schema.norequestservice', 'Unable to load schema from \'{0}\'. No schema request service available', toDisplayString(url));
275 return this.promise.resolve(new UnresolvedSchema({}, [errorMessage]));
277 return this.requestService(url).then(function (content) {
279 var errorMessage = localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': No content.', toDisplayString(url));
280 return new UnresolvedSchema({}, [errorMessage]);
282 var schemaContent = {};
284 schemaContent = Json.parse(content, jsonErrors);
285 var errors = jsonErrors.length ? [localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': Parse error at offset {1}.', toDisplayString(url), jsonErrors[0].offset)] : [];
286 return new UnresolvedSchema(schemaContent, errors);
287 }, function (error) {
288 var errorMessage = error.toString();
289 var errorSplit = error.toString().split('Error: ');
290 if (errorSplit.length > 1) {
291 // more concise error message, URL and context are attached by caller anyways
292 errorMessage = errorSplit[1];
294 if (Strings.endsWith(errorMessage, '.')) {
295 errorMessage = errorMessage.substr(0, errorMessage.length - 1);
297 return new UnresolvedSchema({}, [localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': {1}.', toDisplayString(url), errorMessage)]);
300 JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, schemaURL, dependencies) {
302 var resolveErrors = schemaToResolve.errors.slice(0);
303 var schema = schemaToResolve.schema;
304 if (schema.$schema) {
305 var id = normalizeId(schema.$schema);
306 if (id === 'http://json-schema.org/draft-03/schema') {
307 return this.promise.resolve(new ResolvedSchema({}, [localize('json.schema.draft03.notsupported', "Draft-03 schemas are not supported.")]));
309 else if (id === 'https://json-schema.org/draft/2019-09/schema') {
310 resolveErrors.push(localize('json.schema.draft201909.notsupported', "Draft 2019-09 schemas are not yet fully supported."));
313 var contextService = this.contextService;
314 var findSection = function (schema, path) {
318 var current = schema;
319 if (path[0] === '/') {
320 path = path.substr(1);
322 path.split('/').some(function (part) {
323 part = part.replace(/~1/g, '/').replace(/~0/g, '~');
324 current = current[part];
329 var merge = function (target, sourceRoot, sourceURI, refSegment) {
330 var path = refSegment ? decodeURIComponent(refSegment) : undefined;
331 var section = findSection(sourceRoot, path);
333 for (var key in section) {
334 if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
335 target[key] = section[key];
340 resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in \'{1}\' can not be resolved.', path, sourceURI));
343 var resolveExternalLink = function (node, uri, refSegment, parentSchemaURL, parentSchemaDependencies) {
344 if (contextService && !/^\w+:\/\/.*/.test(uri)) {
345 uri = contextService.resolveRelativePath(uri, parentSchemaURL);
347 uri = normalizeId(uri);
348 var referencedHandle = _this.getOrAddSchemaHandle(uri);
349 return referencedHandle.getUnresolvedSchema().then(function (unresolvedSchema) {
350 parentSchemaDependencies[uri] = true;
351 if (unresolvedSchema.errors.length) {
352 var loc = refSegment ? uri + '#' + refSegment : uri;
353 resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
355 merge(node, unresolvedSchema.schema, uri, refSegment);
356 return resolveRefs(node, unresolvedSchema.schema, uri, referencedHandle.dependencies);
359 var resolveRefs = function (node, parentSchema, parentSchemaURL, parentSchemaDependencies) {
360 if (!node || typeof node !== 'object') {
361 return Promise.resolve(null);
365 var openPromises = [];
366 var collectEntries = function () {
368 for (var _i = 0; _i < arguments.length; _i++) {
369 entries[_i] = arguments[_i];
371 for (var _a = 0, entries_1 = entries; _a < entries_1.length; _a++) {
372 var entry = entries_1[_a];
373 if (typeof entry === 'object') {
378 var collectMapEntries = function () {
380 for (var _i = 0; _i < arguments.length; _i++) {
381 maps[_i] = arguments[_i];
383 for (var _a = 0, maps_1 = maps; _a < maps_1.length; _a++) {
384 var map = maps_1[_a];
385 if (typeof map === 'object') {
388 var entry = map[key];
389 if (typeof entry === 'object') {
396 var collectArrayEntries = function () {
398 for (var _i = 0; _i < arguments.length; _i++) {
399 arrays[_i] = arguments[_i];
401 for (var _a = 0, arrays_1 = arrays; _a < arrays_1.length; _a++) {
402 var array = arrays_1[_a];
403 if (Array.isArray(array)) {
404 for (var _b = 0, array_1 = array; _b < array_1.length; _b++) {
405 var entry = array_1[_b];
406 if (typeof entry === 'object') {
413 var handleRef = function (next) {
417 var segments = ref.split('#', 2);
419 if (segments[0].length > 0) {
420 openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentSchemaURL, parentSchemaDependencies));
424 if (seenRefs.indexOf(ref) === -1) {
425 merge(next, parentSchema, parentSchemaURL, segments[1]); // can set next.$ref again, use seenRefs to avoid circle
430 collectEntries(next.items, next.additionalItems, next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else);
431 collectMapEntries(next.definitions, next.properties, next.patternProperties, next.dependencies);
432 collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.items);
434 while (toWalk.length) {
435 var next = toWalk.pop();
436 if (seen.indexOf(next) >= 0) {
442 return _this.promise.all(openPromises);
444 return resolveRefs(schema, schema, schemaURL, dependencies).then(function (_) { return new ResolvedSchema(schema, resolveErrors); });
446 JSONSchemaService.prototype.getSchemaForResource = function (resource, document) {
447 // first use $schema if present
448 if (document && document.root && document.root.type === 'object') {
449 var schemaProperties = document.root.properties.filter(function (p) { return (p.keyNode.value === '$schema') && p.valueNode && p.valueNode.type === 'string'; });
450 if (schemaProperties.length > 0) {
451 var valueNode = schemaProperties[0].valueNode;
452 if (valueNode && valueNode.type === 'string') {
453 var schemeId = Parser.getNodeValue(valueNode);
454 if (schemeId && Strings.startsWith(schemeId, '.') && this.contextService) {
455 schemeId = this.contextService.resolveRelativePath(schemeId, resource);
458 var id = normalizeId(schemeId);
459 return this.getOrAddSchemaHandle(id).getResolvedSchema();
464 if (this.cachedSchemaForResource && this.cachedSchemaForResource.resource === resource) {
465 return this.cachedSchemaForResource.resolvedSchema;
467 var seen = Object.create(null);
469 var normalizedResource = normalizeResourceForMatching(resource);
470 for (var _i = 0, _a = this.filePatternAssociations; _i < _a.length; _i++) {
472 if (entry.matchesPattern(normalizedResource)) {
473 for (var _b = 0, _c = entry.getURIs(); _b < _c.length; _b++) {
474 var schemaId = _c[_b];
475 if (!seen[schemaId]) {
476 schemas.push(schemaId);
477 seen[schemaId] = true;
482 var resolvedSchema = schemas.length > 0 ? this.createCombinedSchema(resource, schemas).getResolvedSchema() : this.promise.resolve(undefined);
483 this.cachedSchemaForResource = { resource: resource, resolvedSchema: resolvedSchema };
484 return resolvedSchema;
486 JSONSchemaService.prototype.createCombinedSchema = function (resource, schemaIds) {
487 if (schemaIds.length === 1) {
488 return this.getOrAddSchemaHandle(schemaIds[0]);
491 var combinedSchemaId = 'schemaservice://combinedSchema/' + encodeURIComponent(resource);
492 var combinedSchema = {
493 allOf: schemaIds.map(function (schemaId) { return ({ $ref: schemaId }); })
495 return this.addSchemaHandle(combinedSchemaId, combinedSchema);
498 JSONSchemaService.prototype.getMatchingSchemas = function (document, jsonDocument, schema) {
500 var id = schema.id || ('schemaservice://untitled/matchingSchemas/' + idCounter++);
501 return this.resolveSchemaContent(new UnresolvedSchema(schema), id, {}).then(function (resolvedSchema) {
502 return jsonDocument.getMatchingSchemas(resolvedSchema.schema).filter(function (s) { return !s.inverted; });
505 return this.getSchemaForResource(document.uri, jsonDocument).then(function (schema) {
507 return jsonDocument.getMatchingSchemas(schema.schema).filter(function (s) { return !s.inverted; });
512 return JSONSchemaService;
514 exports.JSONSchemaService = JSONSchemaService;
516 function normalizeId(id) {
517 // remove trailing '#', normalize drive capitalization
519 return vscode_uri_1.URI.parse(id).toString();
525 function normalizeResourceForMatching(resource) {
526 // remove queries and fragments, normalize drive capitalization
528 return vscode_uri_1.URI.parse(resource).with({ fragment: null, query: null }).toString();
534 function toDisplayString(url) {
536 var uri = vscode_uri_1.URI.parse(url);
537 if (uri.scheme === 'file') {