123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import * as Json from 'jsonc-parser';
- import { URI } from 'vscode-uri';
- import * as Strings from '../utils/strings';
- import * as Parser from '../parser/jsonParser';
- import * as nls from 'vscode-nls';
- import { createRegex } from '../utils/glob';
- var localize = nls.loadMessageBundle();
- var BANG = '!';
- var PATH_SEP = '/';
- var FilePatternAssociation = /** @class */ (function () {
- function FilePatternAssociation(pattern, uris) {
- this.globWrappers = [];
- try {
- for (var _i = 0, pattern_1 = pattern; _i < pattern_1.length; _i++) {
- var patternString = pattern_1[_i];
- var include = patternString[0] !== BANG;
- if (!include) {
- patternString = patternString.substring(1);
- }
- if (patternString.length > 0) {
- if (patternString[0] === PATH_SEP) {
- patternString = patternString.substring(1);
- }
- this.globWrappers.push({
- regexp: createRegex('**/' + patternString, { extended: true, globstar: true }),
- include: include,
- });
- }
- }
- ;
- this.uris = uris;
- }
- catch (e) {
- this.globWrappers.length = 0;
- this.uris = [];
- }
- }
- FilePatternAssociation.prototype.matchesPattern = function (fileName) {
- var match = false;
- for (var _i = 0, _a = this.globWrappers; _i < _a.length; _i++) {
- var _b = _a[_i], regexp = _b.regexp, include = _b.include;
- if (regexp.test(fileName)) {
- match = include;
- }
- }
- return match;
- };
- FilePatternAssociation.prototype.getURIs = function () {
- return this.uris;
- };
- return FilePatternAssociation;
- }());
- var SchemaHandle = /** @class */ (function () {
- function SchemaHandle(service, url, unresolvedSchemaContent) {
- this.service = service;
- this.url = url;
- this.dependencies = {};
- if (unresolvedSchemaContent) {
- this.unresolvedSchema = this.service.promise.resolve(new UnresolvedSchema(unresolvedSchemaContent));
- }
- }
- SchemaHandle.prototype.getUnresolvedSchema = function () {
- if (!this.unresolvedSchema) {
- this.unresolvedSchema = this.service.loadSchema(this.url);
- }
- return this.unresolvedSchema;
- };
- SchemaHandle.prototype.getResolvedSchema = function () {
- var _this = this;
- if (!this.resolvedSchema) {
- this.resolvedSchema = this.getUnresolvedSchema().then(function (unresolved) {
- return _this.service.resolveSchemaContent(unresolved, _this.url, _this.dependencies);
- });
- }
- return this.resolvedSchema;
- };
- SchemaHandle.prototype.clearSchema = function () {
- this.resolvedSchema = undefined;
- this.unresolvedSchema = undefined;
- this.dependencies = {};
- };
- return SchemaHandle;
- }());
- var UnresolvedSchema = /** @class */ (function () {
- function UnresolvedSchema(schema, errors) {
- if (errors === void 0) { errors = []; }
- this.schema = schema;
- this.errors = errors;
- }
- return UnresolvedSchema;
- }());
- export { UnresolvedSchema };
- var ResolvedSchema = /** @class */ (function () {
- function ResolvedSchema(schema, errors) {
- if (errors === void 0) { errors = []; }
- this.schema = schema;
- this.errors = errors;
- }
- ResolvedSchema.prototype.getSection = function (path) {
- var schemaRef = this.getSectionRecursive(path, this.schema);
- if (schemaRef) {
- return Parser.asSchema(schemaRef);
- }
- return undefined;
- };
- ResolvedSchema.prototype.getSectionRecursive = function (path, schema) {
- if (!schema || typeof schema === 'boolean' || path.length === 0) {
- return schema;
- }
- var next = path.shift();
- if (schema.properties && typeof schema.properties[next]) {
- return this.getSectionRecursive(path, schema.properties[next]);
- }
- else if (schema.patternProperties) {
- for (var _i = 0, _a = Object.keys(schema.patternProperties); _i < _a.length; _i++) {
- var pattern = _a[_i];
- var regex = Strings.extendedRegExp(pattern);
- if (regex.test(next)) {
- return this.getSectionRecursive(path, schema.patternProperties[pattern]);
- }
- }
- }
- else if (typeof schema.additionalProperties === 'object') {
- return this.getSectionRecursive(path, schema.additionalProperties);
- }
- else if (next.match('[0-9]+')) {
- if (Array.isArray(schema.items)) {
- var index = parseInt(next, 10);
- if (!isNaN(index) && schema.items[index]) {
- return this.getSectionRecursive(path, schema.items[index]);
- }
- }
- else if (schema.items) {
- return this.getSectionRecursive(path, schema.items);
- }
- }
- return undefined;
- };
- return ResolvedSchema;
- }());
- export { ResolvedSchema };
- var JSONSchemaService = /** @class */ (function () {
- function JSONSchemaService(requestService, contextService, promiseConstructor) {
- this.contextService = contextService;
- this.requestService = requestService;
- this.promiseConstructor = promiseConstructor || Promise;
- this.callOnDispose = [];
- this.contributionSchemas = {};
- this.contributionAssociations = [];
- this.schemasById = {};
- this.filePatternAssociations = [];
- this.registeredSchemasIds = {};
- }
- JSONSchemaService.prototype.getRegisteredSchemaIds = function (filter) {
- return Object.keys(this.registeredSchemasIds).filter(function (id) {
- var scheme = URI.parse(id).scheme;
- return scheme !== 'schemaservice' && (!filter || filter(scheme));
- });
- };
- Object.defineProperty(JSONSchemaService.prototype, "promise", {
- get: function () {
- return this.promiseConstructor;
- },
- enumerable: false,
- configurable: true
- });
- JSONSchemaService.prototype.dispose = function () {
- while (this.callOnDispose.length > 0) {
- this.callOnDispose.pop()();
- }
- };
- JSONSchemaService.prototype.onResourceChange = function (uri) {
- var _this = this;
- var hasChanges = false;
- uri = normalizeId(uri);
- var toWalk = [uri];
- var all = Object.keys(this.schemasById).map(function (key) { return _this.schemasById[key]; });
- while (toWalk.length) {
- var curr = toWalk.pop();
- for (var i = 0; i < all.length; i++) {
- var handle = all[i];
- if (handle && (handle.url === curr || handle.dependencies[curr])) {
- if (handle.url !== curr) {
- toWalk.push(handle.url);
- }
- handle.clearSchema();
- all[i] = undefined;
- hasChanges = true;
- }
- }
- }
- return hasChanges;
- };
- JSONSchemaService.prototype.setSchemaContributions = function (schemaContributions) {
- if (schemaContributions.schemas) {
- var schemas = schemaContributions.schemas;
- for (var id in schemas) {
- var normalizedId = normalizeId(id);
- this.contributionSchemas[normalizedId] = this.addSchemaHandle(normalizedId, schemas[id]);
- }
- }
- if (Array.isArray(schemaContributions.schemaAssociations)) {
- var schemaAssociations = schemaContributions.schemaAssociations;
- for (var _i = 0, schemaAssociations_1 = schemaAssociations; _i < schemaAssociations_1.length; _i++) {
- var schemaAssociation = schemaAssociations_1[_i];
- var uris = schemaAssociation.uris.map(normalizeId);
- var association = this.addFilePatternAssociation(schemaAssociation.pattern, uris);
- this.contributionAssociations.push(association);
- }
- }
- };
- JSONSchemaService.prototype.addSchemaHandle = function (id, unresolvedSchemaContent) {
- var schemaHandle = new SchemaHandle(this, id, unresolvedSchemaContent);
- this.schemasById[id] = schemaHandle;
- return schemaHandle;
- };
- JSONSchemaService.prototype.getOrAddSchemaHandle = function (id, unresolvedSchemaContent) {
- return this.schemasById[id] || this.addSchemaHandle(id, unresolvedSchemaContent);
- };
- JSONSchemaService.prototype.addFilePatternAssociation = function (pattern, uris) {
- var fpa = new FilePatternAssociation(pattern, uris);
- this.filePatternAssociations.push(fpa);
- return fpa;
- };
- JSONSchemaService.prototype.registerExternalSchema = function (uri, filePatterns, unresolvedSchemaContent) {
- var id = normalizeId(uri);
- this.registeredSchemasIds[id] = true;
- this.cachedSchemaForResource = undefined;
- if (filePatterns) {
- this.addFilePatternAssociation(filePatterns, [uri]);
- }
- return unresolvedSchemaContent ? this.addSchemaHandle(id, unresolvedSchemaContent) : this.getOrAddSchemaHandle(id);
- };
- JSONSchemaService.prototype.clearExternalSchemas = function () {
- this.schemasById = {};
- this.filePatternAssociations = [];
- this.registeredSchemasIds = {};
- this.cachedSchemaForResource = undefined;
- for (var id in this.contributionSchemas) {
- this.schemasById[id] = this.contributionSchemas[id];
- this.registeredSchemasIds[id] = true;
- }
- for (var _i = 0, _a = this.contributionAssociations; _i < _a.length; _i++) {
- var contributionAssociation = _a[_i];
- this.filePatternAssociations.push(contributionAssociation);
- }
- };
- JSONSchemaService.prototype.getResolvedSchema = function (schemaId) {
- var id = normalizeId(schemaId);
- var schemaHandle = this.schemasById[id];
- if (schemaHandle) {
- return schemaHandle.getResolvedSchema();
- }
- return this.promise.resolve(undefined);
- };
- JSONSchemaService.prototype.loadSchema = function (url) {
- if (!this.requestService) {
- var errorMessage = localize('json.schema.norequestservice', 'Unable to load schema from \'{0}\'. No schema request service available', toDisplayString(url));
- return this.promise.resolve(new UnresolvedSchema({}, [errorMessage]));
- }
- return this.requestService(url).then(function (content) {
- if (!content) {
- var errorMessage = localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': No content.', toDisplayString(url));
- return new UnresolvedSchema({}, [errorMessage]);
- }
- var schemaContent = {};
- var jsonErrors = [];
- schemaContent = Json.parse(content, jsonErrors);
- var errors = jsonErrors.length ? [localize('json.schema.invalidFormat', 'Unable to parse content from \'{0}\': Parse error at offset {1}.', toDisplayString(url), jsonErrors[0].offset)] : [];
- return new UnresolvedSchema(schemaContent, errors);
- }, function (error) {
- var errorMessage = error.toString();
- var errorSplit = error.toString().split('Error: ');
- if (errorSplit.length > 1) {
- // more concise error message, URL and context are attached by caller anyways
- errorMessage = errorSplit[1];
- }
- if (Strings.endsWith(errorMessage, '.')) {
- errorMessage = errorMessage.substr(0, errorMessage.length - 1);
- }
- return new UnresolvedSchema({}, [localize('json.schema.nocontent', 'Unable to load schema from \'{0}\': {1}.', toDisplayString(url), errorMessage)]);
- });
- };
- JSONSchemaService.prototype.resolveSchemaContent = function (schemaToResolve, schemaURL, dependencies) {
- var _this = this;
- var resolveErrors = schemaToResolve.errors.slice(0);
- var schema = schemaToResolve.schema;
- if (schema.$schema) {
- var id = normalizeId(schema.$schema);
- if (id === 'http://json-schema.org/draft-03/schema') {
- return this.promise.resolve(new ResolvedSchema({}, [localize('json.schema.draft03.notsupported', "Draft-03 schemas are not supported.")]));
- }
- else if (id === 'https://json-schema.org/draft/2019-09/schema') {
- resolveErrors.push(localize('json.schema.draft201909.notsupported', "Draft 2019-09 schemas are not yet fully supported."));
- }
- }
- var contextService = this.contextService;
- var findSection = function (schema, path) {
- if (!path) {
- return schema;
- }
- var current = schema;
- if (path[0] === '/') {
- path = path.substr(1);
- }
- path.split('/').some(function (part) {
- part = part.replace(/~1/g, '/').replace(/~0/g, '~');
- current = current[part];
- return !current;
- });
- return current;
- };
- var merge = function (target, sourceRoot, sourceURI, refSegment) {
- var path = refSegment ? decodeURIComponent(refSegment) : undefined;
- var section = findSection(sourceRoot, path);
- if (section) {
- for (var key in section) {
- if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
- target[key] = section[key];
- }
- }
- }
- else {
- resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in \'{1}\' can not be resolved.', path, sourceURI));
- }
- };
- var resolveExternalLink = function (node, uri, refSegment, parentSchemaURL, parentSchemaDependencies) {
- if (contextService && !/^[A-Za-z][A-Za-z0-9+\-.+]*:\/\/.*/.test(uri)) {
- uri = contextService.resolveRelativePath(uri, parentSchemaURL);
- }
- uri = normalizeId(uri);
- var referencedHandle = _this.getOrAddSchemaHandle(uri);
- return referencedHandle.getUnresolvedSchema().then(function (unresolvedSchema) {
- parentSchemaDependencies[uri] = true;
- if (unresolvedSchema.errors.length) {
- var loc = refSegment ? uri + '#' + refSegment : uri;
- resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
- }
- merge(node, unresolvedSchema.schema, uri, refSegment);
- return resolveRefs(node, unresolvedSchema.schema, uri, referencedHandle.dependencies);
- });
- };
- var resolveRefs = function (node, parentSchema, parentSchemaURL, parentSchemaDependencies) {
- if (!node || typeof node !== 'object') {
- return Promise.resolve(null);
- }
- var toWalk = [node];
- var seen = [];
- var openPromises = [];
- var collectEntries = function () {
- var entries = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- entries[_i] = arguments[_i];
- }
- for (var _a = 0, entries_1 = entries; _a < entries_1.length; _a++) {
- var entry = entries_1[_a];
- if (typeof entry === 'object') {
- toWalk.push(entry);
- }
- }
- };
- var collectMapEntries = function () {
- var maps = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- maps[_i] = arguments[_i];
- }
- for (var _a = 0, maps_1 = maps; _a < maps_1.length; _a++) {
- var map = maps_1[_a];
- if (typeof map === 'object') {
- for (var k in map) {
- var key = k;
- var entry = map[key];
- if (typeof entry === 'object') {
- toWalk.push(entry);
- }
- }
- }
- }
- };
- var collectArrayEntries = function () {
- var arrays = [];
- for (var _i = 0; _i < arguments.length; _i++) {
- arrays[_i] = arguments[_i];
- }
- for (var _a = 0, arrays_1 = arrays; _a < arrays_1.length; _a++) {
- var array = arrays_1[_a];
- if (Array.isArray(array)) {
- for (var _b = 0, array_1 = array; _b < array_1.length; _b++) {
- var entry = array_1[_b];
- if (typeof entry === 'object') {
- toWalk.push(entry);
- }
- }
- }
- }
- };
- var handleRef = function (next) {
- var seenRefs = [];
- while (next.$ref) {
- var ref = next.$ref;
- var segments = ref.split('#', 2);
- delete next.$ref;
- if (segments[0].length > 0) {
- openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentSchemaURL, parentSchemaDependencies));
- return;
- }
- else {
- if (seenRefs.indexOf(ref) === -1) {
- merge(next, parentSchema, parentSchemaURL, segments[1]); // can set next.$ref again, use seenRefs to avoid circle
- seenRefs.push(ref);
- }
- }
- }
- collectEntries(next.items, next.additionalItems, next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else);
- collectMapEntries(next.definitions, next.properties, next.patternProperties, next.dependencies);
- collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.items);
- };
- while (toWalk.length) {
- var next = toWalk.pop();
- if (seen.indexOf(next) >= 0) {
- continue;
- }
- seen.push(next);
- handleRef(next);
- }
- return _this.promise.all(openPromises);
- };
- return resolveRefs(schema, schema, schemaURL, dependencies).then(function (_) { return new ResolvedSchema(schema, resolveErrors); });
- };
- JSONSchemaService.prototype.getSchemaForResource = function (resource, document) {
- // first use $schema if present
- if (document && document.root && document.root.type === 'object') {
- var schemaProperties = document.root.properties.filter(function (p) { return (p.keyNode.value === '$schema') && p.valueNode && p.valueNode.type === 'string'; });
- if (schemaProperties.length > 0) {
- var valueNode = schemaProperties[0].valueNode;
- if (valueNode && valueNode.type === 'string') {
- var schemeId = Parser.getNodeValue(valueNode);
- if (schemeId && Strings.startsWith(schemeId, '.') && this.contextService) {
- schemeId = this.contextService.resolveRelativePath(schemeId, resource);
- }
- if (schemeId) {
- var id = normalizeId(schemeId);
- return this.getOrAddSchemaHandle(id).getResolvedSchema();
- }
- }
- }
- }
- if (this.cachedSchemaForResource && this.cachedSchemaForResource.resource === resource) {
- return this.cachedSchemaForResource.resolvedSchema;
- }
- var seen = Object.create(null);
- var schemas = [];
- var normalizedResource = normalizeResourceForMatching(resource);
- for (var _i = 0, _a = this.filePatternAssociations; _i < _a.length; _i++) {
- var entry = _a[_i];
- if (entry.matchesPattern(normalizedResource)) {
- for (var _b = 0, _c = entry.getURIs(); _b < _c.length; _b++) {
- var schemaId = _c[_b];
- if (!seen[schemaId]) {
- schemas.push(schemaId);
- seen[schemaId] = true;
- }
- }
- }
- }
- var resolvedSchema = schemas.length > 0 ? this.createCombinedSchema(resource, schemas).getResolvedSchema() : this.promise.resolve(undefined);
- this.cachedSchemaForResource = { resource: resource, resolvedSchema: resolvedSchema };
- return resolvedSchema;
- };
- JSONSchemaService.prototype.createCombinedSchema = function (resource, schemaIds) {
- if (schemaIds.length === 1) {
- return this.getOrAddSchemaHandle(schemaIds[0]);
- }
- else {
- var combinedSchemaId = 'schemaservice://combinedSchema/' + encodeURIComponent(resource);
- var combinedSchema = {
- allOf: schemaIds.map(function (schemaId) { return ({ $ref: schemaId }); })
- };
- return this.addSchemaHandle(combinedSchemaId, combinedSchema);
- }
- };
- JSONSchemaService.prototype.getMatchingSchemas = function (document, jsonDocument, schema) {
- if (schema) {
- var id = schema.id || ('schemaservice://untitled/matchingSchemas/' + idCounter++);
- return this.resolveSchemaContent(new UnresolvedSchema(schema), id, {}).then(function (resolvedSchema) {
- return jsonDocument.getMatchingSchemas(resolvedSchema.schema).filter(function (s) { return !s.inverted; });
- });
- }
- return this.getSchemaForResource(document.uri, jsonDocument).then(function (schema) {
- if (schema) {
- return jsonDocument.getMatchingSchemas(schema.schema).filter(function (s) { return !s.inverted; });
- }
- return [];
- });
- };
- return JSONSchemaService;
- }());
- export { JSONSchemaService };
- var idCounter = 0;
- function normalizeId(id) {
- // remove trailing '#', normalize drive capitalization
- try {
- return URI.parse(id).toString();
- }
- catch (e) {
- return id;
- }
- }
- function normalizeResourceForMatching(resource) {
- // remove queries and fragments, normalize drive capitalization
- try {
- return URI.parse(resource).with({ fragment: null, query: null }).toString();
- }
- catch (e) {
- return resource;
- }
- }
- function toDisplayString(url) {
- try {
- var uri = URI.parse(url);
- if (uri.scheme === 'file') {
- return uri.fsPath;
- }
- }
- catch (e) {
- // ignore
- }
- return url;
- }
|