diff --git a/src/languageservice/jsonSchema.ts b/src/languageservice/jsonSchema.ts index 3f8ae133..00521299 100644 --- a/src/languageservice/jsonSchema.ts +++ b/src/languageservice/jsonSchema.ts @@ -44,6 +44,7 @@ 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 "x-kubernetes-group-version-kind"?; //Kubernetes extension } diff --git a/src/languageservice/services/jsonSchemaService.ts b/src/languageservice/services/jsonSchemaService.ts index 7b4997de..8cda56b0 100644 --- a/src/languageservice/services/jsonSchemaService.ts +++ b/src/languageservice/services/jsonSchemaService.ts @@ -501,7 +501,7 @@ export class JSONSchemaService implements IJSONSchemaService { } collectEntries(next.items, next.additionalProperties, next.not); collectMapEntries(next.definitions, next.properties, next.patternProperties, next.dependencies); - collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.items); + collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.items, next.schemaSequence); } return this.promise.all(openPromises); }; diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index a1abc96a..704426cb 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -50,7 +50,7 @@ export class YAMLCompletion { return this.promise.resolve(item); } - public doComplete(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable { + public doComplete(document: TextDocument, position: Position, doc): Thenable { let result: CompletionList = { items: [], @@ -66,6 +66,7 @@ export class YAMLCompletion { if(currentDoc === null){ return Promise.resolve(result); } + const currentDocIndex = doc.documents.indexOf(currentDoc); let node = currentDoc.getNodeFromOffsetEndInclusive(offset); if (this.isInComment(document, node ? node.start : 0, offset)) { return Promise.resolve(result); @@ -123,6 +124,10 @@ export class YAMLCompletion { if(!schema){ return Promise.resolve(result); } + let newSchema = schema; + if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[currentDocIndex]) { + newSchema = new SchemaService.ResolvedSchema(schema.schema.schemaSequence[currentDocIndex]); + } let collectionPromises: Thenable[] = []; @@ -160,9 +165,9 @@ export class YAMLCompletion { separatorAfter = this.evaluateSeparatorAfter(document, document.offsetAt(overwriteRange.end)); } - if (schema) { + if (newSchema) { // property proposals with schema - this.getPropertyCompletions(schema, currentDoc, node, addValue, collector, separatorAfter); + this.getPropertyCompletions(newSchema, currentDoc, node, addValue, collector, separatorAfter); } let location = node.getPath(); @@ -185,8 +190,8 @@ export class YAMLCompletion { // proposals for values let types: { [type: string]: boolean } = {}; - if (schema) { - this.getValueCompletions(schema, currentDoc, node, offset, document, collector, types); + if (newSchema) { + this.getValueCompletions(newSchema, currentDoc, node, offset, document, collector, types); } if (this.contributions.length > 0) { this.getContributedValueCompletions(currentDoc, node, offset, document, collector, collectionPromises); diff --git a/src/languageservice/services/yamlHover.ts b/src/languageservice/services/yamlHover.ts index 61b637ea..9ca9ecb8 100644 --- a/src/languageservice/services/yamlHover.ts +++ b/src/languageservice/services/yamlHover.ts @@ -26,7 +26,7 @@ export class YAMLHover { this.promise = promiseConstructor || Promise; } - public doHover(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable { + public doHover(document: TextDocument, position: Position, doc): Thenable { if(!document){ this.promise.resolve(void 0); @@ -37,6 +37,7 @@ export class YAMLHover { if(currentDoc === null){ return this.promise.resolve(void 0); } + const currentDocIndex = doc.documents.indexOf(currentDoc); let node = currentDoc.getNodeFromOffset(offset); if (!node || (node.type === 'object' || node.type === 'array') && offset > node.start + 1 && offset < node.end - 1) { return this.promise.resolve(void 0); @@ -76,8 +77,11 @@ export class YAMLHover { return this.schemaService.getSchemaForResource(document.uri).then((schema) => { if (schema) { - - let matchingSchemas = currentDoc.getMatchingSchemas(schema.schema, node.start); + let newSchema = schema; + if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[currentDocIndex]) { + newSchema = new SchemaService.ResolvedSchema(schema.schema.schemaSequence[currentDocIndex]); + } + let matchingSchemas = currentDoc.getMatchingSchemas(newSchema.schema, node.start); let title: string = null; let markdownDescription: string = null; diff --git a/src/languageservice/services/yamlValidation.ts b/src/languageservice/services/yamlValidation.ts index b5e208d4..91c59d49 100644 --- a/src/languageservice/services/yamlValidation.ts +++ b/src/languageservice/services/yamlValidation.ts @@ -5,7 +5,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { JSONSchemaService } from './jsonSchemaService'; +import { JSONSchemaService, ResolvedSchema } from './jsonSchemaService'; import { JSONDocument, ObjectASTNode, IProblem, ProblemSeverity } from '../parser/jsonParser'; import { TextDocument, Diagnostic, DiagnosticSeverity } from 'vscode-languageserver-types'; import { PromiseConstructor, Thenable, LanguageSettings} from '../yamlLanguageService'; @@ -38,22 +38,26 @@ export class YAMLValidation { return this.jsonSchemaService.getSchemaForResource(textDocument.uri).then(function (schema) { var diagnostics = []; var added = {}; - + let newSchema = schema; if (schema) { - + let documentIndex = 0; for(let currentYAMLDoc in yamlDocument.documents){ let currentDoc = yamlDocument.documents[currentYAMLDoc]; - let diagnostics = currentDoc.getValidationProblems(schema.schema); + if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[documentIndex]) { + newSchema = new ResolvedSchema(schema.schema.schemaSequence[documentIndex]); + } + let diagnostics = currentDoc.getValidationProblems(newSchema.schema); for(let diag in diagnostics){ let curDiagnostic = diagnostics[diag]; currentDoc.errors.push({ location: { start: curDiagnostic.location.start, end: curDiagnostic.location.end }, message: curDiagnostic.message }) } + documentIndex++; } } - if(schema && schema.errors.length > 0){ + if(newSchema && newSchema.errors.length > 0){ - for(let curDiagnostic of schema.errors){ + for(let curDiagnostic of newSchema.errors){ diagnostics.push({ severity: DiagnosticSeverity.Error, range: { diff --git a/test/fixtures/customMultipleSchemaSequences.json b/test/fixtures/customMultipleSchemaSequences.json new file mode 100644 index 00000000..6a270745 --- /dev/null +++ b/test/fixtures/customMultipleSchemaSequences.json @@ -0,0 +1,24 @@ +{ + "schemaSequence": [ + { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Person object", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of this person" + }, + "age": { + "type": "number", + "description": "The age of this person" + } + }, + "required":["name","age"] + }, + { + "$ref": "http://json.schemastore.org/bowerrc" + } + ] + +} \ No newline at end of file diff --git a/test/mulipleDocuments.test.ts b/test/mulipleDocuments.test.ts new file mode 100644 index 00000000..381ff1a6 --- /dev/null +++ b/test/mulipleDocuments.test.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Red Hat. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { TextDocument } from 'vscode-languageserver'; +import {getLanguageService} from '../src/languageservice/yamlLanguageService' +import path = require('path'); +import {schemaRequestService, workspaceContext} from './testHelper'; +import { parse as parseYAML } from '../src/languageservice/parser/yamlParser'; +var assert = require('assert'); + +let languageService = getLanguageService(schemaRequestService, workspaceContext, [], null); + +function toFsPath(str): string { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string, got ${typeof str}`); + } + + let pathName; + pathName = path.resolve(str); + pathName = pathName.replace(/\\/g, '/'); + // Windows drive letter must be prefixed with a slash + if (pathName[0] !== '/') { + pathName = `/${pathName}`; + } + return encodeURI(`file://${pathName}`).replace(/[?#]/g, encodeURIComponent); +} + +let uri = toFsPath(path.join(__dirname, './fixtures/customMultipleSchemaSequences.json')); +let languageSettings = { + schemas: [], + validate: true, + customTags: [] +}; +let fileMatch = ["*.yml", "*.yaml"]; +languageSettings.schemas.push({ uri, fileMatch: fileMatch }); +languageSettings.customTags.push("!Test"); +languageSettings.customTags.push("!Ref sequence"); +languageService.configure(languageSettings); +// Defines a Mocha test suite to group tests of similar kind together +suite("Multiple Documents Validation Tests", () => { + + // Tests for validator + describe('Multiple Documents Validation', function() { + function setup(content: string){ + return TextDocument.create("file://~/Desktop/vscode-k8s/test.yaml", "yaml", 0, content); + } + + function validatorSetup(content: string){ + const testTextDocument = setup(content); + const yDoc = parseYAML(testTextDocument.getText(), languageSettings.customTags); + return languageService.doValidation(testTextDocument, yDoc); + } + + function hoverSetup(content: string, position){ + let testTextDocument = setup(content); + let jsonDocument = parseYAML(testTextDocument.getText()); + return languageService.doHover(testTextDocument, testTextDocument.positionAt(position), jsonDocument); + } + + it('Should validate multiple documents', (done) => { + const content = ` +name: jack +age: 22 +--- +analytics: true + `; + const validator = validatorSetup(content); + validator.then((result) => { + assert.equal(result.length, 0); + }).then(done, done); + }); + + it('Should find errors in both documents', (done) => { + let content = `name1: jack +age: asd +--- +cwd: False`; + let validator = validatorSetup(content); + validator.then(function(result){ + assert.equal(result.length, 3); + }).then(done, done); + }); + + it('Should find errors in first document', (done) => { + let content = `name: jack +age: age +--- +analytics: true`; + let validator = validatorSetup(content); + validator.then(function(result){ + assert.equal(result.length, 1); + }).then(done, done); + }); + + it('Should find errors in second document', (done) => { + let content = `name: jack +age: 22 +--- +cwd: False`; + let validator = validatorSetup(content); + validator.then(function(result){ + assert.equal(result.length, 1); + }).then(done, done); + }); + + it('Should hover in first document', (done) => { + let content = `name: jack\nage: 22\n---\ncwd: False`; + let hover = hoverSetup(content, 1 + content.indexOf('age')); + hover.then(function(result){ + assert.notEqual(result.contents.length, 0); + assert.equal(result.contents[0], 'The age of this person'); + }).then(done, done); + }); + }); +}); \ No newline at end of file