Skip to content

Commit

Permalink
Merge pull request #112 from redhat-developer/custom-tags-fix
Browse files Browse the repository at this point in the history
Fixed issue with custom tags that can crash the language server
  • Loading branch information
JPinkney committed Feb 2, 2019
2 parents 4aa28a7 + 149aae4 commit 4bcd36d
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 12 deletions.
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 8 additions & 5 deletions src/languageservice/parser/yamlParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 = {
Expand Down
12 changes: 6 additions & 6 deletions src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);
});
}

Expand Down
19 changes: 19 additions & 0 deletions src/languageservice/utils/arrUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
}

0 comments on commit 4bcd36d

Please sign in to comment.