summaryrefslogtreecommitdiffstats
path: root/public/prism/prism-js-templates.js
diff options
context:
space:
mode:
Diffstat (limited to 'public/prism/prism-js-templates.js')
-rw-r--r--public/prism/prism-js-templates.js379
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);