Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 44 additions & 36 deletions lib/util/identifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,72 @@

"use strict";

const memorize = require("./memoize");

const getUrl = memorize(() => require("url"));

const PATH_QUERY_FRAGMENT_REGEXP =
/^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/;
const ZERO_ESCAPE_REGEXP = /\0(.)/g;
const FILE_REG_EXP = /file:/i;

/**
* @param {string} identifier identifier
* @returns {[string, string, string]|null} parsed identifier
* @returns {[string, string, string] | null} parsed identifier
*/
function parseIdentifier(identifier) {
if (!identifier) {
return null;
}

if (FILE_REG_EXP.test(identifier)) {
identifier = getUrl().fileURLToPath(identifier);
}

const firstEscape = identifier.indexOf("\0");
if (firstEscape < 0) {
// Fast path for inputs that don't use \0 escaping.
const queryStart = identifier.indexOf("?");
// Start at index 1 to ignore a possible leading hash.
const fragmentStart = identifier.indexOf("#", 1);

if (fragmentStart < 0) {
if (queryStart < 0) {
// No fragment, no query
return [identifier, "", ""];
}
// Query, no fragment
return [
identifier.slice(0, queryStart),
identifier.slice(queryStart),
"",
];
}

if (queryStart < 0 || fragmentStart < queryStart) {
// Fragment, no query
return [
identifier.slice(0, fragmentStart),
"",
identifier.slice(fragmentStart),
];
}
// Handle `\0`
if (firstEscape !== -1) {
const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier);

if (!match) return null;

// Query and fragment
return [
identifier.slice(0, queryStart),
identifier.slice(queryStart, fragmentStart),
identifier.slice(fragmentStart),
match[1].replace(ZERO_ESCAPE_REGEXP, "$1"),
match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "",
match[3] || "",
];
}

const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier);
// Fast path for inputs that don't use \0 escaping.
const queryStart = identifier.indexOf("?");
// Start at index 1 to ignore a possible leading hash.
const fragmentStart = identifier.indexOf("#", 1);

if (!match) return null;
if (fragmentStart < 0) {
if (queryStart < 0) {
// No fragment, no query
return [identifier, "", ""];
}

// Query, no fragment
return [identifier.slice(0, queryStart), identifier.slice(queryStart), ""];
}

if (queryStart < 0 || fragmentStart < queryStart) {
// Fragment, no query
return [
identifier.slice(0, fragmentStart),
"",
identifier.slice(fragmentStart),
];
}

// Query and fragment
return [
match[1].replace(ZERO_ESCAPE_REGEXP, "$1"),
match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "",
match[3] || "",
identifier.slice(0, queryStart),
identifier.slice(queryStart, fragmentStart),
identifier.slice(fragmentStart),
];
}

Expand Down
35 changes: 35 additions & 0 deletions test/resolve.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";

const path = require("path");
const url = require("url");
const resolve = require("../");

const fixtures = path.join(__dirname, "fixtures");
Expand Down Expand Up @@ -277,6 +278,40 @@ describe("resolve", () => {
`${path.join(fixtures, "no\0#fragment/\0#", "\0#.js")}#fragment`,
);

testResolve(
"handle file URL",
fixtures,
url.pathToFileURL(path.resolve(fixtures, "./main1.js")).toString(),
path.join(fixtures, "main1.js"),
);

testResolve(
"handle file URL with query",
fixtures,
`${url
.pathToFileURL(path.resolve(fixtures, "./main1.js"))
.toString()}?query`,
path.join(fixtures, "main1.js"),
);

testResolve(
"handle file URL with fragment",
fixtures,
`${url
.pathToFileURL(path.resolve(fixtures, "./main1.js"))
.toString()}#fragment`,
path.join(fixtures, "main1.js"),
);

testResolve(
"handle file URL with query and fragment",
fixtures,
`${url
.pathToFileURL(path.resolve(fixtures, "./main1.js"))
.toString()}?query#fragment`,
path.join(fixtures, "main1.js"),
);

it("should correctly resolve", (done) => {
const issue238 = path.resolve(fixtures, "issue-238");

Expand Down