Skip to content

Commit

Permalink
Fixed up schema sequence
Browse files Browse the repository at this point in the history
  • Loading branch information
JPinkney committed Nov 5, 2019
1 parent 9a1c4fe commit 6dc9a60
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 106 deletions.
2 changes: 2 additions & 0 deletions src/languageservice/jsonSchema04.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export interface JSONSchema {
patternErrorMessage?: string; // VSCode extension
deprecationMessage?: string; // VSCode extension
enumDescriptions?: string[]; // VSCode extension

schemaSequence?: JSONSchema[]; // extension for multiple schemas related to multiple documents in single yaml file
}

export interface JSONSchemaMap {
Expand Down
2 changes: 2 additions & 0 deletions src/languageservice/jsonSchema07.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ export interface JSONSchema {
markdownDescription?: string; // VSCode extension
doNotSuggest?: boolean; // VSCode extension
allowComments?: boolean; // VSCode extension

schemaSequence?: JSONSchema[]; // extension for multiple schemas related to multiple documents in single yaml file
}

export interface JSONSchemaMap {
Expand Down
1 change: 1 addition & 0 deletions src/languageservice/parser/yamlParser07.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class SingleYAMLDocument extends JSONDocument {
public errors;
public warnings;
public isKubernetes: boolean;
public currentDocIndex: number;

constructor(lines: number[]) {
super(null, []);
Expand Down
1 change: 1 addition & 0 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ export class YAMLCompletion {
this.getCustomTagValueCompletions(collector);
}

currentDoc.currentDocIndex = currentDocIndex;
return this.schemaService.getSchemaForResource(document.uri, currentDoc).then(schema => {

if (!schema) {
Expand Down
2 changes: 2 additions & 0 deletions src/languageservice/services/yamlHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export class YAMLHover {
return this.promise.resolve(void 0);
}

const currentDocIndex = doc.documents.indexOf(currentDoc);
currentDoc.currentDocIndex = currentDocIndex;
return this.jsonHover.doHover(document, position, currentDoc);
}
}
157 changes: 152 additions & 5 deletions src/languageservice/services/yamlSchemaService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,21 @@
*--------------------------------------------------------------------------------------------*/
'use strict';

import { JSONSchema } from '../jsonSchema07';
import { JSONSchema, JSONSchemaMap, JSONSchemaRef } from '../jsonSchema07';
import { SchemaRequestService, WorkspaceContextService, PromiseConstructor, Thenable } from '../yamlLanguageService';
import { UnresolvedSchema, ResolvedSchema, JSONSchemaService,
SchemaDependencies, FilePatternAssociation, ISchemaContributions } from 'vscode-json-languageservice/lib/umd/services/jsonSchemaService';

import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();

export declare type CustomSchemaProvider = (uri: string) => Thenable<string>;

export class YAMLSchemaService extends JSONSchemaService {

private customSchemaProvider: CustomSchemaProvider | undefined;
private filePatternAssociations: FilePatternAssociation[];
private contextService: WorkspaceContextService;

constructor(requestService: SchemaRequestService, contextService?: WorkspaceContextService, promiseConstructor?: PromiseConstructor) {
super(requestService, contextService, promiseConstructor);
Expand All @@ -26,6 +30,135 @@ export class YAMLSchemaService extends JSONSchemaService {
this.customSchemaProvider = customSchemaProvider;
}

//tslint:disable
public resolveSchemaContent(schemaToResolve: UnresolvedSchema, schemaURL: string, dependencies: SchemaDependencies): Thenable<ResolvedSchema> {

let resolveErrors: string[] = schemaToResolve.errors.slice(0);
let schema = schemaToResolve.schema;
let contextService = this.contextService;

let findSection = (schema: JSONSchema, path: string): any => {
if (!path) {
return schema;
}
let current: any = schema;
if (path[0] === '/') {
path = path.substr(1);
}
path.split('/').some((part) => {
current = current[part];
return !current;
});
return current;
};

let merge = (target: JSONSchema, sourceRoot: JSONSchema, sourceURI: string, path: string): void => {
let section = findSection(sourceRoot, path);
if (section) {
for (let 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));
}
};

let resolveExternalLink = (node: JSONSchema, uri: string, linkPath: string, parentSchemaURL: string, parentSchemaDependencies: SchemaDependencies): Thenable<any> => {
if (contextService && !/^\w+:\/\/.*/.test(uri)) {
uri = contextService.resolveRelativePath(uri, parentSchemaURL);
}
uri = this.normalizeId(uri);
const referencedHandle = this.getOrAddSchemaHandle(uri);
return referencedHandle.getUnresolvedSchema().then(unresolvedSchema => {
parentSchemaDependencies[uri] = true;
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]));
}
merge(node, unresolvedSchema.schema, uri, linkPath);
return resolveRefs(node, unresolvedSchema.schema, uri, referencedHandle.dependencies);
});
};

let resolveRefs = (node: JSONSchema, parentSchema: JSONSchema, parentSchemaURL: string, parentSchemaDependencies: SchemaDependencies): Thenable<any> => {
if (!node || typeof node !== 'object') {
return Promise.resolve(null);
}

let toWalk: JSONSchema[] = [node];
let seen: JSONSchema[] = [];

let openPromises: Thenable<any>[] = [];

let collectEntries = (...entries: JSONSchemaRef[]) => {
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];
if (typeof entry === 'object') {
toWalk.push(entry);
}
}
}
}
};
let collectArrayEntries = (...arrays: JSONSchemaRef[][]) => {
for (let array of arrays) {
if (Array.isArray(array)) {
for (let entry of array) {
if (typeof entry === 'object') {
toWalk.push(entry);
}
}
}
}
};
let handleRef = (next: JSONSchema) => {
let seenRefs = [];
while (next.$ref) {
const ref = next.$ref;
let 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(<JSONSchema>next.items, <JSONSchema>next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else);
collectMapEntries(next.definitions, next.properties, next.patternProperties, <JSONSchemaMap>next.dependencies);
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, <JSONSchema[]>next.items, next.schemaSequence);
};

while (toWalk.length) {
let next = toWalk.pop();
if (seen.indexOf(next) >= 0) {
continue;
}
seen.push(next);
handleRef(next);
}
return Promise.all(openPromises);
};

return resolveRefs(schema, schema, schemaURL, dependencies).then(_ => new ResolvedSchema(schema, resolveErrors));
}
//tslint:enable

public getSchemaForResource(resource: string, doc = undefined): Thenable<ResolvedSchema> {
const resolveSchema = () => {

Expand All @@ -43,7 +176,12 @@ export class YAMLSchemaService extends JSONSchemaService {
}

if (schemas.length > 0) {
return super.createCombinedSchema(resource, schemas).getResolvedSchema();
return super.createCombinedSchema(resource, schemas).getResolvedSchema().then(schema => {
if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[doc.currentDocIndex]) {
return new ResolvedSchema(schema.schema.schemaSequence[doc.currentDocIndex]);
}
return schema;
});
}

return Promise.resolve(null);
Expand All @@ -56,7 +194,12 @@ export class YAMLSchemaService extends JSONSchemaService {
}

return this.loadSchema(schemaUri)
.then(unsolvedSchema => this.resolveSchemaContent(unsolvedSchema, schemaUri, []));
.then(unsolvedSchema => this.resolveSchemaContent(unsolvedSchema, schemaUri, []).then(schema => {
if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[doc.currentDocIndex]) {
return new ResolvedSchema(schema.schema.schemaSequence[doc.currentDocIndex]);
}
return schema;
}));
})
.then(schema => schema, err => resolveSchema());
} else {
Expand All @@ -69,8 +212,12 @@ export class YAMLSchemaService extends JSONSchemaService {
* to provide a wrapper around the javascript methods we are calling since they have no type
*/

resolveSchemaContent(schemaToResolve: UnresolvedSchema, schemaURL: string, dependencies: SchemaDependencies): Thenable<ResolvedSchema> {
return super.resolveSchemaContent(schemaToResolve, schemaURL, dependencies);
normalizeId(id: string) {
return super.normalizeId(id);
}

getOrAddSchemaHandle(id: string, unresolvedSchemaContent?: JSONSchema) {
return super.getOrAddSchemaHandle(id, unresolvedSchemaContent);
}

// tslint:disable-next-line: no-any
Expand Down
3 changes: 3 additions & 0 deletions src/languageservice/services/yamlValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,10 @@ export class YAMLValidation {
}
const yamlDocument: YAMLDocument = parseYAML(textDocument.getText(), this.customTags);
const validationResult: Diagnostic[] = [];
let index = 0;
for (const currentYAMLDoc of yamlDocument.documents) {
currentYAMLDoc.isKubernetes = isKubernetes;
currentYAMLDoc.currentDocIndex = index;

const validation = await this.jsonValidation.doValidation(textDocument, currentYAMLDoc);
const syd = currentYAMLDoc as unknown as SingleYAMLDocument;
Expand All @@ -51,6 +53,7 @@ export class YAMLValidation {
}

validationResult.push(...validation);
index++;
}

const foundSignatures = new Set();
Expand Down
4 changes: 2 additions & 2 deletions test/fixtures/customMultipleSchemaSequences.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"oneOf": [
"schemaSequence": [
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Person object",
Expand All @@ -14,7 +14,7 @@
"description": "The age of this person"
}
},
"required":["name","age"]
"required": ["name","age"]
},
{
"$ref": "http://json.schemastore.org/bowerrc"
Expand Down
Loading

0 comments on commit 6dc9a60

Please sign in to comment.