UNPKG

6.54 kBJavaScriptView Raw
1/*
2Language: Handlebars
3Requires: xml.js
4Author: Robin Ward <robin.ward@gmail.com>
5Description: Matcher for Handlebars as well as EmberJS additions.
6Website: https://handlebarsjs.com
7Category: template
8*/
9
10function handlebars(hljs) {
11 const regex = hljs.regex;
12 const BUILT_INS = {
13 $pattern: /[\w.\/]+/,
14 built_in: [
15 'action',
16 'bindattr',
17 'collection',
18 'component',
19 'concat',
20 'debugger',
21 'each',
22 'each-in',
23 'get',
24 'hash',
25 'if',
26 'in',
27 'input',
28 'link-to',
29 'loc',
30 'log',
31 'lookup',
32 'mut',
33 'outlet',
34 'partial',
35 'query-params',
36 'render',
37 'template',
38 'textarea',
39 'unbound',
40 'unless',
41 'view',
42 'with',
43 'yield'
44 ]
45 };
46
47 const LITERALS = {
48 $pattern: /[\w.\/]+/,
49 literal: [
50 'true',
51 'false',
52 'undefined',
53 'null'
54 ]
55 };
56
57 // as defined in https://handlebarsjs.com/guide/expressions.html#literal-segments
58 // this regex matches literal segments like ' abc ' or [ abc ] as well as helpers and paths
59 // like a/b, ./abc/cde, and abc.bcd
60
61 const DOUBLE_QUOTED_ID_REGEX = /""|"[^"]+"/;
62 const SINGLE_QUOTED_ID_REGEX = /''|'[^']+'/;
63 const BRACKET_QUOTED_ID_REGEX = /\[\]|\[[^\]]+\]/;
64 const PLAIN_ID_REGEX = /[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/;
65 const PATH_DELIMITER_REGEX = /(\.|\/)/;
66 const ANY_ID = regex.either(
67 DOUBLE_QUOTED_ID_REGEX,
68 SINGLE_QUOTED_ID_REGEX,
69 BRACKET_QUOTED_ID_REGEX,
70 PLAIN_ID_REGEX
71 );
72
73 const IDENTIFIER_REGEX = regex.concat(
74 regex.optional(/\.|\.\/|\//), // relative or absolute path
75 ANY_ID,
76 regex.anyNumberOfTimes(regex.concat(
77 PATH_DELIMITER_REGEX,
78 ANY_ID
79 ))
80 );
81
82 // identifier followed by a equal-sign (without the equal sign)
83 const HASH_PARAM_REGEX = regex.concat(
84 '(',
85 BRACKET_QUOTED_ID_REGEX, '|',
86 PLAIN_ID_REGEX,
87 ')(?==)'
88 );
89
90 const HELPER_NAME_OR_PATH_EXPRESSION = {
91 begin: IDENTIFIER_REGEX
92 };
93
94 const HELPER_PARAMETER = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
95 keywords: LITERALS
96 });
97
98 const SUB_EXPRESSION = {
99 begin: /\(/,
100 end: /\)/
101 // the "contains" is added below when all necessary sub-modes are defined
102 };
103
104 const HASH = {
105 // fka "attribute-assignment", parameters of the form 'key=value'
106 className: 'attr',
107 begin: HASH_PARAM_REGEX,
108 relevance: 0,
109 starts: {
110 begin: /=/,
111 end: /=/,
112 starts: {
113 contains: [
114 hljs.NUMBER_MODE,
115 hljs.QUOTE_STRING_MODE,
116 hljs.APOS_STRING_MODE,
117 HELPER_PARAMETER,
118 SUB_EXPRESSION
119 ]
120 }
121 }
122 };
123
124 const BLOCK_PARAMS = {
125 // parameters of the form '{{#with x as | y |}}...{{/with}}'
126 begin: /as\s+\|/,
127 keywords: {
128 keyword: 'as'
129 },
130 end: /\|/,
131 contains: [
132 {
133 // define sub-mode in order to prevent highlighting of block-parameter named "as"
134 begin: /\w+/
135 }
136 ]
137 };
138
139 const HELPER_PARAMETERS = {
140 contains: [
141 hljs.NUMBER_MODE,
142 hljs.QUOTE_STRING_MODE,
143 hljs.APOS_STRING_MODE,
144 BLOCK_PARAMS,
145 HASH,
146 HELPER_PARAMETER,
147 SUB_EXPRESSION
148 ],
149 returnEnd: true
150 // the property "end" is defined through inheritance when the mode is used. If depends
151 // on the surrounding mode, but "endsWithParent" does not work here (i.e. it includes the
152 // end-token of the surrounding mode)
153 };
154
155 const SUB_EXPRESSION_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
156 className: 'name',
157 keywords: BUILT_INS,
158 starts: hljs.inherit(HELPER_PARAMETERS, {
159 end: /\)/
160 })
161 });
162
163 SUB_EXPRESSION.contains = [SUB_EXPRESSION_CONTENTS];
164
165 const OPENING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
166 keywords: BUILT_INS,
167 className: 'name',
168 starts: hljs.inherit(HELPER_PARAMETERS, {
169 end: /\}\}/
170 })
171 });
172
173 const CLOSING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
174 keywords: BUILT_INS,
175 className: 'name'
176 });
177
178 const BASIC_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
179 className: 'name',
180 keywords: BUILT_INS,
181 starts: hljs.inherit(HELPER_PARAMETERS, {
182 end: /\}\}/
183 })
184 });
185
186 const ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH = {
187 begin: /\\\{\{/,
188 skip: true
189 };
190 const PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH = {
191 begin: /\\\\(?=\{\{)/,
192 skip: true
193 };
194
195 return {
196 name: 'Handlebars',
197 aliases: [
198 'hbs',
199 'html.hbs',
200 'html.handlebars',
201 'htmlbars'
202 ],
203 case_insensitive: true,
204 subLanguage: 'xml',
205 contains: [
206 ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH,
207 PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH,
208 hljs.COMMENT(/\{\{!--/, /--\}\}/),
209 hljs.COMMENT(/\{\{!/, /\}\}/),
210 {
211 // open raw block "{{{{raw}}}} content not evaluated {{{{/raw}}}}"
212 className: 'template-tag',
213 begin: /\{\{\{\{(?!\/)/,
214 end: /\}\}\}\}/,
215 contains: [OPENING_BLOCK_MUSTACHE_CONTENTS],
216 starts: {
217 end: /\{\{\{\{\//,
218 returnEnd: true,
219 subLanguage: 'xml'
220 }
221 },
222 {
223 // close raw block
224 className: 'template-tag',
225 begin: /\{\{\{\{\//,
226 end: /\}\}\}\}/,
227 contains: [CLOSING_BLOCK_MUSTACHE_CONTENTS]
228 },
229 {
230 // open block statement
231 className: 'template-tag',
232 begin: /\{\{#/,
233 end: /\}\}/,
234 contains: [OPENING_BLOCK_MUSTACHE_CONTENTS]
235 },
236 {
237 className: 'template-tag',
238 begin: /\{\{(?=else\}\})/,
239 end: /\}\}/,
240 keywords: 'else'
241 },
242 {
243 className: 'template-tag',
244 begin: /\{\{(?=else if)/,
245 end: /\}\}/,
246 keywords: 'else if'
247 },
248 {
249 // closing block statement
250 className: 'template-tag',
251 begin: /\{\{\//,
252 end: /\}\}/,
253 contains: [CLOSING_BLOCK_MUSTACHE_CONTENTS]
254 },
255 {
256 // template variable or helper-call that is NOT html-escaped
257 className: 'template-variable',
258 begin: /\{\{\{/,
259 end: /\}\}\}/,
260 contains: [BASIC_MUSTACHE_CONTENTS]
261 },
262 {
263 // template variable or helper-call that is html-escaped
264 className: 'template-variable',
265 begin: /\{\{/,
266 end: /\}\}/,
267 contains: [BASIC_MUSTACHE_CONTENTS]
268 }
269 ]
270 };
271}
272
273export { handlebars as default };