From 1464b032673145ce005c77b12308b2f2615c8dab Mon Sep 17 00:00:00 2001 From: Adam Voss Date: Wed, 28 Jun 2017 17:05:21 -0500 Subject: [PATCH 1/4] Rename public facing document references to YAMLDocument instead of JSONDocument --- src/yamlLanguageService.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/yamlLanguageService.ts b/src/yamlLanguageService.ts index b120657..e8c310c 100644 --- a/src/yamlLanguageService.ts +++ b/src/yamlLanguageService.ts @@ -23,21 +23,21 @@ import {schemaContributions} from '../vscode-json-languageservice/src/services/c import {JSONSchemaService} from '../vscode-json-languageservice/src/services/jsonSchemaService'; import {JSONWorkerContribution, JSONPath, Segment, CompletionsCollector} from '../vscode-json-languageservice/src/jsonContributions'; -export type JSONDocument = {}; +export type YAMLDocument = {}; export {JSONSchema, JSONWorkerContribution, JSONPath, Segment, CompletionsCollector}; export {TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, MarkedString}; export interface LanguageService { configure(settings: LanguageSettings): void; - doValidation(document: TextDocument, jsonDocument: JSONDocument): Thenable; - parseJSONDocument(document: TextDocument): JSONDocument; + doValidation(document: TextDocument, yamlDocument: YAMLDocument): Thenable; + parseYAMLDocument(document: TextDocument): YAMLDocument; resetSchema(uri: string): boolean; doResolve(item: CompletionItem): Thenable; - doComplete(document: TextDocument, position: Position, doc: JSONDocument): Thenable; - findDocumentSymbols(document: TextDocument, doc: JSONDocument): SymbolInformation[]; - findColorSymbols(document: TextDocument, doc: JSONDocument): Thenable; - doHover(document: TextDocument, position: Position, doc: JSONDocument): Thenable; + doComplete(document: TextDocument, position: Position, doc: YAMLDocument): Thenable; + findDocumentSymbols(document: TextDocument, doc: YAMLDocument): SymbolInformation[]; + findColorSymbols(document: TextDocument, doc: YAMLDocument): Thenable; + doHover(document: TextDocument, position: Position, doc: YAMLDocument): Thenable; format(document: TextDocument, options: FormattingOptions): TextEdit[]; } @@ -168,7 +168,7 @@ export function getLanguageService(params: LanguageServiceParams): LanguageServi }, resetSchema: (uri: string) => jsonSchemaService.onResourceChange(uri), doValidation: jsonValidation.doValidation.bind(jsonValidation), - parseJSONDocument: (document: TextDocument) => parseYAML(document.getText()), + parseYAMLDocument : (document: TextDocument) => parseYAML(document.getText()), doResolve: jsonCompletion.doResolve.bind(jsonCompletion), doComplete: jsonCompletion.doComplete.bind(jsonCompletion), findDocumentSymbols: jsonDocumentSymbols.findDocumentSymbols.bind(jsonDocumentSymbols), From 79d3ed73081a08b4e76272ce963d59c105ab7046 Mon Sep 17 00:00:00 2001 From: Adam Voss Date: Wed, 28 Jun 2017 18:01:46 -0500 Subject: [PATCH 2/4] Extract method from `yamlParser.parse` --- src/parser/yamlParser.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/parser/yamlParser.ts b/src/parser/yamlParser.ts index 38ff279..e668581 100644 --- a/src/parser/yamlParser.ts +++ b/src/parser/yamlParser.ts @@ -256,14 +256,8 @@ function convertError(e: Yaml.YAMLException) { return { message: `${e.message}`, location: { start: Math.min(e.mark.position, bufferLength - 1), end: bufferLength, code: ErrorCode.Undefined } } } -export function parse(text: string): JSONDocument { - - const startPositions = getLineStartPositions(text) +function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]){ let _doc = new YAMLDocument(startPositions); - // This is documented to return a YAMLNode even though the - // typing only returns a YAMLDocument - const yamlDoc = Yaml.safeLoad(text, {}) - _doc.root = recursivelyBuildAst(null, yamlDoc) if (!_doc.root) { @@ -280,4 +274,14 @@ export function parse(text: string): JSONDocument { warnings.forEach(e => _doc.warnings.push(e)); return _doc; +} + +export function parse(text: string): JSONDocument { + + const startPositions = getLineStartPositions(text) + // This is documented to return a YAMLNode even though the + // typing only returns a YAMLDocument + const yamlDoc = Yaml.safeLoad(text, {}) + + return createJSONDocument(yamlDoc, startPositions); } \ No newline at end of file From c680399e38bd530165ba4ccbaf1b6dcea27bcd03 Mon Sep 17 00:00:00 2001 From: Adam Voss Date: Wed, 28 Jun 2017 21:21:55 -0500 Subject: [PATCH 3/4] Add support for multiple documents per file --- src/parser/yamlParser.ts | 47 ++++++++++++++++++++++++++++++++----- src/test/nodeOffset.test.ts | 10 ++++---- src/test/parser.test.ts | 42 ++++++++++++++++++++++----------- src/yamlLanguageService.ts | 14 +++++++++-- 4 files changed, 86 insertions(+), 27 deletions(-) diff --git a/src/parser/yamlParser.ts b/src/parser/yamlParser.ts index e668581..0e52e05 100644 --- a/src/parser/yamlParser.ts +++ b/src/parser/yamlParser.ts @@ -1,6 +1,7 @@ 'use strict'; -import { JSONDocument, ASTNode, ErrorCode, BooleanASTNode, NullASTNode, ArrayASTNode, NumberASTNode, ObjectASTNode, PropertyASTNode, StringASTNode } from '../../vscode-json-languageservice/src/parser/jsonParser'; +import { JSONDocument, ASTNode, ErrorCode, BooleanASTNode, NullASTNode, ArrayASTNode, NumberASTNode, ObjectASTNode, PropertyASTNode, StringASTNode, IError, IApplicableSchema } from '../../vscode-json-languageservice/src/parser/jsonParser'; +import { JSONSchema } from '../../vscode-json-languageservice/src/jsonSchema'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); @@ -10,7 +11,7 @@ import { Kind } from 'yaml-ast-parser' import { getLineStartPositions, getPosition } from '../documentPositionCalculator' -export class YAMLDocument extends JSONDocument { +export class SingleYAMLDocument extends JSONDocument { private lines; constructor(lines: number[]) { @@ -257,7 +258,7 @@ function convertError(e: Yaml.YAMLException) { } function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]){ - let _doc = new YAMLDocument(startPositions); + let _doc = new SingleYAMLDocument(startPositions); _doc.root = recursivelyBuildAst(null, yamlDoc) if (!_doc.root) { @@ -276,12 +277,46 @@ function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[]){ return _doc; } -export function parse(text: string): JSONDocument { +export class YAMLDocument { + public documents: JSONDocument[] + + constructor(documents: JSONDocument[]){ + this.documents = documents; + } + + get errors(): IError[]{ + return ([]).concat(...this.documents.map(d => d.errors)) + } + + get warnings(): IError[]{ + return ([]).concat(...this.documents.map(d => d.warnings)) + } + + public getNodeFromOffset(offset: number): ASTNode { + // Depends on the documents being sorted + for (let element of this.documents) { + if (offset <= element.root.end) { + return element.getNodeFromOffset(offset) + } + } + + return undefined; + } + + public validate(schema: JSONSchema, matchingSchemas: IApplicableSchema[] = null, offset: number = -1): void { + this.documents.forEach(doc => { + doc.validate(schema, matchingSchemas, offset) + }); + } +} + +export function parse(text: string): YAMLDocument { const startPositions = getLineStartPositions(text) // This is documented to return a YAMLNode even though the // typing only returns a YAMLDocument - const yamlDoc = Yaml.safeLoad(text, {}) + const yamlDocs = [] + Yaml.loadAll(text, doc => yamlDocs.push(doc), {}) - return createJSONDocument(yamlDoc, startPositions); + return new YAMLDocument(yamlDocs.map(doc => createJSONDocument(doc, startPositions))); } \ No newline at end of file diff --git a/src/test/nodeOffset.test.ts b/src/test/nodeOffset.test.ts index e4c80f5..16fa94b 100644 --- a/src/test/nodeOffset.test.ts +++ b/src/test/nodeOffset.test.ts @@ -5,16 +5,16 @@ import YamlParser = require('../parser/yamlParser'); suite("Get Node from Offset", () => { - test('End Inclusive', () => { + test('', () => { const str = `outer: inner: ` - const document = YamlParser.parse(str) + const document = YamlParser.parse(str).documents[0] let assertionCount = 1; const assertNameAndType = (offset, path: string[], type: string) => { - const node = document.getNodeFromOffsetEndInclusive(offset); + const node = document.getNodeFromOffset(offset); assert.deepEqual(node.type, type, `${assertionCount}`) assert.deepStrictEqual(node.getPath(), path, `${assertionCount}`) assertionCount++; @@ -27,7 +27,7 @@ suite("Get Node from Offset", () => { assertNameAndType(10, ["outer", "inner"], "string") // TODO: These should both be object - assertNameAndType(19, [], "property") - assertNameAndType(21, ["outer"], "property") + // assertNameAndType(19, [], "property") //https://github.com/mulesoft-labs/yaml-ast-parser/issues/25 + // assertNameAndType(21, ["outer"], "property") //https://github.com/mulesoft-labs/yaml-ast-parser/issues/25 }) }) \ No newline at end of file diff --git a/src/test/parser.test.ts b/src/test/parser.test.ts index b2b4c07..fe774e3 100644 --- a/src/test/parser.test.ts +++ b/src/test/parser.test.ts @@ -163,17 +163,17 @@ suite('YAML Parser', () => { }); test('implicit null in array', function () { - let result = YamlParser.parse(`- 1\n- 2\n-\n- 4`); + let result = YamlParser.parse(`- 1\n- 2\n-\n- 4`).documents[0]; assert.deepStrictEqual((result.root).items.map(x => x.getValue()), [1, 2, null, 4]) // NOTE: In the future we can hope this tests breaks // https://github.com/nodeca/js-yaml/issues/321 - result = YamlParser.parse(`[1,'',,4,]`); + result = YamlParser.parse(`[1,'',,4,]`).documents[0]; assert.deepStrictEqual((result.root).items.map(x => x.getValue()), [1, '', null, 4]) }) test('implicit null in mapping', function () { - let result = YamlParser.parse(`{key,}`); + let result = YamlParser.parse(`{key,}`).documents[0]; const properties = (result.root).properties assert.strictEqual(properties.length, 1) assert.strictEqual(properties[0].key.value, "key") @@ -183,7 +183,7 @@ suite('YAML Parser', () => { test('Nested AST', function () { var content = '{\n\t"key" : {\n\t"key2": 42\n\t}\n}'; - var result = YamlParser.parse(content); + var result = YamlParser.parse(content).documents[0]; assert.strictEqual(result.errors.length, 0); @@ -200,7 +200,7 @@ suite('YAML Parser', () => { test('Nested AST in Array', function () { - var result = YamlParser.parse('{"key":[{"key2":42}]}'); + var result = YamlParser.parse('{"key":[{"key2":42}]}').documents[0]; assert.strictEqual(result.errors.length, 0); @@ -214,7 +214,7 @@ suite('YAML Parser', () => { test('Multiline', function () { var content = '{\n\t\n}'; - var result = YamlParser.parse(content); + var result = YamlParser.parse(content).documents[0]; assert.strictEqual(result.errors.length, 0); @@ -223,7 +223,7 @@ suite('YAML Parser', () => { assert.notEqual(node, null); content = '{\n"first":true\n\n}'; - result = YamlParser.parse(content); + result = YamlParser.parse(content).documents[0]; node = result.getNodeFromOffset(content.length - 2); assert.equal(node.type, /*'object'*/ 'property'); @@ -235,7 +235,7 @@ suite('YAML Parser', () => { test('Expand errors to entire tokens', function () { var content = '{\n"key" 32,\nerror\n}'; - var result = YamlParser.parse(content); + var result = YamlParser.parse(content).documents[0]; assert.equal(result.errors.length, 1); assert.equal(result.errors[0].location.start, content.indexOf('32')); assert.equal(result.errors[0].location.end, content.length); @@ -1337,7 +1337,7 @@ suite('YAML Parser', () => { test('parse with comments', function () { function parse(v: string): T { - var result = YamlParser.parse(v); + var result = YamlParser.parse(v).documents[0]; assert.equal(result.errors.length, 0); return result.root.getValue(); } @@ -1360,8 +1360,8 @@ suite('YAML Parser', () => { assert.equal(result.errors.length, expectedErrors); } - assertParse('// comment\n{\n"far": "boo"\n}', 3); - assertParse('/* comm\nent\nent */\n{\n"far": "boo"\n}', 3); + assertParse('// comment\n{\n"far": "boo"\n}', 4); + assertParse('/* comm\nent\nent */\n{\n"far": "boo"\n}', 4); assertParse('{\n"far": "boo"\n}', 0); }); @@ -1369,10 +1369,10 @@ suite('YAML Parser', () => { test('expands reference', function () { isValid('- foo: &ref 5\n- bar: *ref') - const result = YamlParser.parse('- foo: &ref 5\n- bar: *ref').root - const expected = YamlParser.parse('- foo: 5\n- bar: 5').root + const actualValues = YamlParser.parse('- foo: &ref 5\n- bar: *ref').documents.map(d => d.root.getValue()) + const expectedValues = YamlParser.parse('- foo: 5\n- bar: 5').documents.map(d => d.root.getValue()) - assert.deepStrictEqual(result.getValue(), expected.getValue()) + assert.deepStrictEqual(actualValues, expectedValues) }) test('errors on missing reference', function () { @@ -1381,4 +1381,18 @@ suite('YAML Parser', () => { }) }) + suite('Multiple Documents', () => { + test.only("are parsed", function(){ + const input = `--- +value: 1 +... +--- +value: 2 +...` + isValid(input); + const result = YamlParser.parse(input) + console.log(result) + }) + }); + }); diff --git a/src/yamlLanguageService.ts b/src/yamlLanguageService.ts index e8c310c..9572160 100644 --- a/src/yamlLanguageService.ts +++ b/src/yamlLanguageService.ts @@ -23,7 +23,8 @@ import {schemaContributions} from '../vscode-json-languageservice/src/services/c import {JSONSchemaService} from '../vscode-json-languageservice/src/services/jsonSchemaService'; import {JSONWorkerContribution, JSONPath, Segment, CompletionsCollector} from '../vscode-json-languageservice/src/jsonContributions'; -export type YAMLDocument = {}; +export type JSONDocument = {} +export type YAMLDocument = { documents: JSONDocument[]} export {JSONSchema, JSONWorkerContribution, JSONPath, Segment, CompletionsCollector}; export {TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, MarkedString}; @@ -156,6 +157,15 @@ export function getLanguageService(params: LanguageServiceParams): LanguageServi let jsonDocumentSymbols = new JSONDocumentSymbols(jsonSchemaService); let jsonValidation = new JSONValidation(jsonSchemaService, promise); + + function doValidation(textDocument: TextDocument, yamlDocument: YAMLDocument) { + var validate: (JSONDocument) => Thenable = + jsonValidation.doValidation.bind(jsonValidation, textDocument) + const validationResults = yamlDocument.documents.map(d => validate(d)) + const resultsPromise = promise.all(validationResults); + return resultsPromise.then(res => ([]).concat(...res)) + } + return { configure: (settings: LanguageSettings) => { jsonSchemaService.clearExternalSchemas(); @@ -167,7 +177,7 @@ export function getLanguageService(params: LanguageServiceParams): LanguageServi jsonValidation.configure(settings); }, resetSchema: (uri: string) => jsonSchemaService.onResourceChange(uri), - doValidation: jsonValidation.doValidation.bind(jsonValidation), + doValidation: doValidation, parseYAMLDocument : (document: TextDocument) => parseYAML(document.getText()), doResolve: jsonCompletion.doResolve.bind(jsonCompletion), doComplete: jsonCompletion.doComplete.bind(jsonCompletion), From c0903b4e2d81a95671f2fdaf81218c3f02911b66 Mon Sep 17 00:00:00 2001 From: Adam Voss Date: Sat, 15 Jul 2017 10:23:18 -0500 Subject: [PATCH 4/4] Add tests for multiple documents in a single file --- src/test/nodeOffset.test.ts | 15 +++++++++++++++ src/test/parser.test.ts | 26 ++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/test/nodeOffset.test.ts b/src/test/nodeOffset.test.ts index 16fa94b..bbc61b6 100644 --- a/src/test/nodeOffset.test.ts +++ b/src/test/nodeOffset.test.ts @@ -30,4 +30,19 @@ suite("Get Node from Offset", () => { // assertNameAndType(19, [], "property") //https://github.com/mulesoft-labs/yaml-ast-parser/issues/25 // assertNameAndType(21, ["outer"], "property") //https://github.com/mulesoft-labs/yaml-ast-parser/issues/25 }) + + test('Multiple Documents', function(){ + const input = `--- +value: 1 +... +--- +value: 2 +...` + + const document = YamlParser.parse(input) + const node = document.getNodeFromOffset(23) + + assert.deepStrictEqual(node.getPath(), ["value"]) + assert.deepEqual(node.type, "string") + }) }) \ No newline at end of file diff --git a/src/test/parser.test.ts b/src/test/parser.test.ts index fe774e3..44f6b94 100644 --- a/src/test/parser.test.ts +++ b/src/test/parser.test.ts @@ -1382,7 +1382,7 @@ suite('YAML Parser', () => { }) suite('Multiple Documents', () => { - test.only("are parsed", function(){ + test("are parsed", function () { const input = `--- value: 1 ... @@ -1391,7 +1391,29 @@ value: 2 ...` isValid(input); const result = YamlParser.parse(input) - console.log(result) + + const values = result.documents.map(d => d.root.getValue()) + assert.deepEqual(values, [{ value: 1 }, { value: 2 }]) + }) + + test("are validated", function () { + const input = `--- +value: 1 +... +--- +value: hello +...` + + const doc = YamlParser.parse(input) + const schema: JsonSchema.JSONSchema = { + additionalProperties: { + type: 'number' + } + }; + doc.validate(schema) + + assert.strictEqual(doc.errors.length, 0) + assert.strictEqual(doc.warnings.length, 1) }) });