| | |
| | | |
| | | const path = require("path"); |
| | | |
| | | const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]/; |
| | | const WINDOWS_ABS_PATH_REGEXP = /^[a-z]:[\\/]/i; |
| | | const SEGMENTS_SPLIT_REGEXP = /([|!])/; |
| | | const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g; |
| | | |
| | | /** |
| | | * Relative path to request. |
| | | * @param {string} relativePath relative path |
| | | * @returns {string} request |
| | | */ |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Absolute to request. |
| | | * @param {string} context context for relative path |
| | | * @param {string} maybeAbsolutePath path to make relative |
| | | * @returns {string} relative path in request style |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Request to absolute. |
| | | * @param {string} context context for relative path |
| | | * @param {string} relativePath path |
| | | * @returns {string} absolute path |
| | |
| | | /** @typedef {EXPECTED_OBJECT} AssociatedObjectForCache */ |
| | | |
| | | /** |
| | | * Defines the make cacheable result type used by this module. |
| | | * @template T |
| | | * @typedef {(value: string, cache?: AssociatedObjectForCache) => T} MakeCacheableResult |
| | | */ |
| | | |
| | | /** |
| | | * Defines the bind cache result fn type used by this module. |
| | | * @template T |
| | | * @typedef {(value: string) => T} BindCacheResultFn |
| | | */ |
| | | |
| | | /** |
| | | * Defines the bind cache type used by this module. |
| | | * @template T |
| | | * @typedef {(cache: AssociatedObjectForCache) => BindCacheResultFn<T>} BindCache |
| | | */ |
| | | |
| | | /** |
| | | * Returns } cacheable function. |
| | | * @template T |
| | | * @param {((value: string) => T)} realFn real function |
| | | * @returns {MakeCacheableResult<T> & { bindCache: BindCache<T> }} cacheable function |
| | | */ |
| | | const makeCacheable = (realFn) => { |
| | | /** |
| | | * Defines the cache item type used by this module. |
| | | * @template T |
| | | * @typedef {Map<string, T>} CacheItem |
| | | */ |
| | |
| | | const cache = new WeakMap(); |
| | | |
| | | /** |
| | | * Returns cache item. |
| | | * @param {AssociatedObjectForCache} associatedObjectForCache an object to which the cache will be attached |
| | | * @returns {CacheItem<T>} cache item |
| | | */ |
| | |
| | | fn.bindCache = (associatedObjectForCache) => { |
| | | const cache = getCache(associatedObjectForCache); |
| | | /** |
| | | * Returns value. |
| | | * @param {string} str string |
| | | * @returns {T} value |
| | | */ |
| | |
| | | /** @typedef {(value: string, associatedObjectForCache?: AssociatedObjectForCache) => BindContextCacheForContextResultFn} BindContextCacheForContext */ |
| | | |
| | | /** |
| | | * Creates cacheable with context. |
| | | * @param {(context: string, identifier: string) => string} fn function |
| | | * @returns {MakeCacheableWithContextResult & { bindCache: BindCacheForContext, bindContextCache: BindContextCacheForContext }} cacheable function with context |
| | | */ |
| | | const makeCacheableWithContext = (fn) => { |
| | | /** @type {WeakMap<AssociatedObjectForCache, Map<string, Map<string, string>>>} */ |
| | | /** @typedef {Map<string, Map<string, string>>} InnerCache */ |
| | | /** @type {WeakMap<AssociatedObjectForCache, InnerCache>} */ |
| | | const cache = new WeakMap(); |
| | | |
| | | /** @type {MakeCacheableWithContextResult & { bindCache: BindCacheForContext, bindContextCache: BindContextCacheForContext }} */ |
| | |
| | | cache.set(associatedObjectForCache, innerCache); |
| | | } |
| | | |
| | | /** @type {undefined | string} */ |
| | | let cachedResult; |
| | | let innerSubCache = innerCache.get(context); |
| | | if (innerSubCache === undefined) { |
| | |
| | | |
| | | /** @type {BindCacheForContext} */ |
| | | cachedFn.bindCache = (associatedObjectForCache) => { |
| | | /** @type {undefined | InnerCache} */ |
| | | let innerCache; |
| | | if (associatedObjectForCache) { |
| | | innerCache = cache.get(associatedObjectForCache); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the returned relative path. |
| | | * @param {string} context context used to create relative path |
| | | * @param {string} identifier identifier used to create relative path |
| | | * @returns {string} the returned relative path |
| | | */ |
| | | const boundFn = (context, identifier) => { |
| | | /** @type {undefined | string} */ |
| | | let cachedResult; |
| | | let innerSubCache = innerCache.get(context); |
| | | if (innerSubCache === undefined) { |
| | |
| | | |
| | | /** @type {BindContextCacheForContext} */ |
| | | cachedFn.bindContextCache = (context, associatedObjectForCache) => { |
| | | /** @type {undefined | Map<string, string>} */ |
| | | let innerSubCache; |
| | | if (associatedObjectForCache) { |
| | | let innerCache = cache.get(associatedObjectForCache); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Returns the returned relative path. |
| | | * @param {string} identifier identifier used to create relative path |
| | | * @returns {string} the returned relative path |
| | | */ |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Make paths relative. |
| | | * @param {string} context context for relative path |
| | | * @param {string} identifier identifier for path |
| | | * @returns {string} a converted relative path |
| | |
| | | .join(""); |
| | | |
| | | /** |
| | | * Make paths absolute. |
| | | * @param {string} context context for relative path |
| | | * @param {string} identifier identifier for path |
| | | * @returns {string} a converted relative path |
| | |
| | | .join(""); |
| | | |
| | | /** |
| | | * Returns a new request string avoiding absolute paths when possible. |
| | | * @param {string} context absolute context path |
| | | * @param {string} request any request string may containing absolute paths, query string, etc. |
| | | * @returns {string} a new request string avoiding absolute paths when possible |
| | |
| | | const contextify = makeCacheableWithContext(_contextify); |
| | | |
| | | /** |
| | | * Returns a new request string using absolute paths when possible. |
| | | * @param {string} context absolute context path |
| | | * @param {string} request any request string |
| | | * @returns {string} a new request string using absolute paths when possible |
| | |
| | | const PATH_QUERY_FRAGMENT_REGEXP = |
| | | /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; |
| | | const PATH_QUERY_REGEXP = /^((?:\0.|[^?\0])*)(\?.*)?$/; |
| | | const ZERO_ESCAPE_REGEXP = /\0(.)/g; |
| | | |
| | | /** @typedef {{ resource: string, path: string, query: string, fragment: string }} ParsedResource */ |
| | | /** @typedef {{ resource: string, path: string, query: string }} ParsedResourceWithoutFragment */ |
| | | |
| | | /** |
| | | * Returns parsed parts. |
| | | * @param {string} str the path with query and fragment |
| | | * @returns {ParsedResource} parsed parts |
| | | */ |
| | | const _parseResource = (str) => { |
| | | const match = |
| | | /** @type {[string, string, string | undefined, string | undefined]} */ |
| | | (/** @type {unknown} */ (PATH_QUERY_FRAGMENT_REGEXP.exec(str))); |
| | | return { |
| | | resource: str, |
| | | path: match[1].replace(/\0(.)/g, "$1"), |
| | | query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "", |
| | | fragment: match[3] || "" |
| | | }; |
| | | const firstEscape = str.indexOf("\0"); |
| | | |
| | | // Handle `\0` |
| | | if (firstEscape !== -1) { |
| | | const match = |
| | | /** @type {[string, string, string | undefined, string | undefined]} */ |
| | | (/** @type {unknown} */ (PATH_QUERY_FRAGMENT_REGEXP.exec(str))); |
| | | |
| | | return { |
| | | resource: str, |
| | | path: match[1].replace(ZERO_ESCAPE_REGEXP, "$1"), |
| | | query: match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "", |
| | | fragment: match[3] || "" |
| | | }; |
| | | } |
| | | |
| | | /** @type {ParsedResource} */ |
| | | const result = { resource: str, path: "", query: "", fragment: "" }; |
| | | const queryStart = str.indexOf("?"); |
| | | const fragmentStart = str.indexOf("#"); |
| | | |
| | | if (fragmentStart < 0) { |
| | | if (queryStart < 0) { |
| | | result.path = result.resource; |
| | | |
| | | // No fragment, no query |
| | | return result; |
| | | } |
| | | |
| | | result.path = str.slice(0, queryStart); |
| | | result.query = str.slice(queryStart); |
| | | |
| | | // Query, no fragment |
| | | return result; |
| | | } |
| | | |
| | | if (queryStart < 0 || fragmentStart < queryStart) { |
| | | result.path = str.slice(0, fragmentStart); |
| | | result.fragment = str.slice(fragmentStart); |
| | | |
| | | // Fragment, no query |
| | | return result; |
| | | } |
| | | |
| | | result.path = str.slice(0, queryStart); |
| | | result.query = str.slice(queryStart, fragmentStart); |
| | | result.fragment = str.slice(fragmentStart); |
| | | |
| | | // Query and fragment |
| | | return result; |
| | | }; |
| | | |
| | | /** |
| | |
| | | * @returns {ParsedResourceWithoutFragment} parsed parts |
| | | */ |
| | | const _parseResourceWithoutFragment = (str) => { |
| | | const match = |
| | | /** @type {[string, string, string | undefined]} */ |
| | | (/** @type {unknown} */ (PATH_QUERY_REGEXP.exec(str))); |
| | | return { |
| | | resource: str, |
| | | path: match[1].replace(/\0(.)/g, "$1"), |
| | | query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "" |
| | | }; |
| | | const firstEscape = str.indexOf("\0"); |
| | | |
| | | // Handle `\0` |
| | | if (firstEscape !== -1) { |
| | | const match = |
| | | /** @type {[string, string, string | undefined]} */ |
| | | (/** @type {unknown} */ (PATH_QUERY_REGEXP.exec(str))); |
| | | |
| | | return { |
| | | resource: str, |
| | | path: match[1].replace(ZERO_ESCAPE_REGEXP, "$1"), |
| | | query: match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "" |
| | | }; |
| | | } |
| | | |
| | | /** @type {ParsedResourceWithoutFragment} */ |
| | | const result = { resource: str, path: "", query: "" }; |
| | | const queryStart = str.indexOf("?"); |
| | | |
| | | if (queryStart < 0) { |
| | | result.path = result.resource; |
| | | |
| | | // No query |
| | | return result; |
| | | } |
| | | |
| | | result.path = str.slice(0, queryStart); |
| | | result.query = str.slice(queryStart); |
| | | |
| | | // Query |
| | | return result; |
| | | }; |
| | | |
| | | /** |
| | | * Returns repeated ../ to leave the directory of the provided filename to be back on output dir. |
| | | * @param {string} filename the filename which should be undone |
| | | * @param {string} outputPath the output path that is restored (only relevant when filename contains "..") |
| | | * @param {boolean} enforceRelative true returns ./ for empty paths |