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);
});
}