| 1 | /*
|
|---|
| 2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
|---|
| 3 | Author Ivan Kopeykin @vankop
|
|---|
| 4 | */
|
|---|
| 5 |
|
|---|
| 6 | "use strict";
|
|---|
| 7 |
|
|---|
| 8 | const memorize = require("./memoize");
|
|---|
| 9 |
|
|---|
| 10 | const getUrl = memorize(() => require("url"));
|
|---|
| 11 |
|
|---|
| 12 | const PATH_QUERY_FRAGMENT_REGEXP =
|
|---|
| 13 | /^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
|
|---|
| 14 | const ZERO_ESCAPE_REGEXP = /\0(.)/g;
|
|---|
| 15 | const FILE_REG_EXP = /file:/i;
|
|---|
| 16 |
|
|---|
| 17 | /**
|
|---|
| 18 | * Index past a DOS device path prefix (`\\?\…` or `\\.\…`), or 0. Kept
|
|---|
| 19 | * out of `parseIdentifier` on purpose: inlining it back bloats the caller
|
|---|
| 20 | * beyond the size where V8's interpreter and JIT both handle it well
|
|---|
| 21 | * (the cause of the description-files-multi CodSpeed regression).
|
|---|
| 22 | * @param {string} identifier identifier known to start with `\`
|
|---|
| 23 | * @returns {number} 4 if identifier starts with a DOS device prefix, else 0
|
|---|
| 24 | */
|
|---|
| 25 | function dosPrefixEnd(identifier) {
|
|---|
| 26 | if (
|
|---|
| 27 | identifier.length >= 4 &&
|
|---|
| 28 | identifier.charCodeAt(1) === 92 &&
|
|---|
| 29 | identifier.charCodeAt(3) === 92
|
|---|
| 30 | ) {
|
|---|
| 31 | const c2 = identifier.charCodeAt(2);
|
|---|
| 32 | if (c2 === 63 || c2 === 46) return 4;
|
|---|
| 33 | }
|
|---|
| 34 | return 0;
|
|---|
| 35 | }
|
|---|
| 36 |
|
|---|
| 37 | /**
|
|---|
| 38 | * @param {string} identifier identifier
|
|---|
| 39 | * @returns {[string, string, string] | null} parsed identifier
|
|---|
| 40 | */
|
|---|
| 41 | function parseIdentifier(identifier) {
|
|---|
| 42 | if (!identifier) {
|
|---|
| 43 | return null;
|
|---|
| 44 | }
|
|---|
| 45 |
|
|---|
| 46 | if (FILE_REG_EXP.test(identifier)) {
|
|---|
| 47 | identifier = getUrl().fileURLToPath(identifier);
|
|---|
| 48 | }
|
|---|
| 49 |
|
|---|
| 50 | const firstEscape = identifier.indexOf("\0");
|
|---|
| 51 |
|
|---|
| 52 | // Handle `\0`
|
|---|
| 53 | if (firstEscape !== -1) {
|
|---|
| 54 | const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier);
|
|---|
| 55 |
|
|---|
| 56 | if (!match) return null;
|
|---|
| 57 |
|
|---|
| 58 | return [
|
|---|
| 59 | match[1].replace(ZERO_ESCAPE_REGEXP, "$1"),
|
|---|
| 60 | match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "",
|
|---|
| 61 | match[3] || "",
|
|---|
| 62 | ];
|
|---|
| 63 | }
|
|---|
| 64 |
|
|---|
| 65 | // Fast path for inputs that don't use \0 escaping. DOS device paths
|
|---|
| 66 | // (`\\?\…`, `\\.\…`) embed a literal `?` / `.` that must not be read
|
|---|
| 67 | // as a query separator; skip past the prefix when the input actually
|
|---|
| 68 | // starts with `\`. Gate is a single char-code compare so this function
|
|---|
| 69 | // stays inside V8's inline budget for its hot callers (resolver parse).
|
|---|
| 70 | const scanStart =
|
|---|
| 71 | identifier.charCodeAt(0) === 92 ? dosPrefixEnd(identifier) : 0;
|
|---|
| 72 | const queryStart = identifier.indexOf("?", scanStart);
|
|---|
| 73 | // Start at index 1 (or past a DOS prefix) to ignore a possible leading hash.
|
|---|
| 74 | const fragmentStart = identifier.indexOf("#", scanStart || 1);
|
|---|
| 75 |
|
|---|
| 76 | if (fragmentStart < 0) {
|
|---|
| 77 | if (queryStart < 0) {
|
|---|
| 78 | // No fragment, no query
|
|---|
| 79 | return [identifier, "", ""];
|
|---|
| 80 | }
|
|---|
| 81 |
|
|---|
| 82 | // Query, no fragment
|
|---|
| 83 | return [identifier.slice(0, queryStart), identifier.slice(queryStart), ""];
|
|---|
| 84 | }
|
|---|
| 85 |
|
|---|
| 86 | if (queryStart < 0 || fragmentStart < queryStart) {
|
|---|
| 87 | // Fragment, no query
|
|---|
| 88 | return [
|
|---|
| 89 | identifier.slice(0, fragmentStart),
|
|---|
| 90 | "",
|
|---|
| 91 | identifier.slice(fragmentStart),
|
|---|
| 92 | ];
|
|---|
| 93 | }
|
|---|
| 94 |
|
|---|
| 95 | // Query and fragment
|
|---|
| 96 | return [
|
|---|
| 97 | identifier.slice(0, queryStart),
|
|---|
| 98 | identifier.slice(queryStart, fragmentStart),
|
|---|
| 99 | identifier.slice(fragmentStart),
|
|---|
| 100 | ];
|
|---|
| 101 | }
|
|---|
| 102 |
|
|---|
| 103 | module.exports.parseIdentifier = parseIdentifier;
|
|---|