You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
5.5 KiB

4 years ago
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const ContextDependencyHelpers = exports;
  7. /**
  8. * Escapes regular expression metacharacters
  9. * @param {string} str String to quote
  10. * @returns {string} Escaped string
  11. */
  12. const quotemeta = str => {
  13. return str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
  14. };
  15. const splitContextFromPrefix = prefix => {
  16. const idx = prefix.lastIndexOf("/");
  17. let context = ".";
  18. if (idx >= 0) {
  19. context = prefix.substr(0, idx);
  20. prefix = `.${prefix.substr(idx)}`;
  21. }
  22. return {
  23. context,
  24. prefix
  25. };
  26. };
  27. const splitQueryFromPostfix = postfix => {
  28. const idx = postfix.indexOf("?");
  29. let query = "";
  30. if (idx >= 0) {
  31. query = postfix.substr(idx);
  32. postfix = postfix.substr(0, idx);
  33. }
  34. return {
  35. postfix,
  36. query
  37. };
  38. };
  39. ContextDependencyHelpers.create = (
  40. Dep,
  41. range,
  42. param,
  43. expr,
  44. options,
  45. contextOptions,
  46. // when parser is not passed in, expressions won't be walked
  47. parser = null
  48. ) => {
  49. if (param.isTemplateString()) {
  50. let prefixRaw = param.quasis[0].string;
  51. let postfixRaw =
  52. param.quasis.length > 1
  53. ? param.quasis[param.quasis.length - 1].string
  54. : "";
  55. const valueRange = param.range;
  56. const { context, prefix } = splitContextFromPrefix(prefixRaw);
  57. const { postfix, query } = splitQueryFromPostfix(postfixRaw);
  58. // When there are more than two quasis, the generated RegExp can be more precise
  59. // We join the quasis with the expression regexp
  60. const innerQuasis = param.quasis.slice(1, param.quasis.length - 1);
  61. const innerRegExp =
  62. options.wrappedContextRegExp.source +
  63. innerQuasis
  64. .map(q => quotemeta(q.string) + options.wrappedContextRegExp.source)
  65. .join("");
  66. // Example: `./context/pre${e}inner${e}inner2${e}post?query`
  67. // context: "./context"
  68. // prefix: "./pre"
  69. // innerQuasis: [BEE("inner"), BEE("inner2")]
  70. // (BEE = BasicEvaluatedExpression)
  71. // postfix: "post"
  72. // query: "?query"
  73. // regExp: /^\.\/pre.*inner.*inner2.*post$/
  74. const regExp = new RegExp(
  75. `^${quotemeta(prefix)}${innerRegExp}${quotemeta(postfix)}$`
  76. );
  77. const dep = new Dep(
  78. Object.assign(
  79. {
  80. request: context + query,
  81. recursive: options.wrappedContextRecursive,
  82. regExp,
  83. mode: "sync"
  84. },
  85. contextOptions
  86. ),
  87. range,
  88. valueRange
  89. );
  90. dep.loc = expr.loc;
  91. const replaces = [];
  92. param.parts.forEach((part, i) => {
  93. if (i % 2 === 0) {
  94. // Quasis or merged quasi
  95. let range = part.range;
  96. let value = part.string;
  97. if (param.templateStringKind === "cooked") {
  98. value = JSON.stringify(value);
  99. value = value.slice(1, value.length - 1);
  100. }
  101. if (i === 0) {
  102. // prefix
  103. value = prefix;
  104. range = [param.range[0], part.range[1]];
  105. value =
  106. (param.templateStringKind === "cooked" ? "`" : "String.raw`") +
  107. value;
  108. } else if (i === param.parts.length - 1) {
  109. // postfix
  110. value = postfix;
  111. range = [part.range[0], param.range[1]];
  112. value = value + "`";
  113. } else if (
  114. part.expression &&
  115. part.expression.type === "TemplateElement" &&
  116. part.expression.value.raw === value
  117. ) {
  118. // Shortcut when it's a single quasi and doesn't need to be replaced
  119. return;
  120. }
  121. replaces.push({
  122. range,
  123. value
  124. });
  125. } else {
  126. // Expression
  127. if (parser) {
  128. parser.walkExpression(part.expression);
  129. }
  130. }
  131. });
  132. dep.replaces = replaces;
  133. dep.critical =
  134. options.wrappedContextCritical &&
  135. "a part of the request of a dependency is an expression";
  136. return dep;
  137. } else if (
  138. param.isWrapped() &&
  139. ((param.prefix && param.prefix.isString()) ||
  140. (param.postfix && param.postfix.isString()))
  141. ) {
  142. let prefixRaw =
  143. param.prefix && param.prefix.isString() ? param.prefix.string : "";
  144. let postfixRaw =
  145. param.postfix && param.postfix.isString() ? param.postfix.string : "";
  146. const prefixRange =
  147. param.prefix && param.prefix.isString() ? param.prefix.range : null;
  148. const postfixRange =
  149. param.postfix && param.postfix.isString() ? param.postfix.range : null;
  150. const valueRange = param.range;
  151. const { context, prefix } = splitContextFromPrefix(prefixRaw);
  152. const { postfix, query } = splitQueryFromPostfix(postfixRaw);
  153. const regExp = new RegExp(
  154. `^${quotemeta(prefix)}${options.wrappedContextRegExp.source}${quotemeta(
  155. postfix
  156. )}$`
  157. );
  158. const dep = new Dep(
  159. Object.assign(
  160. {
  161. request: context + query,
  162. recursive: options.wrappedContextRecursive,
  163. regExp,
  164. mode: "sync"
  165. },
  166. contextOptions
  167. ),
  168. range,
  169. valueRange
  170. );
  171. dep.loc = expr.loc;
  172. const replaces = [];
  173. if (prefixRange) {
  174. replaces.push({
  175. range: prefixRange,
  176. value: JSON.stringify(prefix)
  177. });
  178. }
  179. if (postfixRange) {
  180. replaces.push({
  181. range: postfixRange,
  182. value: JSON.stringify(postfix)
  183. });
  184. }
  185. dep.replaces = replaces;
  186. dep.critical =
  187. options.wrappedContextCritical &&
  188. "a part of the request of a dependency is an expression";
  189. if (parser && param.wrappedInnerExpressions) {
  190. for (const part of param.wrappedInnerExpressions) {
  191. if (part.expression) parser.walkExpression(part.expression);
  192. }
  193. }
  194. return dep;
  195. } else {
  196. const dep = new Dep(
  197. Object.assign(
  198. {
  199. request: options.exprContextRequest,
  200. recursive: options.exprContextRecursive,
  201. regExp: options.exprContextRegExp,
  202. mode: "sync"
  203. },
  204. contextOptions
  205. ),
  206. range,
  207. param.range
  208. );
  209. dep.loc = expr.loc;
  210. dep.critical =
  211. options.exprContextCritical &&
  212. "the request of a dependency is an expression";
  213. if (parser) {
  214. parser.walkExpression(param.expression);
  215. }
  216. return dep;
  217. }
  218. };