You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
9.6 KiB
349 lines
9.6 KiB
(function (Prism) { |
|
|
|
var templateString = Prism.languages.javascript['template-string']; |
|
|
|
// see the pattern in prism-javascript.js |
|
var templateLiteralPattern = templateString.pattern.source; |
|
var interpolationObject = templateString.inside['interpolation']; |
|
var interpolationPunctuationObject = interpolationObject.inside['interpolation-punctuation']; |
|
var interpolationPattern = interpolationObject.pattern.source; |
|
|
|
|
|
/** |
|
* Creates a new pattern to match a template string with a special tag. |
|
* |
|
* This will return `undefined` if there is no grammar with the given language id. |
|
* |
|
* @param {string} language The language id of the embedded language. E.g. `markdown`. |
|
* @param {string} tag The regex pattern to match the tag. |
|
* @returns {object | undefined} |
|
* @example |
|
* createTemplate('css', /\bcss/.source); |
|
*/ |
|
function createTemplate(language, tag) { |
|
if (!Prism.languages[language]) { |
|
return undefined; |
|
} |
|
|
|
return { |
|
pattern: RegExp('((?:' + tag + ')\\s*)' + templateLiteralPattern), |
|
lookbehind: true, |
|
greedy: true, |
|
inside: { |
|
'template-punctuation': { |
|
pattern: /^`|`$/, |
|
alias: 'string' |
|
}, |
|
'embedded-code': { |
|
pattern: /[\s\S]+/, |
|
alias: language |
|
} |
|
} |
|
}; |
|
} |
|
|
|
|
|
Prism.languages.javascript['template-string'] = [ |
|
// styled-jsx: |
|
// css`a { color: #25F; }` |
|
// styled-components: |
|
// styled.h1`color: red;` |
|
createTemplate('css', /\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source), |
|
|
|
// html`<p></p>` |
|
// div.innerHTML = `<p></p>` |
|
createTemplate('html', /\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source), |
|
|
|
// svg`<path fill="#fff" d="M55.37 ..."/>` |
|
createTemplate('svg', /\bsvg/.source), |
|
|
|
// md`# h1`, markdown`## h2` |
|
createTemplate('markdown', /\b(?:markdown|md)/.source), |
|
|
|
// gql`...`, graphql`...`, graphql.experimental`...` |
|
createTemplate('graphql', /\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source), |
|
|
|
// sql`...` |
|
createTemplate('sql', /\bsql/.source), |
|
|
|
// vanilla template string |
|
templateString |
|
].filter(Boolean); |
|
|
|
|
|
/** |
|
* Returns a specific placeholder literal for the given language. |
|
* |
|
* @param {number} counter |
|
* @param {string} language |
|
* @returns {string} |
|
*/ |
|
function getPlaceholder(counter, language) { |
|
return '___' + language.toUpperCase() + '_' + counter + '___'; |
|
} |
|
|
|
/** |
|
* Returns the tokens of `Prism.tokenize` but also runs the `before-tokenize` and `after-tokenize` hooks. |
|
* |
|
* @param {string} code |
|
* @param {any} grammar |
|
* @param {string} language |
|
* @returns {(string|Token)[]} |
|
*/ |
|
function tokenizeWithHooks(code, grammar, language) { |
|
var env = { |
|
code: code, |
|
grammar: grammar, |
|
language: language |
|
}; |
|
Prism.hooks.run('before-tokenize', env); |
|
env.tokens = Prism.tokenize(env.code, env.grammar); |
|
Prism.hooks.run('after-tokenize', env); |
|
return env.tokens; |
|
} |
|
|
|
/** |
|
* Returns the token of the given JavaScript interpolation expression. |
|
* |
|
* @param {string} expression The code of the expression. E.g. `"${42}"` |
|
* @returns {Token} |
|
*/ |
|
function tokenizeInterpolationExpression(expression) { |
|
var tempGrammar = {}; |
|
tempGrammar['interpolation-punctuation'] = interpolationPunctuationObject; |
|
|
|
/** @type {Array} */ |
|
var tokens = Prism.tokenize(expression, tempGrammar); |
|
if (tokens.length === 3) { |
|
/** |
|
* The token array will look like this |
|
* [ |
|
* ["interpolation-punctuation", "${"] |
|
* "..." // JavaScript expression of the interpolation |
|
* ["interpolation-punctuation", "}"] |
|
* ] |
|
*/ |
|
|
|
var args = [1, 1]; |
|
args.push.apply(args, tokenizeWithHooks(tokens[1], Prism.languages.javascript, 'javascript')); |
|
|
|
tokens.splice.apply(tokens, args); |
|
} |
|
|
|
return new Prism.Token('interpolation', tokens, interpolationObject.alias, expression); |
|
} |
|
|
|
/** |
|
* Tokenizes the given code with support for JavaScript interpolation expressions mixed in. |
|
* |
|
* This function has 3 phases: |
|
* |
|
* 1. Replace all JavaScript interpolation expression with a placeholder. |
|
* The placeholder will have the syntax of a identify of the target language. |
|
* 2. Tokenize the code with placeholders. |
|
* 3. Tokenize the interpolation expressions and re-insert them into the tokenize code. |
|
* The insertion only works if a placeholder hasn't been "ripped apart" meaning that the placeholder has been |
|
* tokenized as two tokens by the grammar of the embedded language. |
|
* |
|
* @param {string} code |
|
* @param {object} grammar |
|
* @param {string} language |
|
* @returns {Token} |
|
*/ |
|
function tokenizeEmbedded(code, grammar, language) { |
|
// 1. First filter out all interpolations |
|
|
|
// because they might be escaped, we need a lookbehind, so we use Prism |
|
/** @type {(Token|string)[]} */ |
|
var _tokens = Prism.tokenize(code, { |
|
'interpolation': { |
|
pattern: RegExp(interpolationPattern), |
|
lookbehind: true |
|
} |
|
}); |
|
|
|
// replace all interpolations with a placeholder which is not in the code already |
|
var placeholderCounter = 0; |
|
/** @type {Object<string, string>} */ |
|
var placeholderMap = {}; |
|
var embeddedCode = _tokens.map(function (token) { |
|
if (typeof token === 'string') { |
|
return token; |
|
} else { |
|
var interpolationExpression = token.content; |
|
|
|
var placeholder; |
|
while (code.indexOf(placeholder = getPlaceholder(placeholderCounter++, language)) !== -1) { /* noop */ } |
|
placeholderMap[placeholder] = interpolationExpression; |
|
return placeholder; |
|
} |
|
}).join(''); |
|
|
|
|
|
// 2. Tokenize the embedded code |
|
|
|
var embeddedTokens = tokenizeWithHooks(embeddedCode, grammar, language); |
|
|
|
|
|
// 3. Re-insert the interpolation |
|
|
|
var placeholders = Object.keys(placeholderMap); |
|
placeholderCounter = 0; |
|
|
|
/** |
|
* |
|
* @param {(Token|string)[]} tokens |
|
* @returns {void} |
|
*/ |
|
function walkTokens(tokens) { |
|
for (var i = 0; i < tokens.length; i++) { |
|
if (placeholderCounter >= placeholders.length) { |
|
return; |
|
} |
|
|
|
var token = tokens[i]; |
|
|
|
if (typeof token === 'string' || typeof token.content === 'string') { |
|
var placeholder = placeholders[placeholderCounter]; |
|
var s = typeof token === 'string' ? token : /** @type {string} */ (token.content); |
|
|
|
var index = s.indexOf(placeholder); |
|
if (index !== -1) { |
|
++placeholderCounter; |
|
|
|
var before = s.substring(0, index); |
|
var middle = tokenizeInterpolationExpression(placeholderMap[placeholder]); |
|
var after = s.substring(index + placeholder.length); |
|
|
|
var replacement = []; |
|
if (before) { |
|
replacement.push(before); |
|
} |
|
replacement.push(middle); |
|
if (after) { |
|
var afterTokens = [after]; |
|
walkTokens(afterTokens); |
|
replacement.push.apply(replacement, afterTokens); |
|
} |
|
|
|
if (typeof token === 'string') { |
|
tokens.splice.apply(tokens, [i, 1].concat(replacement)); |
|
i += replacement.length - 1; |
|
} else { |
|
token.content = replacement; |
|
} |
|
} |
|
} else { |
|
var content = token.content; |
|
if (Array.isArray(content)) { |
|
walkTokens(content); |
|
} else { |
|
walkTokens([content]); |
|
} |
|
} |
|
} |
|
} |
|
walkTokens(embeddedTokens); |
|
|
|
return new Prism.Token(language, embeddedTokens, 'language-' + language, code); |
|
} |
|
|
|
/** |
|
* The languages for which JS templating will handle tagged template literals. |
|
* |
|
* JS templating isn't active for only JavaScript but also related languages like TypeScript, JSX, and TSX. |
|
*/ |
|
var supportedLanguages = { |
|
'javascript': true, |
|
'js': true, |
|
'typescript': true, |
|
'ts': true, |
|
'jsx': true, |
|
'tsx': true, |
|
}; |
|
Prism.hooks.add('after-tokenize', function (env) { |
|
if (!(env.language in supportedLanguages)) { |
|
return; |
|
} |
|
|
|
/** |
|
* Finds and tokenizes all template strings with an embedded languages. |
|
* |
|
* @param {(Token | string)[]} tokens |
|
* @returns {void} |
|
*/ |
|
function findTemplateStrings(tokens) { |
|
for (var i = 0, l = tokens.length; i < l; i++) { |
|
var token = tokens[i]; |
|
|
|
if (typeof token === 'string') { |
|
continue; |
|
} |
|
|
|
var content = token.content; |
|
if (!Array.isArray(content)) { |
|
if (typeof content !== 'string') { |
|
findTemplateStrings([content]); |
|
} |
|
continue; |
|
} |
|
|
|
if (token.type === 'template-string') { |
|
/** |
|
* A JavaScript template-string token will look like this: |
|
* |
|
* ["template-string", [ |
|
* ["template-punctuation", "`"], |
|
* ( |
|
* An array of "string" and "interpolation" tokens. This is the simple string case. |
|
* or |
|
* ["embedded-code", "..."] This is the token containing the embedded code. |
|
* It also has an alias which is the language of the embedded code. |
|
* ), |
|
* ["template-punctuation", "`"] |
|
* ]] |
|
*/ |
|
|
|
var embedded = content[1]; |
|
if (content.length === 3 && typeof embedded !== 'string' && embedded.type === 'embedded-code') { |
|
// get string content |
|
var code = stringContent(embedded); |
|
|
|
var alias = embedded.alias; |
|
var language = Array.isArray(alias) ? alias[0] : alias; |
|
|
|
var grammar = Prism.languages[language]; |
|
if (!grammar) { |
|
// the embedded language isn't registered. |
|
continue; |
|
} |
|
|
|
content[1] = tokenizeEmbedded(code, grammar, language); |
|
} |
|
} else { |
|
findTemplateStrings(content); |
|
} |
|
} |
|
} |
|
|
|
findTemplateStrings(env.tokens); |
|
}); |
|
|
|
|
|
/** |
|
* Returns the string content of a token or token stream. |
|
* |
|
* @param {string | Token | (string | Token)[]} value |
|
* @returns {string} |
|
*/ |
|
function stringContent(value) { |
|
if (typeof value === 'string') { |
|
return value; |
|
} else if (Array.isArray(value)) { |
|
return value.map(stringContent).join(''); |
|
} else { |
|
return stringContent(value.content); |
|
} |
|
} |
|
|
|
}(Prism));
|
|
|