| [9af201e] | 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 path = require("path");
|
|---|
| 9 |
|
|---|
| 10 | const CHAR_HASH = "#".charCodeAt(0);
|
|---|
| 11 | const CHAR_SLASH = "/".charCodeAt(0);
|
|---|
| 12 | const CHAR_BACKSLASH = "\\".charCodeAt(0);
|
|---|
| 13 | const CHAR_A = "A".charCodeAt(0);
|
|---|
| 14 | const CHAR_Z = "Z".charCodeAt(0);
|
|---|
| 15 | const CHAR_LOWER_A = "a".charCodeAt(0);
|
|---|
| 16 | const CHAR_LOWER_Z = "z".charCodeAt(0);
|
|---|
| 17 | const CHAR_DOT = ".".charCodeAt(0);
|
|---|
| 18 | const CHAR_COLON = ":".charCodeAt(0);
|
|---|
| 19 | const CHAR_QUESTION = "?".charCodeAt(0);
|
|---|
| 20 |
|
|---|
| 21 | const posixNormalize = path.posix.normalize;
|
|---|
| 22 | const winNormalize = path.win32.normalize;
|
|---|
| 23 |
|
|---|
| 24 | /**
|
|---|
| 25 | * @enum {number}
|
|---|
| 26 | */
|
|---|
| 27 | const PathType = Object.freeze({
|
|---|
| 28 | Empty: 0,
|
|---|
| 29 | Normal: 1,
|
|---|
| 30 | Relative: 2,
|
|---|
| 31 | AbsoluteWin: 3,
|
|---|
| 32 | AbsolutePosix: 4,
|
|---|
| 33 | Internal: 5,
|
|---|
| 34 | });
|
|---|
| 35 |
|
|---|
| 36 | const deprecatedInvalidSegmentRegEx =
|
|---|
| 37 | /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))(\\|\/|$)/i;
|
|---|
| 38 |
|
|---|
| 39 | const invalidSegmentRegEx =
|
|---|
| 40 | /(^|\\|\/)((\.|%2e)(\.|%2e)?|(n|%6e|%4e)(o|%6f|%4f)(d|%64|%44)(e|%65|%45)(_|%5f)(m|%6d|%4d)(o|%6f|%4f)(d|%64|%44)(u|%75|%55)(l|%6c|%4c)(e|%65|%45)(s|%73|%53))?(\\|\/|$)/i;
|
|---|
| 41 |
|
|---|
| 42 | /**
|
|---|
| 43 | * @param {string} maybePath a path known to start with `\\`
|
|---|
| 44 | * @returns {PathType} AbsoluteWin for `\\?\…` / `\\.\…`, otherwise Normal
|
|---|
| 45 | */
|
|---|
| 46 | const getDosDeviceType = (maybePath) => {
|
|---|
| 47 | if (maybePath.length >= 4 && maybePath.charCodeAt(3) === CHAR_BACKSLASH) {
|
|---|
| 48 | const c2 = maybePath.charCodeAt(2);
|
|---|
| 49 | if (c2 === CHAR_QUESTION || c2 === CHAR_DOT) {
|
|---|
| 50 | return PathType.AbsoluteWin;
|
|---|
| 51 | }
|
|---|
| 52 | }
|
|---|
| 53 | return PathType.Normal;
|
|---|
| 54 | };
|
|---|
| 55 |
|
|---|
| 56 | /**
|
|---|
| 57 | * @param {string} maybePath a path
|
|---|
| 58 | * @returns {PathType} type of path
|
|---|
| 59 | */
|
|---|
| 60 | const getType = (maybePath) => {
|
|---|
| 61 | switch (maybePath.length) {
|
|---|
| 62 | case 0:
|
|---|
| 63 | return PathType.Empty;
|
|---|
| 64 | case 1: {
|
|---|
| 65 | const c0 = maybePath.charCodeAt(0);
|
|---|
| 66 | switch (c0) {
|
|---|
| 67 | case CHAR_DOT:
|
|---|
| 68 | return PathType.Relative;
|
|---|
| 69 | case CHAR_SLASH:
|
|---|
| 70 | return PathType.AbsolutePosix;
|
|---|
| 71 | case CHAR_HASH:
|
|---|
| 72 | return PathType.Internal;
|
|---|
| 73 | }
|
|---|
| 74 | return PathType.Normal;
|
|---|
| 75 | }
|
|---|
| 76 | case 2: {
|
|---|
| 77 | const c0 = maybePath.charCodeAt(0);
|
|---|
| 78 | switch (c0) {
|
|---|
| 79 | case CHAR_DOT: {
|
|---|
| 80 | const c1 = maybePath.charCodeAt(1);
|
|---|
| 81 | switch (c1) {
|
|---|
| 82 | case CHAR_DOT:
|
|---|
| 83 | case CHAR_SLASH:
|
|---|
| 84 | return PathType.Relative;
|
|---|
| 85 | }
|
|---|
| 86 | return PathType.Normal;
|
|---|
| 87 | }
|
|---|
| 88 | case CHAR_SLASH:
|
|---|
| 89 | return PathType.AbsolutePosix;
|
|---|
| 90 | case CHAR_HASH:
|
|---|
| 91 | return PathType.Internal;
|
|---|
| 92 | }
|
|---|
| 93 | const c1 = maybePath.charCodeAt(1);
|
|---|
| 94 | if (
|
|---|
| 95 | c1 === CHAR_COLON &&
|
|---|
| 96 | ((c0 >= CHAR_A && c0 <= CHAR_Z) ||
|
|---|
| 97 | (c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z))
|
|---|
| 98 | ) {
|
|---|
| 99 | return PathType.AbsoluteWin;
|
|---|
| 100 | }
|
|---|
| 101 | return PathType.Normal;
|
|---|
| 102 | }
|
|---|
| 103 | }
|
|---|
| 104 | const c0 = maybePath.charCodeAt(0);
|
|---|
| 105 | switch (c0) {
|
|---|
| 106 | case CHAR_DOT: {
|
|---|
| 107 | const c1 = maybePath.charCodeAt(1);
|
|---|
| 108 | switch (c1) {
|
|---|
| 109 | case CHAR_SLASH:
|
|---|
| 110 | return PathType.Relative;
|
|---|
| 111 | case CHAR_DOT: {
|
|---|
| 112 | const c2 = maybePath.charCodeAt(2);
|
|---|
| 113 | if (c2 === CHAR_SLASH) return PathType.Relative;
|
|---|
| 114 | return PathType.Normal;
|
|---|
| 115 | }
|
|---|
| 116 | }
|
|---|
| 117 | return PathType.Normal;
|
|---|
| 118 | }
|
|---|
| 119 | case CHAR_SLASH:
|
|---|
| 120 | return PathType.AbsolutePosix;
|
|---|
| 121 | case CHAR_HASH:
|
|---|
| 122 | return PathType.Internal;
|
|---|
| 123 | }
|
|---|
| 124 | const c1 = maybePath.charCodeAt(1);
|
|---|
| 125 | if (c1 === CHAR_COLON) {
|
|---|
| 126 | const c2 = maybePath.charCodeAt(2);
|
|---|
| 127 | if (
|
|---|
| 128 | (c2 === CHAR_BACKSLASH || c2 === CHAR_SLASH) &&
|
|---|
| 129 | ((c0 >= CHAR_A && c0 <= CHAR_Z) ||
|
|---|
| 130 | (c0 >= CHAR_LOWER_A && c0 <= CHAR_LOWER_Z))
|
|---|
| 131 | ) {
|
|---|
| 132 | return PathType.AbsoluteWin;
|
|---|
| 133 | }
|
|---|
| 134 | }
|
|---|
| 135 | // DOS device paths (`\\?\…`, `\\.\…`) are handled in a cold helper so
|
|---|
| 136 | // this function stays small — inlining the full check here regressed
|
|---|
| 137 | // `description-files-multi` under `--no-opt` interpretation. Here we
|
|---|
| 138 | // only pay the two-byte gate for non-DOS inputs.
|
|---|
| 139 | if (c0 === CHAR_BACKSLASH && c1 === CHAR_BACKSLASH) {
|
|---|
| 140 | return getDosDeviceType(maybePath);
|
|---|
| 141 | }
|
|---|
| 142 | return PathType.Normal;
|
|---|
| 143 | };
|
|---|
| 144 |
|
|---|
| 145 | /**
|
|---|
| 146 | * @param {string} maybePath a path
|
|---|
| 147 | * @returns {string} the normalized path
|
|---|
| 148 | */
|
|---|
| 149 | const normalize = (maybePath) => {
|
|---|
| 150 | switch (getType(maybePath)) {
|
|---|
| 151 | case PathType.Empty:
|
|---|
| 152 | return maybePath;
|
|---|
| 153 | case PathType.AbsoluteWin:
|
|---|
| 154 | return winNormalize(maybePath);
|
|---|
| 155 | case PathType.Relative: {
|
|---|
| 156 | const r = posixNormalize(maybePath);
|
|---|
| 157 | return getType(r) === PathType.Relative ? r : `./${r}`;
|
|---|
| 158 | }
|
|---|
| 159 | }
|
|---|
| 160 | return posixNormalize(maybePath);
|
|---|
| 161 | };
|
|---|
| 162 |
|
|---|
| 163 | /**
|
|---|
| 164 | * @param {string} rootPath the root path
|
|---|
| 165 | * @param {string | undefined} request the request path
|
|---|
| 166 | * @returns {string} the joined path
|
|---|
| 167 | */
|
|---|
| 168 | const join = (rootPath, request) => {
|
|---|
| 169 | if (!request) return normalize(rootPath);
|
|---|
| 170 | const requestType = getType(request);
|
|---|
| 171 | switch (requestType) {
|
|---|
| 172 | case PathType.AbsolutePosix:
|
|---|
| 173 | return posixNormalize(request);
|
|---|
| 174 | case PathType.AbsoluteWin:
|
|---|
| 175 | return winNormalize(request);
|
|---|
| 176 | }
|
|---|
| 177 | switch (getType(rootPath)) {
|
|---|
| 178 | case PathType.Normal:
|
|---|
| 179 | case PathType.Relative:
|
|---|
| 180 | case PathType.AbsolutePosix:
|
|---|
| 181 | return posixNormalize(`${rootPath}/${request}`);
|
|---|
| 182 | case PathType.AbsoluteWin:
|
|---|
| 183 | return winNormalize(`${rootPath}\\${request}`);
|
|---|
| 184 | }
|
|---|
| 185 | switch (requestType) {
|
|---|
| 186 | case PathType.Empty:
|
|---|
| 187 | return rootPath;
|
|---|
| 188 | case PathType.Relative: {
|
|---|
| 189 | const r = posixNormalize(rootPath);
|
|---|
| 190 | return getType(r) === PathType.Relative ? r : `./${r}`;
|
|---|
| 191 | }
|
|---|
| 192 | }
|
|---|
| 193 | return posixNormalize(rootPath);
|
|---|
| 194 | };
|
|---|
| 195 |
|
|---|
| 196 | /**
|
|---|
| 197 | * @param {string} maybePath a path
|
|---|
| 198 | * @returns {string} the directory name
|
|---|
| 199 | */
|
|---|
| 200 | const dirname = (maybePath) => {
|
|---|
| 201 | switch (getType(maybePath)) {
|
|---|
| 202 | case PathType.AbsoluteWin:
|
|---|
| 203 | return path.win32.dirname(maybePath);
|
|---|
| 204 | }
|
|---|
| 205 | return path.posix.dirname(maybePath);
|
|---|
| 206 | };
|
|---|
| 207 |
|
|---|
| 208 | /** @typedef {{ fn: (rootPath: string, request: string) => string, cache: Map<string, Map<string, string | undefined>> }} CachedJoin */
|
|---|
| 209 |
|
|---|
| 210 | /**
|
|---|
| 211 | * @returns {CachedJoin} cached join
|
|---|
| 212 | */
|
|---|
| 213 | const createCachedJoin = () => {
|
|---|
| 214 | /** @type {CachedJoin["cache"]} */
|
|---|
| 215 | const cache = new Map();
|
|---|
| 216 | /** @type {CachedJoin["fn"]} */
|
|---|
| 217 | const fn = (rootPath, request) => {
|
|---|
| 218 | /** @type {string | undefined} */
|
|---|
| 219 | let cacheEntry;
|
|---|
| 220 | let inner = cache.get(rootPath);
|
|---|
| 221 | if (inner === undefined) {
|
|---|
| 222 | cache.set(rootPath, (inner = new Map()));
|
|---|
| 223 | } else {
|
|---|
| 224 | cacheEntry = inner.get(request);
|
|---|
| 225 | if (cacheEntry !== undefined) return cacheEntry;
|
|---|
| 226 | }
|
|---|
| 227 | cacheEntry = join(rootPath, request);
|
|---|
| 228 | inner.set(request, cacheEntry);
|
|---|
| 229 | return cacheEntry;
|
|---|
| 230 | };
|
|---|
| 231 | return { fn, cache };
|
|---|
| 232 | };
|
|---|
| 233 |
|
|---|
| 234 | /** @typedef {{ fn: (maybePath: string) => string, cache: Map<string, string> }} CachedDirname */
|
|---|
| 235 |
|
|---|
| 236 | /**
|
|---|
| 237 | * @returns {CachedDirname} cached dirname
|
|---|
| 238 | */
|
|---|
| 239 | const createCachedDirname = () => {
|
|---|
| 240 | /** @type {CachedDirname["cache"]} */
|
|---|
| 241 | const cache = new Map();
|
|---|
| 242 | /** @type {CachedDirname["fn"]} */
|
|---|
| 243 | const fn = (maybePath) => {
|
|---|
| 244 | const cacheEntry = cache.get(maybePath);
|
|---|
| 245 | if (cacheEntry !== undefined) return cacheEntry;
|
|---|
| 246 | const result = dirname(maybePath);
|
|---|
| 247 | cache.set(maybePath, result);
|
|---|
| 248 | return result;
|
|---|
| 249 | };
|
|---|
| 250 | return { fn, cache };
|
|---|
| 251 | };
|
|---|
| 252 |
|
|---|
| 253 | /** @typedef {{ fn: (maybePath: string, suffix?: string) => string, cache: Map<string, Map<string | undefined, string | undefined>> }} CachedBasename */
|
|---|
| 254 |
|
|---|
| 255 | /**
|
|---|
| 256 | * @returns {CachedBasename} cached basename
|
|---|
| 257 | */
|
|---|
| 258 | const createCachedBasename = () => {
|
|---|
| 259 | /** @type {CachedBasename["cache"]} */
|
|---|
| 260 | const cache = new Map();
|
|---|
| 261 | /** @type {CachedBasename["fn"]} */
|
|---|
| 262 | const fn = (maybePath, suffix) => {
|
|---|
| 263 | /** @type {string | undefined} */
|
|---|
| 264 | let cacheEntry;
|
|---|
| 265 | let inner = cache.get(maybePath);
|
|---|
| 266 | if (inner === undefined) {
|
|---|
| 267 | cache.set(maybePath, (inner = new Map()));
|
|---|
| 268 | } else {
|
|---|
| 269 | cacheEntry = inner.get(suffix);
|
|---|
| 270 | if (cacheEntry !== undefined) return cacheEntry;
|
|---|
| 271 | }
|
|---|
| 272 | cacheEntry = path.basename(maybePath, suffix);
|
|---|
| 273 | inner.set(suffix, cacheEntry);
|
|---|
| 274 | return cacheEntry;
|
|---|
| 275 | };
|
|---|
| 276 | return { fn, cache };
|
|---|
| 277 | };
|
|---|
| 278 |
|
|---|
| 279 | /**
|
|---|
| 280 | * Whether `request` is a relative request — i.e. matches `^\.\.?(?:\/|$)`.
|
|---|
| 281 | *
|
|---|
| 282 | * This is called on every `doResolve` via `UnsafeCachePlugin` and
|
|---|
| 283 | * `getInnerRequest`, so the char-code form is meaningfully faster than the
|
|---|
| 284 | * equivalent regex test: no regex state machine, no string object churn.
|
|---|
| 285 | * @param {string} request request string
|
|---|
| 286 | * @returns {boolean} true if request is relative
|
|---|
| 287 | */
|
|---|
| 288 | const isRelativeRequest = (request) => {
|
|---|
| 289 | const len = request.length;
|
|---|
| 290 | if (len === 0 || request.charCodeAt(0) !== CHAR_DOT) return false;
|
|---|
| 291 | if (len === 1) return true; // "."
|
|---|
| 292 | const c1 = request.charCodeAt(1);
|
|---|
| 293 | if (c1 === CHAR_SLASH) return true; // "./..."
|
|---|
| 294 | if (c1 !== CHAR_DOT) return false; // ".x..."
|
|---|
| 295 | if (len === 2) return true; // ".."
|
|---|
| 296 | return request.charCodeAt(2) === CHAR_SLASH; // "../..."
|
|---|
| 297 | };
|
|---|
| 298 |
|
|---|
| 299 | /**
|
|---|
| 300 | * Check if childPath is a subdirectory of parentPath.
|
|---|
| 301 | *
|
|---|
| 302 | * Called from `TsconfigPathsPlugin._selectPathsDataForContext` inside a loop
|
|---|
| 303 | * over every tsconfig-paths context on every resolve, so it's worth keeping
|
|---|
| 304 | * cheap. Compared to the previous `startsWith(normalize(parent + "/"))`
|
|---|
| 305 | * version, this: checks the last char with `charCodeAt` instead of two
|
|---|
| 306 | * `endsWith` calls; and skips `normalize()` entirely in the common case
|
|---|
| 307 | * (parent has no trailing separator), since all we really need is the same
|
|---|
| 308 | * anchoring effect — a cheap `startsWith` plus a separator char check on the
|
|---|
| 309 | * byte immediately after `parentPath.length`.
|
|---|
| 310 | * @param {string} parentPath parent directory path
|
|---|
| 311 | * @param {string} childPath child path to check
|
|---|
| 312 | * @returns {boolean} true if childPath is under parentPath
|
|---|
| 313 | */
|
|---|
| 314 | const isSubPath = (parentPath, childPath) => {
|
|---|
| 315 | const parentLen = parentPath.length;
|
|---|
| 316 | if (parentLen === 0) {
|
|---|
| 317 | // Match the old `normalize("" + "/") === "/"` fallback: an empty
|
|---|
| 318 | // parent only "contains" a child that starts with a forward slash.
|
|---|
| 319 | return childPath.length > 0 && childPath.charCodeAt(0) === CHAR_SLASH;
|
|---|
| 320 | }
|
|---|
| 321 | const lastChar = parentPath.charCodeAt(parentLen - 1);
|
|---|
| 322 | if (lastChar === CHAR_SLASH || lastChar === CHAR_BACKSLASH) {
|
|---|
| 323 | // Parent already ends with a separator — a plain prefix test is enough.
|
|---|
| 324 | return childPath.startsWith(parentPath);
|
|---|
| 325 | }
|
|---|
| 326 | if (childPath.length <= parentLen) return false;
|
|---|
| 327 | if (!childPath.startsWith(parentPath)) return false;
|
|---|
| 328 | // Must be followed by a separator so "/app" doesn't match "/app-other".
|
|---|
| 329 | const nextChar = childPath.charCodeAt(parentLen);
|
|---|
| 330 | return nextChar === CHAR_SLASH || nextChar === CHAR_BACKSLASH;
|
|---|
| 331 | };
|
|---|
| 332 |
|
|---|
| 333 | module.exports.PathType = PathType;
|
|---|
| 334 | module.exports.createCachedBasename = createCachedBasename;
|
|---|
| 335 | module.exports.createCachedDirname = createCachedDirname;
|
|---|
| 336 | module.exports.createCachedJoin = createCachedJoin;
|
|---|
| 337 | module.exports.deprecatedInvalidSegmentRegEx = deprecatedInvalidSegmentRegEx;
|
|---|
| 338 | module.exports.dirname = dirname;
|
|---|
| 339 | module.exports.getType = getType;
|
|---|
| 340 | module.exports.invalidSegmentRegEx = invalidSegmentRegEx;
|
|---|
| 341 | module.exports.isRelativeRequest = isRelativeRequest;
|
|---|
| 342 | module.exports.isSubPath = isSubPath;
|
|---|
| 343 | module.exports.join = join;
|
|---|
| 344 | module.exports.normalize = normalize;
|
|---|