alm
Version:
The best IDE for TypeScript
197 lines (179 loc) • 7.11 kB
text/typescript
/**
* From : https://raw.githubusercontent.com/Microsoft/vscode/master/extensions/json/server/src/jsonSchemaService.ts
*
* The original has a lot of code (around XHRs and looking up schemas mainly) that we do not need
*
* We simply need the `JSONSchemaService.resolveSchemaContent`
* - And the class `ResolvedSchema` (not sure I need this yet, but good container for errors)
*/
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
;
import Json = require('../core/jsonc-parser');
import {JSONSchema, JSONSchemaMap} from '../core/jsonSchema';
import Strings = require('../utils/strings');
import Parser = require('../core/jsonParser');
import {localize} from "../core/localize";
import * as utils from "../../../../../common/utils";
type Thenable<T> = Promise<T>;
/**
* Register our schemas
* Mostly downloaded from http://json.schemastore.org/
*
* New additions need to
* - Download and put in schemas
* - Update utils.ts for "supportedConfigFileNames"
*/
export const schemas: { fileName: string, content: JSONSchema }[] = [];
Object.keys(utils.supportedAutocompleteConfigFileNames).forEach(fileName => {
const rawContent = require(`../schemas/${fileName}`);
resolveSchemaContent(rawContent); // Mutates it in place
schemas.push({
fileName,
content: rawContent
});
});
export function getSchemaForResource(filePath): Promise<ResolvedSchema> {
const fileName = utils.getFileName(filePath);
const schema = schemas.find(s=>s.fileName === fileName).content;
return Promise.resolve(new ResolvedSchema(schema));
}
/**
* Copied straight out of JSONSchemaService
* Resolves *links* in schema definitions
* NOTE: Mutates the argument `schema` too!
*/
export function resolveSchemaContent(schema: JSONSchema): Thenable<ResolvedSchema> {
let resolveErrors: string[] = [];
let findSection = (schema: JSONSchema, path: string): any => {
if (!path) {
return schema;
}
let current: any = schema;
path.substr(1).split('/').some((part) => {
current = current[part];
return !current;
});
return current;
};
let resolveLink = (node: any, linkedSchema: JSONSchema, linkPath: string): void => {
let section = findSection(linkedSchema, linkPath);
if (section) {
for (let key in section) {
if (section.hasOwnProperty(key) && !node.hasOwnProperty(key)) {
node[key] = section[key];
}
}
} else {
resolveErrors.push(localize('json.schema.invalidref', '$ref \'{0}\' in {1} can not be resolved.', linkPath, linkedSchema.id));
}
delete node.$ref;
};
let resolveExternalLink = (node: any, uri: string, linkPath: string): Thenable<any> => {
return this.getOrAddSchemaHandle(uri).getUnresolvedSchema().then(unresolvedSchema => {
if (unresolvedSchema.errors.length) {
let loc = linkPath ? uri + '#' + linkPath : uri;
resolveErrors.push(localize('json.schema.problemloadingref', 'Problems loading reference \'{0}\': {1}', loc, unresolvedSchema.errors[0]));
}
resolveLink(node, unresolvedSchema.schema, linkPath);
return resolveRefs(node, unresolvedSchema.schema);
});
};
let resolveRefs = (node: JSONSchema, parentSchema: JSONSchema): Thenable<any> => {
let toWalk : JSONSchema[] = [node];
let seen: JSONSchema[] = [];
let openPromises: Thenable<any>[] = [];
let collectEntries = (...entries: JSONSchema[]) => {
for (let entry of entries) {
if (typeof entry === 'object') {
toWalk.push(entry);
}
}
};
let collectMapEntries = (...maps: JSONSchemaMap[]) => {
for (let map of maps) {
if (typeof map === 'object') {
for (let key in map) {
let entry = map[key];
toWalk.push(entry);
}
}
}
};
let collectArrayEntries = (...arrays: JSONSchema[][]) => {
for (let array of arrays) {
if (Array.isArray(array)) {
toWalk.push.apply(toWalk, array);
}
}
};
while (toWalk.length) {
let next = toWalk.pop();
if (seen.indexOf(next) >= 0) {
continue;
}
seen.push(next);
if (next.$ref) {
let segments = next.$ref.split('#', 2);
if (segments[0].length > 0) {
openPromises.push(resolveExternalLink(next, segments[0], segments[1]));
continue;
} else {
resolveLink(next, parentSchema, segments[1]);
}
}
collectEntries(next.items as any, next.additionalProperties as any, next.not);
collectMapEntries(next.definitions, next.properties, next.patternProperties, <JSONSchemaMap> next.dependencies);
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, <JSONSchema[]> next.items);
}
return Promise.all(openPromises);
};
return resolveRefs(schema, schema).then(_ => new ResolvedSchema(schema, resolveErrors));
}
export class ResolvedSchema {
public schema: JSONSchema;
public errors: string[];
constructor(schema: JSONSchema, errors: string[] = []) {
this.schema = schema;
this.errors = errors;
}
public getSection(path: string[]): JSONSchema {
return this.getSectionRecursive(path, this.schema);
}
private getSectionRecursive(path: string[], schema: JSONSchema): JSONSchema {
if (!schema || path.length === 0) {
return schema;
}
let next = path.shift();
if (schema.properties && schema.properties[next]) {
return this.getSectionRecursive(path, schema.properties[next]);
} else if (schema.patternProperties) {
Object.keys(schema.patternProperties).forEach((pattern) => {
let regex = new RegExp(pattern);
if (regex.test(next)) {
return this.getSectionRecursive(path, schema.patternProperties[pattern]);
}
});
} else if (schema.additionalProperties) {
return this.getSectionRecursive(path, schema.additionalProperties as any);
} else if (next.match('[0-9]+')) {
if (schema.items) {
return this.getSectionRecursive(path, schema.items as any);
} else if (Array.isArray(schema.items)) {
try {
let index = parseInt(next, 10);
if (schema.items[index]) {
return this.getSectionRecursive(path, schema.items[index]);
}
return null;
}
catch (e) {
return null;
}
}
}
return null;
}
}