Skip to content

Commit d98cd4f

Browse files
(yaml) Add support for inline sequences and mappings (#2513)
* Use containers to match inline sequences and mappings * Add string type for inside inline elements * Handle nested inline sequences and mappings * Disallow all braces brackets and commas from strings inside inline mappings or sequences * clean up implementation * feed the linter Co-authored-by: Josh Goebel <[email protected]>
1 parent a23f19e commit d98cd4f

File tree

3 files changed

+126
-77
lines changed

3 files changed

+126
-77
lines changed

src/languages/yaml.js

Lines changed: 120 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default function(hljs) {
1111
var LITERALS = 'true false yes no null';
1212

1313
// YAML spec allows non-reserved URI characters in tags.
14-
var URI_CHARACTERS = '[\\w#;/?:@&=+$,.~*\\\'()[\\]]+'
14+
var URI_CHARACTERS = '[\\w#;/?:@&=+$,.~*\\\'()[\\]]+';
1515

1616
// Define keys as starting with a word character
1717
// ...containing word chars, spaces, colons, forward-slashes, hyphens and periods
@@ -21,111 +21,154 @@ export default function(hljs) {
2121
className: 'attr',
2222
variants: [
2323
{ begin: '\\w[\\w :\\/.-]*:(?=[ \t]|$)' },
24-
{ begin: '"\\w[\\w :\\/.-]*":(?=[ \t]|$)' }, //double quoted keys
25-
{ begin: '\'\\w[\\w :\\/.-]*\':(?=[ \t]|$)' } //single quoted keys
24+
{ begin: '"\\w[\\w :\\/.-]*":(?=[ \t]|$)' }, // double quoted keys
25+
{ begin: '\'\\w[\\w :\\/.-]*\':(?=[ \t]|$)' } // single quoted keys
2626
]
2727
};
2828

2929
var TEMPLATE_VARIABLES = {
3030
className: 'template-variable',
3131
variants: [
32-
{ begin: '\{\{', end: '\}\}' }, // jinja templates Ansible
33-
{ begin: '%\{', end: '\}' } // Ruby i18n
32+
{ begin: '{{', end: '}}' }, // jinja templates Ansible
33+
{ begin: '%{', end: '}' } // Ruby i18n
3434
]
3535
};
3636
var STRING = {
3737
className: 'string',
3838
relevance: 0,
3939
variants: [
40-
{begin: /'/, end: /'/},
41-
{begin: /"/, end: /"/},
42-
{begin: /\S+/}
40+
{ begin: /'/, end: /'/ },
41+
{ begin: /"/, end: /"/ },
42+
{ begin: /\S+/ }
4343
],
4444
contains: [
4545
hljs.BACKSLASH_ESCAPE,
4646
TEMPLATE_VARIABLES
4747
]
4848
};
4949

50+
// Strings inside of value containers (objects) can't contain braces,
51+
// brackets, or commas
52+
var CONTAINER_STRING = hljs.inherit(STRING, {
53+
variants: [
54+
{ begin: /'/, end: /'/ },
55+
{ begin: /"/, end: /"/ },
56+
{ begin: /[^\s,{}[\]]+/ }
57+
]
58+
});
59+
5060
var DATE_RE = '[0-9]{4}(-[0-9][0-9]){0,2}';
5161
var TIME_RE = '([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?';
5262
var FRACTION_RE = '(\\.[0-9]*)?';
5363
var ZONE_RE = '([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?';
5464
var TIMESTAMP = {
5565
className: 'number',
56-
begin: '\\b' + DATE_RE + TIME_RE + FRACTION_RE + ZONE_RE + '\\b',
57-
}
66+
begin: '\\b' + DATE_RE + TIME_RE + FRACTION_RE + ZONE_RE + '\\b'
67+
};
68+
69+
var VALUE_CONTAINER = {
70+
end: ',',
71+
endsWithParent: true,
72+
excludeEnd: true,
73+
contains: [],
74+
keywords: LITERALS,
75+
relevance: 0
76+
};
77+
var OBJECT = {
78+
begin: '{',
79+
end: '}',
80+
contains: [VALUE_CONTAINER],
81+
illegal: '\\n',
82+
relevance: 0
83+
};
84+
var ARRAY = {
85+
begin: '\\[',
86+
end: '\\]',
87+
contains: [VALUE_CONTAINER],
88+
illegal: '\\n',
89+
relevance: 0
90+
};
91+
92+
var MODES = [
93+
KEY,
94+
{
95+
className: 'meta',
96+
begin: '^---\s*$',
97+
relevance: 10
98+
},
99+
{ // multi line string
100+
// Blocks start with a | or > followed by a newline
101+
//
102+
// Indentation of subsequent lines must be the same to
103+
// be considered part of the block
104+
className: 'string',
105+
begin: '[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*'
106+
},
107+
{ // Ruby/Rails erb
108+
begin: '<%[%=-]?',
109+
end: '[%-]?%>',
110+
subLanguage: 'ruby',
111+
excludeBegin: true,
112+
excludeEnd: true,
113+
relevance: 0
114+
},
115+
{ // named tags
116+
className: 'type',
117+
begin: '!\\w+!' + URI_CHARACTERS
118+
},
119+
// https://yaml.org/spec/1.2/spec.html#id2784064
120+
{ // verbatim tags
121+
className: 'type',
122+
begin: '!<' + URI_CHARACTERS + ">"
123+
},
124+
{ // primary tags
125+
className: 'type',
126+
begin: '!' + URI_CHARACTERS
127+
},
128+
{ // secondary tags
129+
className: 'type',
130+
begin: '!!' + URI_CHARACTERS
131+
},
132+
{ // fragment id &ref
133+
className: 'meta',
134+
begin: '&' + hljs.UNDERSCORE_IDENT_RE + '$'
135+
},
136+
{ // fragment reference *ref
137+
className: 'meta',
138+
begin: '\\*' + hljs.UNDERSCORE_IDENT_RE + '$'
139+
},
140+
{ // array listing
141+
className: 'bullet',
142+
// TODO: remove |$ hack when we have proper look-ahead support
143+
begin: '\\-(?=[ ]|$)',
144+
relevance: 0
145+
},
146+
hljs.HASH_COMMENT_MODE,
147+
{
148+
beginKeywords: LITERALS,
149+
keywords: { literal: LITERALS }
150+
},
151+
TIMESTAMP,
152+
// numbers are any valid C-style number that
153+
// sit isolated from other words
154+
{
155+
className: 'number',
156+
begin: hljs.C_NUMBER_RE + '\\b'
157+
},
158+
OBJECT,
159+
ARRAY,
160+
STRING
161+
];
162+
163+
var VALUE_MODES = [...MODES];
164+
VALUE_MODES.pop();
165+
VALUE_MODES.push(CONTAINER_STRING);
166+
VALUE_CONTAINER.contains = VALUE_MODES;
58167

59168
return {
60169
name: 'YAML',
61170
case_insensitive: true,
62171
aliases: ['yml', 'YAML'],
63-
contains: [
64-
KEY,
65-
{
66-
className: 'meta',
67-
begin: '^---\s*$',
68-
relevance: 10
69-
},
70-
{ // multi line string
71-
// Blocks start with a | or > followed by a newline
72-
//
73-
// Indentation of subsequent lines must be the same to
74-
// be considered part of the block
75-
className: 'string',
76-
begin: '[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*',
77-
},
78-
{ // Ruby/Rails erb
79-
begin: '<%[%=-]?', end: '[%-]?%>',
80-
subLanguage: 'ruby',
81-
excludeBegin: true,
82-
excludeEnd: true,
83-
relevance: 0
84-
},
85-
{ // named tags
86-
className: 'type',
87-
begin: '!\\w+!' + URI_CHARACTERS,
88-
},
89-
// https://yaml.org/spec/1.2/spec.html#id2784064
90-
{ // verbatim tags
91-
className: 'type',
92-
begin: '!<' + URI_CHARACTERS + ">",
93-
},
94-
{ // primary tags
95-
className: 'type',
96-
begin: '!' + URI_CHARACTERS,
97-
},
98-
{ // secondary tags
99-
className: 'type',
100-
begin: '!!' + URI_CHARACTERS,
101-
},
102-
{ // fragment id &ref
103-
className: 'meta',
104-
begin: '&' + hljs.UNDERSCORE_IDENT_RE + '$',
105-
},
106-
{ // fragment reference *ref
107-
className: 'meta',
108-
begin: '\\*' + hljs.UNDERSCORE_IDENT_RE + '$'
109-
},
110-
{ // array listing
111-
className: 'bullet',
112-
// TODO: remove |$ hack when we have proper look-ahead support
113-
begin: '\\-(?=[ ]|$)',
114-
relevance: 0
115-
},
116-
hljs.HASH_COMMENT_MODE,
117-
{
118-
beginKeywords: LITERALS,
119-
keywords: {literal: LITERALS}
120-
},
121-
TIMESTAMP,
122-
// numbers are any valid C-style number that
123-
// sit isolated from other words
124-
{
125-
className: 'number',
126-
begin: hljs.C_NUMBER_RE + '\\b'
127-
},
128-
STRING
129-
]
172+
contains: MODES
130173
};
131174
}

test/markup/yaml/inline.expect.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<span class="hljs-attr">foo:</span> [<span class="hljs-string">bar</span>, <span class="hljs-string">bar2</span>, [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>], <span class="hljs-number">3</span>]
2+
<span class="hljs-attr">foo:</span> {<span class="hljs-attr">bar:</span> [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>], <span class="hljs-attr">baz:</span> {<span class="hljs-attr">inside:</span> <span class="hljs-number">3</span>}}
3+
<span class="hljs-attr">foo:</span> <span class="hljs-string">ba{}r,ba[]z</span>

test/markup/yaml/inline.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
foo: [bar, bar2, [1, 2], 3]
2+
foo: {bar: [1, 2], baz: {inside: 3}}
3+
foo: ba{}r,ba[]z

0 commit comments

Comments
 (0)