123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349 |
- (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));
|