diff options
Diffstat (limited to 'node_modules/webpack/lib/util/identifier.js')
| -rw-r--r-- | node_modules/webpack/lib/util/identifier.js | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/node_modules/webpack/lib/util/identifier.js b/node_modules/webpack/lib/util/identifier.js new file mode 100644 index 00000000..ea5ec2ea --- /dev/null +++ b/node_modules/webpack/lib/util/identifier.js @@ -0,0 +1,381 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php +*/ + +"use strict"; + +const path = require("path"); + +const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]/; +const SEGMENTS_SPLIT_REGEXP = /([|!])/; +const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g; + +/** + * @typedef {Object} MakeRelativePathsCache + * @property {Map<string, Map<string, string>>=} relativePaths + */ + +/** + * @param {string} relativePath relative path + * @returns {string} request + */ +const relativePathToRequest = relativePath => { + if (relativePath === "") return "./."; + if (relativePath === "..") return "../."; + if (relativePath.startsWith("../")) return relativePath; + return `./${relativePath}`; +}; + +/** + * @param {string} context context for relative path + * @param {string} maybeAbsolutePath path to make relative + * @returns {string} relative path in request style + */ +const absoluteToRequest = (context, maybeAbsolutePath) => { + if (maybeAbsolutePath[0] === "/") { + if ( + maybeAbsolutePath.length > 1 && + maybeAbsolutePath[maybeAbsolutePath.length - 1] === "/" + ) { + // this 'path' is actually a regexp generated by dynamic requires. + // Don't treat it as an absolute path. + return maybeAbsolutePath; + } + + const querySplitPos = maybeAbsolutePath.indexOf("?"); + let resource = + querySplitPos === -1 + ? maybeAbsolutePath + : maybeAbsolutePath.slice(0, querySplitPos); + resource = relativePathToRequest(path.posix.relative(context, resource)); + return querySplitPos === -1 + ? resource + : resource + maybeAbsolutePath.slice(querySplitPos); + } + + if (WINDOWS_ABS_PATH_REGEXP.test(maybeAbsolutePath)) { + const querySplitPos = maybeAbsolutePath.indexOf("?"); + let resource = + querySplitPos === -1 + ? maybeAbsolutePath + : maybeAbsolutePath.slice(0, querySplitPos); + resource = path.win32.relative(context, resource); + if (!WINDOWS_ABS_PATH_REGEXP.test(resource)) { + resource = relativePathToRequest( + resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/") + ); + } + return querySplitPos === -1 + ? resource + : resource + maybeAbsolutePath.slice(querySplitPos); + } + + // not an absolute path + return maybeAbsolutePath; +}; + +/** + * @param {string} context context for relative path + * @param {string} relativePath path + * @returns {string} absolute path + */ +const requestToAbsolute = (context, relativePath) => { + if (relativePath.startsWith("./") || relativePath.startsWith("../")) + return path.join(context, relativePath); + return relativePath; +}; + +const makeCacheable = realFn => { + /** @type {WeakMap<object, Map<string, ParsedResource>>} */ + const cache = new WeakMap(); + + const getCache = associatedObjectForCache => { + const entry = cache.get(associatedObjectForCache); + if (entry !== undefined) return entry; + /** @type {Map<string, ParsedResource>} */ + const map = new Map(); + cache.set(associatedObjectForCache, map); + return map; + }; + + /** + * @param {string} str the path with query and fragment + * @param {Object=} associatedObjectForCache an object to which the cache will be attached + * @returns {ParsedResource} parsed parts + */ + const fn = (str, associatedObjectForCache) => { + if (!associatedObjectForCache) return realFn(str); + const cache = getCache(associatedObjectForCache); + const entry = cache.get(str); + if (entry !== undefined) return entry; + const result = realFn(str); + cache.set(str, result); + return result; + }; + + fn.bindCache = associatedObjectForCache => { + const cache = getCache(associatedObjectForCache); + return str => { + const entry = cache.get(str); + if (entry !== undefined) return entry; + const result = realFn(str); + cache.set(str, result); + return result; + }; + }; + + return fn; +}; + +const makeCacheableWithContext = fn => { + /** @type {WeakMap<object, Map<string, Map<string, string>>>} */ + const cache = new WeakMap(); + + /** + * @param {string} context context used to create relative path + * @param {string} identifier identifier used to create relative path + * @param {Object=} associatedObjectForCache an object to which the cache will be attached + * @returns {string} the returned relative path + */ + const cachedFn = (context, identifier, associatedObjectForCache) => { + if (!associatedObjectForCache) return fn(context, identifier); + + let innerCache = cache.get(associatedObjectForCache); + if (innerCache === undefined) { + innerCache = new Map(); + cache.set(associatedObjectForCache, innerCache); + } + + let cachedResult; + let innerSubCache = innerCache.get(context); + if (innerSubCache === undefined) { + innerCache.set(context, (innerSubCache = new Map())); + } else { + cachedResult = innerSubCache.get(identifier); + } + + if (cachedResult !== undefined) { + return cachedResult; + } else { + const result = fn(context, identifier); + innerSubCache.set(identifier, result); + return result; + } + }; + + /** + * @param {Object=} associatedObjectForCache an object to which the cache will be attached + * @returns {function(string, string): string} cached function + */ + cachedFn.bindCache = associatedObjectForCache => { + let innerCache; + if (associatedObjectForCache) { + innerCache = cache.get(associatedObjectForCache); + if (innerCache === undefined) { + innerCache = new Map(); + cache.set(associatedObjectForCache, innerCache); + } + } else { + innerCache = new Map(); + } + + /** + * @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) => { + let cachedResult; + let innerSubCache = innerCache.get(context); + if (innerSubCache === undefined) { + innerCache.set(context, (innerSubCache = new Map())); + } else { + cachedResult = innerSubCache.get(identifier); + } + + if (cachedResult !== undefined) { + return cachedResult; + } else { + const result = fn(context, identifier); + innerSubCache.set(identifier, result); + return result; + } + }; + + return boundFn; + }; + + /** + * @param {string} context context used to create relative path + * @param {Object=} associatedObjectForCache an object to which the cache will be attached + * @returns {function(string): string} cached function + */ + cachedFn.bindContextCache = (context, associatedObjectForCache) => { + let innerSubCache; + if (associatedObjectForCache) { + let innerCache = cache.get(associatedObjectForCache); + if (innerCache === undefined) { + innerCache = new Map(); + cache.set(associatedObjectForCache, innerCache); + } + + innerSubCache = innerCache.get(context); + if (innerSubCache === undefined) { + innerCache.set(context, (innerSubCache = new Map())); + } + } else { + innerSubCache = new Map(); + } + + /** + * @param {string} identifier identifier used to create relative path + * @returns {string} the returned relative path + */ + const boundFn = identifier => { + const cachedResult = innerSubCache.get(identifier); + if (cachedResult !== undefined) { + return cachedResult; + } else { + const result = fn(context, identifier); + innerSubCache.set(identifier, result); + return result; + } + }; + + return boundFn; + }; + + return cachedFn; +}; + +/** + * + * @param {string} context context for relative path + * @param {string} identifier identifier for path + * @returns {string} a converted relative path + */ +const _makePathsRelative = (context, identifier) => { + return identifier + .split(SEGMENTS_SPLIT_REGEXP) + .map(str => absoluteToRequest(context, str)) + .join(""); +}; + +exports.makePathsRelative = makeCacheableWithContext(_makePathsRelative); + +/** + * + * @param {string} context context for relative path + * @param {string} identifier identifier for path + * @returns {string} a converted relative path + */ +const _makePathsAbsolute = (context, identifier) => { + return identifier + .split(SEGMENTS_SPLIT_REGEXP) + .map(str => requestToAbsolute(context, str)) + .join(""); +}; + +exports.makePathsAbsolute = makeCacheableWithContext(_makePathsAbsolute); + +/** + * @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 = (context, request) => { + return request + .split("!") + .map(r => absoluteToRequest(context, r)) + .join("!"); +}; + +const contextify = makeCacheableWithContext(_contextify); +exports.contextify = contextify; + +/** + * @param {string} context absolute context path + * @param {string} request any request string + * @returns {string} a new request string using absolute paths when possible + */ +const _absolutify = (context, request) => { + return request + .split("!") + .map(r => requestToAbsolute(context, r)) + .join("!"); +}; + +const absolutify = makeCacheableWithContext(_absolutify); +exports.absolutify = absolutify; + +const PATH_QUERY_FRAGMENT_REGEXP = + /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; +const PATH_QUERY_REGEXP = /^((?:\0.|[^?\0])*)(\?.*)?$/; + +/** @typedef {{ resource: string, path: string, query: string, fragment: string }} ParsedResource */ +/** @typedef {{ resource: string, path: string, query: string }} ParsedResourceWithoutFragment */ + +/** + * @param {string} str the path with query and fragment + * @returns {ParsedResource} parsed parts + */ +const _parseResource = str => { + const match = 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] || "" + }; +}; +exports.parseResource = makeCacheable(_parseResource); + +/** + * Parse resource, skips fragment part + * @param {string} str the path with query and fragment + * @returns {ParsedResourceWithoutFragment} parsed parts + */ +const _parseResourceWithoutFragment = str => { + const match = PATH_QUERY_REGEXP.exec(str); + return { + resource: str, + path: match[1].replace(/\0(.)/g, "$1"), + query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "" + }; +}; +exports.parseResourceWithoutFragment = makeCacheable( + _parseResourceWithoutFragment +); + +/** + * @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 + * @returns {string} repeated ../ to leave the directory of the provided filename to be back on output dir + */ +exports.getUndoPath = (filename, outputPath, enforceRelative) => { + let depth = -1; + let append = ""; + outputPath = outputPath.replace(/[\\/]$/, ""); + for (const part of filename.split(/[/\\]+/)) { + if (part === "..") { + if (depth > -1) { + depth--; + } else { + const i = outputPath.lastIndexOf("/"); + const j = outputPath.lastIndexOf("\\"); + const pos = i < 0 ? j : j < 0 ? i : Math.max(i, j); + if (pos < 0) return outputPath + "/"; + append = outputPath.slice(pos + 1) + "/" + append; + outputPath = outputPath.slice(0, pos); + } + } else if (part !== ".") { + depth++; + } + } + return depth > 0 + ? `${"../".repeat(depth)}${append}` + : enforceRelative + ? `./${append}` + : append; +}; |
