06dfc2b9fc3ae4de8e7abb2faf26c50422a7f100749ccdb0753092a5d1e3c2d335839565ed397f6b0c4e141315101fb04d162453b56ac404f81b3543853bcc 4.9 KB

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