Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for schema sequences #197

Merged
merged 1 commit into from
Nov 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
});
JPinkney marked this conversation as resolved.
Show resolved Hide resolved
}

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