Skip to content

Commit 4bcd36d

Browse files
authored
Merge pull request #112 from redhat-developer/custom-tags-fix
Fixed issue with custom tags that can crash the language server
2 parents 4aa28a7 + 149aae4 commit 4bcd36d

File tree

4 files changed

+62
-12
lines changed

4 files changed

+62
-12
lines changed

README.md

+29-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,35 @@ The following settings are supported:
3333
* `yaml.hover`: Enable/disable hover
3434
* `yaml.completion`: Enable/disable autocompletion
3535
* `yaml.schemas`: Helps you associate schemas with files in a glob pattern
36-
* `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.
36+
* `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).
37+
38+
##### Adding custom tags
39+
40+
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:
41+
42+
```YAML
43+
"yaml.customTags": [
44+
"!Scalar-example scalar",
45+
"!Seq-example sequence",
46+
"!Mapping-example mapping"
47+
]
48+
```
49+
50+
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.
51+
52+
We can then use the newly defined custom tags inside our YAML file:
53+
54+
```YAML
55+
some_key: !Scalar-example some_value
56+
some_sequence: !Seq-example
57+
- some_seq_key_1: some_seq_value_1
58+
- some_seq_key_2: some_seq_value_2
59+
some_mapping: !Mapping-example
60+
some_mapping_key_1: some_mapping_value_1
61+
some_mapping_key_2: some_mapping_value_2
62+
```
63+
64+
3765
##### Associating a schema to a glob pattern via yaml.schemas:
3866
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.
3967

src/languageservice/parser/yamlParser.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { Schema, Type } from 'js-yaml';
1616

1717
import { getLineStartPositions, getPosition } from '../utils/documentPositionCalculator'
1818
import { parseYamlBoolean } from './scalar-type';
19+
import { filterInvalidCustomTags } from '../utils/arrUtils';
1920

2021
export class SingleYAMLDocument extends JSONDocument {
2122
private lines;
@@ -251,17 +252,19 @@ export function parse(text: string, customTags = []): YAMLDocument {
251252
const startPositions = getLineStartPositions(text)
252253
// This is documented to return a YAMLNode even though the
253254
// typing only returns a YAMLDocument
254-
const yamlDocs = []
255+
const yamlDocs = [];
255256

256-
let schemaWithAdditionalTags = Schema.create(customTags.map((tag) => {
257+
const filteredTags = filterInvalidCustomTags(customTags);
258+
259+
let schemaWithAdditionalTags = Schema.create(filteredTags.map((tag) => {
257260
const typeInfo = tag.split(' ');
258-
return new Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' });
261+
return new Type(typeInfo[0], { kind: (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar' });
259262
}));
260263

261264
//We need compiledTypeMap to be available from schemaWithAdditionalTags before we add the new custom properties
262-
customTags.map((tag) => {
265+
filteredTags.map((tag) => {
263266
const typeInfo = tag.split(' ');
264-
schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' });
267+
schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new Type(typeInfo[0], { kind: (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar' });
265268
});
266269

267270
let additionalOptions: Yaml.LoadOptions = {

src/languageservice/services/yamlCompletion.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { PromiseConstructor, Thenable } from 'vscode-json-languageservice';
1616
import { CompletionItem, CompletionItemKind, CompletionList, TextDocument, Position, Range, TextEdit, InsertTextFormat } from 'vscode-languageserver-types';
1717

1818
import * as nls from 'vscode-nls';
19-
import { matchOffsetToDocument } from '../utils/arrUtils';
19+
import { matchOffsetToDocument, filterInvalidCustomTags } from '../utils/arrUtils';
2020
import { LanguageSettings } from '../yamlLanguageService';
2121
const localize = nls.loadMessageBundle();
2222

@@ -354,11 +354,11 @@ export class YAMLCompletion {
354354
}
355355

356356
private getCustomTagValueCompletions(collector: CompletionsCollector) {
357-
this.customTags.forEach((customTagItem) => {
358-
let tagItemSplit = customTagItem.split(" ");
359-
if(tagItemSplit && tagItemSplit[0]){
360-
this.addCustomTagValueCompletion(collector, " ", tagItemSplit[0]);
361-
}
357+
const validCustomTags = filterInvalidCustomTags(this.customTags);
358+
validCustomTags.forEach((validTag) => {
359+
// Valid custom tags are guarenteed to be strings
360+
const label = validTag.split(' ')[0];
361+
this.addCustomTagValueCompletion(collector, " ", label);
362362
});
363363
}
364364

src/languageservice/utils/arrUtils.ts

+19
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,23 @@ export function matchOffsetToDocument(offset: number, jsonDocuments): SingleYAML
7272

7373
return null;
7474

75+
}
76+
77+
export function filterInvalidCustomTags(customTags: String[]): String[] {
78+
const validCustomTags = ['mapping', 'scalar', 'sequence'];
79+
80+
return customTags.filter(tag => {
81+
if (typeof tag === 'string') {
82+
const typeInfo = tag.split(' ');
83+
const type = (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar';
84+
85+
// We need to check if map is a type because map will throw an error within the yaml-ast-parser
86+
if (type === 'map') {
87+
return false;
88+
}
89+
90+
return validCustomTags.indexOf(type) !== -1;
91+
}
92+
return false;
93+
});
7594
}

0 commit comments

Comments
 (0)