diff options
| author | Armand Philippot <git@armandphilippot.com> | 2021-12-30 19:47:21 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2021-12-30 19:47:21 +0100 |
| commit | a98b5ea6fe8e8cc98a55e0fd793e6e8660ea31c1 (patch) | |
| tree | 542810ab5aef99150db228bb54fd58303dcb31c7 /public/prism/prism-js-templates.js | |
| parent | ab355897a12b7bda1089a44de326d41455a0f7a3 (diff) | |
chore: add prismjs for syntax highlighting
Diffstat (limited to 'public/prism/prism-js-templates.js')
| -rw-r--r-- | public/prism/prism-js-templates.js | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/public/prism/prism-js-templates.js b/public/prism/prism-js-templates.js new file mode 100644 index 0000000..1c819d2 --- /dev/null +++ b/public/prism/prism-js-templates.js @@ -0,0 +1,379 @@ +(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); |
