(function (Prism) { var javascript = Prism.util.clone(Prism.languages.javascript); var space = /(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source; var braces = /(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source; var spread = /(?:\{*\.{3}(?:[^{}]|)*\})/.source; /** * @param {string} source * @param {string} [flags] */ function re(source, flags) { source = source .replace(//g, function () { return space; }) .replace(//g, function () { return braces; }) .replace(//g, function () { return spread; }); return RegExp(source, flags); } spread = re(spread).source; Prism.languages.jsx = Prism.languages.extend('markup', javascript); Prism.languages.jsx.tag.pattern = re( /<\/?(?:[\w.:-]+(?:+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|))?|))**\/?)?>/.source ); Prism.languages.jsx.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/; Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/; Prism.languages.jsx.tag.inside['tag'].inside['class-name'] = /^[A-Z]\w*(?:\.[A-Z]\w*)*$/; Prism.languages.jsx.tag.inside['comment'] = javascript['comment']; Prism.languages.insertBefore('inside', 'attr-name', { 'spread': { pattern: re(//.source), inside: Prism.languages.jsx } }, Prism.languages.jsx.tag); Prism.languages.insertBefore('inside', 'special-attr', { 'script': { // Allow for two levels of nesting pattern: re(/=/.source), alias: 'language-javascript', inside: { 'script-punctuation': { pattern: /^=(?=\{)/, alias: 'punctuation' }, rest: Prism.languages.jsx }, } }, Prism.languages.jsx.tag); // The following will handle plain text inside tags var stringifyToken = function (token) { if (!token) { return ''; } if (typeof token === 'string') { return token; } if (typeof token.content === 'string') { return token.content; } return token.content.map(stringifyToken).join(''); }; var walkTokens = function (tokens) { var openedTags = []; for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; var notTagNorBrace = false; if (typeof token !== 'string') { if (token.type === 'tag' && token.content[0] && token.content[0].type === 'tag') { // We found a tag, now find its kind if (token.content[0].content[0].content === ' 0 && openedTags[openedTags.length - 1].tagName === stringifyToken(token.content[0].content[1])) { // Pop matching opening tag openedTags.pop(); } } else { if (token.content[token.content.length - 1].content === '/>') { // Autoclosed tag, ignore } else { // Opening tag openedTags.push({ tagName: stringifyToken(token.content[0].content[1]), openedBraces: 0 }); } } } else if (openedTags.length > 0 && token.type === 'punctuation' && token.content === '{') { // Here we might have entered a JSX context inside a tag openedTags[openedTags.length - 1].openedBraces++; } else if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces > 0 && token.type === 'punctuation' && token.content === '}') { // Here we might have left a JSX context inside a tag openedTags[openedTags.length - 1].openedBraces--; } else { notTagNorBrace = true; } } if (notTagNorBrace || typeof token === 'string') { if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces === 0) { // Here we are inside a tag, and not inside a JSX context. // That's plain text: drop any tokens matched. var plainText = stringifyToken(token); // And merge text with adjacent text if (i < tokens.length - 1 && (typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')) { plainText += stringifyToken(tokens[i + 1]); tokens.splice(i + 1, 1); } if (i > 0 && (typeof tokens[i - 1] === 'string' || tokens[i - 1].type === 'plain-text')) { plainText = stringifyToken(tokens[i - 1]) + plainText; tokens.splice(i - 1, 1); i--; } tokens[i] = new Prism.Token('plain-text', plainText, null, plainText); } } if (token.content && typeof token.content !== 'string') { walkTokens(token.content); } } }; Prism.hooks.add('after-tokenize', function (env) { if (env.language !== 'jsx' && env.language !== 'tsx') { return; } walkTokens(env.tokens); }); }(Prism));