Skip to content

Commit

Permalink
fix: Support highlighting inline HTML and CSS tagged template strings…
Browse files Browse the repository at this point in the history
… 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.
  • Loading branch information
rictic authored and Marcos Cáceres committed Aug 16, 2019
1 parent 9a7e3e6 commit 3e78df7
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 20 deletions.
26 changes: 26 additions & 0 deletions src/languages/javascript.js
Expand Up @@ -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: '`',
Expand All @@ -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
Expand All @@ -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,
Expand Down
80 changes: 60 additions & 20 deletions src/languages/typescript.js
Expand Up @@ -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'],
Expand All @@ -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',
Expand Down
24 changes: 24 additions & 0 deletions test/markup/javascript/inline-languages.expect.txt
@@ -0,0 +1,24 @@
<span class="hljs-keyword">let</span> foo = <span class="hljs-literal">true</span>;
<span class="hljs-string">`hello <span class="hljs-subst">${foo ? <span class="hljs-string">`Mr <span class="hljs-subst">${name}</span>`</span> : <span class="hljs-string">'there'</span>}</span>`</span>;
foo = <span class="hljs-literal">false</span>;

html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"foo"</span>&gt;</span>Hello world<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>`</span>;

html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"foo"</span>&gt;</span>Hello times </span><span class="hljs-subst">${<span class="hljs-number">10</span>}</span><span class="xml"> <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"bar"</span>&gt;</span>world<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>`</span>;

html`<span class="xml">
<span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"list"</span>&gt;</span>
</span><span class="hljs-subst">${repeat([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>], (v) =&gt; {
<span class="hljs-keyword">return</span> html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"item"</span>&gt;</span></span><span class="hljs-subst">${v}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>`</span>;
}</span><span class="xml">}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
`</span>;

css`<span class="css">
<span class="hljs-selector-tag">body</span> {
<span class="hljs-attribute">color</span>: red;
}
`</span>;

<span class="hljs-comment">// Ensure that we're back in JavaScript mode.</span>
<span class="hljs-keyword">var</span> foo = <span class="hljs-number">10</span>;
24 changes: 24 additions & 0 deletions test/markup/javascript/inline-languages.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;
24 changes: 24 additions & 0 deletions test/markup/typescript/inline-languages.expect.txt
@@ -0,0 +1,24 @@
<span class="hljs-keyword">let</span> foo = <span class="hljs-literal">true</span>;
<span class="hljs-string">`hello <span class="hljs-subst">${foo ? <span class="hljs-string">`Mr <span class="hljs-subst">${name}</span>`</span> : <span class="hljs-string">'there'</span>}</span>`</span>;
foo = <span class="hljs-literal">false</span>;

html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"foo"</span>&gt;</span>Hello world<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>`</span>;

html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"foo"</span>&gt;</span>Hello times </span><span class="hljs-subst">${<span class="hljs-number">10</span>}</span><span class="xml"> <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"bar"</span>&gt;</span>world<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>`</span>;

html`<span class="xml">
<span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"list"</span>&gt;</span>
</span><span class="hljs-subst">${repeat([<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>], (v) =&gt; {
<span class="hljs-keyword">return</span> html`<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"item"</span>&gt;</span></span><span class="hljs-subst">${v}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>`</span>;
}</span><span class="xml">}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
`</span>;

css`<span class="css">
<span class="hljs-selector-tag">body</span> {
<span class="hljs-attribute">color</span>: red;
}
`</span>;

<span class="hljs-comment">// Ensure that we're back in TypeScript mode.</span>
<span class="hljs-keyword">var</span> foo = <span class="hljs-number">10</span>;
24 changes: 24 additions & 0 deletions test/markup/typescript/inline-languages.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;
3 changes: 3 additions & 0 deletions test/markup/typescript/nested-templates.expect.txt
@@ -0,0 +1,3 @@
<span class="hljs-keyword">let</span> foo = <span class="hljs-literal">true</span>;
<span class="hljs-string">`hello <span class="hljs-subst">${foo ? <span class="hljs-string">`Mr <span class="hljs-subst">${name}</span>`</span> : <span class="hljs-string">'there'</span>}</span>`</span>;
foo = <span class="hljs-literal">false</span>;
3 changes: 3 additions & 0 deletions test/markup/typescript/nested-templates.txt
@@ -0,0 +1,3 @@
let foo = true;
`hello ${foo ? `Mr ${name}` : 'there'}`;
foo = false;

0 comments on commit 3e78df7

Please sign in to comment.