From 149aae4ac8fefb5b23e4d0400a19402116f75276 Mon Sep 17 00:00:00 2001 From: jpinkney Date: Sat, 29 Dec 2018 16:38:06 -0500 Subject: [PATCH] Fixed issue with custom tags that can crash the language server --- README.md | 30 ++++++++++++++++++- src/languageservice/parser/yamlParser.ts | 13 ++++---- .../services/yamlCompletion.ts | 12 ++++---- src/languageservice/utils/arrUtils.ts | 19 ++++++++++++ 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0bb495a8..4b91acd0 100755 --- a/README.md +++ b/README.md @@ -33,7 +33,35 @@ The following settings are supported: * `yaml.hover`: Enable/disable hover * `yaml.completion`: Enable/disable autocompletion * `yaml.schemas`: Helps you associate schemas with files in a glob pattern -* `yaml.customTags`: Array of custom tags that the parser will validate against. It has two ways to be used. Either an item in the array is a custom tag such as "!Ref" or you can specify the type of the object !Ref should be by doing "!Ref Scalar". For example: ["!Ref", "!Some-Tag Scalar"]. The type of object can be one of Scalar, Sequence, Mapping, Map. +* `yaml.customTags`: Array of custom tags that the parser will validate against. It has two ways to be used. Either an item in the array is a custom tag such as "!Ref" and it will automatically map !Ref to scalar or you can specify the type of the object !Ref should be e.g. "!Ref sequence". The type of object can be either scalar (for strings and booleans), sequence (for arrays), map (for objects). + +##### Adding custom tags + +In order to use the custom tags in your YAML file you need to first specify the custom tags in the setting of your code editor. For example, we can have the following custom tags: + +```YAML +"yaml.customTags": [ + "!Scalar-example scalar", + "!Seq-example sequence", + "!Mapping-example mapping" +] +``` + +The !Scalar-example would map to a scalar custom tag, the !Seq-example would map to a sequence custom tag, the !Mapping-example would map to a mapping custom tag. + +We can then use the newly defined custom tags inside our YAML file: + +```YAML +some_key: !Scalar-example some_value +some_sequence: !Seq-example + - some_seq_key_1: some_seq_value_1 + - some_seq_key_2: some_seq_value_2 +some_mapping: !Mapping-example + some_mapping_key_1: some_mapping_value_1 + some_mapping_key_2: some_mapping_value_2 +``` + + ##### Associating a schema to a glob pattern via yaml.schemas: yaml.schemas applies a schema to a file. In other words, the schema (placed on the left) is applied to the glob pattern on the right. Your schema can be local or online. Your schema path must be relative to the project root and not an absolute path to the schema. diff --git a/src/languageservice/parser/yamlParser.ts b/src/languageservice/parser/yamlParser.ts index 3f30848f..66028ebb 100644 --- a/src/languageservice/parser/yamlParser.ts +++ b/src/languageservice/parser/yamlParser.ts @@ -16,6 +16,7 @@ import { Schema, Type } from 'js-yaml'; import { getLineStartPositions, getPosition } from '../utils/documentPositionCalculator' import { parseYamlBoolean } from './scalar-type'; +import { filterInvalidCustomTags } from '../utils/arrUtils'; export class SingleYAMLDocument extends JSONDocument { private lines; @@ -251,17 +252,19 @@ export function parse(text: string, customTags = []): YAMLDocument { const startPositions = getLineStartPositions(text) // This is documented to return a YAMLNode even though the // typing only returns a YAMLDocument - const yamlDocs = [] + const yamlDocs = []; - let schemaWithAdditionalTags = Schema.create(customTags.map((tag) => { + const filteredTags = filterInvalidCustomTags(customTags); + + let schemaWithAdditionalTags = Schema.create(filteredTags.map((tag) => { const typeInfo = tag.split(' '); - return new Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' }); + return new Type(typeInfo[0], { kind: (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar' }); })); //We need compiledTypeMap to be available from schemaWithAdditionalTags before we add the new custom properties - customTags.map((tag) => { + filteredTags.map((tag) => { const typeInfo = tag.split(' '); - schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' }); + schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new Type(typeInfo[0], { kind: (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar' }); }); let additionalOptions: Yaml.LoadOptions = { diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index a3230de3..6d0c0dad 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -16,7 +16,7 @@ import { PromiseConstructor, Thenable } from 'vscode-json-languageservice'; import { CompletionItem, CompletionItemKind, CompletionList, TextDocument, Position, Range, TextEdit, InsertTextFormat } from 'vscode-languageserver-types'; import * as nls from 'vscode-nls'; -import { matchOffsetToDocument } from '../utils/arrUtils'; +import { matchOffsetToDocument, filterInvalidCustomTags } from '../utils/arrUtils'; import { LanguageSettings } from '../yamlLanguageService'; const localize = nls.loadMessageBundle(); @@ -354,11 +354,11 @@ export class YAMLCompletion { } private getCustomTagValueCompletions(collector: CompletionsCollector) { - this.customTags.forEach((customTagItem) => { - let tagItemSplit = customTagItem.split(" "); - if(tagItemSplit && tagItemSplit[0]){ - this.addCustomTagValueCompletion(collector, " ", tagItemSplit[0]); - } + const validCustomTags = filterInvalidCustomTags(this.customTags); + validCustomTags.forEach((validTag) => { + // Valid custom tags are guarenteed to be strings + const label = validTag.split(' ')[0]; + this.addCustomTagValueCompletion(collector, " ", label); }); } diff --git a/src/languageservice/utils/arrUtils.ts b/src/languageservice/utils/arrUtils.ts index 4130f35b..8e63a41d 100644 --- a/src/languageservice/utils/arrUtils.ts +++ b/src/languageservice/utils/arrUtils.ts @@ -72,4 +72,23 @@ export function matchOffsetToDocument(offset: number, jsonDocuments): SingleYAML return null; +} + +export function filterInvalidCustomTags(customTags: String[]): String[] { + const validCustomTags = ['mapping', 'scalar', 'sequence']; + + return customTags.filter(tag => { + if (typeof tag === 'string') { + const typeInfo = tag.split(' '); + const type = (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar'; + + // We need to check if map is a type because map will throw an error within the yaml-ast-parser + if (type === 'map') { + return false; + } + + return validCustomTags.indexOf(type) !== -1; + } + return false; + }); } \ No newline at end of file