| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Tobias Koppers @sokra
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const { parseResource } = require("../util/identifier");
|
|---|
| 9 |
|
|---|
| 10 | /** @typedef {import("estree").Expression} Expression */
|
|---|
| 11 | /** @typedef {import("../../declarations/WebpackOptions").JavascriptParserOptions} JavascriptParserOptions */
|
|---|
| 12 | /** @typedef {import("../Dependency").DependencyLocation} DependencyLocation */
|
|---|
| 13 | /** @typedef {import("../javascript/BasicEvaluatedExpression")} BasicEvaluatedExpression */
|
|---|
| 14 | /** @typedef {import("../javascript/JavascriptParser")} JavascriptParser */
|
|---|
| 15 | /** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
|---|
| 16 | /** @typedef {import("./ContextDependency")} ContextDependency */
|
|---|
| 17 | /** @typedef {import("./ContextDependency").ContextDependencyOptions} ContextDependencyOptions */
|
|---|
| 18 | /** @typedef {import("./ContextDependency").Replaces} Replaces */
|
|---|
| 19 |
|
|---|
| 20 | /**
|
|---|
| 21 | * Escapes regular expression metacharacters
|
|---|
| 22 | * @param {string} str String to quote
|
|---|
| 23 | * @returns {string} Escaped string
|
|---|
| 24 | */
|
|---|
| 25 | const quoteMeta = (str) => str.replace(/[-[\]\\/{}()*+?.^$|]/g, "\\$&");
|
|---|
| 26 |
|
|---|
| 27 | /**
|
|---|
| 28 | * Split context from prefix.
|
|---|
| 29 | * @param {string} prefix prefix
|
|---|
| 30 | * @returns {{ prefix: string, context: string }} result
|
|---|
| 31 | */
|
|---|
| 32 | const splitContextFromPrefix = (prefix) => {
|
|---|
| 33 | const idx = prefix.lastIndexOf("/");
|
|---|
| 34 | let context = ".";
|
|---|
| 35 | if (idx >= 0) {
|
|---|
| 36 | context = prefix.slice(0, idx);
|
|---|
| 37 | prefix = `.${prefix.slice(idx)}`;
|
|---|
| 38 | }
|
|---|
| 39 | return {
|
|---|
| 40 | context,
|
|---|
| 41 | prefix
|
|---|
| 42 | };
|
|---|
| 43 | };
|
|---|
| 44 |
|
|---|
| 45 | /** @typedef {Partial<Omit<ContextDependencyOptions, "resource">>} PartialContextDependencyOptions */
|
|---|
| 46 | /** @typedef {{ new (options: ContextDependencyOptions, range: Range, valueRange: Range, ...args: EXPECTED_ANY[]): ContextDependency }} ContextDependencyConstructor */
|
|---|
| 47 |
|
|---|
| 48 | /**
|
|---|
| 49 | * Defines the get additional dep args type used by this module.
|
|---|
| 50 | * @template T
|
|---|
| 51 | * @typedef {T extends new (options: ContextDependencyOptions, range: Range, valueRange: Range, ...remains: infer R) => ContextDependency ? R : []} GetAdditionalDepArgs
|
|---|
| 52 | */
|
|---|
| 53 |
|
|---|
| 54 | /**
|
|---|
| 55 | * Returns the created Dependency.
|
|---|
| 56 | * @template {ContextDependencyConstructor} T
|
|---|
| 57 | * @param {T} Dep the Dependency class
|
|---|
| 58 | * @param {Range} range source range
|
|---|
| 59 | * @param {BasicEvaluatedExpression} param context param
|
|---|
| 60 | * @param {Expression} expr expr
|
|---|
| 61 | * @param {Pick<JavascriptParserOptions, `${"expr" | "wrapped"}Context${"Critical" | "Recursive" | "RegExp"}` | "exprContextRequest">} options options for context creation
|
|---|
| 62 | * @param {PartialContextDependencyOptions} contextOptions options for the ContextModule
|
|---|
| 63 | * @param {JavascriptParser} parser the parser
|
|---|
| 64 | * @param {GetAdditionalDepArgs<T>} depArgs depArgs
|
|---|
| 65 | * @returns {ContextDependency} the created Dependency
|
|---|
| 66 | */
|
|---|
| 67 | module.exports.create = (
|
|---|
| 68 | Dep,
|
|---|
| 69 | range,
|
|---|
| 70 | param,
|
|---|
| 71 | expr,
|
|---|
| 72 | options,
|
|---|
| 73 | contextOptions,
|
|---|
| 74 | parser,
|
|---|
| 75 | ...depArgs
|
|---|
| 76 | ) => {
|
|---|
| 77 | if (param.isTemplateString()) {
|
|---|
| 78 | const quasis = /** @type {BasicEvaluatedExpression[]} */ (param.quasis);
|
|---|
| 79 | const prefixRaw = /** @type {string} */ (quasis[0].string);
|
|---|
| 80 | const postfixRaw =
|
|---|
| 81 | /** @type {string} */
|
|---|
| 82 | (quasis.length > 1 ? quasis[quasis.length - 1].string : "");
|
|---|
| 83 |
|
|---|
| 84 | const valueRange = /** @type {Range} */ (param.range);
|
|---|
| 85 | const { context, prefix } = splitContextFromPrefix(prefixRaw);
|
|---|
| 86 | const {
|
|---|
| 87 | path: postfix,
|
|---|
| 88 | query,
|
|---|
| 89 | fragment
|
|---|
| 90 | } = parseResource(postfixRaw, parser);
|
|---|
| 91 |
|
|---|
| 92 | // When there are more than two quasis, the generated RegExp can be more precise
|
|---|
| 93 | // We join the quasis with the expression regexp
|
|---|
| 94 | const innerQuasis = quasis.slice(1, -1);
|
|---|
| 95 | const innerRegExp =
|
|---|
| 96 | /** @type {RegExp} */ (options.wrappedContextRegExp).source +
|
|---|
| 97 | innerQuasis
|
|---|
| 98 | .map(
|
|---|
| 99 | (q) =>
|
|---|
| 100 | quoteMeta(/** @type {string} */ (q.string)) +
|
|---|
| 101 | /** @type {RegExp} */ (options.wrappedContextRegExp).source
|
|---|
| 102 | )
|
|---|
| 103 | .join("");
|
|---|
| 104 |
|
|---|
| 105 | // Example: `./context/pre${e}inner${e}inner2${e}post?query#frag`
|
|---|
| 106 | // context: "./context"
|
|---|
| 107 | // prefix: "./pre"
|
|---|
| 108 | // innerQuasis: [BEE("inner"), BEE("inner2")]
|
|---|
| 109 | // (BEE = BasicEvaluatedExpression)
|
|---|
| 110 | // postfix: "post"
|
|---|
| 111 | // query: "?query"
|
|---|
| 112 | // fragment: "#frag"
|
|---|
| 113 | // regExp: /^\.\/pre.*inner.*inner2.*post$/
|
|---|
| 114 | const regExp = new RegExp(
|
|---|
| 115 | `^${quoteMeta(prefix)}${innerRegExp}${quoteMeta(postfix)}$`
|
|---|
| 116 | );
|
|---|
| 117 | const dep = new Dep(
|
|---|
| 118 | {
|
|---|
| 119 | request: context + query + fragment,
|
|---|
| 120 | recursive: /** @type {boolean} */ (options.wrappedContextRecursive),
|
|---|
| 121 | regExp,
|
|---|
| 122 | mode: "sync",
|
|---|
| 123 | ...contextOptions
|
|---|
| 124 | },
|
|---|
| 125 | range,
|
|---|
| 126 | valueRange,
|
|---|
| 127 | ...depArgs
|
|---|
| 128 | );
|
|---|
| 129 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 130 |
|
|---|
| 131 | /** @type {Replaces} */
|
|---|
| 132 | const replaces = [];
|
|---|
| 133 | const parts = /** @type {BasicEvaluatedExpression[]} */ (param.parts);
|
|---|
| 134 |
|
|---|
| 135 | for (const [i, part] of parts.entries()) {
|
|---|
| 136 | if (i % 2 === 0) {
|
|---|
| 137 | // Quasis or merged quasi
|
|---|
| 138 | let range = /** @type {Range} */ (part.range);
|
|---|
| 139 | let value = /** @type {string} */ (part.string);
|
|---|
| 140 | if (param.templateStringKind === "cooked") {
|
|---|
| 141 | value = JSON.stringify(value);
|
|---|
| 142 | value = value.slice(1, -1);
|
|---|
| 143 | }
|
|---|
| 144 | if (i === 0) {
|
|---|
| 145 | // prefix
|
|---|
| 146 | value = prefix;
|
|---|
| 147 | range = [
|
|---|
| 148 | /** @type {Range} */ (param.range)[0],
|
|---|
| 149 | /** @type {Range} */ (part.range)[1]
|
|---|
| 150 | ];
|
|---|
| 151 | value =
|
|---|
| 152 | (param.templateStringKind === "cooked" ? "`" : "String.raw`") +
|
|---|
| 153 | value;
|
|---|
| 154 | } else if (i === parts.length - 1) {
|
|---|
| 155 | // postfix
|
|---|
| 156 | value = postfix;
|
|---|
| 157 | range = [
|
|---|
| 158 | /** @type {Range} */ (part.range)[0],
|
|---|
| 159 | /** @type {Range} */ (param.range)[1]
|
|---|
| 160 | ];
|
|---|
| 161 | value = `${value}\``;
|
|---|
| 162 | } else if (
|
|---|
| 163 | part.expression &&
|
|---|
| 164 | part.expression.type === "TemplateElement" &&
|
|---|
| 165 | part.expression.value.raw === value
|
|---|
| 166 | ) {
|
|---|
| 167 | // Shortcut when it's a single quasi and doesn't need to be replaced
|
|---|
| 168 | continue;
|
|---|
| 169 | }
|
|---|
| 170 | replaces.push({
|
|---|
| 171 | range,
|
|---|
| 172 | value
|
|---|
| 173 | });
|
|---|
| 174 | } else {
|
|---|
| 175 | // Expression
|
|---|
| 176 | parser.walkExpression(
|
|---|
| 177 | /** @type {Expression} */
|
|---|
| 178 | (part.expression)
|
|---|
| 179 | );
|
|---|
| 180 | }
|
|---|
| 181 | }
|
|---|
| 182 |
|
|---|
| 183 | dep.replaces = replaces;
|
|---|
| 184 | dep.critical =
|
|---|
| 185 | options.wrappedContextCritical &&
|
|---|
| 186 | "a part of the request of a dependency is an expression";
|
|---|
| 187 | return dep;
|
|---|
| 188 | } else if (
|
|---|
| 189 | param.isWrapped() &&
|
|---|
| 190 | ((param.prefix && param.prefix.isString()) ||
|
|---|
| 191 | (param.postfix && param.postfix.isString()))
|
|---|
| 192 | ) {
|
|---|
| 193 | const prefixRaw =
|
|---|
| 194 | /** @type {string} */
|
|---|
| 195 | (param.prefix && param.prefix.isString() ? param.prefix.string : "");
|
|---|
| 196 | const postfixRaw =
|
|---|
| 197 | /** @type {string} */
|
|---|
| 198 | (param.postfix && param.postfix.isString() ? param.postfix.string : "");
|
|---|
| 199 | const prefixRange =
|
|---|
| 200 | param.prefix && param.prefix.isString() ? param.prefix.range : null;
|
|---|
| 201 | const postfixRange =
|
|---|
| 202 | param.postfix && param.postfix.isString() ? param.postfix.range : null;
|
|---|
| 203 | const valueRange = /** @type {Range} */ (param.range);
|
|---|
| 204 | const { context, prefix } = splitContextFromPrefix(prefixRaw);
|
|---|
| 205 | const {
|
|---|
| 206 | path: postfix,
|
|---|
| 207 | query,
|
|---|
| 208 | fragment
|
|---|
| 209 | } = parseResource(postfixRaw, parser);
|
|---|
| 210 | const regExp = new RegExp(
|
|---|
| 211 | `^${quoteMeta(prefix)}${
|
|---|
| 212 | /** @type {RegExp} */ (options.wrappedContextRegExp).source
|
|---|
| 213 | }${quoteMeta(postfix)}$`
|
|---|
| 214 | );
|
|---|
| 215 | const dep = new Dep(
|
|---|
| 216 | {
|
|---|
| 217 | request: context + query + fragment,
|
|---|
| 218 | recursive: /** @type {boolean} */ (options.wrappedContextRecursive),
|
|---|
| 219 | regExp,
|
|---|
| 220 | mode: "sync",
|
|---|
| 221 | ...contextOptions
|
|---|
| 222 | },
|
|---|
| 223 | range,
|
|---|
| 224 | valueRange,
|
|---|
| 225 | ...depArgs
|
|---|
| 226 | );
|
|---|
| 227 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 228 | /** @type {Replaces} */
|
|---|
| 229 | const replaces = [];
|
|---|
| 230 | if (prefixRange) {
|
|---|
| 231 | replaces.push({
|
|---|
| 232 | range: prefixRange,
|
|---|
| 233 | value: JSON.stringify(prefix)
|
|---|
| 234 | });
|
|---|
| 235 | }
|
|---|
| 236 | if (postfixRange) {
|
|---|
| 237 | replaces.push({
|
|---|
| 238 | range: postfixRange,
|
|---|
| 239 | value: JSON.stringify(postfix)
|
|---|
| 240 | });
|
|---|
| 241 | }
|
|---|
| 242 | dep.replaces = replaces;
|
|---|
| 243 | dep.critical =
|
|---|
| 244 | options.wrappedContextCritical &&
|
|---|
| 245 | "a part of the request of a dependency is an expression";
|
|---|
| 246 |
|
|---|
| 247 | if (parser && param.wrappedInnerExpressions) {
|
|---|
| 248 | for (const part of param.wrappedInnerExpressions) {
|
|---|
| 249 | if (part.expression) {
|
|---|
| 250 | parser.walkExpression(
|
|---|
| 251 | /** @type {Expression} */
|
|---|
| 252 | (part.expression)
|
|---|
| 253 | );
|
|---|
| 254 | }
|
|---|
| 255 | }
|
|---|
| 256 | }
|
|---|
| 257 |
|
|---|
| 258 | return dep;
|
|---|
| 259 | }
|
|---|
| 260 | const dep = new Dep(
|
|---|
| 261 | {
|
|---|
| 262 | request: /** @type {string} */ (options.exprContextRequest),
|
|---|
| 263 | recursive: /** @type {boolean} */ (options.exprContextRecursive),
|
|---|
| 264 | regExp: /** @type {RegExp} */ (options.exprContextRegExp),
|
|---|
| 265 | mode: "sync",
|
|---|
| 266 | ...contextOptions
|
|---|
| 267 | },
|
|---|
| 268 | range,
|
|---|
| 269 | /** @type {Range} */ (param.range),
|
|---|
| 270 | ...depArgs
|
|---|
| 271 | );
|
|---|
| 272 | dep.loc = /** @type {DependencyLocation} */ (expr.loc);
|
|---|
| 273 | dep.critical =
|
|---|
| 274 | options.exprContextCritical &&
|
|---|
| 275 | "the request of a dependency is an expression";
|
|---|
| 276 |
|
|---|
| 277 | parser.walkExpression(/** @type {Expression} */ (param.expression));
|
|---|
| 278 |
|
|---|
| 279 | return dep;
|
|---|
| 280 | };
|
|---|