public resolveSchemaContent()

in src/services/jsonSchemaService.ts [409:538]


	public resolveSchemaContent(schemaToResolve: UnresolvedSchema, handle: SchemaHandle): Thenable<ResolvedSchema> {

		const resolveErrors: string[] = schemaToResolve.errors.slice(0);
		const schema = schemaToResolve.schema;

		if (schema.$schema) {
			const 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."));
			} else if (id === 'https://json-schema.org/draft/2020-12/schema') {
				resolveErrors.push(localize('json.schema.draft202012.notsupported', "Draft 2020-12 schemas are not yet fully supported."));
			}
		}

		const contextService = this.contextService;

		const findSectionByJSONPointer = (schema: JSONSchema, path: string): any => {
			path = decodeURIComponent(path);
			let current: any = schema;
			if (path[0] === '/') {
				path = path.substring(1);
			}
			path.split('/').some((part) => {
				part = part.replace(/~1/g, '/').replace(/~0/g, '~');
				current = current[part];
				return !current;
			});
			return current;
		};

		const findSchemaById = (schema: JSONSchema, handle: SchemaHandle, id: string) => {
			if (!handle.anchors) {
				handle.anchors = collectAnchors(schema);
			}
			return handle.anchors.get(id);
		};

		const merge = (target: JSONSchema, section: any): void => {
			for (const key in section) {
				if (section.hasOwnProperty(key) && !target.hasOwnProperty(key) && key !== 'id' && key !== '$id') {
					(<any>target)[key] = section[key];
				}
			}
		};

		const mergeRef = (target: JSONSchema, sourceRoot: JSONSchema, sourceHandle: SchemaHandle, refSegment: string | undefined): void => {
			let section;
			if (refSegment === undefined || refSegment.length === 0) {
				section = sourceRoot;
			} else if (refSegment.charAt(0) === '/') {
				// A $ref to a JSON Pointer (i.e #/definitions/foo)
				section = findSectionByJSONPointer(sourceRoot, refSegment);
			} else {
				// A $ref to a sub-schema with an $id (i.e #hello)
				section = findSchemaById(sourceRoot, sourceHandle, refSegment);
			}
			if (section) {
				merge(target, section);
			} else {
				resolveErrors.push(localize('json.schema.invalidid', '$ref \'{0}\' in \'{1}\' can not be resolved.', refSegment, sourceHandle.uri));
			}
		};

		const resolveExternalLink = (node: JSONSchema, uri: string, refSegment: string | undefined, parentHandle: SchemaHandle): Thenable<any> => {
			if (contextService && !/^[A-Za-z][A-Za-z0-9+\-.+]*:\/\/.*/.test(uri)) {
				uri = contextService.resolveRelativePath(uri, parentHandle.uri);
			}
			uri = normalizeId(uri);
			const referencedHandle = this.getOrAddSchemaHandle(uri);
			return referencedHandle.getUnresolvedSchema().then(unresolvedSchema => {
				parentHandle.dependencies.add(uri);
				if (unresolvedSchema.errors.length) {
					const loc = refSegment ? uri + '#' + refSegment : uri;
					resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
				}
				mergeRef(node, unresolvedSchema.schema, referencedHandle, refSegment);
				return resolveRefs(node, unresolvedSchema.schema, referencedHandle);
			});
		};

		const resolveRefs = (node: JSONSchema, parentSchema: JSONSchema, parentHandle: SchemaHandle): Thenable<any> => {
			const openPromises: Thenable<any>[] = [];

			this.traverseNodes(node, next => {
				const seenRefs = new Set<string>();
				while (next.$ref) {
					const ref = next.$ref;
					const segments = ref.split('#', 2);
					delete next.$ref;
					if (segments[0].length > 0) {
						// This is a reference to an external schema
						openPromises.push(resolveExternalLink(next, segments[0], segments[1], parentHandle));
						return;
					} else {
						// This is a reference inside the current schema
						if (!seenRefs.has(ref)) {
							const id = segments[1];
							mergeRef(next, parentSchema, parentHandle, id);
							seenRefs.add(ref);
						}
					}
				}
			});

			return this.promise.all(openPromises);
		};

		const collectAnchors = (root: JSONSchema): Map<string, JSONSchema> => {
			const result = new Map<string, JSONSchema>();
			this.traverseNodes(root, next => {
				const id = next.$id || next.id;
				if (typeof id === 'string' && id.charAt(0) === '#') {
					// delete next.$id;
					// delete next.id;
					const anchor = id.substring(1);
					if (result.has(anchor)) {
						resolveErrors.push(localize('json.schema.duplicateid', 'Duplicate id declaration: \'{0}\'', id));
					} else {
						result.set(anchor, next);
					}
				}
			});
			return result;
		};
		return resolveRefs(schema, schema, handle).then(_ => {
			return new ResolvedSchema(schema, resolveErrors);
		});
	}