findCommentsInRaws.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports["default"] = findCommentsInRaws;
  6. /**
  7. * Finds comments, both CSS comments and double slash ones, in a CSS string
  8. * This helper exists because PostCSS drops some inline comments (those
  9. * between selectors, property values, etc.)
  10. * https://github.com/postcss/postcss/issues/845#issuecomment-232306259
  11. *
  12. * @param {string} rawString -- the source raw CSS string
  13. * @return {array} array of objects with these props:
  14. * � type -- "css" or "double-slash"
  15. * � source: { start, end }
  16. * IMPORTANT: the function itself considers \r as a character, and counts
  17. * it for `start` and `end`. But if their values are passed to PostCSS's
  18. * result.warn(), than "\r\n" is consideren ONE CHAR (newline)!
  19. * � raws
  20. * raws.startToken -- `/*`, `/**`, `/**!`, etc.
  21. * raws.left -- whitespace after the comment opening marker
  22. * raws.text -- the full comment, including markers (//, /*)
  23. * raws.right -- whitespace before the comment closing marker
  24. * raws.endToken -- `*\/`, `**\/` for CSS comments
  25. * � text -- the comment text only, excluding //, /*, trailing whitespaces
  26. * � inlineAfter -- true, if there is something before the comment on
  27. * the same line
  28. * � inlineBefore -- true, if there is something after the comment on
  29. * the same line
  30. */
  31. function findCommentsInRaws(rawString) {
  32. var result = [];
  33. var comment = {};
  34. // Keeps track of which structure the parser is inside (string, comment,
  35. // url function, parens). E.g., /* comment */ inside a string doesn't
  36. // constitute a comment, so as url(//path)
  37. var modesEntered = [{
  38. mode: "normal",
  39. character: null
  40. }];
  41. var commentStart = null;
  42. // postcss-scss transforms //-comments into CSS comments, like so:
  43. // `// comment` -> `/* comment*/`. So to have a correct intex we need to
  44. // keep track on the added `*/` sequences
  45. var offset = 0;
  46. for (var i = 0; i < rawString.length; i++) {
  47. var character = rawString[i];
  48. var prevChar = i > 0 ? rawString[i - 1] : null;
  49. var nextChar = i + 1 < rawString.length ? rawString[i + 1] : null;
  50. var lastModeIndex = modesEntered.length - 1;
  51. var mode = modesEntered[lastModeIndex] && modesEntered[lastModeIndex].mode;
  52. switch (character) {
  53. // If entering/exiting a string
  54. case '"':
  55. case "'":
  56. {
  57. if (mode === "comment") {
  58. break;
  59. }
  60. if (mode === "string" && modesEntered[lastModeIndex].character === character && prevChar !== "\\") {
  61. // Exiting a string
  62. modesEntered.pop();
  63. } else {
  64. // Entering a string
  65. modesEntered.push({
  66. mode: "string",
  67. character: character
  68. });
  69. }
  70. break;
  71. }
  72. // Entering url, other function or parens (only url matters)
  73. case "(":
  74. {
  75. if (mode === "comment" || mode === "string") {
  76. break;
  77. }
  78. var functionNameRegSearch = /(?:^|[\n\r]|\s-|[:\s,.(){}*+/%])([\w-]*)$/.exec(rawString.substring(0, i));
  79. // A `\S(` can be in, say, `@media(`
  80. if (!functionNameRegSearch) {
  81. modesEntered.push({
  82. mode: "parens",
  83. character: "("
  84. });
  85. break;
  86. }
  87. var functionName = functionNameRegSearch[1];
  88. modesEntered.push({
  89. mode: functionName === "url" ? "url" : "parens",
  90. character: "("
  91. });
  92. break;
  93. }
  94. // Exiting url, other function or parens
  95. case ")":
  96. {
  97. if (mode === "comment" || mode === "string") {
  98. break;
  99. }
  100. modesEntered.pop();
  101. break;
  102. }
  103. // checking for comment
  104. case "/":
  105. {
  106. // Break if the / is inside a comment because we leap over the second
  107. // slash in // and in */, so the / is not from a marker. Also break
  108. // if inside a string
  109. if (mode === "comment" || mode === "string") {
  110. break;
  111. }
  112. if (nextChar === "*") {
  113. modesEntered.push({
  114. mode: "comment",
  115. character: "/*"
  116. });
  117. comment = {
  118. type: "css",
  119. source: {
  120. start: i + offset
  121. },
  122. // If i is 0 then the file/the line starts with this comment
  123. inlineAfter: i > 0 && rawString.substring(0, i).search(/\n\s*$/) === -1
  124. };
  125. commentStart = i;
  126. // Skip the next iteration as the * is already checked
  127. i++;
  128. } else if (nextChar === "/") {
  129. // `url(//path/to/file)` has no comment
  130. if (mode === "url") {
  131. break;
  132. }
  133. modesEntered.push({
  134. mode: "comment",
  135. character: "//"
  136. });
  137. comment = {
  138. type: "double-slash",
  139. source: {
  140. start: i + offset
  141. },
  142. // If i is 0 then the file/the line starts with this comment
  143. inlineAfter: i > 0 && rawString.substring(0, i).search(/\n\s*$/) === -1
  144. };
  145. commentStart = i;
  146. // Skip the next iteration as the second slash in // is already checked
  147. i++;
  148. }
  149. break;
  150. }
  151. // Might be a closing `*/`
  152. case "*":
  153. {
  154. if (mode === "comment" && modesEntered[lastModeIndex].character === "/*" && nextChar === "/") {
  155. comment.source.end = i + 1 + offset;
  156. var commentRaw = rawString.substring(commentStart, i + 2);
  157. var matches = /^(\/\*+[!#]?)(\s*)([\s\S]*?)(\s*)(\*+\/)$/.exec(commentRaw);
  158. modesEntered.pop();
  159. comment.raws = {
  160. startToken: matches[1],
  161. left: matches[2],
  162. text: commentRaw,
  163. right: matches[4],
  164. endToken: matches[5]
  165. };
  166. comment.text = matches[3];
  167. comment.inlineBefore = rawString.substring(i + 2).search(/^\s*\S+\s*?\n/) !== -1;
  168. result.push(Object.assign({}, comment));
  169. comment = {};
  170. // Skip the next loop as the / in */ is already checked
  171. i++;
  172. }
  173. break;
  174. }
  175. default:
  176. {
  177. var isNewline = character === "\r" && rawString[i + 1] === "\n" || character === "\n" && rawString[i - 1] !== "\r";
  178. // //-comments end before newline and if the code string ends
  179. if (isNewline || i === rawString.length - 1) {
  180. if (mode === "comment" && modesEntered[lastModeIndex].character === "//") {
  181. comment.source.end = (isNewline ? i - 1 : i) + offset;
  182. var _commentRaw = rawString.substring(commentStart, isNewline ? i : i + 1);
  183. var _matches = /^(\/+)(\s*)(.*?)(\s*)$/.exec(_commentRaw);
  184. modesEntered.pop();
  185. comment.raws = {
  186. startToken: _matches[1],
  187. left: _matches[2],
  188. text: _commentRaw,
  189. right: _matches[4]
  190. };
  191. comment.text = _matches[3];
  192. comment.inlineBefore = false;
  193. result.push(Object.assign({}, comment));
  194. comment = {};
  195. // Compensate for the `*/` added by postcss-scss
  196. offset += 2;
  197. }
  198. }
  199. break;
  200. }
  201. }
  202. }
  203. return result;
  204. }