prism-jsx.js 3.9 KB

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