source: frontend/node_modules/webpack/lib/util/identifier.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 16.5 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3*/
4
5"use strict";
6
7const path = require("path");
8
9const WINDOWS_ABS_PATH_REGEXP = /^[a-z]:[\\/]/i;
10const SEGMENTS_SPLIT_REGEXP = /([|!])/;
11const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g;
12
13/**
14 * Relative path to request.
15 * @param {string} relativePath relative path
16 * @returns {string} request
17 */
18const relativePathToRequest = (relativePath) => {
19 if (relativePath === "") return "./.";
20 if (relativePath === "..") return "../.";
21 if (relativePath.startsWith("../")) return relativePath;
22 return `./${relativePath}`;
23};
24
25/**
26 * Absolute to request.
27 * @param {string} context context for relative path
28 * @param {string} maybeAbsolutePath path to make relative
29 * @returns {string} relative path in request style
30 */
31const absoluteToRequest = (context, maybeAbsolutePath) => {
32 if (maybeAbsolutePath[0] === "/") {
33 if (
34 maybeAbsolutePath.length > 1 &&
35 maybeAbsolutePath[maybeAbsolutePath.length - 1] === "/"
36 ) {
37 // this 'path' is actually a regexp generated by dynamic requires.
38 // Don't treat it as an absolute path.
39 return maybeAbsolutePath;
40 }
41
42 const querySplitPos = maybeAbsolutePath.indexOf("?");
43 let resource =
44 querySplitPos === -1
45 ? maybeAbsolutePath
46 : maybeAbsolutePath.slice(0, querySplitPos);
47 resource = relativePathToRequest(path.posix.relative(context, resource));
48 return querySplitPos === -1
49 ? resource
50 : resource + maybeAbsolutePath.slice(querySplitPos);
51 }
52
53 if (WINDOWS_ABS_PATH_REGEXP.test(maybeAbsolutePath)) {
54 const querySplitPos = maybeAbsolutePath.indexOf("?");
55 let resource =
56 querySplitPos === -1
57 ? maybeAbsolutePath
58 : maybeAbsolutePath.slice(0, querySplitPos);
59 resource = path.win32.relative(context, resource);
60 if (!WINDOWS_ABS_PATH_REGEXP.test(resource)) {
61 resource = relativePathToRequest(
62 resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/")
63 );
64 }
65 return querySplitPos === -1
66 ? resource
67 : resource + maybeAbsolutePath.slice(querySplitPos);
68 }
69
70 // not an absolute path
71 return maybeAbsolutePath;
72};
73
74/**
75 * Request to absolute.
76 * @param {string} context context for relative path
77 * @param {string} relativePath path
78 * @returns {string} absolute path
79 */
80const requestToAbsolute = (context, relativePath) => {
81 if (relativePath.startsWith("./") || relativePath.startsWith("../")) {
82 return path.join(context, relativePath);
83 }
84 return relativePath;
85};
86
87/** @typedef {EXPECTED_OBJECT} AssociatedObjectForCache */
88
89/**
90 * Defines the make cacheable result type used by this module.
91 * @template T
92 * @typedef {(value: string, cache?: AssociatedObjectForCache) => T} MakeCacheableResult
93 */
94
95/**
96 * Defines the bind cache result fn type used by this module.
97 * @template T
98 * @typedef {(value: string) => T} BindCacheResultFn
99 */
100
101/**
102 * Defines the bind cache type used by this module.
103 * @template T
104 * @typedef {(cache: AssociatedObjectForCache) => BindCacheResultFn<T>} BindCache
105 */
106
107/**
108 * Returns } cacheable function.
109 * @template T
110 * @param {((value: string) => T)} realFn real function
111 * @returns {MakeCacheableResult<T> & { bindCache: BindCache<T> }} cacheable function
112 */
113const makeCacheable = (realFn) => {
114 /**
115 * Defines the cache item type used by this module.
116 * @template T
117 * @typedef {Map<string, T>} CacheItem
118 */
119 /** @type {WeakMap<AssociatedObjectForCache, CacheItem<T>>} */
120 const cache = new WeakMap();
121
122 /**
123 * Returns cache item.
124 * @param {AssociatedObjectForCache} associatedObjectForCache an object to which the cache will be attached
125 * @returns {CacheItem<T>} cache item
126 */
127 const getCache = (associatedObjectForCache) => {
128 const entry = cache.get(associatedObjectForCache);
129 if (entry !== undefined) return entry;
130 /** @type {Map<string, T>} */
131 const map = new Map();
132 cache.set(associatedObjectForCache, map);
133 return map;
134 };
135
136 /** @type {MakeCacheableResult<T> & { bindCache: BindCache<T> }} */
137 const fn = (str, associatedObjectForCache) => {
138 if (!associatedObjectForCache) return realFn(str);
139 const cache = getCache(associatedObjectForCache);
140 const entry = cache.get(str);
141 if (entry !== undefined) return entry;
142 const result = realFn(str);
143 cache.set(str, result);
144 return result;
145 };
146
147 /** @type {BindCache<T>} */
148 fn.bindCache = (associatedObjectForCache) => {
149 const cache = getCache(associatedObjectForCache);
150 /**
151 * Returns value.
152 * @param {string} str string
153 * @returns {T} value
154 */
155 return (str) => {
156 const entry = cache.get(str);
157 if (entry !== undefined) return entry;
158 const result = realFn(str);
159 cache.set(str, result);
160 return result;
161 };
162 };
163
164 return fn;
165};
166
167/** @typedef {(context: string, value: string, associatedObjectForCache?: AssociatedObjectForCache) => string} MakeCacheableWithContextResult */
168/** @typedef {(context: string, value: string) => string} BindCacheForContextResultFn */
169/** @typedef {(value: string) => string} BindContextCacheForContextResultFn */
170/** @typedef {(associatedObjectForCache?: AssociatedObjectForCache) => BindCacheForContextResultFn} BindCacheForContext */
171/** @typedef {(value: string, associatedObjectForCache?: AssociatedObjectForCache) => BindContextCacheForContextResultFn} BindContextCacheForContext */
172
173/**
174 * Creates cacheable with context.
175 * @param {(context: string, identifier: string) => string} fn function
176 * @returns {MakeCacheableWithContextResult & { bindCache: BindCacheForContext, bindContextCache: BindContextCacheForContext }} cacheable function with context
177 */
178const makeCacheableWithContext = (fn) => {
179 /** @typedef {Map<string, Map<string, string>>} InnerCache */
180 /** @type {WeakMap<AssociatedObjectForCache, InnerCache>} */
181 const cache = new WeakMap();
182
183 /** @type {MakeCacheableWithContextResult & { bindCache: BindCacheForContext, bindContextCache: BindContextCacheForContext }} */
184 const cachedFn = (context, identifier, associatedObjectForCache) => {
185 if (!associatedObjectForCache) return fn(context, identifier);
186
187 let innerCache = cache.get(associatedObjectForCache);
188 if (innerCache === undefined) {
189 innerCache = new Map();
190 cache.set(associatedObjectForCache, innerCache);
191 }
192
193 /** @type {undefined | string} */
194 let cachedResult;
195 let innerSubCache = innerCache.get(context);
196 if (innerSubCache === undefined) {
197 innerCache.set(context, (innerSubCache = new Map()));
198 } else {
199 cachedResult = innerSubCache.get(identifier);
200 }
201
202 if (cachedResult !== undefined) {
203 return cachedResult;
204 }
205 const result = fn(context, identifier);
206 innerSubCache.set(identifier, result);
207 return result;
208 };
209
210 /** @type {BindCacheForContext} */
211 cachedFn.bindCache = (associatedObjectForCache) => {
212 /** @type {undefined | InnerCache} */
213 let innerCache;
214 if (associatedObjectForCache) {
215 innerCache = cache.get(associatedObjectForCache);
216 if (innerCache === undefined) {
217 innerCache = new Map();
218 cache.set(associatedObjectForCache, innerCache);
219 }
220 } else {
221 innerCache = new Map();
222 }
223
224 /**
225 * Returns the returned relative path.
226 * @param {string} context context used to create relative path
227 * @param {string} identifier identifier used to create relative path
228 * @returns {string} the returned relative path
229 */
230 const boundFn = (context, identifier) => {
231 /** @type {undefined | string} */
232 let cachedResult;
233 let innerSubCache = innerCache.get(context);
234 if (innerSubCache === undefined) {
235 innerCache.set(context, (innerSubCache = new Map()));
236 } else {
237 cachedResult = innerSubCache.get(identifier);
238 }
239
240 if (cachedResult !== undefined) {
241 return cachedResult;
242 }
243 const result = fn(context, identifier);
244 innerSubCache.set(identifier, result);
245 return result;
246 };
247
248 return boundFn;
249 };
250
251 /** @type {BindContextCacheForContext} */
252 cachedFn.bindContextCache = (context, associatedObjectForCache) => {
253 /** @type {undefined | Map<string, string>} */
254 let innerSubCache;
255 if (associatedObjectForCache) {
256 let innerCache = cache.get(associatedObjectForCache);
257 if (innerCache === undefined) {
258 innerCache = new Map();
259 cache.set(associatedObjectForCache, innerCache);
260 }
261
262 innerSubCache = innerCache.get(context);
263 if (innerSubCache === undefined) {
264 innerCache.set(context, (innerSubCache = new Map()));
265 }
266 } else {
267 innerSubCache = new Map();
268 }
269
270 /**
271 * Returns the returned relative path.
272 * @param {string} identifier identifier used to create relative path
273 * @returns {string} the returned relative path
274 */
275 const boundFn = (identifier) => {
276 const cachedResult = innerSubCache.get(identifier);
277 if (cachedResult !== undefined) {
278 return cachedResult;
279 }
280 const result = fn(context, identifier);
281 innerSubCache.set(identifier, result);
282 return result;
283 };
284
285 return boundFn;
286 };
287
288 return cachedFn;
289};
290
291/**
292 * Make paths relative.
293 * @param {string} context context for relative path
294 * @param {string} identifier identifier for path
295 * @returns {string} a converted relative path
296 */
297const _makePathsRelative = (context, identifier) =>
298 identifier
299 .split(SEGMENTS_SPLIT_REGEXP)
300 .map((str) => absoluteToRequest(context, str))
301 .join("");
302
303/**
304 * Make paths absolute.
305 * @param {string} context context for relative path
306 * @param {string} identifier identifier for path
307 * @returns {string} a converted relative path
308 */
309const _makePathsAbsolute = (context, identifier) =>
310 identifier
311 .split(SEGMENTS_SPLIT_REGEXP)
312 .map((str) => requestToAbsolute(context, str))
313 .join("");
314
315/**
316 * Returns a new request string avoiding absolute paths when possible.
317 * @param {string} context absolute context path
318 * @param {string} request any request string may containing absolute paths, query string, etc.
319 * @returns {string} a new request string avoiding absolute paths when possible
320 */
321const _contextify = (context, request) =>
322 request
323 .split("!")
324 .map((r) => absoluteToRequest(context, r))
325 .join("!");
326
327const contextify = makeCacheableWithContext(_contextify);
328
329/**
330 * Returns a new request string using absolute paths when possible.
331 * @param {string} context absolute context path
332 * @param {string} request any request string
333 * @returns {string} a new request string using absolute paths when possible
334 */
335const _absolutify = (context, request) =>
336 request
337 .split("!")
338 .map((r) => requestToAbsolute(context, r))
339 .join("!");
340
341const absolutify = makeCacheableWithContext(_absolutify);
342
343const PATH_QUERY_FRAGMENT_REGEXP =
344 /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
345const PATH_QUERY_REGEXP = /^((?:\0.|[^?\0])*)(\?.*)?$/;
346const ZERO_ESCAPE_REGEXP = /\0(.)/g;
347
348/** @typedef {{ resource: string, path: string, query: string, fragment: string }} ParsedResource */
349/** @typedef {{ resource: string, path: string, query: string }} ParsedResourceWithoutFragment */
350
351/**
352 * Returns parsed parts.
353 * @param {string} str the path with query and fragment
354 * @returns {ParsedResource} parsed parts
355 */
356const _parseResource = (str) => {
357 const firstEscape = str.indexOf("\0");
358
359 // Handle `\0`
360 if (firstEscape !== -1) {
361 const match =
362 /** @type {[string, string, string | undefined, string | undefined]} */
363 (/** @type {unknown} */ (PATH_QUERY_FRAGMENT_REGEXP.exec(str)));
364
365 return {
366 resource: str,
367 path: match[1].replace(ZERO_ESCAPE_REGEXP, "$1"),
368 query: match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "",
369 fragment: match[3] || ""
370 };
371 }
372
373 /** @type {ParsedResource} */
374 const result = { resource: str, path: "", query: "", fragment: "" };
375 const queryStart = str.indexOf("?");
376 const fragmentStart = str.indexOf("#");
377
378 if (fragmentStart < 0) {
379 if (queryStart < 0) {
380 result.path = result.resource;
381
382 // No fragment, no query
383 return result;
384 }
385
386 result.path = str.slice(0, queryStart);
387 result.query = str.slice(queryStart);
388
389 // Query, no fragment
390 return result;
391 }
392
393 if (queryStart < 0 || fragmentStart < queryStart) {
394 result.path = str.slice(0, fragmentStart);
395 result.fragment = str.slice(fragmentStart);
396
397 // Fragment, no query
398 return result;
399 }
400
401 result.path = str.slice(0, queryStart);
402 result.query = str.slice(queryStart, fragmentStart);
403 result.fragment = str.slice(fragmentStart);
404
405 // Query and fragment
406 return result;
407};
408
409/**
410 * Parse resource, skips fragment part
411 * @param {string} str the path with query and fragment
412 * @returns {ParsedResourceWithoutFragment} parsed parts
413 */
414const _parseResourceWithoutFragment = (str) => {
415 const firstEscape = str.indexOf("\0");
416
417 // Handle `\0`
418 if (firstEscape !== -1) {
419 const match =
420 /** @type {[string, string, string | undefined]} */
421 (/** @type {unknown} */ (PATH_QUERY_REGEXP.exec(str)));
422
423 return {
424 resource: str,
425 path: match[1].replace(ZERO_ESCAPE_REGEXP, "$1"),
426 query: match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : ""
427 };
428 }
429
430 /** @type {ParsedResourceWithoutFragment} */
431 const result = { resource: str, path: "", query: "" };
432 const queryStart = str.indexOf("?");
433
434 if (queryStart < 0) {
435 result.path = result.resource;
436
437 // No query
438 return result;
439 }
440
441 result.path = str.slice(0, queryStart);
442 result.query = str.slice(queryStart);
443
444 // Query
445 return result;
446};
447
448/**
449 * Returns repeated ../ to leave the directory of the provided filename to be back on output dir.
450 * @param {string} filename the filename which should be undone
451 * @param {string} outputPath the output path that is restored (only relevant when filename contains "..")
452 * @param {boolean} enforceRelative true returns ./ for empty paths
453 * @returns {string} repeated ../ to leave the directory of the provided filename to be back on output dir
454 */
455const getUndoPath = (filename, outputPath, enforceRelative) => {
456 let depth = -1;
457 let append = "";
458 outputPath = outputPath.replace(/[\\/]$/, "");
459 for (const part of filename.split(/[/\\]+/)) {
460 if (part === "..") {
461 if (depth > -1) {
462 depth--;
463 } else {
464 const i = outputPath.lastIndexOf("/");
465 const j = outputPath.lastIndexOf("\\");
466 const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j);
467 if (pos < 0) return `${outputPath}/`;
468 append = `${outputPath.slice(pos + 1)}/${append}`;
469 outputPath = outputPath.slice(0, pos);
470 }
471 } else if (part !== ".") {
472 depth++;
473 }
474 }
475 return depth > 0
476 ? `${"../".repeat(depth)}${append}`
477 : enforceRelative
478 ? `./${append}`
479 : append;
480};
481
482const HASH_REGEXP = /(?<!\0)#/g;
483
484/**
485 * Escape `#` characters that appear inside a path request's directory portion
486 * with the `\0#` escape recognized by enhanced-resolve, so a project located at
487 * a path like `/home/user/proj#1/` (or `./proj#1/`) resolves correctly. Applies
488 * to absolute paths (Unix or Windows) and relative paths (starting with `./` or
489 * `../`). Only triggers when a query string is present, because that is the case
490 * where the resolver's parseIdentifier fails (without a `?`, the resolver
491 * handles directory `#` via its own fallback). A `#` after the last path
492 * separator is left alone so that explicit fragment requests like
493 * `/abs/path/file.js#fragment` still behave the same. Bare module specifiers
494 * are not touched. Already-escaped `\0#` sequences are preserved so the
495 * explicit opt-out remains stable.
496 * @param {string} request request to potentially escape
497 * @returns {string} request with directory `#` characters escaped
498 */
499const escapeHashInPathRequest = (request) => {
500 if (request.length === 0) return request;
501 const queryStart = request.indexOf("?");
502 if (queryStart < 0) return request;
503 const hashStart = request.indexOf("#");
504 if (hashStart < 0 || hashStart >= queryStart) return request;
505 const c0 = request.charCodeAt(0);
506 const isAbsolute =
507 c0 === 47 /* "/" */ || WINDOWS_ABS_PATH_REGEXP.test(request);
508 let isRelative = false;
509 if (!isAbsolute && c0 === 46 /* "." */) {
510 const c1 = request.charCodeAt(1);
511 if (c1 === 47 || c1 === 92 /* "/" or "\" */) {
512 isRelative = true;
513 } else if (c1 === 46 /* "." */) {
514 const c2 = request.charCodeAt(2);
515 if (c2 === 47 || c2 === 92) isRelative = true;
516 }
517 }
518 if (!isAbsolute && !isRelative) return request;
519 const lastSep = Math.max(
520 request.lastIndexOf("/", queryStart - 1),
521 request.lastIndexOf("\\", queryStart - 1)
522 );
523 if (hashStart >= lastSep) return request;
524 const pathPart = request.slice(0, lastSep);
525 return pathPart.replace(HASH_REGEXP, "\0#") + request.slice(lastSep);
526};
527
528module.exports.absolutify = absolutify;
529module.exports.contextify = contextify;
530module.exports.escapeHashInPathRequest = escapeHashInPathRequest;
531module.exports.getUndoPath = getUndoPath;
532module.exports.makeCacheable = makeCacheable;
533module.exports.makePathsAbsolute = makeCacheableWithContext(_makePathsAbsolute);
534module.exports.makePathsRelative = makeCacheableWithContext(_makePathsRelative);
535module.exports.parseResource = makeCacheable(_parseResource);
536module.exports.parseResourceWithoutFragment = makeCacheable(
537 _parseResourceWithoutFragment
538);
Note: See TracBrowser for help on using the repository browser.