diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 213896a..345b92d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,7 +55,14 @@ jobs: with: node-version: ${{ matrix.node-version }} architecture: ${{ steps.calculate_architecture.outputs.result }} - cache: ${{ matrix.node-version == '6.x' && '' || 'npm' }} + if: matrix.node-version == '6.x' + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + architecture: ${{ steps.calculate_architecture.outputs.result }} + cache: "npm" + if: matrix.node-version != '6.x' - run: | sudo npm i npm@6.x sudo npm install --ignore-engines diff --git a/lib/LoaderRunner.js b/lib/LoaderRunner.js index 2d319bd..34a7a22 100644 --- a/lib/LoaderRunner.js +++ b/lib/LoaderRunner.js @@ -20,19 +20,60 @@ function utf8BufferToString(buf) { } const PATH_QUERY_FRAGMENT_REGEXP = - /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; + /^(#?(?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; +const ZERO_ESCAPE_REGEXP = /\0(.)/g; /** - * @param {string} str the path with query and fragment - * @returns {{ path: string, query: string, fragment: string }} parsed parts + * @param {string} identifier identifier + * @returns {[string, string, string]} parsed identifier */ -function parsePathQueryFragment(str) { - const match = PATH_QUERY_FRAGMENT_REGEXP.exec(str); - return { - path: match[1].replace(/\0(.)/g, "$1"), - query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "", - fragment: match[3] || "", - }; +function parseIdentifier(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("#"); + + 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 [ + identifier.slice(0, queryStart), + identifier.slice(queryStart, fragmentStart), + identifier.slice(fragmentStart), + ]; + } + + const match = PATH_QUERY_FRAGMENT_REGEXP.exec(identifier); + + return [ + match[1].replace(ZERO_ESCAPE_REGEXP, "$1"), + match[2] ? match[2].replace(ZERO_ESCAPE_REGEXP, "$1") : "", + match[3] || "", + ]; } function dirname(path) { @@ -73,10 +114,10 @@ function createLoaderObject(loader) { }, set(value) { if (typeof value === "string") { - const splittedRequest = parsePathQueryFragment(value); - obj.path = splittedRequest.path; - obj.query = splittedRequest.query; - obj.fragment = splittedRequest.fragment; + const [path, query, fragment] = parseIdentifier(value); + obj.path = path; + obj.query = query; + obj.fragment = fragment; obj.options = undefined; obj.ident = undefined; } else { @@ -298,7 +339,7 @@ function iteratePitchingLoaders(options, loaderContext, callback) { } module.exports.getContext = function getContext(resource) { - const { path } = parsePathQueryFragment(resource); + const [path] = parseIdentifier(resource); return dirname(path); }; @@ -314,10 +355,10 @@ module.exports.runLoaders = function runLoaders(options, callback) { readResource(resource, callback); }).bind(null, options.readResource || readFile); - const splittedResource = resource && parsePathQueryFragment(resource); - const resourcePath = splittedResource ? splittedResource.path : ""; - const resourceQuery = splittedResource ? splittedResource.query : ""; - const resourceFragment = splittedResource ? splittedResource.fragment : ""; + const splittedResource = resource && parseIdentifier(resource); + const resourcePath = splittedResource ? splittedResource[0] : ""; + const resourceQuery = splittedResource ? splittedResource[1] : ""; + const resourceFragment = splittedResource ? splittedResource[2] : ""; const contextDirectory = resourcePath ? dirname(resourcePath) : null; // execution state @@ -378,15 +419,15 @@ module.exports.runLoaders = function runLoaders(options, callback) { ); }, set(value) { - const splittedResource = value && parsePathQueryFragment(value); + const splittedResource = value && parseIdentifier(value); loaderContext.resourcePath = splittedResource - ? splittedResource.path + ? splittedResource[0] : undefined; loaderContext.resourceQuery = splittedResource - ? splittedResource.query + ? splittedResource[1] : undefined; loaderContext.resourceFragment = splittedResource - ? splittedResource.fragment + ? splittedResource[2] : undefined; }, });