[d24f17c] | 1 | /**
|
---|
| 2 | * @param {string} value
|
---|
| 3 | * @returns {RegExp}
|
---|
| 4 | * */
|
---|
| 5 |
|
---|
| 6 | /**
|
---|
| 7 | * @param {RegExp | string } re
|
---|
| 8 | * @returns {string}
|
---|
| 9 | */
|
---|
| 10 | function source(re) {
|
---|
| 11 | if (!re) return null;
|
---|
| 12 | if (typeof re === "string") return re;
|
---|
| 13 |
|
---|
| 14 | return re.source;
|
---|
| 15 | }
|
---|
| 16 |
|
---|
| 17 | /**
|
---|
| 18 | * @param {RegExp | string } re
|
---|
| 19 | * @returns {string}
|
---|
| 20 | */
|
---|
| 21 | function anyNumberOfTimes(re) {
|
---|
| 22 | return concat('(', re, ')*');
|
---|
| 23 | }
|
---|
| 24 |
|
---|
| 25 | /**
|
---|
| 26 | * @param {RegExp | string } re
|
---|
| 27 | * @returns {string}
|
---|
| 28 | */
|
---|
| 29 | function optional(re) {
|
---|
| 30 | return concat('(', re, ')?');
|
---|
| 31 | }
|
---|
| 32 |
|
---|
| 33 | /**
|
---|
| 34 | * @param {...(RegExp | string) } args
|
---|
| 35 | * @returns {string}
|
---|
| 36 | */
|
---|
| 37 | function concat(...args) {
|
---|
| 38 | const joined = args.map((x) => source(x)).join("");
|
---|
| 39 | return joined;
|
---|
| 40 | }
|
---|
| 41 |
|
---|
| 42 | /**
|
---|
| 43 | * Any of the passed expresssions may match
|
---|
| 44 | *
|
---|
| 45 | * Creates a huge this | this | that | that match
|
---|
| 46 | * @param {(RegExp | string)[] } args
|
---|
| 47 | * @returns {string}
|
---|
| 48 | */
|
---|
| 49 | function either(...args) {
|
---|
| 50 | const joined = '(' + args.map((x) => source(x)).join("|") + ")";
|
---|
| 51 | return joined;
|
---|
| 52 | }
|
---|
| 53 |
|
---|
| 54 | /*
|
---|
| 55 | Language: Handlebars
|
---|
| 56 | Requires: xml.js
|
---|
| 57 | Author: Robin Ward <robin.ward@gmail.com>
|
---|
| 58 | Description: Matcher for Handlebars as well as EmberJS additions.
|
---|
| 59 | Website: https://handlebarsjs.com
|
---|
| 60 | Category: template
|
---|
| 61 | */
|
---|
| 62 |
|
---|
| 63 | function handlebars(hljs) {
|
---|
| 64 | const BUILT_INS = {
|
---|
| 65 | 'builtin-name': [
|
---|
| 66 | 'action',
|
---|
| 67 | 'bindattr',
|
---|
| 68 | 'collection',
|
---|
| 69 | 'component',
|
---|
| 70 | 'concat',
|
---|
| 71 | 'debugger',
|
---|
| 72 | 'each',
|
---|
| 73 | 'each-in',
|
---|
| 74 | 'get',
|
---|
| 75 | 'hash',
|
---|
| 76 | 'if',
|
---|
| 77 | 'in',
|
---|
| 78 | 'input',
|
---|
| 79 | 'link-to',
|
---|
| 80 | 'loc',
|
---|
| 81 | 'log',
|
---|
| 82 | 'lookup',
|
---|
| 83 | 'mut',
|
---|
| 84 | 'outlet',
|
---|
| 85 | 'partial',
|
---|
| 86 | 'query-params',
|
---|
| 87 | 'render',
|
---|
| 88 | 'template',
|
---|
| 89 | 'textarea',
|
---|
| 90 | 'unbound',
|
---|
| 91 | 'unless',
|
---|
| 92 | 'view',
|
---|
| 93 | 'with',
|
---|
| 94 | 'yield'
|
---|
| 95 | ]
|
---|
| 96 | };
|
---|
| 97 |
|
---|
| 98 | const LITERALS = {
|
---|
| 99 | literal: [
|
---|
| 100 | 'true',
|
---|
| 101 | 'false',
|
---|
| 102 | 'undefined',
|
---|
| 103 | 'null'
|
---|
| 104 | ]
|
---|
| 105 | };
|
---|
| 106 |
|
---|
| 107 | // as defined in https://handlebarsjs.com/guide/expressions.html#literal-segments
|
---|
| 108 | // this regex matches literal segments like ' abc ' or [ abc ] as well as helpers and paths
|
---|
| 109 | // like a/b, ./abc/cde, and abc.bcd
|
---|
| 110 |
|
---|
| 111 | const DOUBLE_QUOTED_ID_REGEX = /""|"[^"]+"/;
|
---|
| 112 | const SINGLE_QUOTED_ID_REGEX = /''|'[^']+'/;
|
---|
| 113 | const BRACKET_QUOTED_ID_REGEX = /\[\]|\[[^\]]+\]/;
|
---|
| 114 | const PLAIN_ID_REGEX = /[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/;
|
---|
| 115 | const PATH_DELIMITER_REGEX = /(\.|\/)/;
|
---|
| 116 | const ANY_ID = either(
|
---|
| 117 | DOUBLE_QUOTED_ID_REGEX,
|
---|
| 118 | SINGLE_QUOTED_ID_REGEX,
|
---|
| 119 | BRACKET_QUOTED_ID_REGEX,
|
---|
| 120 | PLAIN_ID_REGEX
|
---|
| 121 | );
|
---|
| 122 |
|
---|
| 123 | const IDENTIFIER_REGEX = concat(
|
---|
| 124 | optional(/\.|\.\/|\//), // relative or absolute path
|
---|
| 125 | ANY_ID,
|
---|
| 126 | anyNumberOfTimes(concat(
|
---|
| 127 | PATH_DELIMITER_REGEX,
|
---|
| 128 | ANY_ID
|
---|
| 129 | ))
|
---|
| 130 | );
|
---|
| 131 |
|
---|
| 132 | // identifier followed by a equal-sign (without the equal sign)
|
---|
| 133 | const HASH_PARAM_REGEX = concat(
|
---|
| 134 | '(',
|
---|
| 135 | BRACKET_QUOTED_ID_REGEX, '|',
|
---|
| 136 | PLAIN_ID_REGEX,
|
---|
| 137 | ')(?==)'
|
---|
| 138 | );
|
---|
| 139 |
|
---|
| 140 | const HELPER_NAME_OR_PATH_EXPRESSION = {
|
---|
| 141 | begin: IDENTIFIER_REGEX,
|
---|
| 142 | lexemes: /[\w.\/]+/
|
---|
| 143 | };
|
---|
| 144 |
|
---|
| 145 | const HELPER_PARAMETER = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
|
---|
| 146 | keywords: LITERALS
|
---|
| 147 | });
|
---|
| 148 |
|
---|
| 149 | const SUB_EXPRESSION = {
|
---|
| 150 | begin: /\(/,
|
---|
| 151 | end: /\)/
|
---|
| 152 | // the "contains" is added below when all necessary sub-modes are defined
|
---|
| 153 | };
|
---|
| 154 |
|
---|
| 155 | const HASH = {
|
---|
| 156 | // fka "attribute-assignment", parameters of the form 'key=value'
|
---|
| 157 | className: 'attr',
|
---|
| 158 | begin: HASH_PARAM_REGEX,
|
---|
| 159 | relevance: 0,
|
---|
| 160 | starts: {
|
---|
| 161 | begin: /=/,
|
---|
| 162 | end: /=/,
|
---|
| 163 | starts: {
|
---|
| 164 | contains: [
|
---|
| 165 | hljs.NUMBER_MODE,
|
---|
| 166 | hljs.QUOTE_STRING_MODE,
|
---|
| 167 | hljs.APOS_STRING_MODE,
|
---|
| 168 | HELPER_PARAMETER,
|
---|
| 169 | SUB_EXPRESSION
|
---|
| 170 | ]
|
---|
| 171 | }
|
---|
| 172 | }
|
---|
| 173 | };
|
---|
| 174 |
|
---|
| 175 | const BLOCK_PARAMS = {
|
---|
| 176 | // parameters of the form '{{#with x as | y |}}...{{/with}}'
|
---|
| 177 | begin: /as\s+\|/,
|
---|
| 178 | keywords: {
|
---|
| 179 | keyword: 'as'
|
---|
| 180 | },
|
---|
| 181 | end: /\|/,
|
---|
| 182 | contains: [
|
---|
| 183 | {
|
---|
| 184 | // define sub-mode in order to prevent highlighting of block-parameter named "as"
|
---|
| 185 | begin: /\w+/
|
---|
| 186 | }
|
---|
| 187 | ]
|
---|
| 188 | };
|
---|
| 189 |
|
---|
| 190 | const HELPER_PARAMETERS = {
|
---|
| 191 | contains: [
|
---|
| 192 | hljs.NUMBER_MODE,
|
---|
| 193 | hljs.QUOTE_STRING_MODE,
|
---|
| 194 | hljs.APOS_STRING_MODE,
|
---|
| 195 | BLOCK_PARAMS,
|
---|
| 196 | HASH,
|
---|
| 197 | HELPER_PARAMETER,
|
---|
| 198 | SUB_EXPRESSION
|
---|
| 199 | ],
|
---|
| 200 | returnEnd: true
|
---|
| 201 | // the property "end" is defined through inheritance when the mode is used. If depends
|
---|
| 202 | // on the surrounding mode, but "endsWithParent" does not work here (i.e. it includes the
|
---|
| 203 | // end-token of the surrounding mode)
|
---|
| 204 | };
|
---|
| 205 |
|
---|
| 206 | const SUB_EXPRESSION_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
|
---|
| 207 | className: 'name',
|
---|
| 208 | keywords: BUILT_INS,
|
---|
| 209 | starts: hljs.inherit(HELPER_PARAMETERS, {
|
---|
| 210 | end: /\)/
|
---|
| 211 | })
|
---|
| 212 | });
|
---|
| 213 |
|
---|
| 214 | SUB_EXPRESSION.contains = [SUB_EXPRESSION_CONTENTS];
|
---|
| 215 |
|
---|
| 216 | const OPENING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
|
---|
| 217 | keywords: BUILT_INS,
|
---|
| 218 | className: 'name',
|
---|
| 219 | starts: hljs.inherit(HELPER_PARAMETERS, {
|
---|
| 220 | end: /\}\}/
|
---|
| 221 | })
|
---|
| 222 | });
|
---|
| 223 |
|
---|
| 224 | const CLOSING_BLOCK_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
|
---|
| 225 | keywords: BUILT_INS,
|
---|
| 226 | className: 'name'
|
---|
| 227 | });
|
---|
| 228 |
|
---|
| 229 | const BASIC_MUSTACHE_CONTENTS = hljs.inherit(HELPER_NAME_OR_PATH_EXPRESSION, {
|
---|
| 230 | className: 'name',
|
---|
| 231 | keywords: BUILT_INS,
|
---|
| 232 | starts: hljs.inherit(HELPER_PARAMETERS, {
|
---|
| 233 | end: /\}\}/
|
---|
| 234 | })
|
---|
| 235 | });
|
---|
| 236 |
|
---|
| 237 | const ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH = {
|
---|
| 238 | begin: /\\\{\{/,
|
---|
| 239 | skip: true
|
---|
| 240 | };
|
---|
| 241 | const PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH = {
|
---|
| 242 | begin: /\\\\(?=\{\{)/,
|
---|
| 243 | skip: true
|
---|
| 244 | };
|
---|
| 245 |
|
---|
| 246 | return {
|
---|
| 247 | name: 'Handlebars',
|
---|
| 248 | aliases: [
|
---|
| 249 | 'hbs',
|
---|
| 250 | 'html.hbs',
|
---|
| 251 | 'html.handlebars',
|
---|
| 252 | 'htmlbars'
|
---|
| 253 | ],
|
---|
| 254 | case_insensitive: true,
|
---|
| 255 | subLanguage: 'xml',
|
---|
| 256 | contains: [
|
---|
| 257 | ESCAPE_MUSTACHE_WITH_PRECEEDING_BACKSLASH,
|
---|
| 258 | PREVENT_ESCAPE_WITH_ANOTHER_PRECEEDING_BACKSLASH,
|
---|
| 259 | hljs.COMMENT(/\{\{!--/, /--\}\}/),
|
---|
| 260 | hljs.COMMENT(/\{\{!/, /\}\}/),
|
---|
| 261 | {
|
---|
| 262 | // open raw block "{{{{raw}}}} content not evaluated {{{{/raw}}}}"
|
---|
| 263 | className: 'template-tag',
|
---|
| 264 | begin: /\{\{\{\{(?!\/)/,
|
---|
| 265 | end: /\}\}\}\}/,
|
---|
| 266 | contains: [OPENING_BLOCK_MUSTACHE_CONTENTS],
|
---|
| 267 | starts: {
|
---|
| 268 | end: /\{\{\{\{\//,
|
---|
| 269 | returnEnd: true,
|
---|
| 270 | subLanguage: 'xml'
|
---|
| 271 | }
|
---|
| 272 | },
|
---|
| 273 | {
|
---|
| 274 | // close raw block
|
---|
| 275 | className: 'template-tag',
|
---|
| 276 | begin: /\{\{\{\{\//,
|
---|
| 277 | end: /\}\}\}\}/,
|
---|
| 278 | contains: [CLOSING_BLOCK_MUSTACHE_CONTENTS]
|
---|
| 279 | },
|
---|
| 280 | {
|
---|
| 281 | // open block statement
|
---|
| 282 | className: 'template-tag',
|
---|
| 283 | begin: /\{\{#/,
|
---|
| 284 | end: /\}\}/,
|
---|
| 285 | contains: [OPENING_BLOCK_MUSTACHE_CONTENTS]
|
---|
| 286 | },
|
---|
| 287 | {
|
---|
| 288 | className: 'template-tag',
|
---|
| 289 | begin: /\{\{(?=else\}\})/,
|
---|
| 290 | end: /\}\}/,
|
---|
| 291 | keywords: 'else'
|
---|
| 292 | },
|
---|
| 293 | {
|
---|
| 294 | className: 'template-tag',
|
---|
| 295 | begin: /\{\{(?=else if)/,
|
---|
| 296 | end: /\}\}/,
|
---|
| 297 | keywords: 'else if'
|
---|
| 298 | },
|
---|
| 299 | {
|
---|
| 300 | // closing block statement
|
---|
| 301 | className: 'template-tag',
|
---|
| 302 | begin: /\{\{\//,
|
---|
| 303 | end: /\}\}/,
|
---|
| 304 | contains: [CLOSING_BLOCK_MUSTACHE_CONTENTS]
|
---|
| 305 | },
|
---|
| 306 | {
|
---|
| 307 | // template variable or helper-call that is NOT html-escaped
|
---|
| 308 | className: 'template-variable',
|
---|
| 309 | begin: /\{\{\{/,
|
---|
| 310 | end: /\}\}\}/,
|
---|
| 311 | contains: [BASIC_MUSTACHE_CONTENTS]
|
---|
| 312 | },
|
---|
| 313 | {
|
---|
| 314 | // template variable or helper-call that is html-escaped
|
---|
| 315 | className: 'template-variable',
|
---|
| 316 | begin: /\{\{/,
|
---|
| 317 | end: /\}\}/,
|
---|
| 318 | contains: [BASIC_MUSTACHE_CONTENTS]
|
---|
| 319 | }
|
---|
| 320 | ]
|
---|
| 321 | };
|
---|
| 322 | }
|
---|
| 323 |
|
---|
| 324 | /*
|
---|
| 325 | Language: HTMLBars (legacy)
|
---|
| 326 | Requires: xml.js
|
---|
| 327 | Description: Matcher for Handlebars as well as EmberJS additions.
|
---|
| 328 | Website: https://github.com/tildeio/htmlbars
|
---|
| 329 | Category: template
|
---|
| 330 | */
|
---|
| 331 |
|
---|
| 332 | function htmlbars(hljs) {
|
---|
| 333 | const definition = handlebars(hljs);
|
---|
| 334 |
|
---|
| 335 | definition.name = "HTMLbars";
|
---|
| 336 |
|
---|
| 337 | // HACK: This lets handlebars do the auto-detection if it's been loaded (by
|
---|
| 338 | // default the build script will load in alphabetical order) and if not (perhaps
|
---|
| 339 | // an install is only using `htmlbars`, not `handlebars`) then this will still
|
---|
| 340 | // allow HTMLBars to participate in the auto-detection
|
---|
| 341 |
|
---|
| 342 | // worse case someone will have HTMLbars and handlebars competing for the same
|
---|
| 343 | // content and will need to change their setup to only require handlebars, but
|
---|
| 344 | // I don't consider this a breaking change
|
---|
| 345 | if (hljs.getLanguage("handlebars")) {
|
---|
| 346 | definition.disableAutodetect = true;
|
---|
| 347 | }
|
---|
| 348 |
|
---|
| 349 | return definition;
|
---|
| 350 | }
|
---|
| 351 |
|
---|
| 352 | module.exports = htmlbars;
|
---|