UNPKG

6.46 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 = { begin: IDENTIFIER_REGEX };
91
92 const HELPER_PARAMETER = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, { keywords: LITERALS });
93
94 const SUB_EXPRESSION = {
95 begin: /\(/,
96 end: /\)/
97 // the "contains" is added below when all necessary sub-modes are defined
98 };
99
100 const HASH = {
101 // fka "attribute-assignment", parameters of the form 'key=value'
102 className: 'attr',
103 begin: HASH_PARAM_REGEX,
104 relevance: 0,
105 starts: {
106 begin: /=/,
107 end: /=/,
108 starts: { contains: [
109 hljs.NUMBER_MODE,
110 hljs.QUOTE_STRING_MODE,
111 hljs.APOS_STRING_MODE,
112 HELPER_PARAMETER,
113 SUB_EXPRESSION
114 ] }
115 }
116 };
117
118 const BLOCK_PARAMS = {
119 // parameters of the form '{{#with x as | y |}}...{{/with}}'
120 begin: /as\s+\|/,
121 keywords: { keyword: 'as' },
122 end: /\|/,
123 contains: [
124 {
125 // define sub-mode in order to prevent highlighting of block-parameter named "as"
126 begin: /\w+/ }
127 ]
128 };
129
130 const HELPER_PARAMETERS = {
131 contains: [
132 hljs.NUMBER_MODE,
133 hljs.QUOTE_STRING_MODE,
134 hljs.APOS_STRING_MODE,
135 BLOCK_PARAMS,
136 HASH,
137 HELPER_PARAMETER,
138 SUB_EXPRESSION
139 ],
140 returnEnd: true
141 // the property "end" is defined through inheritance when the mode is used. If depends
142 // on the surrounding mode, but "endsWithParent" does not work here (i.e. it includes the
143 // end-token of the surrounding mode)
144 };
145
146 const SUB_EXPRESSION_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
147 className: 'name',
148 keywords: BUILT_INS,
149 starts: hljs.inherit(HELPER_PARAMETERS, { end: /\)/ })
150 });
151
152 SUB_EXPRESSION.contains = [ SUB_EXPRESSION_CONTENTS ];
153
154 const OPENING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
155 keywords: BUILT_INS,
156 className: 'name',
157 starts: hljs.inherit(HELPER_PARAMETERS, { end: /\}\}/ })
158 });
159
160 const CLOSING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
161 keywords: BUILT_INS,
162 className: 'name'
163 });
164
165 const BASIC_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
166 className: 'name',
167 keywords: BUILT_INS,
168 starts: hljs.inherit(HELPER_PARAMETERS, { end: /\}\}/ })
169 });
170
171 const ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH = {
172 begin: /\\\{\{/,
173 skip: true
174 };
175 const PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH = {
176 begin: /\\\\(?=\{\{)/,
177 skip: true
178 };
179
180 return {
181 name: 'Handlebars',
182 aliases: [
183 'hbs',
184 'html.hbs',
185 'html.handlebars',
186 'htmlbars'
187 ],
188 case_insensitive: true,
189 subLanguage: 'xml',
190 contains: [
191 ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH,
192 PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH,
193 hljs.COMMENT(/\{\{!--/, /--\}\}/),
194 hljs.COMMENT(/\{\{!/, /\}\}/),
195 {
196 // open raw block "{{{{raw}}}} content not evaluated {{{{/raw}}}}"
197 className: 'template-tag',
198 begin: /\{\{\{\{(?!\/)/,
199 end: /\}\}\}\}/,
200 contains: [ OPENING_BLOCK_MUSTACHE_CONTENTS ],
201 starts: {
202 end: /\{\{\{\{\//,
203 returnEnd: true,
204 subLanguage: 'xml'
205 }
206 },
207 {
208 // close raw block
209 className: 'template-tag',
210 begin: /\{\{\{\{\//,
211 end: /\}\}\}\}/,
212 contains: [ CLOSING_BLOCK_MUSTACHE_CONTENTS ]
213 },
214 {
215 // open block statement
216 className: 'template-tag',
217 begin: /\{\{#/,
218 end: /\}\}/,
219 contains: [ OPENING_BLOCK_MUSTACHE_CONTENTS ]
220 },
221 {
222 className: 'template-tag',
223 begin: /\{\{(?=else\}\})/,
224 end: /\}\}/,
225 keywords: 'else'
226 },
227 {
228 className: 'template-tag',
229 begin: /\{\{(?=else if)/,
230 end: /\}\}/,
231 keywords: 'else if'
232 },
233 {
234 // closing block statement
235 className: 'template-tag',
236 begin: /\{\{\//,
237 end: /\}\}/,
238 contains: [ CLOSING_BLOCK_MUSTACHE_CONTENTS ]
239 },
240 {
241 // template variable or helper-call that is NOT html-escaped
242 className: 'template-variable',
243 begin: /\{\{\{/,
244 end: /\}\}\}/,
245 contains: [ BASIC_MUSTACHE_CONTENTS ]
246 },
247 {
248 // template variable or helper-call that is html-escaped
249 className: 'template-variable',
250 begin: /\{\{/,
251 end: /\}\}/,
252 contains: [ BASIC_MUSTACHE_CONTENTS ]
253 }
254 ]
255 };
256}
257
258module.exports = handlebars;