From 3e78df73dc2f69253dfb8bd6f24b20111fa3e9d1 Mon Sep 17 00:00:00 2001 From: Peter Burns Date: Fri, 16 Aug 2019 14:03:37 -0700 Subject: [PATCH] fix: Support highlighting inline HTML and CSS tagged template strings in JS and TS (#2105) * Support nested template strings in TypeScript by copying how JavaScript does it. * Add support for highlighting inline HTML and CSS in TypeScript. With libraries like lit-html, htm, hyperHTML, and others, it's becoming common to write inline HTML and CSS as tagged template literals in source code. This change adds support for highlighting such code as HTML and CSS using highlight.js's inline language support. * Also add support for inline HTML and CSS to JavaScript highlighter. --- src/languages/javascript.js | 26 ++++++ src/languages/typescript.js | 80 ++++++++++++++----- .../javascript/inline-languages.expect.txt | 24 ++++++ test/markup/javascript/inline-languages.txt | 24 ++++++ .../typescript/inline-languages.expect.txt | 24 ++++++ test/markup/typescript/inline-languages.txt | 24 ++++++ .../typescript/nested-templates.expect.txt | 3 + test/markup/typescript/nested-templates.txt | 3 + 8 files changed, 188 insertions(+), 20 deletions(-) create mode 100644 test/markup/javascript/inline-languages.expect.txt create mode 100644 test/markup/javascript/inline-languages.txt create mode 100644 test/markup/typescript/inline-languages.expect.txt create mode 100644 test/markup/typescript/inline-languages.txt create mode 100644 test/markup/typescript/nested-templates.expect.txt create mode 100644 test/markup/typescript/nested-templates.txt diff --git a/src/languages/javascript.js b/src/languages/javascript.js index be3aa1467f..9cefb7268c 100644 --- a/src/languages/javascript.js +++ b/src/languages/javascript.js @@ -40,6 +40,28 @@ function(hljs) { keywords: KEYWORDS, contains: [] // defined later }; + var HTML_TEMPLATE = { + begin: 'html`', end: '', + starts: { + end: '`', returnEnd: false, + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ], + subLanguage: 'xml', + } + }; + var CSS_TEMPLATE = { + begin: 'css`', end: '', + starts: { + end: '`', returnEnd: false, + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ], + subLanguage: 'css', + } + }; var TEMPLATE_STRING = { className: 'string', begin: '`', end: '`', @@ -51,6 +73,8 @@ function(hljs) { SUBST.contains = [ hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, + HTML_TEMPLATE, + CSS_TEMPLATE, TEMPLATE_STRING, NUMBER, hljs.REGEXP_MODE @@ -75,6 +99,8 @@ function(hljs) { }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, + HTML_TEMPLATE, + CSS_TEMPLATE, TEMPLATE_STRING, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, diff --git a/src/languages/typescript.js b/src/languages/typescript.js index 84511b2bc5..985949d807 100644 --- a/src/languages/typescript.js +++ b/src/languages/typescript.js @@ -58,6 +58,62 @@ function(hljs) { ARGS ] }; + var NUMBER = { + className: 'number', + variants: [ + { begin: '\\b(0[bB][01]+)' }, + { begin: '\\b(0[oO][0-7]+)' }, + { begin: hljs.C_NUMBER_RE } + ], + relevance: 0 + }; + var SUBST = { + className: 'subst', + begin: '\\$\\{', end: '\\}', + keywords: KEYWORDS, + contains: [] // defined later + }; + var HTML_TEMPLATE = { + begin: 'html`', end: '', + starts: { + end: '`', returnEnd: false, + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ], + subLanguage: 'xml', + } + }; + var CSS_TEMPLATE = { + begin: 'css`', end: '', + starts: { + end: '`', returnEnd: false, + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ], + subLanguage: 'css', + } + }; + var TEMPLATE_STRING = { + className: 'string', + begin: '`', end: '`', + contains: [ + hljs.BACKSLASH_ESCAPE, + SUBST + ] + }; + SUBST.contains = [ + hljs.APOS_STRING_MODE, + hljs.QUOTE_STRING_MODE, + HTML_TEMPLATE, + CSS_TEMPLATE, + TEMPLATE_STRING, + NUMBER, + hljs.REGEXP_MODE + ]; + + return { aliases: ['ts'], @@ -69,28 +125,12 @@ function(hljs) { }, hljs.APOS_STRING_MODE, hljs.QUOTE_STRING_MODE, - { // template string - className: 'string', - begin: '`', end: '`', - contains: [ - hljs.BACKSLASH_ESCAPE, - { - className: 'subst', - begin: '\\$\\{', end: '\\}' - } - ] - }, + HTML_TEMPLATE, + CSS_TEMPLATE, + TEMPLATE_STRING, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE, - { - className: 'number', - variants: [ - { begin: '\\b(0[bB][01]+)' }, - { begin: '\\b(0[oO][0-7]+)' }, - { begin: hljs.C_NUMBER_RE } - ], - relevance: 0 - }, + NUMBER, { // "value" container begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*', keywords: 'return throw case', diff --git a/test/markup/javascript/inline-languages.expect.txt b/test/markup/javascript/inline-languages.expect.txt new file mode 100644 index 0000000000..5f38b24334 --- /dev/null +++ b/test/markup/javascript/inline-languages.expect.txt @@ -0,0 +1,24 @@ +let foo = true; +`hello ${foo ? `Mr ${name}` : 'there'}`; +foo = false; + +html`<div id="foo">Hello world</div>`; + +html`<div id="foo">Hello times ${10} <span id="bar">world</span></div>`; + +html` + <ul id="list"> + ${repeat(['a', 'b', 'c'], (v) => { + return html`<li class="item">${v}</li>`; + }} + </ul> +`; + +css` + body { + color: red; + } +`; + +// Ensure that we're back in JavaScript mode. +var foo = 10; diff --git a/test/markup/javascript/inline-languages.txt b/test/markup/javascript/inline-languages.txt new file mode 100644 index 0000000000..0b9989ad5e --- /dev/null +++ b/test/markup/javascript/inline-languages.txt @@ -0,0 +1,24 @@ +let foo = true; +`hello ${foo ? `Mr ${name}` : 'there'}`; +foo = false; + +html`
Hello world
`; + +html`
Hello times ${10} world
`; + +html` + +`; + +css` + body { + color: red; + } +`; + +// Ensure that we're back in JavaScript mode. +var foo = 10; diff --git a/test/markup/typescript/inline-languages.expect.txt b/test/markup/typescript/inline-languages.expect.txt new file mode 100644 index 0000000000..da28efe79f --- /dev/null +++ b/test/markup/typescript/inline-languages.expect.txt @@ -0,0 +1,24 @@ +let foo = true; +`hello ${foo ? `Mr ${name}` : 'there'}`; +foo = false; + +html`<div id="foo">Hello world</div>`; + +html`<div id="foo">Hello times ${10} <span id="bar">world</span></div>`; + +html` + <ul id="list"> + ${repeat(['a', 'b', 'c'], (v) => { + return html`<li class="item">${v}</li>`; + }} + </ul> +`; + +css` + body { + color: red; + } +`; + +// Ensure that we're back in TypeScript mode. +var foo = 10; diff --git a/test/markup/typescript/inline-languages.txt b/test/markup/typescript/inline-languages.txt new file mode 100644 index 0000000000..1cbead6d79 --- /dev/null +++ b/test/markup/typescript/inline-languages.txt @@ -0,0 +1,24 @@ +let foo = true; +`hello ${foo ? `Mr ${name}` : 'there'}`; +foo = false; + +html`
Hello world
`; + +html`
Hello times ${10} world
`; + +html` + +`; + +css` + body { + color: red; + } +`; + +// Ensure that we're back in TypeScript mode. +var foo = 10; diff --git a/test/markup/typescript/nested-templates.expect.txt b/test/markup/typescript/nested-templates.expect.txt new file mode 100644 index 0000000000..fde5752e01 --- /dev/null +++ b/test/markup/typescript/nested-templates.expect.txt @@ -0,0 +1,3 @@ +let foo = true; +`hello ${foo ? `Mr ${name}` : 'there'}`; +foo = false; diff --git a/test/markup/typescript/nested-templates.txt b/test/markup/typescript/nested-templates.txt new file mode 100644 index 0000000000..af7c9b22dc --- /dev/null +++ b/test/markup/typescript/nested-templates.txt @@ -0,0 +1,3 @@ +let foo = true; +`hello ${foo ? `Mr ${name}` : 'there'}`; +foo = false;