template-parser-helper.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. 'use strict';
  2. const Literal = require('./literal');
  3. const postcssParse = require('postcss/lib/parse');
  4. const reNewLine = /(?:\r?\n|\r)/gm;
  5. const isLiteral = (token) => token[0] === 'word' && /^\$\{[\s\S]*\}$/.test(token[1]);
  6. function literal(start) {
  7. if (!isLiteral(start)) {
  8. return;
  9. }
  10. const tokens = [];
  11. let hasWord;
  12. let type;
  13. let token;
  14. while ((token = this.tokenizer.nextToken())) {
  15. tokens.push(token);
  16. type = token[0];
  17. if (type.length === 1) {
  18. break;
  19. } else if (type === 'word') {
  20. hasWord = true;
  21. }
  22. }
  23. while (tokens.length) {
  24. this.tokenizer.back(tokens.pop());
  25. }
  26. if (type === '{' || (type === ':' && !hasWord)) {
  27. return;
  28. }
  29. const node = new Literal({
  30. text: start[1],
  31. });
  32. this.init(node, start[2], start[3]);
  33. const input = this.input;
  34. if (input.templateLiteralStyles) {
  35. const offset = input.quasis[0].start;
  36. const nodeIndex = getNodeIndex(node, input);
  37. const start = offset + nodeIndex;
  38. const end = start + node.text.length;
  39. const templateLiteralStyles = input.templateLiteralStyles.filter(
  40. (style) => style.startIndex <= end && start < style.endIndex,
  41. );
  42. if (templateLiteralStyles.length) {
  43. const nodes = parseTemplateLiteralStyles(templateLiteralStyles, input, [
  44. nodeIndex,
  45. nodeIndex + node.text.length,
  46. ]);
  47. if (nodes.length) {
  48. node.nodes = nodes;
  49. nodes.forEach((n) => (n.parent = node));
  50. }
  51. }
  52. }
  53. return node;
  54. }
  55. function freeSemicolon(token) {
  56. this.spaces += token[1];
  57. const nodes = this.current.nodes;
  58. const prev = nodes && nodes[nodes.length - 1];
  59. if (prev && /^(rule|literal)$/.test(prev.type) && !prev.raws.ownSemicolon) {
  60. prev.raws.ownSemicolon = this.spaces;
  61. this.spaces = '';
  62. }
  63. }
  64. module.exports = {
  65. freeSemicolon,
  66. literal,
  67. };
  68. function parseTemplateLiteralStyles(styles, input, range) {
  69. const offset = input.quasis[0].start;
  70. const source = input.css;
  71. const opts = Object.assign({}, input.parseOptions);
  72. delete opts.templateLiteralStyles;
  73. delete opts.expressions;
  74. delete opts.quasis;
  75. const parseStyle = docFixer(offset, source, opts);
  76. const nodes = [];
  77. let index = range[0];
  78. styles
  79. .sort((a, b) => a.startIndex - b.startIndex)
  80. .forEach((style) => {
  81. const root = parseStyle(style);
  82. if (!root || !root.nodes.length) {
  83. return;
  84. }
  85. root.raws.beforeStart = source.slice(index, style.startIndex - offset);
  86. root.raws.afterEnd = '';
  87. if (style.endIndex) {
  88. index = style.endIndex - offset;
  89. } else {
  90. index = style.startIndex - offset + (style.content || root.source.input.css).length;
  91. }
  92. nodes.push(root);
  93. });
  94. if (nodes.length) {
  95. nodes[nodes.length - 1].raws.afterEnd = source.slice(index, range[1]);
  96. }
  97. return nodes;
  98. }
  99. class LocalFixer {
  100. constructor(offset, lines, style, templateParse) {
  101. const startIndex = style.startIndex - offset;
  102. let line = 0;
  103. let column = startIndex;
  104. lines.some((lineEndIndex, lineNumber) => {
  105. if (lineEndIndex >= startIndex) {
  106. line = lineNumber--;
  107. if (lineNumber in lines) {
  108. column = startIndex - lines[lineNumber] - 1;
  109. }
  110. return true;
  111. }
  112. });
  113. this.line = line;
  114. this.column = column;
  115. this.style = style;
  116. this.templateParse = templateParse;
  117. }
  118. object(object) {
  119. if (object) {
  120. if (object.line === 1) {
  121. object.column += this.column;
  122. }
  123. object.line += this.line;
  124. }
  125. }
  126. node(node) {
  127. this.object(node.source.start);
  128. this.object(node.source.end);
  129. }
  130. root(root) {
  131. this.node(root);
  132. root.walk((node) => {
  133. this.node(node);
  134. });
  135. }
  136. error(error) {
  137. if (error && error.name === 'CssSyntaxError') {
  138. this.object(error);
  139. this.object(error.input);
  140. error.message = error.message.replace(
  141. /:\d+:\d+:/,
  142. ':' + error.line + ':' + error.column + ':',
  143. );
  144. }
  145. return error;
  146. }
  147. parse(opts) {
  148. const style = this.style;
  149. const syntax = style.syntax;
  150. let root = style.root;
  151. try {
  152. root = this.templateParse(
  153. style.content,
  154. Object.assign(
  155. {},
  156. opts,
  157. {
  158. map: false,
  159. },
  160. style.opts,
  161. ),
  162. );
  163. } catch (error) {
  164. if (style.ignoreErrors) {
  165. return;
  166. } else if (!style.skipConvert) {
  167. this.error(error);
  168. }
  169. throw error;
  170. }
  171. if (!style.skipConvert) {
  172. this.root(root);
  173. }
  174. root.source.inline = Boolean(style.inline);
  175. root.source.lang = style.lang;
  176. root.source.syntax = syntax;
  177. return root;
  178. }
  179. }
  180. function docFixer(offset, source, opts) {
  181. let match;
  182. const lines = [];
  183. reNewLine.lastIndex = 0;
  184. while ((match = reNewLine.exec(source))) {
  185. lines.push(match.index);
  186. }
  187. lines.push(source.length);
  188. return function parseStyle(style) {
  189. const parse = style.syntax ? style.syntax.parse : postcssParse;
  190. return new LocalFixer(offset, lines, style, parse).parse(opts);
  191. };
  192. }
  193. function getNodeIndex(node, input) {
  194. const source = input.css;
  195. let match;
  196. let line = 1;
  197. let lastIndex = -1;
  198. reNewLine.lastIndex = 0;
  199. while ((match = reNewLine.exec(source))) {
  200. if (line === node.source.start.line) {
  201. return lastIndex + node.source.start.column;
  202. }
  203. lastIndex = match.index;
  204. line++;
  205. }
  206. if (line === node.source.start.line) {
  207. return lastIndex + node.source.start.column;
  208. }
  209. return source.length;
  210. }