diff --git a/.editorconfig b/.editorconfig index e3fc128..7389ffb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,23 +1,16 @@ root = true [*] -indent_style = tab -indent_size = 4 charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true -max_line_length = 233 - -[*.json] -indent_style = space -indent_size = 2 - -[*.yml] indent_style = space indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true [test/cases/parsing/bom/bomfile.{css,js}] charset = utf-8-bom [*.md] +insert_final_newline = true trim_trailing_whitespace = false diff --git a/.eslintrc.json b/.eslintrc.json index 0c0b372..40dcd80 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,58 +1,35 @@ { - "root": true, - "plugins": ["node"], - "extends": ["eslint:recommended", "plugin:node/recommended"], - "env": { - "node": true - }, - "rules": { - "quotes": ["error", "double"], - "no-undef": "error", - "no-extra-semi": "error", - "semi": "error", - "no-template-curly-in-string": "error", - "no-caller": "error", - "yoda": "error", - "eqeqeq": "error", - "global-require": "off", - "brace-style": "error", - "eol-last": "error", - "indent": ["error", "tab", { "SwitchCase": 1 }], - "no-extra-bind": "warn", - "no-empty": "off", - "no-multiple-empty-lines": "error", - "no-multi-spaces": "error", - "no-process-exit": "warn", - "space-in-parens": "error", - "no-trailing-spaces": "error", - "no-use-before-define": "off", - "no-unused-vars": ["error", {"args": "none"}], - "key-spacing": "error", - "space-infix-ops": "error", - "no-unsafe-negation": "error", - "no-loop-func": "warn", - "space-before-function-paren": ["error", "never"], - "space-before-blocks": "error", - "object-curly-spacing": ["error", "always"], - "keyword-spacing": ["error", { - "after": false, - "overrides": { - "try": {"after": true}, - "else": {"after": true}, - "throw": {"after": true}, - "case": {"after": true}, - "return": {"after": true}, - "finally": {"after": true}, - "do": {"after": true} - } - }], - "no-console": "off", - "valid-jsdoc": "error", - "no-var": "error", - "prefer-const": "error", - "prefer-arrow-callback": "error", + "root": true, + "plugins": ["node", "prettier"], + "extends": ["eslint:recommended", "plugin:node/recommended"], + "env": { + "node": true + }, + "rules": { + "no-template-curly-in-string": "error", + "no-caller": "error", + "yoda": "error", + "eqeqeq": "error", + "no-extra-bind": "error", + "no-process-exit": "error", + "no-loop-func": "error", + "no-console": "off", + "valid-jsdoc": "error", + "no-var": "error", + "prefer-const": "error", + "prefer-arrow-callback": "error", "object-shorthand": "error", + // Allow to use `assert` module - "node/no-deprecated-api": "off" - } + "node/no-deprecated-api": "off", + + "prettier/prettier": [ + "error", + { + "singleQuote": true, + "trailingComma": "es5", + "arrowParens": "always" + } + ] + } } diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..e537c8a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "trailingComma": "es5", + "arrowParens": "always" +} diff --git a/appveyor.yml b/appveyor.yml index a33136b..ee75e48 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,7 +10,7 @@ environment: clone_depth: 10 -version: "{build}" +version: '{build}' build: off deploy: off @@ -29,4 +29,4 @@ cache: test_script: - node --version - npm --version - - cmd: "yarn travis" + - cmd: 'yarn travis' diff --git a/lib/getCurrentRequest.js b/lib/getCurrentRequest.js index f05e112..4818563 100644 --- a/lib/getCurrentRequest.js +++ b/lib/getCurrentRequest.js @@ -1,13 +1,16 @@ -"use strict"; +'use strict'; function getCurrentRequest(loaderContext) { - if(loaderContext.currentRequest) - return loaderContext.currentRequest; - const request = loaderContext.loaders - .slice(loaderContext.loaderIndex) - .map(obj => obj.request) - .concat([loaderContext.resource]); - return request.join("!"); + if (loaderContext.currentRequest) { + return loaderContext.currentRequest; + } + + const request = loaderContext.loaders + .slice(loaderContext.loaderIndex) + .map((obj) => obj.request) + .concat([loaderContext.resource]); + + return request.join('!'); } module.exports = getCurrentRequest; diff --git a/lib/getHashDigest.js b/lib/getHashDigest.js index 6f4a427..bbc4e03 100644 --- a/lib/getHashDigest.js +++ b/lib/getHashDigest.js @@ -1,53 +1,69 @@ -"use strict"; +'use strict'; const baseEncodeTables = { - 26: "abcdefghijklmnopqrstuvwxyz", - 32: "123456789abcdefghjkmnpqrstuvwxyz", // no 0lio - 36: "0123456789abcdefghijklmnopqrstuvwxyz", - 49: "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no lIO - 52: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - 58: "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ", // no 0lIO - 62: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", - 64: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_" + 26: 'abcdefghijklmnopqrstuvwxyz', + 32: '123456789abcdefghjkmnpqrstuvwxyz', // no 0lio + 36: '0123456789abcdefghijklmnopqrstuvwxyz', + 49: 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ', // no lIO + 52: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', + 58: '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ', // no 0lIO + 62: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', + 64: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_', }; function encodeBufferToBase(buffer, base) { - const encodeTable = baseEncodeTables[base]; - if(!encodeTable) throw new Error("Unknown encoding base" + base); + const encodeTable = baseEncodeTables[base]; + if (!encodeTable) { + throw new Error('Unknown encoding base' + base); + } - const readLength = buffer.length; + const readLength = buffer.length; + const Big = require('big.js'); - const Big = require("big.js"); - Big.RM = Big.DP = 0; - let b = new Big(0); - for(let i = readLength - 1; i >= 0; i--) { - b = b.times(256).plus(buffer[i]); - } + Big.RM = Big.DP = 0; + let b = new Big(0); - let output = ""; - while(b.gt(0)) { - output = encodeTable[b.mod(base)] + output; - b = b.div(base); - } + for (let i = readLength - 1; i >= 0; i--) { + b = b.times(256).plus(buffer[i]); + } - Big.DP = 20; - Big.RM = 1; + let output = ''; + while (b.gt(0)) { + output = encodeTable[b.mod(base)] + output; + b = b.div(base); + } - return output; + Big.DP = 20; + Big.RM = 1; + + return output; } function getHashDigest(buffer, hashType, digestType, maxLength) { - hashType = hashType || "md5"; - maxLength = maxLength || 9999; - const hash = require("crypto").createHash(hashType); - hash.update(buffer); - if(digestType === "base26" || digestType === "base32" || digestType === "base36" || - digestType === "base49" || digestType === "base52" || digestType === "base58" || - digestType === "base62" || digestType === "base64") { - return encodeBufferToBase(hash.digest(), digestType.substr(4)).substr(0, maxLength); - } else { - return hash.digest(digestType || "hex").substr(0, maxLength); - } + hashType = hashType || 'md5'; + maxLength = maxLength || 9999; + + const hash = require('crypto').createHash(hashType); + + hash.update(buffer); + + if ( + digestType === 'base26' || + digestType === 'base32' || + digestType === 'base36' || + digestType === 'base49' || + digestType === 'base52' || + digestType === 'base58' || + digestType === 'base62' || + digestType === 'base64' + ) { + return encodeBufferToBase(hash.digest(), digestType.substr(4)).substr( + 0, + maxLength + ); + } else { + return hash.digest(digestType || 'hex').substr(0, maxLength); + } } module.exports = getHashDigest; diff --git a/lib/getOptions.js b/lib/getOptions.js index 24bcae4..9cbbed4 100644 --- a/lib/getOptions.js +++ b/lib/getOptions.js @@ -1,17 +1,20 @@ -"use strict"; +'use strict'; -const parseQuery = require("./parseQuery"); +const parseQuery = require('./parseQuery'); function getOptions(loaderContext) { - const query = loaderContext.query; - if(typeof query === "string" && query !== "") { - return parseQuery(loaderContext.query); - } - if(!query || typeof query !== "object") { - // Not object-like queries are not supported. - return null; - } - return query; + const query = loaderContext.query; + + if (typeof query === 'string' && query !== '') { + return parseQuery(loaderContext.query); + } + + if (!query || typeof query !== 'object') { + // Not object-like queries are not supported. + return null; + } + + return query; } module.exports = getOptions; diff --git a/lib/getRemainingRequest.js b/lib/getRemainingRequest.js index f48c71a..5839b2f 100644 --- a/lib/getRemainingRequest.js +++ b/lib/getRemainingRequest.js @@ -1,13 +1,16 @@ -"use strict"; +'use strict'; function getRemainingRequest(loaderContext) { - if(loaderContext.remainingRequest) - return loaderContext.remainingRequest; - const request = loaderContext.loaders - .slice(loaderContext.loaderIndex + 1) - .map(obj => obj.request) - .concat([loaderContext.resource]); - return request.join("!"); + if (loaderContext.remainingRequest) { + return loaderContext.remainingRequest; + } + + const request = loaderContext.loaders + .slice(loaderContext.loaderIndex + 1) + .map((obj) => obj.request) + .concat([loaderContext.resource]); + + return request.join('!'); } module.exports = getRemainingRequest; diff --git a/lib/index.js b/lib/index.js index a595092..2591310 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,15 +1,15 @@ -"use strict"; +'use strict'; -const getOptions = require("./getOptions"); -const parseQuery = require("./parseQuery"); -const stringifyRequest = require("./stringifyRequest"); -const getRemainingRequest = require("./getRemainingRequest"); -const getCurrentRequest = require("./getCurrentRequest"); -const isUrlRequest = require("./isUrlRequest"); -const urlToRequest = require("./urlToRequest"); -const parseString = require("./parseString"); -const getHashDigest = require("./getHashDigest"); -const interpolateName = require("./interpolateName"); +const getOptions = require('./getOptions'); +const parseQuery = require('./parseQuery'); +const stringifyRequest = require('./stringifyRequest'); +const getRemainingRequest = require('./getRemainingRequest'); +const getCurrentRequest = require('./getCurrentRequest'); +const isUrlRequest = require('./isUrlRequest'); +const urlToRequest = require('./urlToRequest'); +const parseString = require('./parseString'); +const getHashDigest = require('./getHashDigest'); +const interpolateName = require('./interpolateName'); exports.getOptions = getOptions; exports.parseQuery = parseQuery; diff --git a/lib/interpolateName.js b/lib/interpolateName.js index 1b1c1b4..7aa69bc 100644 --- a/lib/interpolateName.js +++ b/lib/interpolateName.js @@ -1,101 +1,133 @@ -"use strict"; +'use strict'; -const path = require("path"); -const emojisList = require("emojis-list"); -const getHashDigest = require("./getHashDigest"); +const path = require('path'); +const emojisList = require('emojis-list'); +const getHashDigest = require('./getHashDigest'); const emojiRegex = /[\uD800-\uDFFF]./; -const emojiList = emojisList.filter(emoji => emojiRegex.test(emoji)); +const emojiList = emojisList.filter((emoji) => emojiRegex.test(emoji)); const emojiCache = {}; function encodeStringToEmoji(content, length) { - if(emojiCache[content]) return emojiCache[content]; - length = length || 1; - const emojis = []; - do { - if(!emojiList.length) { - throw new Error("Ran out of emoji"); - } - - const index = Math.floor(Math.random() * emojiList.length); - emojis.push(emojiList[index]); - emojiList.splice(index, 1); - } while(--length > 0); - const emojiEncoding = emojis.join(""); - emojiCache[content] = emojiEncoding; - return emojiEncoding; + if (emojiCache[content]) { + return emojiCache[content]; + } + + length = length || 1; + + const emojis = []; + + do { + if (!emojiList.length) { + throw new Error('Ran out of emoji'); + } + + const index = Math.floor(Math.random() * emojiList.length); + + emojis.push(emojiList[index]); + emojiList.splice(index, 1); + } while (--length > 0); + + const emojiEncoding = emojis.join(''); + + emojiCache[content] = emojiEncoding; + + return emojiEncoding; } function interpolateName(loaderContext, name, options) { - let filename; - if(typeof name === "function") { - filename = name(loaderContext.resourcePath); - } else { - filename = name || "[hash].[ext]"; - } - const context = options.context; - const content = options.content; - const regExp = options.regExp; - let ext = "bin"; - let basename = "file"; - let directory = ""; - let folder = ""; - if(loaderContext.resourcePath) { - const parsed = path.parse(loaderContext.resourcePath); - let resourcePath = loaderContext.resourcePath; - - if(parsed.ext) { - ext = parsed.ext.substr(1); - } - if(parsed.dir) { - basename = parsed.name; - resourcePath = parsed.dir + path.sep; - } - if(typeof context !== "undefined") { - directory = path.relative(context, resourcePath + "_").replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1"); - directory = directory.substr(0, directory.length - 1); - } else { - directory = resourcePath.replace(/\\/g, "/").replace(/\.\.(\/)?/g, "_$1"); - } - if(directory.length === 1) { - directory = ""; - } else if(directory.length > 1) { - folder = path.basename(directory); - } - } - let url = filename; - if(content) { - // Match hash template - url = url - // `hash` and `contenthash` are same in `loader-utils` context - // let's keep `hash` for backward compatibility - .replace( - /\[(?:([^:]+):)?(?:hash||contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/ig, - (all, hashType, digestType, maxLength) => getHashDigest(content, hashType, digestType, parseInt(maxLength, 10)) - ) - .replace( - /\[emoji(?::(\d+))?\]/ig, - (all, length) => encodeStringToEmoji(content, parseInt(length, 10)) - ); - } - url = url - .replace(/\[ext\]/ig, () => ext) - .replace(/\[name\]/ig, () => basename) - .replace(/\[path\]/ig, () => directory) - .replace(/\[folder\]/ig, () => folder); - if(regExp && loaderContext.resourcePath) { - const match = loaderContext.resourcePath.match(new RegExp(regExp)); - match && match.forEach((matched, i) => { - url = url.replace( - new RegExp("\\[" + i + "\\]", "ig"), - matched - ); - }); - } - if(typeof loaderContext.options === "object" && typeof loaderContext.options.customInterpolateName === "function") { - url = loaderContext.options.customInterpolateName.call(loaderContext, url, name, options); - } - return url; + let filename; + + if (typeof name === 'function') { + filename = name(loaderContext.resourcePath); + } else { + filename = name || '[hash].[ext]'; + } + + const context = options.context; + const content = options.content; + const regExp = options.regExp; + + let ext = 'bin'; + let basename = 'file'; + let directory = ''; + let folder = ''; + + if (loaderContext.resourcePath) { + const parsed = path.parse(loaderContext.resourcePath); + let resourcePath = loaderContext.resourcePath; + + if (parsed.ext) { + ext = parsed.ext.substr(1); + } + + if (parsed.dir) { + basename = parsed.name; + resourcePath = parsed.dir + path.sep; + } + + if (typeof context !== 'undefined') { + directory = path + .relative(context, resourcePath + '_') + .replace(/\\/g, '/') + .replace(/\.\.(\/)?/g, '_$1'); + directory = directory.substr(0, directory.length - 1); + } else { + directory = resourcePath.replace(/\\/g, '/').replace(/\.\.(\/)?/g, '_$1'); + } + + if (directory.length === 1) { + directory = ''; + } else if (directory.length > 1) { + folder = path.basename(directory); + } + } + + let url = filename; + + if (content) { + // Match hash template + url = url + // `hash` and `contenthash` are same in `loader-utils` context + // let's keep `hash` for backward compatibility + .replace( + /\[(?:([^:]+):)?(?:hash||contenthash)(?::([a-z]+\d*))?(?::(\d+))?\]/gi, + (all, hashType, digestType, maxLength) => + getHashDigest(content, hashType, digestType, parseInt(maxLength, 10)) + ) + .replace(/\[emoji(?::(\d+))?\]/gi, (all, length) => + encodeStringToEmoji(content, parseInt(length, 10)) + ); + } + + url = url + .replace(/\[ext\]/gi, () => ext) + .replace(/\[name\]/gi, () => basename) + .replace(/\[path\]/gi, () => directory) + .replace(/\[folder\]/gi, () => folder); + + if (regExp && loaderContext.resourcePath) { + const match = loaderContext.resourcePath.match(new RegExp(regExp)); + + match && + match.forEach((matched, i) => { + url = url.replace(new RegExp('\\[' + i + '\\]', 'ig'), matched); + }); + } + + if ( + typeof loaderContext.options === 'object' && + typeof loaderContext.options.customInterpolateName === 'function' + ) { + url = loaderContext.options.customInterpolateName.call( + loaderContext, + url, + name, + options + ); + } + + return url; } module.exports = interpolateName; diff --git a/lib/isUrlRequest.js b/lib/isUrlRequest.js index 1de028a..c59c218 100644 --- a/lib/isUrlRequest.js +++ b/lib/isUrlRequest.js @@ -1,20 +1,24 @@ -"use strict"; +'use strict'; function isUrlRequest(url, root) { - // An URL is not an request if - // 1. it's a Data Url - // 2. it's an absolute url or and protocol-relative - // 3. it's some kind of url for a template - if(/^data:|^.+-extension:\/|^about:blank$|^(https?:)?\/\/|^[{}[]#*;,'§\$%&\(=?`´\^°<>]/.test(url)) { - return false; - } + // An URL is not an request if + // 1. it's a Data Url + // 2. it's an absolute url or and protocol-relative + // 3. it's some kind of url for a template + if ( + /^data:|^.+-extension:\/|^about:blank$|^(https?:)?\/\/|^[{}[]#*;,'§\$%&\(=?`´\^°<>]/.test( + url + ) + ) { + return false; + } - // 4. It's also not an request if root isn't set and it's a root-relative url - if((root === undefined || root === false) && /^\//.test(url)) { - return false; - } + // 4. It's also not an request if root isn't set and it's a root-relative url + if ((root === undefined || root === false) && /^\//.test(url)) { + return false; + } - return true; + return true; } module.exports = isUrlRequest; diff --git a/lib/parseQuery.js b/lib/parseQuery.js index 5748cde..12b3efc 100644 --- a/lib/parseQuery.js +++ b/lib/parseQuery.js @@ -1,54 +1,68 @@ -"use strict"; +'use strict'; -const JSON5 = require("json5"); +const JSON5 = require('json5'); const specialValues = { - "null": null, - "true": true, - "false": false + null: null, + true: true, + false: false, }; function parseQuery(query) { - if(query.substr(0, 1) !== "?") { - throw new Error("A valid query string passed to parseQuery should begin with '?'"); - } - query = query.substr(1); - if(!query) { - return {}; - } - if(query.substr(0, 1) === "{" && query.substr(-1) === "}") { - return JSON5.parse(query); - } - const queryArgs = query.split(/[,&]/g); - const result = {}; - queryArgs.forEach(arg => { - const idx = arg.indexOf("="); - if(idx >= 0) { - let name = arg.substr(0, idx); - let value = decodeURIComponent(arg.substr(idx + 1)); - if(specialValues.hasOwnProperty(value)) { - value = specialValues[value]; - } - if(name.substr(-2) === "[]") { - name = decodeURIComponent(name.substr(0, name.length - 2)); - if(!Array.isArray(result[name])) - result[name] = []; - result[name].push(value); - } else { - name = decodeURIComponent(name); - result[name] = value; - } - } else { - if(arg.substr(0, 1) === "-") { - result[decodeURIComponent(arg.substr(1))] = false; - } else if(arg.substr(0, 1) === "+") { - result[decodeURIComponent(arg.substr(1))] = true; - } else { - result[decodeURIComponent(arg)] = true; - } - } - }); - return result; + if (query.substr(0, 1) !== '?') { + throw new Error( + "A valid query string passed to parseQuery should begin with '?'" + ); + } + + query = query.substr(1); + + if (!query) { + return {}; + } + + if (query.substr(0, 1) === '{' && query.substr(-1) === '}') { + return JSON5.parse(query); + } + + const queryArgs = query.split(/[,&]/g); + const result = {}; + + queryArgs.forEach((arg) => { + const idx = arg.indexOf('='); + + if (idx >= 0) { + let name = arg.substr(0, idx); + let value = decodeURIComponent(arg.substr(idx + 1)); + + if (specialValues.hasOwnProperty(value)) { + value = specialValues[value]; + } + + if (name.substr(-2) === '[]') { + name = decodeURIComponent(name.substr(0, name.length - 2)); + + if (!Array.isArray(result[name])) { + result[name] = []; + } + + result[name].push(value); + } else { + name = decodeURIComponent(name); + result[name] = value; + } + } else { + if (arg.substr(0, 1) === '-') { + result[decodeURIComponent(arg.substr(1))] = false; + } else if (arg.substr(0, 1) === '+') { + result[decodeURIComponent(arg.substr(1))] = true; + } else { + result[decodeURIComponent(arg)] = true; + } + } + }); + + return result; } module.exports = parseQuery; diff --git a/lib/parseString.js b/lib/parseString.js index ca69dfd..6e36f29 100644 --- a/lib/parseString.js +++ b/lib/parseString.js @@ -1,19 +1,23 @@ -"use strict"; +'use strict'; function parseString(str) { - try { - if(str[0] === "\"") return JSON.parse(str); - if(str[0] === "'" && str.substr(str.length - 1) === "'") { - return parseString( - str - .replace(/\\.|"/g, x => x === "\"" ? "\\\"" : x) - .replace(/^'|'$/g, "\"") - ); - } - return JSON.parse("\"" + str + "\""); - } catch(e) { - return str; - } + try { + if (str[0] === '"') { + return JSON.parse(str); + } + + if (str[0] === "'" && str.substr(str.length - 1) === "'") { + return parseString( + str + .replace(/\\.|"/g, (x) => (x === '"' ? '\\"' : x)) + .replace(/^'|'$/g, '"') + ); + } + + return JSON.parse('"' + str + '"'); + } catch (e) { + return str; + } } module.exports = parseString; diff --git a/lib/stringifyRequest.js b/lib/stringifyRequest.js index 46e9ae2..9bd5593 100644 --- a/lib/stringifyRequest.js +++ b/lib/stringifyRequest.js @@ -1,40 +1,51 @@ -"use strict"; +'use strict'; -const path = require("path"); +const path = require('path'); const matchRelativePath = /^\.\.?[/\\]/; function isAbsolutePath(str) { - return path.posix.isAbsolute(str) || path.win32.isAbsolute(str); + return path.posix.isAbsolute(str) || path.win32.isAbsolute(str); } function isRelativePath(str) { - return matchRelativePath.test(str); + return matchRelativePath.test(str); } function stringifyRequest(loaderContext, request) { - const splitted = request.split("!"); - const context = loaderContext.context || (loaderContext.options && loaderContext.options.context); - return JSON.stringify(splitted.map(part => { - // First, separate singlePath from query, because the query might contain paths again - const splittedPart = part.match(/^(.*?)(\?.*)/); - let singlePath = splittedPart ? splittedPart[1] : part; - const query = splittedPart ? splittedPart[2] : ""; - if(isAbsolutePath(singlePath) && context) { - singlePath = path.relative(context, singlePath); - if(isAbsolutePath(singlePath)) { - // If singlePath still matches an absolute path, singlePath was on a different drive than context. - // In this case, we leave the path platform-specific without replacing any separators. - // @see https://github.com/webpack/loader-utils/pull/14 - return singlePath + query; - } - if(isRelativePath(singlePath) === false) { - // Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules). - singlePath = "./" + singlePath; - } - } - return singlePath.replace(/\\/g, "/") + query; - }).join("!")); + const splitted = request.split('!'); + const context = + loaderContext.context || + (loaderContext.options && loaderContext.options.context); + + return JSON.stringify( + splitted + .map((part) => { + // First, separate singlePath from query, because the query might contain paths again + const splittedPart = part.match(/^(.*?)(\?.*)/); + const query = splittedPart ? splittedPart[2] : ''; + let singlePath = splittedPart ? splittedPart[1] : part; + + if (isAbsolutePath(singlePath) && context) { + singlePath = path.relative(context, singlePath); + + if (isAbsolutePath(singlePath)) { + // If singlePath still matches an absolute path, singlePath was on a different drive than context. + // In this case, we leave the path platform-specific without replacing any separators. + // @see https://github.com/webpack/loader-utils/pull/14 + return singlePath + query; + } + + if (isRelativePath(singlePath) === false) { + // Ensure that the relative path starts at least with ./ otherwise it would be a request into the modules directory (like node_modules). + singlePath = './' + singlePath; + } + } + + return singlePath.replace(/\\/g, '/') + query; + }) + .join('!') + ); } module.exports = stringifyRequest; diff --git a/lib/urlToRequest.js b/lib/urlToRequest.js index 85d750e..597115f 100644 --- a/lib/urlToRequest.js +++ b/lib/urlToRequest.js @@ -1,57 +1,63 @@ -"use strict"; +'use strict'; // we can't use path.win32.isAbsolute because it also matches paths starting with a forward slash const matchNativeWin32Path = /^[A-Z]:[/\\]|^\\\\/i; function urlToRequest(url, root) { - // Do not rewrite an empty url - if(url === "") { - return ""; - } + // Do not rewrite an empty url + if (url === '') { + return ''; + } - const moduleRequestRegex = /^[^?]*~/; - let request; + const moduleRequestRegex = /^[^?]*~/; + let request; - if(matchNativeWin32Path.test(url)) { - // absolute windows path, keep it - request = url; - } else if(root !== undefined && root !== false && /^\//.test(url)) { - // if root is set and the url is root-relative - switch(typeof root) { - // 1. root is a string: root is prefixed to the url - case "string": - // special case: `~` roots convert to module request - if(moduleRequestRegex.test(root)) { - request = root.replace(/([^~/])$/, "$1/") + url.slice(1); - } else { - request = root + url; - } - break; - // 2. root is `true`: absolute paths are allowed - // *nix only, windows-style absolute paths are always allowed as they doesn't start with a `/` - case "boolean": - request = url; - break; - default: - throw new Error("Unexpected parameters to loader-utils 'urlToRequest': url = " + url + ", root = " + root + "."); - } - } else if(/^(?:https?:)?\/\//.test(url)) { - // Preserve http and https urls - request = url; - } else if(/^\.\.?\//.test(url)) { - // A relative url stays - request = url; - } else { - // every other url is threaded like a relative url - request = "./" + url; - } + if (matchNativeWin32Path.test(url)) { + // absolute windows path, keep it + request = url; + } else if (root !== undefined && root !== false && /^\//.test(url)) { + // if root is set and the url is root-relative + switch (typeof root) { + // 1. root is a string: root is prefixed to the url + case 'string': + // special case: `~` roots convert to module request + if (moduleRequestRegex.test(root)) { + request = root.replace(/([^~/])$/, '$1/') + url.slice(1); + } else { + request = root + url; + } + break; + // 2. root is `true`: absolute paths are allowed + // *nix only, windows-style absolute paths are always allowed as they doesn't start with a `/` + case 'boolean': + request = url; + break; + default: + throw new Error( + "Unexpected parameters to loader-utils 'urlToRequest': url = " + + url + + ', root = ' + + root + + '.' + ); + } + } else if (/^(?:https?:)?\/\//.test(url)) { + // Preserve http and https urls + request = url; + } else if (/^\.\.?\//.test(url)) { + // A relative url stays + request = url; + } else { + // every other url is threaded like a relative url + request = './' + url; + } - // A `~` makes the url an module - if(moduleRequestRegex.test(request)) { - request = request.replace(moduleRequestRegex, ""); - } + // A `~` makes the url an module + if (moduleRequestRegex.test(request)) { + request = request.replace(moduleRequestRegex, ''); + } - return request; + return request; } module.exports = urlToRequest; diff --git a/package.json b/package.json index cc56227..4980321 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,10 @@ "coveralls": "^3.0.2", "eslint": "^5.11.0", "eslint-plugin-node": "^8.0.0", + "eslint-plugin-prettier": "^3.0.0", "istanbul": "^0.4.5", "mocha": "^5.2.0", + "prettier": "^1.15.3", "standard-version": "^4.0.0" }, "main": "lib/index.js", diff --git a/test/.eslintrc.json b/test/.eslintrc.json index dffabfe..7eeefc3 100644 --- a/test/.eslintrc.json +++ b/test/.eslintrc.json @@ -1,5 +1,5 @@ { - "env": { - "mocha": true - } -} \ No newline at end of file + "env": { + "mocha": true + } +} diff --git a/test/getHashDigest.test.js b/test/getHashDigest.test.js index 6bfe9f5..bb8f5f9 100644 --- a/test/getHashDigest.test.js +++ b/test/getHashDigest.test.js @@ -1,21 +1,54 @@ -"use strict"; +'use strict'; -const assert = require("assert"); -const loaderUtils = require("../"); +const assert = require('assert'); +const loaderUtils = require('../'); -describe("getHashDigest()", () => { - [ - ["test string", "md5", "hex", undefined, "6f8db599de986fab7a21625b7916589c"], - ["test string", "md5", "hex", 4, "6f8d"], - ["test string", "md5", "base64", undefined, "2sm1pVmS8xuGJLCdWpJoRL"], - ["test string", "md5", "base52", undefined, "dJnldHSAutqUacjgfBQGLQx"], - ["test string", "md5", "base26", 6, "bhtsgu"], - ["test string", "sha512", "base64", undefined, "2IS-kbfIPnVflXb9CzgoNESGCkvkb0urMmucPD9z8q6HuYz8RShY1-tzSUpm5-Ivx_u4H1MEzPgAhyhaZ7RKog"], - ["test string", "md5", "hex", undefined, "6f8db599de986fab7a21625b7916589c"] - ].forEach(test => { - it("should getHashDigest " + test[0] + " " + test[1] + " " + test[2] + " " + test[3], () => { - const hashDigest = loaderUtils.getHashDigest(test[0], test[1], test[2], test[3]); - assert.equal(hashDigest, test[4]); - }); - }); +describe('getHashDigest()', () => { + [ + [ + 'test string', + 'md5', + 'hex', + undefined, + '6f8db599de986fab7a21625b7916589c', + ], + ['test string', 'md5', 'hex', 4, '6f8d'], + ['test string', 'md5', 'base64', undefined, '2sm1pVmS8xuGJLCdWpJoRL'], + ['test string', 'md5', 'base52', undefined, 'dJnldHSAutqUacjgfBQGLQx'], + ['test string', 'md5', 'base26', 6, 'bhtsgu'], + [ + 'test string', + 'sha512', + 'base64', + undefined, + '2IS-kbfIPnVflXb9CzgoNESGCkvkb0urMmucPD9z8q6HuYz8RShY1-tzSUpm5-Ivx_u4H1MEzPgAhyhaZ7RKog', + ], + [ + 'test string', + 'md5', + 'hex', + undefined, + '6f8db599de986fab7a21625b7916589c', + ], + ].forEach((test) => { + it( + 'should getHashDigest ' + + test[0] + + ' ' + + test[1] + + ' ' + + test[2] + + ' ' + + test[3], + () => { + const hashDigest = loaderUtils.getHashDigest( + test[0], + test[1], + test[2], + test[3] + ); + assert.equal(hashDigest, test[4]); + } + ); + }); }); diff --git a/test/getOptions.test.js b/test/getOptions.test.js index 803a293..4a73d5a 100644 --- a/test/getOptions.test.js +++ b/test/getOptions.test.js @@ -1,77 +1,77 @@ -"use strict"; +'use strict'; -const assert = require("assert"); -const loaderUtils = require("../lib"); +const assert = require('assert'); +const loaderUtils = require('../lib'); -describe("getOptions()", () => { - describe("when loaderContext.query is a string with length > 0", () => { - it("should call parseQuery() and return its result", () => { - assert.deepEqual( - loaderUtils.getOptions({ - query: "?something=getOptions_cannot_parse" - }), - { something: "getOptions_cannot_parse" } - ); - }); - }); - describe("when loaderContext.query is an empty string", () => { - it("should return null", () => { - assert.strictEqual( - loaderUtils.getOptions({ - query: "" - }), - null - ); - }); - }); - describe("when loaderContext.query is an object", () => { - it("should just return it", () => { - const query = {}; - assert.strictEqual( - loaderUtils.getOptions({ - query - }), - query - ); - }); - }); - describe("when loaderContext.query is an array", () => { - it("should just return it", () => { - const query = []; - assert.strictEqual( - loaderUtils.getOptions({ - query - }), - query - ); - }); - }); - describe("when loaderContext.query is anything else", () => { - it("should return null", () => { - assert.strictEqual( - loaderUtils.getOptions({ - query: undefined - }), - null - ); - assert.strictEqual( - loaderUtils.getOptions({ - query: null - }), - null - ); - assert.strictEqual( - loaderUtils.getOptions({ - query: 1 - }), - null - ); - assert.strictEqual( - loaderUtils.getOptions({ - query: 0 - }), - null - ); - }); - }); +describe('getOptions()', () => { + describe('when loaderContext.query is a string with length > 0', () => { + it('should call parseQuery() and return its result', () => { + assert.deepEqual( + loaderUtils.getOptions({ + query: '?something=getOptions_cannot_parse', + }), + { something: 'getOptions_cannot_parse' } + ); + }); + }); + describe('when loaderContext.query is an empty string', () => { + it('should return null', () => { + assert.strictEqual( + loaderUtils.getOptions({ + query: '', + }), + null + ); + }); + }); + describe('when loaderContext.query is an object', () => { + it('should just return it', () => { + const query = {}; + assert.strictEqual( + loaderUtils.getOptions({ + query, + }), + query + ); + }); + }); + describe('when loaderContext.query is an array', () => { + it('should just return it', () => { + const query = []; + assert.strictEqual( + loaderUtils.getOptions({ + query, + }), + query + ); + }); + }); + describe('when loaderContext.query is anything else', () => { + it('should return null', () => { + assert.strictEqual( + loaderUtils.getOptions({ + query: undefined, + }), + null + ); + assert.strictEqual( + loaderUtils.getOptions({ + query: null, + }), + null + ); + assert.strictEqual( + loaderUtils.getOptions({ + query: 1, + }), + null + ); + assert.strictEqual( + loaderUtils.getOptions({ + query: 0, + }), + null + ); + }); + }); }); diff --git a/test/interpolateName.test.js b/test/interpolateName.test.js index 0c88df9..2af321d 100644 --- a/test/interpolateName.test.js +++ b/test/interpolateName.test.js @@ -1,135 +1,251 @@ -"use strict"; +'use strict'; -const assert = require("assert"); -const loaderUtils = require("../"); +const assert = require('assert'); +const loaderUtils = require('../'); const emojiRegex = /[\uD800-\uDFFF]./; -describe("interpolateName()", () => { - function run(tests) { - tests.forEach((test) => { - const args = test[0]; - const expected = test[1]; - const message = test[2]; - it(message, () => { - const result = loaderUtils.interpolateName.apply(loaderUtils, args); - if(typeof expected === "function") { - expected(result); - } else { - assert.equal(result, expected); - } - }); - }); - } +describe('interpolateName()', () => { + function run(tests) { + tests.forEach((test) => { + const args = test[0]; + const expected = test[1]; + const message = test[2]; + it(message, () => { + const result = loaderUtils.interpolateName.apply(loaderUtils, args); + if (typeof expected === 'function') { + expected(result); + } else { + assert.equal(result, expected); + } + }); + }); + } - [ - ["/app/js/javascript.js", "js/[hash].script.[ext]", "test content", "js/9473fdd0d880a43c21b7778d34872157.script.js"], - ["/app/js/javascript.js", "js/[contenthash].script.[ext]", "test content", "js/9473fdd0d880a43c21b7778d34872157.script.js"], - ["/app/page.html", "html-[hash:6].html", "test content", "html-9473fd.html"], - ["/app/page.html", "html-[contenthash:6].html", "test content", "html-9473fd.html"], - ["/app/flash.txt", "[hash]", "test content", "9473fdd0d880a43c21b7778d34872157"], - ["/app/flash.txt", "[contenthash]", "test content", "9473fdd0d880a43c21b7778d34872157"], - ["/app/img/image.png", "[sha512:hash:base64:7].[ext]", "test content", "2BKDTjl.png"], - ["/app/img/image.png", "[sha512:contenthash:base64:7].[ext]", "test content", "2BKDTjl.png"], - ["/app/dir/file.png", "[path][name].[ext]?[hash]", "test content", "/app/dir/file.png?9473fdd0d880a43c21b7778d34872157"], - ["/app/dir/file.png", "[path][name].[ext]?[contenthash]", "test content", "/app/dir/file.png?9473fdd0d880a43c21b7778d34872157"], - ["/vendor/test/images/loading.gif", path => path.replace(/\/?vendor\/?/, ""), "test content", "test/images/loading.gif"], - ["/pathWith.period/filename.js", "js/[name].[ext]", "test content", "js/filename.js"], - ["/pathWith.period/filenameWithoutExt", "js/[name].[ext]", "test content", "js/filenameWithoutExt.bin"] - ].forEach(test => { - it("should interpolate " + test[0] + " " + test[1], () => { - const interpolatedName = loaderUtils.interpolateName({ resourcePath: test[0] }, test[1], { content: test[2] }); - assert.equal(interpolatedName, test[3]); - }); - }); + [ + [ + '/app/js/javascript.js', + 'js/[hash].script.[ext]', + 'test content', + 'js/9473fdd0d880a43c21b7778d34872157.script.js', + ], + [ + '/app/js/javascript.js', + 'js/[contenthash].script.[ext]', + 'test content', + 'js/9473fdd0d880a43c21b7778d34872157.script.js', + ], + [ + '/app/page.html', + 'html-[hash:6].html', + 'test content', + 'html-9473fd.html', + ], + [ + '/app/page.html', + 'html-[contenthash:6].html', + 'test content', + 'html-9473fd.html', + ], + [ + '/app/flash.txt', + '[hash]', + 'test content', + '9473fdd0d880a43c21b7778d34872157', + ], + [ + '/app/flash.txt', + '[contenthash]', + 'test content', + '9473fdd0d880a43c21b7778d34872157', + ], + [ + '/app/img/image.png', + '[sha512:hash:base64:7].[ext]', + 'test content', + '2BKDTjl.png', + ], + [ + '/app/img/image.png', + '[sha512:contenthash:base64:7].[ext]', + 'test content', + '2BKDTjl.png', + ], + [ + '/app/dir/file.png', + '[path][name].[ext]?[hash]', + 'test content', + '/app/dir/file.png?9473fdd0d880a43c21b7778d34872157', + ], + [ + '/app/dir/file.png', + '[path][name].[ext]?[contenthash]', + 'test content', + '/app/dir/file.png?9473fdd0d880a43c21b7778d34872157', + ], + [ + '/vendor/test/images/loading.gif', + (path) => path.replace(/\/?vendor\/?/, ''), + 'test content', + 'test/images/loading.gif', + ], + [ + '/pathWith.period/filename.js', + 'js/[name].[ext]', + 'test content', + 'js/filename.js', + ], + [ + '/pathWith.period/filenameWithoutExt', + 'js/[name].[ext]', + 'test content', + 'js/filenameWithoutExt.bin', + ], + ].forEach((test) => { + it('should interpolate ' + test[0] + ' ' + test[1], () => { + const interpolatedName = loaderUtils.interpolateName( + { resourcePath: test[0] }, + test[1], + { content: test[2] } + ); + assert.equal(interpolatedName, test[3]); + }); + }); - [ "sha1fakename", - "9dxfakename", - "RSA-SHA256-fakename", - "ecdsa-with-SHA1-fakename", - "tls1.1-sha512-fakename", - ].forEach(hashName => { - it("should pick hash algorithm by name " + hashName, () => { - assert.throws( - () => { - const interpolatedName = loaderUtils.interpolateName( - { }, "[" + hashName + ":hash:base64:10]", { content: "a" } - ); - // if for any reason the system we're running on has a hash - // algorithm matching any of our bogus names, at least make sure - // the output is not the unmodified name: - assert(interpolatedName[0] !== "["); - }, - /digest method not supported/i - ); - }); - }); + [ + 'sha1fakename', + '9dxfakename', + 'RSA-SHA256-fakename', + 'ecdsa-with-SHA1-fakename', + 'tls1.1-sha512-fakename', + ].forEach((hashName) => { + it('should pick hash algorithm by name ' + hashName, () => { + assert.throws(() => { + const interpolatedName = loaderUtils.interpolateName( + {}, + '[' + hashName + ':hash:base64:10]', + { content: 'a' } + ); + // if for any reason the system we're running on has a hash + // algorithm matching any of our bogus names, at least make sure + // the output is not the unmodified name: + assert(interpolatedName[0] !== '['); + }, /digest method not supported/i); + }); + }); + run([ + [ + [{}, '', { content: 'test string' }], + '6f8db599de986fab7a21625b7916589c.bin', + 'should interpolate default tokens', + ], + [ + [{}, '[hash:base64]', { content: 'test string' }], + '2sm1pVmS8xuGJLCdWpJoRL', + 'should interpolate [hash] token with options', + ], + [ + [{}, '[unrecognized]', { content: 'test string' }], + '[unrecognized]', + 'should not interpolate unrecognized token', + ], + [ + [{}, '[emoji]', { content: 'test' }], + (result) => { + assert.ok(emojiRegex.test(result), result); + }, + 'should interpolate [emoji]', + ], + [ + [{}, '[emoji:3]', { content: 'string' }], + (result) => { + assert.ok(emojiRegex.test(result), result); + assert.ok(result.length, 6); + }, + 'should interpolate [emoji:3]', + ], + ]); - run([ - [[{}, "", { content: "test string" }], "6f8db599de986fab7a21625b7916589c.bin", "should interpolate default tokens"], - [[{}, "[hash:base64]", { content: "test string" }], "2sm1pVmS8xuGJLCdWpJoRL", "should interpolate [hash] token with options"], - [[{}, "[unrecognized]", { content: "test string" }], "[unrecognized]", "should not interpolate unrecognized token"], - [ - [{}, "[emoji]", { content: "test" }], - result => { - assert.ok(emojiRegex.test(result), result); - }, - "should interpolate [emoji]" - ], - [ - [{}, "[emoji:3]", { content: "string" }], - result => { - assert.ok(emojiRegex.test(result), result); - assert.ok(result.length, 6); - }, - "should interpolate [emoji:3]" - ], - ]); + it('should return the same emoji for the same string', () => { + const args = [{}, '[emoji:5]', { content: 'same_emoji' }]; + const result1 = loaderUtils.interpolateName.apply(loaderUtils, args); + const result2 = loaderUtils.interpolateName.apply(loaderUtils, args); + assert.equal(result1, result2); + }); - it("should return the same emoji for the same string", () => { - const args = [{}, "[emoji:5]", { content: "same_emoji" }]; - const result1 = loaderUtils.interpolateName.apply(loaderUtils, args); - const result2 = loaderUtils.interpolateName.apply(loaderUtils, args); - assert.equal(result1, result2); - }); + it('should throw error when out of emoji', () => { + assert.throws( + () => { + loaderUtils.interpolateName.apply(loaderUtils, [ + {}, + '[emoji:5000]', + { content: 'foo' }, + ]); + }, + Error, + 'Ran out of emoji' + ); + }); - it("should throw error when out of emoji", () => { - assert.throws(() => { - loaderUtils.interpolateName.apply(loaderUtils, [{}, "[emoji:5000]", { content: "foo" }]); - }, Error, "Ran out of emoji"); - }); + context('no loader context', () => { + const loaderContext = {}; + run([ + [[loaderContext, '[ext]', {}], 'bin', 'should interpolate [ext] token'], + [ + [loaderContext, '[name]', {}], + 'file', + 'should interpolate [name] token', + ], + [[loaderContext, '[path]', {}], '', 'should interpolate [path] token'], + [ + [loaderContext, '[folder]', {}], + '', + 'should interpolate [folder] token', + ], + ]); + }); - context("no loader context", () => { - const loaderContext = {}; - run([ - [[loaderContext, "[ext]", {}], "bin", "should interpolate [ext] token"], - [[loaderContext, "[name]", {}], "file", "should interpolate [name] token"], - [[loaderContext, "[path]", {}], "", "should interpolate [path] token"], - [[loaderContext, "[folder]", {}], "", "should interpolate [folder] token"] - ]); - }); + context('with loader context', () => { + const loaderContext = { resourcePath: '/path/to/file.exe' }; + run([ + [[loaderContext, '[ext]', {}], 'exe', 'should interpolate [ext] token'], + [ + [loaderContext, '[name]', {}], + 'file', + 'should interpolate [name] token', + ], + [ + [loaderContext, '[path]', {}], + '/path/to/', + 'should interpolate [path] token', + ], + [ + [loaderContext, '[folder]', {}], + 'to', + 'should interpolate [folder] token', + ], + ]); + }); - context("with loader context", () => { - const loaderContext = { resourcePath: "/path/to/file.exe" }; - run([ - [[loaderContext, "[ext]", {}], "exe", "should interpolate [ext] token"], - [[loaderContext, "[name]", {}], "file", "should interpolate [name] token"], - [[loaderContext, "[path]", {}], "/path/to/", "should interpolate [path] token"], - [[loaderContext, "[folder]", {}], "to", "should interpolate [folder] token"] - ]); - }); - - run([ - [[{ - resourcePath: "/xyz", - options: { - customInterpolateName(str, name, options) { - return str + "-" + name + "-" + options.special; - } - } - }, "[name]", { - special: "special" - }], "xyz-[name]-special", "should provide a custom interpolateName function in options"], - ]); + run([ + [ + [ + { + resourcePath: '/xyz', + options: { + customInterpolateName(str, name, options) { + return str + '-' + name + '-' + options.special; + }, + }, + }, + '[name]', + { + special: 'special', + }, + ], + 'xyz-[name]-special', + 'should provide a custom interpolateName function in options', + ], + ]); }); diff --git a/test/isUrlRequest.test.js b/test/isUrlRequest.test.js index 80c4da4..7eb2318 100644 --- a/test/isUrlRequest.test.js +++ b/test/isUrlRequest.test.js @@ -1,73 +1,129 @@ -"use strict"; +'use strict'; -const assert = require("assert"); -const loaderUtils = require("../"); +const assert = require('assert'); +const loaderUtils = require('../'); function ExpectedError(regex) { - this.regex = regex; + this.regex = regex; } ExpectedError.prototype.matches = function(err) { - return this.regex.test(err.message); + return this.regex.test(err.message); }; -describe("isUrlRequest()", () => { - [ - // without root - [["//google.com"], false, "should be negative for scheme-agnostic urls"], - [["http://google.com"], false, "should be negative for http urls"], - [["https://google.com"], false, "should be negative for https urls"], +describe('isUrlRequest()', () => { + [ + // without root + [['//google.com'], false, 'should be negative for scheme-agnostic urls'], + [['http://google.com'], false, 'should be negative for http urls'], + [['https://google.com'], false, 'should be negative for https urls'], - [["chrome-extension://"], false, "should be negative for nonstandard urls"], - [["moz-extension://"], false, "should be negative for nonstandard urls"], - [["ms-browser-extension://"], false, "should be negative for nonstandard urls"], - [["custom-extension://"], false, "should be negative for nonstandard urls"], + [['chrome-extension://'], false, 'should be negative for nonstandard urls'], + [['moz-extension://'], false, 'should be negative for nonstandard urls'], + [ + ['ms-browser-extension://'], + false, + 'should be negative for nonstandard urls', + ], + [['custom-extension://'], false, 'should be negative for nonstandard urls'], - [["path/to/thing"], true, "should be positive for implicit relative urls"], - [["./path/to/thing"], true, "should be positive for explicit relative urls"], - [["~path/to/thing"], true, "should be positive for module urls (with ~)"], - [["some/other/stuff/and/then~path/to/thing"], true, "should be positive for module urls with path prefix"], - [["./some/other/stuff/and/then~path/to/thing"], true, "should be positive for module urls with relative path prefix"], + [['path/to/thing'], true, 'should be positive for implicit relative urls'], + [ + ['./path/to/thing'], + true, + 'should be positive for explicit relative urls', + ], + [['~path/to/thing'], true, 'should be positive for module urls (with ~)'], + [ + ['some/other/stuff/and/then~path/to/thing'], + true, + 'should be positive for module urls with path prefix', + ], + [ + ['./some/other/stuff/and/then~path/to/thing'], + true, + 'should be positive for module urls with relative path prefix', + ], - // with root (normal path) - [["path/to/thing", "root/dir"], true, "should be positive with root if implicit relative url"], - [["./path/to/thing", "root/dir"], true, "should be positive with root if explicit relative url"], - [["/path/to/thing", "root/dir"], true, "should be positive with root if root-relative url"], + // with root (normal path) + [ + ['path/to/thing', 'root/dir'], + true, + 'should be positive with root if implicit relative url', + ], + [ + ['./path/to/thing', 'root/dir'], + true, + 'should be positive with root if explicit relative url', + ], + [ + ['/path/to/thing', 'root/dir'], + true, + 'should be positive with root if root-relative url', + ], - // with root (boolean) - [["/path/to/thing", true], true, "should be positive for root-relative if root = `true`"], + // with root (boolean) + [ + ['/path/to/thing', true], + true, + 'should be positive for root-relative if root = `true`', + ], - // with root (boolean) on Windows - [["C:\\path\\to\\thing"], true, "should be positive for Windows absolute paths with drive letter"], - [["\\\\?\\UNC\\ComputerName\\path\\to\\thing"], true, "should be positive for Windows absolute UNC paths"], + // with root (boolean) on Windows + [ + ['C:\\path\\to\\thing'], + true, + 'should be positive for Windows absolute paths with drive letter', + ], + [ + ['\\\\?\\UNC\\ComputerName\\path\\to\\thing'], + true, + 'should be positive for Windows absolute UNC paths', + ], - // with root (module) - [["/path/to/thing", "~"], true, "should be positive for module url if root = ~"], + // with root (module) + [ + ['/path/to/thing', '~'], + true, + 'should be positive for module url if root = ~', + ], - // with root (module path) - [["/path/to/thing", "~module"], true, "should be positive for module prefixes when root starts with ~"], - [["/path/to/thing", "~module/"], true, "should be positive for module prefixes (with trailing slash) when root starts with ~"], + // with root (module path) + [ + ['/path/to/thing', '~module'], + true, + 'should be positive for module prefixes when root starts with ~', + ], + [ + ['/path/to/thing', '~module/'], + true, + 'should be positive for module prefixes (with trailing slash) when root starts with ~', + ], - // error cases - [["/path/to/thing", 1], new ExpectedError(/unexpected parameters/i), "should throw an error on invalid root"], + // error cases + [ + ['/path/to/thing', 1], + new ExpectedError(/unexpected parameters/i), + 'should throw an error on invalid root', + ], - // empty url - [[""], true, "should be positive if url is empty"], + // empty url + [[''], true, 'should be positive if url is empty'], - // about url - [["about:blank"], false, "should be negative for about:blank"] - ].forEach((test) => { - it(test[2], () => { - const expected = test[1]; - try { - const request = loaderUtils.isUrlRequest.apply(loaderUtils, test[0]); - assert.equal(request, expected); - } catch(e) { - if(expected instanceof ExpectedError) { - assert.ok(expected.matches(e)); - } else { - assert.ok(false, "should not have thrown an error: " + e.message); - } - } - }); - }); + // about url + [['about:blank'], false, 'should be negative for about:blank'], + ].forEach((test) => { + it(test[2], () => { + const expected = test[1]; + try { + const request = loaderUtils.isUrlRequest.apply(loaderUtils, test[0]); + assert.equal(request, expected); + } catch (e) { + if (expected instanceof ExpectedError) { + assert.ok(expected.matches(e)); + } else { + assert.ok(false, 'should not have thrown an error: ' + e.message); + } + } + }); + }); }); diff --git a/test/parseQuery.test.js b/test/parseQuery.test.js index 8ef1e41..67d82dd 100644 --- a/test/parseQuery.test.js +++ b/test/parseQuery.test.js @@ -1,83 +1,85 @@ -"use strict"; +'use strict'; -const assert = require("assert"); -const loaderUtils = require("../"); +const assert = require('assert'); +const loaderUtils = require('../'); -describe("parseQuery()", () => { - describe("when passed string is a query string starting with ?", () => { - [{ - it: "should return an empty object by default", - query: "?", - expected: {} - }, - { - it: "should parse query params", - query: "?name=cheesecake&slices=8&delicious&warm=false", - expected: { - delicious: true, - name: "cheesecake", - slices: "8", // numbers are still strings with query params - warm: false - } - }, - { - it: "should parse query params with arrays", - query: "?ingredients[]=flour&ingredients[]=sugar", - expected: { - ingredients: ["flour", "sugar"] - } - }, - { - it: "should parse query params in JSON format", - query: "?" + JSON.stringify({ - delicious: true, - name: "cheesecake", - slices: 8, - warm: false - }), - expected: { - delicious: true, - name: "cheesecake", - slices: 8, - warm: false - } - }, - { - it: "should use decodeURIComponent", - query: "?%3d", - expected: { "=": true } - }, - { - it: "should recognize params starting with + as boolean params with the value true", - query: "?+%3d", - expected: { "=": true } - }, - { - it: "should recognize params starting with - as boolean params with the value false", - query: "?-%3d", - expected: { "=": false } - }, - { - it: "should not confuse regular equal signs and encoded equal signs", - query: "?%3d=%3D", - expected: { "=": "=" } - }].forEach(test => { - it(test.it, () => { - assert.deepEqual( - loaderUtils.parseQuery(test.query), - test.expected - ); - }); - }); - }); - - describe("when passed string is any other string not starting with ?", () => { - it("should throw an error", () => { - assert.throws( - () => loaderUtils.parseQuery("a"), - /A valid query string passed to parseQuery should begin with '\?'/ - ); - }); - }); +describe('parseQuery()', () => { + describe('when passed string is a query string starting with ?', () => { + [ + { + it: 'should return an empty object by default', + query: '?', + expected: {}, + }, + { + it: 'should parse query params', + query: '?name=cheesecake&slices=8&delicious&warm=false', + expected: { + delicious: true, + name: 'cheesecake', + slices: '8', // numbers are still strings with query params + warm: false, + }, + }, + { + it: 'should parse query params with arrays', + query: '?ingredients[]=flour&ingredients[]=sugar', + expected: { + ingredients: ['flour', 'sugar'], + }, + }, + { + it: 'should parse query params in JSON format', + query: + '?' + + JSON.stringify({ + delicious: true, + name: 'cheesecake', + slices: 8, + warm: false, + }), + expected: { + delicious: true, + name: 'cheesecake', + slices: 8, + warm: false, + }, + }, + { + it: 'should use decodeURIComponent', + query: '?%3d', + expected: { '=': true }, + }, + { + it: + 'should recognize params starting with + as boolean params with the value true', + query: '?+%3d', + expected: { '=': true }, + }, + { + it: + 'should recognize params starting with - as boolean params with the value false', + query: '?-%3d', + expected: { '=': false }, + }, + { + it: 'should not confuse regular equal signs and encoded equal signs', + query: '?%3d=%3D', + expected: { '=': '=' }, + }, + ].forEach((test) => { + it(test.it, () => { + assert.deepEqual(loaderUtils.parseQuery(test.query), test.expected); + }); + }); + }); + describe('when passed string is any other string not starting with ?', () => { + it('should throw an error', () => { + assert.throws( + () => loaderUtils.parseQuery('a'), + /A valid query string passed to parseQuery should begin with '\?'/ + ); + }); + }); }); diff --git a/test/parseString.test.js b/test/parseString.test.js index 7ea14c9..f4f8cff 100644 --- a/test/parseString.test.js +++ b/test/parseString.test.js @@ -1,19 +1,22 @@ -"use strict"; +'use strict'; -const assert = require("assert"); -const loaderUtils = require("../"); +const assert = require('assert'); +const loaderUtils = require('../'); -describe("parseString()", () => { - [ - ["test string", "test string"], - [JSON.stringify("!\"§$%&/()=?'*#+,.-;öäü:_test"), "!\"§$%&/()=?'*#+,.-;öäü:_test"], - ["'escaped with single \"'", "escaped with single \""], - ["invalid \"' string", "invalid \"' string"], - ["'inconsistent start and end\"", "'inconsistent start and end\""] - ].forEach(test => { - it("should parse " + test[0], () => { - const parsed = loaderUtils.parseString(test[0]); - assert.equal(parsed, test[1]); - }); - }); +describe('parseString()', () => { + [ + ['test string', 'test string'], + [ + JSON.stringify('!"§$%&/()=?\'*#+,.-;öäü:_test'), + '!"§$%&/()=?\'*#+,.-;öäü:_test', + ], + ["'escaped with single \"'", 'escaped with single "'], + ['invalid "\' string', 'invalid "\' string'], + ['\'inconsistent start and end"', '\'inconsistent start and end"'], + ].forEach((test) => { + it('should parse ' + test[0], () => { + const parsed = loaderUtils.parseString(test[0]); + assert.equal(parsed, test[1]); + }); + }); }); diff --git a/test/stringifyRequest.test.js b/test/stringifyRequest.test.js index ab0a4a9..798e519 100644 --- a/test/stringifyRequest.test.js +++ b/test/stringifyRequest.test.js @@ -1,109 +1,190 @@ -"use strict"; +'use strict'; -const assert = require("assert"); -const path = require("path"); -const loaderUtils = require("../"); +const assert = require('assert'); +const path = require('path'); +const loaderUtils = require('../'); const s = JSON.stringify; -describe("stringifyRequest()", () => { - // We know that query strings that contain paths and question marks can be problematic. - // We must ensure that stringifyRequest is not messing with them - const paramQueryString = "?questionMark?posix=path/to/thing&win=path\\to\\thing"; - const jsonQueryString = "?" + s({ - questionMark: "?", - posix: "path/to/thing", - win: "path\\to\\file" - }); - [ - { test: 1, request: "./a.js", expected: s("./a.js") }, - { test: 2, request: ".\\a.js", expected: s("./a.js") }, - { test: 3, request: "./a/b.js", expected: s("./a/b.js") }, - { test: 4, request: ".\\a\\b.js", expected: s("./a/b.js") }, - { test: 5, request: "module", expected: s("module") }, // without ./ is a request into the modules directory - { test: 6, request: "module/a.js", expected: s("module/a.js") }, - { test: 7, request: "module\\a.js", expected: s("module/a.js") }, - { test: 8, request: "./a.js" + paramQueryString, expected: s("./a.js" + paramQueryString) }, - { test: 9, request: "./a.js" + jsonQueryString, expected: s("./a.js" + jsonQueryString) }, - { test: 10, request: "module" + paramQueryString, expected: s("module" + paramQueryString) }, - { test: 11, request: "module" + jsonQueryString, expected: s("module" + jsonQueryString) }, - { test: 12, os: "posix", context: "/path/to", request: "/path/to/module/a.js", expected: s("./module/a.js") }, - { test: 13, os: "win32", context: "C:\\path\\to\\", request: "C:\\path\\to\\module\\a.js", expected: s("./module/a.js") }, - { test: 14, os: "posix", context: "/path/to/thing", request: "/path/to/module/a.js", expected: s("../module/a.js") }, - { test: 15, os: "win32", context: "C:\\path\\to\\thing", request: "C:\\path\\to\\module\\a.js", expected: s("../module/a.js") }, - { test: 16, os: "win32", context: "\\\\A\\path\\to\\thing", request: "\\\\A\\path\\to\\module\\a.js", expected: s("../module/a.js") }, - // If context and request are on different drives, the path should not be relative - // @see https://github.com/webpack/loader-utils/pull/14 - { test: 17, os: "win32", context: "D:\\path\\to\\thing", request: "C:\\path\\to\\module\\a.js", expected: s("C:\\path\\to\\module\\a.js") }, - { test: 18, os: "win32", context: "\\\\A\\path\\to\\thing", request: "\\\\B\\path\\to\\module\\a.js", expected: s("\\\\B\\path\\to\\module\\a.js") }, - { - test: 19, - os: "posix", - context: "/path/to", - request: "/path/to/module/a.js" + paramQueryString, - expected: s("./module/a.js" + paramQueryString) - }, - { - test: 20, - os: "win32", - context: "C:\\path\\to\\", - request: "C:\\path\\to\\module\\a.js" + paramQueryString, - expected: s("./module/a.js" + paramQueryString) - }, - { - test: 21, - request: - ["./a.js", "./b.js", "./c.js"].join("!"), - expected: s( - ["./a.js", "./b.js", "./c.js"].join("!") - ) - }, - { - test: 22, - request: - ["a/b.js", "c/d.js", "e/f.js", "g"].join("!"), - expected: s( - ["a/b.js", "c/d.js", "e/f.js", "g"].join("!") - ) - }, - { - test: 23, - request: - ["a/b.js" + paramQueryString, "c/d.js" + jsonQueryString, "e/f.js"].join("!"), - expected: s( - ["a/b.js" + paramQueryString, "c/d.js" + jsonQueryString, "e/f.js"].join("!") - ) - }, - { - test: 24, - os: "posix", - context: "/path/to", - request: - ["/a/b.js" + paramQueryString, "c/d.js" + jsonQueryString, "/path/to/e/f.js"].join("!"), - expected: s( - ["../../a/b.js" + paramQueryString, "c/d.js" + jsonQueryString, "./e/f.js"].join("!") - ) - }, - { - test: 25, - os: "win32", - context: "C:\\path\\to\\", - request: - ["C:\\a\\b.js" + paramQueryString, "c\\d.js" + jsonQueryString, "C:\\path\\to\\e\\f.js"].join("!"), - expected: s( - ["../../a/b.js" + paramQueryString, "c/d.js" + jsonQueryString, "./e/f.js"].join("!") - ) - } - ].forEach(testCase => { - it(`${ testCase.test }. should stringify request ${ testCase.request } to ${ testCase.expected } inside context ${ testCase.context }`, () => { - const relative = path.relative; - if(testCase.os) { - // monkey patch path.relative in order to make this test work in every OS - path.relative = path[testCase.os].relative; - } - const actual = loaderUtils.stringifyRequest({ context: testCase.context }, testCase.request); - assert.equal(actual, testCase.expected); - path.relative = relative; - }); - }); +describe('stringifyRequest()', () => { + // We know that query strings that contain paths and question marks can be problematic. + // We must ensure that stringifyRequest is not messing with them + const paramQueryString = + '?questionMark?posix=path/to/thing&win=path\\to\\thing'; + const jsonQueryString = + '?' + + s({ + questionMark: '?', + posix: 'path/to/thing', + win: 'path\\to\\file', + }); + [ + { test: 1, request: './a.js', expected: s('./a.js') }, + { test: 2, request: '.\\a.js', expected: s('./a.js') }, + { test: 3, request: './a/b.js', expected: s('./a/b.js') }, + { test: 4, request: '.\\a\\b.js', expected: s('./a/b.js') }, + { test: 5, request: 'module', expected: s('module') }, // without ./ is a request into the modules directory + { test: 6, request: 'module/a.js', expected: s('module/a.js') }, + { test: 7, request: 'module\\a.js', expected: s('module/a.js') }, + { + test: 8, + request: './a.js' + paramQueryString, + expected: s('./a.js' + paramQueryString), + }, + { + test: 9, + request: './a.js' + jsonQueryString, + expected: s('./a.js' + jsonQueryString), + }, + { + test: 10, + request: 'module' + paramQueryString, + expected: s('module' + paramQueryString), + }, + { + test: 11, + request: 'module' + jsonQueryString, + expected: s('module' + jsonQueryString), + }, + { + test: 12, + os: 'posix', + context: '/path/to', + request: '/path/to/module/a.js', + expected: s('./module/a.js'), + }, + { + test: 13, + os: 'win32', + context: 'C:\\path\\to\\', + request: 'C:\\path\\to\\module\\a.js', + expected: s('./module/a.js'), + }, + { + test: 14, + os: 'posix', + context: '/path/to/thing', + request: '/path/to/module/a.js', + expected: s('../module/a.js'), + }, + { + test: 15, + os: 'win32', + context: 'C:\\path\\to\\thing', + request: 'C:\\path\\to\\module\\a.js', + expected: s('../module/a.js'), + }, + { + test: 16, + os: 'win32', + context: '\\\\A\\path\\to\\thing', + request: '\\\\A\\path\\to\\module\\a.js', + expected: s('../module/a.js'), + }, + // If context and request are on different drives, the path should not be relative + // @see https://github.com/webpack/loader-utils/pull/14 + { + test: 17, + os: 'win32', + context: 'D:\\path\\to\\thing', + request: 'C:\\path\\to\\module\\a.js', + expected: s('C:\\path\\to\\module\\a.js'), + }, + { + test: 18, + os: 'win32', + context: '\\\\A\\path\\to\\thing', + request: '\\\\B\\path\\to\\module\\a.js', + expected: s('\\\\B\\path\\to\\module\\a.js'), + }, + { + test: 19, + os: 'posix', + context: '/path/to', + request: '/path/to/module/a.js' + paramQueryString, + expected: s('./module/a.js' + paramQueryString), + }, + { + test: 20, + os: 'win32', + context: 'C:\\path\\to\\', + request: 'C:\\path\\to\\module\\a.js' + paramQueryString, + expected: s('./module/a.js' + paramQueryString), + }, + { + test: 21, + request: ['./a.js', './b.js', './c.js'].join('!'), + expected: s(['./a.js', './b.js', './c.js'].join('!')), + }, + { + test: 22, + request: ['a/b.js', 'c/d.js', 'e/f.js', 'g'].join('!'), + expected: s(['a/b.js', 'c/d.js', 'e/f.js', 'g'].join('!')), + }, + { + test: 23, + request: [ + 'a/b.js' + paramQueryString, + 'c/d.js' + jsonQueryString, + 'e/f.js', + ].join('!'), + expected: s( + [ + 'a/b.js' + paramQueryString, + 'c/d.js' + jsonQueryString, + 'e/f.js', + ].join('!') + ), + }, + { + test: 24, + os: 'posix', + context: '/path/to', + request: [ + '/a/b.js' + paramQueryString, + 'c/d.js' + jsonQueryString, + '/path/to/e/f.js', + ].join('!'), + expected: s( + [ + '../../a/b.js' + paramQueryString, + 'c/d.js' + jsonQueryString, + './e/f.js', + ].join('!') + ), + }, + { + test: 25, + os: 'win32', + context: 'C:\\path\\to\\', + request: [ + 'C:\\a\\b.js' + paramQueryString, + 'c\\d.js' + jsonQueryString, + 'C:\\path\\to\\e\\f.js', + ].join('!'), + expected: s( + [ + '../../a/b.js' + paramQueryString, + 'c/d.js' + jsonQueryString, + './e/f.js', + ].join('!') + ), + }, + ].forEach((testCase) => { + it(`${testCase.test}. should stringify request ${testCase.request} to ${ + testCase.expected + } inside context ${testCase.context}`, () => { + const relative = path.relative; + if (testCase.os) { + // monkey patch path.relative in order to make this test work in every OS + path.relative = path[testCase.os].relative; + } + const actual = loaderUtils.stringifyRequest( + { context: testCase.context }, + testCase.request + ); + assert.equal(actual, testCase.expected); + path.relative = relative; + }); + }); }); diff --git a/test/urlToRequest.test.js b/test/urlToRequest.test.js index 40b5794..5ede29d 100644 --- a/test/urlToRequest.test.js +++ b/test/urlToRequest.test.js @@ -1,59 +1,119 @@ -"use strict"; +'use strict'; -const assert = require("assert"); -const loaderUtils = require("../"); +const assert = require('assert'); +const loaderUtils = require('../'); function ExpectedError(regex) { - this.regex = regex; + this.regex = regex; } ExpectedError.prototype.matches = function(err) { - return this.regex.test(err.message); + return this.regex.test(err.message); }; -describe("urlToRequest()", () => { - [ - // without root - [["//google.com"], "//google.com", "should handle scheme-agnostic urls"], - [["http://google.com"], "http://google.com", "should handle http urls"], - [["https://google.com"], "https://google.com", "should handle https urls"], - [["path/to/thing"], "./path/to/thing", "should handle implicit relative urls"], - [["./path/to/thing"], "./path/to/thing", "should handle explicit relative urls"], - [["~path/to/thing"], "path/to/thing", "should handle module urls (with ~)"], - [["some/other/stuff/and/then~path/to/thing"], "path/to/thing", "should handle module urls with path prefix"], - [["./some/other/stuff/and/then~path/to/thing"], "path/to/thing", "should handle module urls with relative path prefix"], - // with root (normal path) - [["path/to/thing", "root/dir"], "./path/to/thing", "should do nothing with root if implicit relative url"], - [["./path/to/thing", "root/dir"], "./path/to/thing", "should do nothing with root if explicit relative url"], - [["/path/to/thing", "root/dir"], "root/dir/path/to/thing", "should include root if root-relative url"], - // with root (boolean) - [["/path/to/thing", true], "/path/to/thing", "should allow root-relative to exist as-is if root = `true`"], - // with root (boolean) on Windows - [["C:\\path\\to\\thing"], "C:\\path\\to\\thing", "should handle Windows absolute paths with drive letter"], - [["\\\\?\\UNC\\ComputerName\\path\\to\\thing"], "\\\\?\\UNC\\ComputerName\\path\\to\\thing", "should handle Windows absolute UNC paths"], - // with root (module) - [["/path/to/thing", "~"], "path/to/thing", "should convert to module url if root = ~"], - // with root (module path) - [["/path/to/thing", "~module"], "module/path/to/thing", "should allow module prefixes when root starts with ~"], - [["/path/to/thing", "~module/"], "module/path/to/thing", "should allow module prefixes (with trailing slash) when root starts with ~"], - // error cases - [["/path/to/thing", 1], new ExpectedError(/unexpected parameters/i), "should throw an error on invalid root"], - // difficult cases - [["a:b-not-\\window-path"], "./a:b-not-\\window-path", "should not incorrectly detect windows paths"], - // empty url - [[""], "", "should do nothing if url is empty"] - ].forEach((test) => { - it(test[2], () => { - const expected = test[1]; - try { - const request = loaderUtils.urlToRequest.apply(loaderUtils, test[0]); - assert.equal(request, expected); - } catch(e) { - if(expected instanceof ExpectedError) { - assert.ok(expected.matches(e)); - } else { - assert.ok(false, "should not have thrown an error: " + e.message); - } - } - }); - }); +describe('urlToRequest()', () => { + [ + // without root + [['//google.com'], '//google.com', 'should handle scheme-agnostic urls'], + [['http://google.com'], 'http://google.com', 'should handle http urls'], + [['https://google.com'], 'https://google.com', 'should handle https urls'], + [ + ['path/to/thing'], + './path/to/thing', + 'should handle implicit relative urls', + ], + [ + ['./path/to/thing'], + './path/to/thing', + 'should handle explicit relative urls', + ], + [['~path/to/thing'], 'path/to/thing', 'should handle module urls (with ~)'], + [ + ['some/other/stuff/and/then~path/to/thing'], + 'path/to/thing', + 'should handle module urls with path prefix', + ], + [ + ['./some/other/stuff/and/then~path/to/thing'], + 'path/to/thing', + 'should handle module urls with relative path prefix', + ], + // with root (normal path) + [ + ['path/to/thing', 'root/dir'], + './path/to/thing', + 'should do nothing with root if implicit relative url', + ], + [ + ['./path/to/thing', 'root/dir'], + './path/to/thing', + 'should do nothing with root if explicit relative url', + ], + [ + ['/path/to/thing', 'root/dir'], + 'root/dir/path/to/thing', + 'should include root if root-relative url', + ], + // with root (boolean) + [ + ['/path/to/thing', true], + '/path/to/thing', + 'should allow root-relative to exist as-is if root = `true`', + ], + // with root (boolean) on Windows + [ + ['C:\\path\\to\\thing'], + 'C:\\path\\to\\thing', + 'should handle Windows absolute paths with drive letter', + ], + [ + ['\\\\?\\UNC\\ComputerName\\path\\to\\thing'], + '\\\\?\\UNC\\ComputerName\\path\\to\\thing', + 'should handle Windows absolute UNC paths', + ], + // with root (module) + [ + ['/path/to/thing', '~'], + 'path/to/thing', + 'should convert to module url if root = ~', + ], + // with root (module path) + [ + ['/path/to/thing', '~module'], + 'module/path/to/thing', + 'should allow module prefixes when root starts with ~', + ], + [ + ['/path/to/thing', '~module/'], + 'module/path/to/thing', + 'should allow module prefixes (with trailing slash) when root starts with ~', + ], + // error cases + [ + ['/path/to/thing', 1], + new ExpectedError(/unexpected parameters/i), + 'should throw an error on invalid root', + ], + // difficult cases + [ + ['a:b-not-\\window-path'], + './a:b-not-\\window-path', + 'should not incorrectly detect windows paths', + ], + // empty url + [[''], '', 'should do nothing if url is empty'], + ].forEach((test) => { + it(test[2], () => { + const expected = test[1]; + try { + const request = loaderUtils.urlToRequest.apply(loaderUtils, test[0]); + assert.equal(request, expected); + } catch (e) { + if (expected instanceof ExpectedError) { + assert.ok(expected.matches(e)); + } else { + assert.ok(false, 'should not have thrown an error: ' + e.message); + } + } + }); + }); }); diff --git a/yarn.lock b/yarn.lock index 0c556b6..c39d179 100644 --- a/yarn.lock +++ b/yarn.lock @@ -691,6 +691,13 @@ eslint-plugin-node@^8.0.0: resolve "^1.8.1" semver "^5.5.0" +eslint-plugin-prettier@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.0.0.tgz#f6b823e065f8c36529918cdb766d7a0e975ec30c" + integrity sha512-4g11opzhqq/8+AMmo5Vc2Gn7z9alZ4JqrbZ+D4i8KlSyxeQhZHlmIrY8U9Akf514MoEhogPa87Jgkq87aZ2Ohw== + dependencies: + prettier-linter-helpers "^1.0.0" + eslint-scope@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" @@ -842,6 +849,11 @@ fast-deep-equal@^2.0.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" @@ -1839,6 +1851,18 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +prettier@^1.15.3: + version "1.15.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a" + integrity sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg== + process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"