Skip to content

Commit

Permalink
handle scenario of multiple documents in single yaml file (#81)
Browse files Browse the repository at this point in the history
* add custom schemaSequence in schema object to map schema to multiple documents in single yaml file
  • Loading branch information
andxu authored and JPinkney committed Aug 27, 2018
1 parent 9b80bcc commit 38da500
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 15 deletions.
1 change: 1 addition & 0 deletions src/languageservice/jsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion src/languageservice/services/jsonSchemaService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ export class JSONSchemaService implements IJSONSchemaService {
}
collectEntries(next.items, next.additionalProperties, next.not);
collectMapEntries(next.definitions, next.properties, next.patternProperties, <JSONSchemaMap>next.dependencies);
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, <JSONSchema[]>next.items);
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, <JSONSchema[]>next.items, next.schemaSequence);
}
return this.promise.all(openPromises);
};
Expand Down
15 changes: 10 additions & 5 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class YAMLCompletion {
return this.promise.resolve(item);
}

public doComplete(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable<CompletionList> {
public doComplete(document: TextDocument, position: Position, doc): Thenable<CompletionList> {

let result: CompletionList = {
items: [],
Expand All @@ -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);
Expand Down Expand Up @@ -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<any>[] = [];

Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand Down
10 changes: 7 additions & 3 deletions src/languageservice/services/yamlHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class YAMLHover {
this.promise = promiseConstructor || Promise;
}

public doHover(document: TextDocument, position: Position, doc: Parser.JSONDocument): Thenable<Hover> {
public doHover(document: TextDocument, position: Position, doc): Thenable<Hover> {

if(!document){
this.promise.resolve(void 0);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 10 additions & 6 deletions src/languageservice/services/yamlValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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: {
Expand Down
24 changes: 24 additions & 0 deletions test/fixtures/customMultipleSchemaSequences.json
Original file line number Diff line number Diff line change
@@ -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"
}
]

}
116 changes: 116 additions & 0 deletions test/mulipleDocuments.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});

0 comments on commit 38da500

Please sign in to comment.