prism-jsx.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. (function (Prism) {
  2. var javascript = Prism.util.clone(Prism.languages.javascript);
  3. var space = /(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source;
  4. var braces = /(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source;
  5. var spread = /(?:\{<S>*\.{3}(?:[^{}]|<BRACES>)*\})/.source;
  6. /**
  7. * @param {string} source
  8. * @param {string} [flags]
  9. */
  10. function re(source, flags) {
  11. source = source
  12. .replace(/<S>/g, function () { return space; })
  13. .replace(/<BRACES>/g, function () { return braces; })
  14. .replace(/<SPREAD>/g, function () { return spread; });
  15. return RegExp(source, flags);
  16. }
  17. spread = re(spread).source;
  18. Prism.languages.jsx = Prism.languages.extend('markup', javascript);
  19. Prism.languages.jsx.tag.pattern = re(
  20. /<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source
  21. );
  22. Prism.languages.jsx.tag.inside['tag'].pattern = /^<\/?[^\s>\/]*/;
  23. Prism.languages.jsx.tag.inside['attr-value'].pattern = /=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/;
  24. Prism.languages.jsx.tag.inside['tag'].inside['class-name'] = /^[A-Z]\w*(?:\.[A-Z]\w*)*$/;
  25. Prism.languages.jsx.tag.inside['comment'] = javascript['comment'];
  26. Prism.languages.insertBefore('inside', 'attr-name', {
  27. 'spread': {
  28. pattern: re(/<SPREAD>/.source),
  29. inside: Prism.languages.jsx
  30. }
  31. }, Prism.languages.jsx.tag);
  32. Prism.languages.insertBefore('inside', 'special-attr', {
  33. 'script': {
  34. // Allow for two levels of nesting
  35. pattern: re(/=<BRACES>/.source),
  36. alias: 'language-javascript',
  37. inside: {
  38. 'script-punctuation': {
  39. pattern: /^=(?=\{)/,
  40. alias: 'punctuation'
  41. },
  42. rest: Prism.languages.jsx
  43. },
  44. }
  45. }, Prism.languages.jsx.tag);
  46. // The following will handle plain text inside tags
  47. var stringifyToken = function (token) {
  48. if (!token) {
  49. return '';
  50. }
  51. if (typeof token === 'string') {
  52. return token;
  53. }
  54. if (typeof token.content === 'string') {
  55. return token.content;
  56. }
  57. return token.content.map(stringifyToken).join('');
  58. };
  59. var walkTokens = function (tokens) {
  60. var openedTags = [];
  61. for (var i = 0; i < tokens.length; i++) {
  62. var token = tokens[i];
  63. var notTagNorBrace = false;
  64. if (typeof token !== 'string') {
  65. if (token.type === 'tag' && token.content[0] && token.content[0].type === 'tag') {
  66. // We found a tag, now find its kind
  67. if (token.content[0].content[0].content === '</') {
  68. // Closing tag
  69. if (openedTags.length > 0 && openedTags[openedTags.length - 1].tagName === stringifyToken(token.content[0].content[1])) {
  70. // Pop matching opening tag
  71. openedTags.pop();
  72. }
  73. } else {
  74. if (token.content[token.content.length - 1].content === '/>') {
  75. // Autoclosed tag, ignore
  76. } else {
  77. // Opening tag
  78. openedTags.push({
  79. tagName: stringifyToken(token.content[0].content[1]),
  80. openedBraces: 0
  81. });
  82. }
  83. }
  84. } else if (openedTags.length > 0 && token.type === 'punctuation' && token.content === '{') {
  85. // Here we might have entered a JSX context inside a tag
  86. openedTags[openedTags.length - 1].openedBraces++;
  87. } else if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces > 0 && token.type === 'punctuation' && token.content === '}') {
  88. // Here we might have left a JSX context inside a tag
  89. openedTags[openedTags.length - 1].openedBraces--;
  90. } else {
  91. notTagNorBrace = true;
  92. }
  93. }
  94. if (notTagNorBrace || typeof token === 'string') {
  95. if (openedTags.length > 0 && openedTags[openedTags.length - 1].openedBraces === 0) {
  96. // Here we are inside a tag, and not inside a JSX context.
  97. // That's plain text: drop any tokens matched.
  98. var plainText = stringifyToken(token);
  99. // And merge text with adjacent text
  100. if (i < tokens.length - 1 && (typeof tokens[i + 1] === 'string' || tokens[i + 1].type === 'plain-text')) {
  101. plainText += stringifyToken(tokens[i + 1]);
  102. tokens.splice(i + 1, 1);
  103. }
  104. if (i > 0 && (typeof tokens[i - 1] === 'string' || tokens[i - 1].type === 'plain-text')) {
  105. plainText = stringifyToken(tokens[i - 1]) + plainText;
  106. tokens.splice(i - 1, 1);
  107. i--;
  108. }
  109. tokens[i] = new Prism.Token('plain-text', plainText, null, plainText);
  110. }
  111. }
  112. if (token.content && typeof token.content !== 'string') {
  113. walkTokens(token.content);
  114. }
  115. }
  116. };
  117. Prism.hooks.add('after-tokenize', function (env) {
  118. if (env.language !== 'jsx' && env.language !== 'tsx') {
  119. return;
  120. }
  121. walkTokens(env.tokens);
  122. });
  123. }(Prism));