257ac4061e2a55a60b42ab06605ee351fbda4c46e11b2051fd2ee296357f1c3e0367965bfab5098f12d59c5de62bdf7d826799d5eb63954287e956ec266a20 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import { tokenizer } from 'acorn';
  2. function _stripLiteralAcorn(code, options) {
  3. const FILL = options?.fillChar ?? " ";
  4. const FILL_COMMENT = " ";
  5. let result = "";
  6. const filter = options?.filter ?? (() => true);
  7. function fillupTo(index) {
  8. if (index > result.length)
  9. result += code.slice(result.length, index).replace(/[^\n]/g, FILL_COMMENT);
  10. }
  11. const tokens = [];
  12. const pasers = tokenizer(code, {
  13. ecmaVersion: "latest",
  14. sourceType: "module",
  15. allowHashBang: true,
  16. allowAwaitOutsideFunction: true,
  17. allowImportExportEverywhere: true
  18. });
  19. const iter = pasers[Symbol.iterator]();
  20. let error;
  21. try {
  22. while (true) {
  23. const { done, value: token } = iter.next();
  24. if (done)
  25. break;
  26. tokens.push(token);
  27. fillupTo(token.start);
  28. if (token.type.label === "string") {
  29. const body = code.slice(token.start + 1, token.end - 1);
  30. if (filter(body)) {
  31. result += code[token.start] + FILL.repeat(token.end - token.start - 2) + code[token.end - 1];
  32. continue;
  33. }
  34. } else if (token.type.label === "template") {
  35. const body = code.slice(token.start, token.end);
  36. if (filter(body)) {
  37. result += FILL.repeat(token.end - token.start);
  38. continue;
  39. }
  40. } else if (token.type.label === "regexp") {
  41. const body = code.slice(token.start, token.end);
  42. if (filter(body)) {
  43. result += body.replace(/\/(.*)\/(\w?)$/g, (_, $1, $2) => `/${FILL.repeat($1.length)}/${$2}`);
  44. continue;
  45. }
  46. }
  47. result += code.slice(token.start, token.end);
  48. }
  49. fillupTo(code.length);
  50. } catch (e) {
  51. error = e;
  52. }
  53. return {
  54. error,
  55. result,
  56. tokens
  57. };
  58. }
  59. function stripLiteralAcorn(code, options) {
  60. const result = _stripLiteralAcorn(code, options);
  61. if (result.error)
  62. throw result.error;
  63. return result.result;
  64. }
  65. function createIsLiteralPositionAcorn(code) {
  66. const positionList = [];
  67. const tokens = tokenizer(code, {
  68. ecmaVersion: "latest",
  69. sourceType: "module",
  70. allowHashBang: true,
  71. allowAwaitOutsideFunction: true,
  72. allowImportExportEverywhere: true,
  73. onComment(_isBlock, _text, start, end) {
  74. positionList.push(start);
  75. positionList.push(end);
  76. }
  77. });
  78. const inter = tokens[Symbol.iterator]();
  79. while (true) {
  80. const { done, value: token } = inter.next();
  81. if (done)
  82. break;
  83. if (token.type.label === "string") {
  84. positionList.push(token.start + 1);
  85. positionList.push(token.end - 1);
  86. } else if (token.type.label === "template") {
  87. positionList.push(token.start);
  88. positionList.push(token.end);
  89. }
  90. }
  91. return (position) => {
  92. const i = binarySearch(positionList, (v) => position < v);
  93. return (i - 1) % 2 === 0;
  94. };
  95. }
  96. function binarySearch(array, pred) {
  97. let low = -1;
  98. let high = array.length;
  99. while (1 + low < high) {
  100. const mid = low + (high - low >> 1);
  101. if (pred(array[mid]))
  102. high = mid;
  103. else
  104. low = mid;
  105. }
  106. return high;
  107. }
  108. const multilineCommentsRE = /\/\*([^*\/])*?\*\//gms;
  109. const singlelineCommentsRE = /(?:^|\n|\r)\s*\/\/.*(?:\r|\n|$)/gm;
  110. const templateLiteralRE = /\$\{(\s*(?:|{.*}|(?!\$\{).|\n|\r)*?\s*)\}/g;
  111. const quotesRE = [
  112. /(["'`])((?:\\\1|(?!\1)|.|\r)*?)\1/gm,
  113. /([`])((?:\\\1|(?!\1)|.|\n|\r)*?)\1/gm
  114. // multi-line strings (i.e. template literals only)
  115. ];
  116. function stripLiteralRegex(code, options) {
  117. const FILL_COMMENT = " ";
  118. const FILL = options?.fillChar ?? " ";
  119. const filter = options?.filter ?? (() => true);
  120. code = code.replace(multilineCommentsRE, (s) => filter(s) ? FILL_COMMENT.repeat(s.length) : s).replace(singlelineCommentsRE, (s) => filter(s) ? FILL_COMMENT.repeat(s.length) : s);
  121. let expanded = code;
  122. for (let i = 0; i < 16; i++) {
  123. const before = expanded;
  124. expanded = expanded.replace(templateLiteralRE, "` $1`");
  125. if (expanded === before)
  126. break;
  127. }
  128. quotesRE.forEach((re) => {
  129. expanded = expanded.replace(re, (s, quote, body, index) => {
  130. if (!filter(s.slice(1, -1)))
  131. return s;
  132. code = code.slice(0, index + 1) + FILL.repeat(s.length - 2) + code.slice(index + s.length - 1);
  133. return quote + FILL.repeat(s.length - 2) + quote;
  134. });
  135. });
  136. return code;
  137. }
  138. function stripLiteral(code, options) {
  139. return stripLiteralDetailed(code, options).result;
  140. }
  141. function stripLiteralDetailed(code, options) {
  142. const acorn = _stripLiteralAcorn(code, options);
  143. if (!acorn.error) {
  144. return {
  145. mode: "acorn",
  146. result: acorn.result,
  147. acorn
  148. };
  149. }
  150. return {
  151. mode: "regex",
  152. result: stripLiteralRegex(acorn.result + code.slice(acorn.result.length), options),
  153. acorn
  154. };
  155. }
  156. export { createIsLiteralPositionAcorn, stripLiteral, stripLiteralAcorn, stripLiteralDetailed, stripLiteralRegex };