diff --git a/.npmrc b/.npmrc index c1ca392..1b8d617 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ package-lock = false +@jsr:registry=https://npm.jsr.io diff --git a/packages/config-array/README.md b/packages/config-array/README.md index 972093f..9060449 100644 --- a/packages/config-array/README.md +++ b/packages/config-array/README.md @@ -170,7 +170,7 @@ export default [ In this example, the array contains both config objects and a config array. When a config array is normalized (see details below), it is flattened so only config objects remain. However, the order of evaluation remains the same. -If the `files` array contains a function, then that function is called with the absolute path of the file and is expected to return `true` if there is a match and `false` if not. (The `ignores` array can also contain functions.) +If the `files` array contains a function, then that function is called with the path of the file as it was passed in. The function is expected to return `true` if there is a match and `false` if not. (The `ignores` array can also contain functions.) If the `files` array contains an item that is an array of strings and functions, then all patterns must match in order for the config to match. In the preceding examples, both `*.test.*` and `*.js` must match in order for the config object to be used. @@ -273,7 +273,7 @@ await configs.normalizeSync({ To get the config for a file, use the `getConfig()` method on a normalized config array and pass in the filename to get a config for: ```js -// pass in absolute filename +// pass in filename const fileConfig = configs.getConfig( path.resolve(process.cwd(), "package.json"), ); @@ -283,14 +283,14 @@ The config array always returns an object, even if there are no configs matching A few things to keep in mind: -- You must pass in the absolute filename to get a config for. +- If a filename is not an absolute path, it will be resolved relative to the base path directory. - The returned config object never has `files`, `ignores`, or `name` properties; the only properties on the object will be the other configuration options specified. - The config array caches configs, so subsequent calls to `getConfig()` with the same filename will return in a fast lookup rather than another calculation. - A config will only be generated if the filename matches an entry in a `files` key. A config will not be generated without matching a `files` key (configs without a `files` key are only applied when another config with a `files` key is applied; configs without `files` are never applied on their own). Any config with a `files` key entry that is `*` or ends with `/**` or `/*` will only be applied if another entry in the same `files` key matches or another config matches. ## Determining Ignored Paths -You can determine if a file is ignored by using the `isFileIgnored()` method and passing in the absolute path of any file, as in this example: +You can determine if a file is ignored by using the `isFileIgnored()` method and passing in the path of any file, as in this example: ```js const ignored = configs.isFileIgnored("/foo/bar/baz.txt"); @@ -304,7 +304,7 @@ A file is considered ignored if any of the following is true: - **If it matches an entry in `files` and also in `ignores`.** For example, if `**/*.js` is in `files` and `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored. - **The file is outside the `basePath`.** If the `basePath` is `/usr/me`, then `/foo/a.js` is considered ignored. -For directories, use the `isDirectoryIgnored()` method and pass in the absolute path of any directory, as in this example: +For directories, use the `isDirectoryIgnored()` method and pass in the path of any directory, as in this example: ```js const ignored = configs.isDirectoryIgnored("/foo/bar/"); diff --git a/packages/config-array/fix-std__path-imports.js b/packages/config-array/fix-std__path-imports.js new file mode 100644 index 0000000..9966e0b --- /dev/null +++ b/packages/config-array/fix-std__path-imports.js @@ -0,0 +1,26 @@ +/* + * Replace import specifiers in "dist" modules to use the bundled versions of "@jsr/std__path". + * + * In "dist/cjs/index.cjs": + * - '@jsr/std__path/posix' → './std__path/posix.cjs' + * - '@jsr/std__path/windows' → './std__path/windows.cjs' + * + * In "dist/esm/index.js": + * - '@jsr/std__path/posix' → './std__path/posix.js' + * - '@jsr/std__path/windows' → './std__path/windows.js' + */ + +import { readFile, writeFile } from "node:fs/promises"; + +async function replaceInFile(file, search, replacement) { + let text = await readFile(file, "utf-8"); + text = text.replace(search, replacement); + await writeFile(file, text); +} + +const SEARCH_REGEXP = /'@jsr\/std__path\/(.+?)'/gu; + +await Promise.all([ + replaceInFile("dist/cjs/index.cjs", SEARCH_REGEXP, "'./std__path/$1.cjs'"), + replaceInFile("dist/esm/index.js", SEARCH_REGEXP, "'./std__path/$1.js'"), +]); diff --git a/packages/config-array/jsr.json b/packages/config-array/jsr.json index 5e4bd32..c2b2fdd 100644 --- a/packages/config-array/jsr.json +++ b/packages/config-array/jsr.json @@ -8,6 +8,8 @@ "dist/esm/index.d.ts", "dist/esm/types.ts", "dist/esm/types.d.ts", + "dist/esm/std__path/posix.js", + "dist/esm/std__path/windows.js", "README.md", "jsr.json", "LICENSE" diff --git a/packages/config-array/package.json b/packages/config-array/package.json index 2dfdd5e..246ce5e 100644 --- a/packages/config-array/package.json +++ b/packages/config-array/package.json @@ -33,7 +33,8 @@ "scripts": { "build:dedupe-types": "node ../../tools/dedupe-types.js dist/cjs/index.cjs dist/esm/index.js", "build:cts": "node -e \"fs.copyFileSync('dist/esm/index.d.ts', 'dist/cjs/index.d.cts')\"", - "build": "rollup -c && npm run build:dedupe-types && tsc -p tsconfig.esm.json && npm run build:cts", + "build:std__path": "rollup -c rollup.std__path-config.js && node fix-std__path-imports", + "build": "rollup -c && npm run build:dedupe-types && tsc -p tsconfig.esm.json && npm run build:cts && npm run build:std__path", "test:jsr": "npx jsr@latest publish --dry-run", "pretest": "npm run build", "test": "mocha tests/", @@ -51,6 +52,7 @@ "minimatch": "^3.1.2" }, "devDependencies": { + "@jsr/std__path": "^1.0.4", "@types/minimatch": "^3.0.5", "c8": "^9.1.0", "mocha": "^10.4.0", diff --git a/packages/config-array/rollup.std__path-config.js b/packages/config-array/rollup.std__path-config.js new file mode 100644 index 0000000..2a9317e --- /dev/null +++ b/packages/config-array/rollup.std__path-config.js @@ -0,0 +1,32 @@ +import { createRequire } from "node:module"; + +const { resolve } = createRequire(import.meta.url); + +export default [ + { + input: resolve("@jsr/std__path/posix"), + output: [ + { + file: "./dist/cjs/std__path/posix.cjs", + format: "cjs", + }, + { + file: "./dist/esm/std__path/posix.js", + format: "esm", + }, + ], + }, + { + input: resolve("@jsr/std__path/windows"), + output: [ + { + file: "./dist/cjs/std__path/windows.cjs", + format: "cjs", + }, + { + file: "./dist/esm/std__path/windows.js", + format: "esm", + }, + ], + }, +]; diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index 51a525b..7d55640 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -7,7 +7,8 @@ // Imports //------------------------------------------------------------------------------ -import path from "node:path"; +import * as posixPath from "@jsr/std__path/posix"; +import * as windowsPath from "@jsr/std__path/windows"; import minimatch from "minimatch"; import createDebug from "debug"; @@ -24,6 +25,7 @@ import { filesAndIgnoresSchema } from "./files-and-ignores-schema.js"; /** @typedef {import("./types.ts").ConfigObject} ConfigObject */ /** @typedef {import("minimatch").IMinimatchStatic} IMinimatchStatic */ /** @typedef {import("minimatch").IMinimatch} IMinimatch */ +/** @typedef {import("@jsr/std__path")} PathImpl */ /* * This is a bit of a hack to make TypeScript happy with the Rollup-created @@ -89,6 +91,9 @@ const CONFIG_WITH_STATUS_UNCONFIGURED = Object.freeze({ status: "unconfigured", }); +// Match two leading dots followed by a slash or the end of input. +const EXTERNAL_PATH_REGEX = /^\.\.(?:\/|$)/u; + /** * Wrapper error for config validation errors that adds a name to the front of the * error message. @@ -347,16 +352,12 @@ function normalizeSync(items, context, extraConfigTypes) { * Determines if a given file path should be ignored based on the given * matcher. * @param {Array boolean)>} ignores The ignore patterns to check. - * @param {string} filePath The absolute path of the file to check. - * @param {string} relativeFilePath The relative path of the file to check. + * @param {string} filePath The unprocessed file path to check. + * @param {string} relativeFilePath The path of the file to check relative to the base path, + * using forward slash (`"/"`) as a separator. * @returns {boolean} True if the path should be ignored and false if not. */ function shouldIgnorePath(ignores, filePath, relativeFilePath) { - // all files outside of the basePath are ignored - if (relativeFilePath.startsWith("..")) { - return true; - } - return ignores.reduce((ignored, matcher) => { if (!ignored) { if (typeof matcher === "function") { @@ -386,20 +387,14 @@ function shouldIgnorePath(ignores, filePath, relativeFilePath) { /** * Determines if a given file path is matched by a config based on * `ignores` only. - * @param {string} filePath The absolute file path to check. - * @param {string} basePath The base path for the config. + * @param {string} filePath The unprocessed file path to check. + * @param {string} relativeFilePath The path of the file to check relative to the base path, + * using forward slash (`"/"`) as a separator. * @param {Object} config The config object to check. * @returns {boolean} True if the file path is matched by the config, * false if not. */ -function pathMatchesIgnores(filePath, basePath, config) { - /* - * For both files and ignores, functions are passed the absolute - * file path while strings are compared against the relative - * file path. - */ - const relativeFilePath = path.relative(basePath, filePath); - +function pathMatchesIgnores(filePath, relativeFilePath, config) { return ( Object.keys(config).filter(key => !META_FIELDS.has(key)).length > 1 && !shouldIgnorePath(config.ignores, filePath, relativeFilePath) @@ -411,20 +406,14 @@ function pathMatchesIgnores(filePath, basePath, config) { * has no `files` field, then it matches; otherwise, if a `files` field * is present then we match the globs in `files` and exclude any globs in * `ignores`. - * @param {string} filePath The absolute file path to check. - * @param {string} basePath The base path for the config. + * @param {string} filePath The unprocessed file path to check. + * @param {string} relativeFilePath The path of the file to check relative to the base path, + * using forward slash (`"/"`) as a separator. * @param {Object} config The config object to check. * @returns {boolean} True if the file path is matched by the config, * false if not. */ -function pathMatches(filePath, basePath, config) { - /* - * For both files and ignores, functions are passed the absolute - * file path while strings are compared against the relative - * file path. - */ - const relativeFilePath = path.relative(basePath, filePath); - +function pathMatches(filePath, relativeFilePath, config) { // match both strings and functions function match(pattern) { if (isString(pattern)) { @@ -499,6 +488,44 @@ function assertExtraConfigTypes(extraConfigTypes) { } } +/** + * Returns path-handling implementations for Unix or Windows, depending on a given absolute path. + * @param {string} fileOrDirPath The absolute path to check. + * @returns {PathImpl} Path-handling implementations for the specified path. + * @throws An error is thrown if the specified argument is not an absolute path. + */ +function getPathImpl(fileOrDirPath) { + // Posix absolute paths always start with a slash. + if (fileOrDirPath.startsWith("/")) { + return posixPath; + } + + // Windows absolute paths start with a letter followed by a colon and at least one backslash, + // or with two backslashes in the case of UNC paths. + // Forward slashed are automatically normalized to backslashes. + if (/^(?:[A-Za-z]:[/\\]|[/\\]{2})/u.test(fileOrDirPath)) { + return windowsPath; + } + + throw new Error( + `Expected an absolute path but received "${fileOrDirPath}"`, + ); +} + +/** + * Converts a given path to a relative path with all separator characters replaced by forward slashes (`"/"`). + * @param {string} fileOrDirPath The unprocessed path to convert. + * @param {string} namespacedBasePath The namespaced base path of the directory to which the calculated path shall be relative. + * @param {PathImpl} path Path-handling implementations. + * @returns {string} A relative path with all separator characters replaced by forward slashes. + */ +function toRelativePath(fileOrDirPath, namespacedBasePath, path) { + const fullPath = path.resolve(namespacedBasePath, fileOrDirPath); + const namespacedFullPath = path.toNamespacedPath(fullPath); + const relativePath = path.relative(namespacedBasePath, namespacedFullPath); + return relativePath.replaceAll(path.SEPARATOR, "/"); +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -519,12 +546,25 @@ const dataCache = new WeakMap(); * those config objects. */ export class ConfigArray extends Array { + /** + * The namespaced path of the config file directory. + * @type {string} + */ + #namespacedBasePath; + + /** + * Path-handling implementations. + * @type {PathImpl} + */ + #path; + /** * Creates a new instance of ConfigArray. * @param {Iterable|Function|Object} configs An iterable yielding config * objects, or a config function, or a config object. * @param {Object} options The options for the ConfigArray. - * @param {string} [options.basePath=""] The path of the config file + * @param {string} [options.basePath="/"] The absolute path of the config file directory. + * Defaults to `"/"`. * @param {boolean} [options.normalized=false] Flag indicating if the * configs have already been normalized. * @param {Object} [options.schema] The additional schema @@ -534,7 +574,7 @@ export class ConfigArray extends Array { constructor( configs, { - basePath = "", + basePath = "/", normalized = false, schema: customSchema, extraConfigTypes = [], @@ -560,6 +600,10 @@ export class ConfigArray extends Array { Object.assign({}, customSchema, baseSchema), ); + if (!isString(basePath) || !basePath) { + throw new TypeError("basePath must be a non-empty string"); + } + /** * The path of the config file that this array was loaded from. * This is used to calculate filename matches. @@ -599,6 +643,14 @@ export class ConfigArray extends Array { } else { this.push(configs); } + + // select path-handling implementations depending on the base path + this.#path = getPathImpl(basePath); + + // On Windows, `path.relative()` returns an absolute path when given two paths on different drives. + // The namespaced base path is useful to make sure that calculated relative paths are always relative. + // On Unix, it is identical to the base path. + this.#namespacedBasePath = this.#path.toNamespacedPath(basePath); } /** @@ -786,7 +838,7 @@ export class ConfigArray extends Array { /** * Returns the config object for a given file path and a status that can be used to determine why a file has no config. - * @param {string} filePath The complete path of a file to get a config for. + * @param {string} filePath The path of a file to get a config for. * @returns {{ config?: Object, status: "ignored"|"external"|"unconfigured"|"matched" }} * An object with an optional property `config` and property `status`. * `config` is the config object for the specified file as returned by {@linkcode ConfigArray.getConfig}, @@ -804,9 +856,13 @@ export class ConfigArray extends Array { // check to see if the file is outside the base path - const relativeFilePath = path.relative(this.basePath, filePath); + const relativeFilePath = toRelativePath( + filePath, + this.#namespacedBasePath, + this.#path, + ); - if (relativeFilePath.startsWith("..")) { + if (EXTERNAL_PATH_REGEX.test(relativeFilePath)) { debug(`No config for file ${filePath} outside of base path`); // cache and return result @@ -817,7 +873,7 @@ export class ConfigArray extends Array { // next check to see if the file should be ignored // check if this should be ignored due to its directory - if (this.isDirectoryIgnored(path.dirname(filePath))) { + if (this.isDirectoryIgnored(this.#path.dirname(filePath))) { debug(`Ignoring ${filePath} based on directory pattern`); // cache and return result @@ -842,12 +898,12 @@ export class ConfigArray extends Array { this.forEach((config, index) => { if (!config.files) { if (!config.ignores) { - debug(`Anonymous universal config found for ${filePath}`); + debug(`Universal config found for ${filePath}`); matchingConfigIndices.push(index); return; } - if (pathMatchesIgnores(filePath, this.basePath, config)) { + if (pathMatchesIgnores(filePath, relativeFilePath, config)) { debug( `Matching config found for ${filePath} (based on ignores: ${config.ignores})`, ); @@ -883,7 +939,7 @@ export class ConfigArray extends Array { // check that the config matches without the non-universal files first if ( nonUniversalFiles.length && - pathMatches(filePath, this.basePath, { + pathMatches(filePath, relativeFilePath, { files: nonUniversalFiles, ignores: config.ignores, }) @@ -897,7 +953,7 @@ export class ConfigArray extends Array { // if there wasn't a match then check if it matches with universal files if ( universalFiles.length && - pathMatches(filePath, this.basePath, { + pathMatches(filePath, relativeFilePath, { files: universalFiles, ignores: config.ignores, }) @@ -912,7 +968,7 @@ export class ConfigArray extends Array { } // the normal case - if (pathMatches(filePath, this.basePath, config)) { + if (pathMatches(filePath, relativeFilePath, config)) { debug(`Matching config found for ${filePath}`); matchingConfigIndices.push(index); matchFound = true; @@ -967,7 +1023,7 @@ export class ConfigArray extends Array { /** * Returns the config object for a given file path. - * @param {string} filePath The complete path of a file to get a config for. + * @param {string} filePath The path of a file to get a config for. * @returns {Object|undefined} The config object for this file or `undefined`. */ getConfig(filePath) { @@ -976,7 +1032,7 @@ export class ConfigArray extends Array { /** * Determines whether a file has a config or why it doesn't. - * @param {string} filePath The complete path of the file to check. + * @param {string} filePath The path of the file to check. * @returns {"ignored"|"external"|"unconfigured"|"matched"} One of the following values: * * `"ignored"`: the file is ignored * * `"external"`: the file is outside the base path @@ -989,7 +1045,7 @@ export class ConfigArray extends Array { /** * Determines if the given filepath is ignored based on the configs. - * @param {string} filePath The complete path of a file to check. + * @param {string} filePath The path of a file to check. * @returns {boolean} True if the path is ignored, false if not. * @deprecated Use `isFileIgnored` instead. */ @@ -999,7 +1055,7 @@ export class ConfigArray extends Array { /** * Determines if the given filepath is ignored based on the configs. - * @param {string} filePath The complete path of a file to check. + * @param {string} filePath The path of a file to check. * @returns {boolean} True if the path is ignored, false if not. */ isFileIgnored(filePath) { @@ -1012,7 +1068,7 @@ export class ConfigArray extends Array { * same config. A pattern such as `/foo` be considered to ignore the directory * while a pattern such as `/foo/**` is not considered to ignore the * directory because it is matching files. - * @param {string} directoryPath The complete path of a directory to check. + * @param {string} directoryPath The path of a directory to check. * @returns {boolean} True if the directory is ignored, false if not. Will * return true for any directory that is not inside of `basePath`. * @throws {Error} When the `ConfigArray` is not normalized. @@ -1020,16 +1076,18 @@ export class ConfigArray extends Array { isDirectoryIgnored(directoryPath) { assertNormalized(this); - const relativeDirectoryPath = path - .relative(this.basePath, directoryPath) - .replace(/\\/gu, "/"); + const relativeDirectoryPath = toRelativePath( + directoryPath, + this.#namespacedBasePath, + this.#path, + ); // basePath directory can never be ignored if (relativeDirectoryPath === "") { return false; } - if (relativeDirectoryPath.startsWith("..")) { + if (EXTERNAL_PATH_REGEX.test(relativeDirectoryPath)) { return true; } @@ -1058,7 +1116,7 @@ export class ConfigArray extends Array { result = shouldIgnorePath( this.ignores, - path.join(this.basePath, relativeDirectoryToCheck), + this.#path.join(this.basePath, relativeDirectoryToCheck), relativeDirectoryToCheck, ); diff --git a/packages/config-array/tests/config-array.test.js b/packages/config-array/tests/config-array.test.js index 11a0961..fbfb0c2 100644 --- a/packages/config-array/tests/config-array.test.js +++ b/packages/config-array/tests/config-array.test.js @@ -8,15 +8,15 @@ //----------------------------------------------------------------------------- import { ConfigArray, ConfigArraySymbol } from "../src/config-array.js"; -import path from "node:path"; import assert from "node:assert"; +import { fileURLToPath } from "node:url"; //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- // calculate base path using import.meta -const basePath = path.dirname(new URL(import.meta.url).pathname); +const basePath = fileURLToPath(new URL(".", import.meta.url)); const schema = { language: { @@ -413,7 +413,7 @@ describe("ConfigArray", () => { 'Config "foo": Key "ignores": Expected array to only contain strings and functions.', }); - it("should throw an error when a config is not an object", async () => { + it("should throw an error when a config is not an object", () => { configs = new ConfigArray( [ { @@ -443,7 +443,7 @@ describe("ConfigArray", () => { await configs.normalize(); assert.throws(() => { - configs.getConfig(path.resolve(basePath, "foo.js")); + configs.getConfig("foo.js"); }, 'Config Error: Config (unnamed): Key "name": Property must be a string.'); }); @@ -457,11 +457,11 @@ describe("ConfigArray", () => { await configs.normalize(); assert.throws(() => { - configs.getConfig(path.resolve(basePath, "foo.js")); + configs.getConfig("foo.js"); }, 'Config Error: Config (unnamed): Key "name": Property must be a string.'); }); - it("should throw an error when base config is undefined", async () => { + it("should throw an error when base config is undefined", () => { configs = new ConfigArray([undefined], { basePath }); assert.throws(() => { @@ -469,7 +469,7 @@ describe("ConfigArray", () => { }, "ConfigError: Config (unnamed): Unexpected undefined config."); }); - it("should throw an error when base config is null", async () => { + it("should throw an error when base config is null", () => { configs = new ConfigArray([null], { basePath }); assert.throws(() => { @@ -477,7 +477,7 @@ describe("ConfigArray", () => { }, "Config Error: Config (unnamed): Unexpected null config."); }); - it("should throw an error when additional config is undefined", async () => { + it("should throw an error when additional config is undefined", () => { configs = new ConfigArray([{}], { basePath }); configs.push(undefined); @@ -486,7 +486,7 @@ describe("ConfigArray", () => { }, "Config Error: Config (unnamed): Unexpected undefined config."); }); - it("should throw an error when additional config is null", async () => { + it("should throw an error when additional config is null", () => { configs = new ConfigArray([{}], { basePath }); configs.push(null); @@ -494,6 +494,36 @@ describe("ConfigArray", () => { configs.normalizeSync(); }, "Config Error: Config (unnamed): Unexpected null config."); }); + + it("should throw an error when basePath is a relative path", () => { + assert.throws(() => { + void new ConfigArray([{}], { basePath: "foo/bar" }); + }, /Expected an absolute path/u); + }); + + it("should throw an error when basePath is an empty string", () => { + assert.throws( + () => { + void new ConfigArray([{}], { basePath: "" }); + }, + { + constructor: TypeError, + message: "basePath must be a non-empty string", + }, + ); + }); + + it("should throw an error when basePath is not a string", () => { + assert.throws( + () => { + void new ConfigArray([{}], { basePath: ["/tmp/foo"] }); + }, + { + constructor: TypeError, + message: "basePath must be a non-empty string", + }, + ); + }); }); describe("ConfigArray members", () => { @@ -515,12 +545,12 @@ describe("ConfigArray", () => { name: "from-context", }); - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; const config = configs.getConfig(filename); assert.strictEqual(config.name, "from-finalize"); }); - it("should allow finalizeConfig to alter config before returning when calling normalizeSync()", async () => { + it("should allow finalizeConfig to alter config before returning when calling normalizeSync()", () => { configs = createConfigArray(); configs[ConfigArraySymbol.finalizeConfig] = () => ({ name: "from-finalize", @@ -530,7 +560,7 @@ describe("ConfigArray", () => { name: "from-context", }); - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; const config = configs.getConfig(filename); assert.strictEqual(config.name, "from-finalize"); }); @@ -557,7 +587,7 @@ describe("ConfigArray", () => { name: "from-context", }); - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; const config = configs.getConfig(filename); assert.strictEqual(config.defs.name, "foo:bar"); }); @@ -590,7 +620,7 @@ describe("ConfigArray", () => { assert.strictEqual(internalThis, configs); }); - it('should have "this" inside of function be equal to config array when calling normalizeSync()', async () => { + it('should have "this" inside of function be equal to config array when calling normalizeSync()', () => { configs = createConfigArray(); configs.push("foo:bar"); let internalThis; @@ -624,6 +654,10 @@ describe("ConfigArray", () => { assert.strictEqual(unnormalizedConfigs.basePath, basePath); assert.strictEqual(configs.basePath, basePath); }); + + it("should default basePath property to '/'", () => { + assert.strictEqual(new ConfigArray([]).basePath, "/"); + }); }); describe("isNormalized()", () => { @@ -638,7 +672,7 @@ describe("ConfigArray", () => { describe("getConfigWithStatus()", () => { it("should throw an error when not normalized", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; assert.throws(() => { unnormalizedConfigs.getConfigWithStatus(filename); @@ -647,7 +681,7 @@ describe("ConfigArray", () => { describe("should return expected results", () => { it("for a file outside the base path", () => { - const filename = path.resolve(basePath, "../foo.js"); + const filename = "../foo.js"; const configWithStatus = configs.getConfigWithStatus(filename); @@ -655,7 +689,7 @@ describe("ConfigArray", () => { assert.strictEqual(configWithStatus.config, undefined); assert.strictEqual(configWithStatus.status, "external"); - const newFilename = path.resolve(basePath, "../bar.js"); + const newFilename = "../bar.js"; const newConfigWithStatus = configs.getConfigWithStatus(newFilename); @@ -664,10 +698,7 @@ describe("ConfigArray", () => { }); it("for a file ignored based on directory pattern", () => { - const filename = path.resolve( - basePath, - "node_modules/foo.js", - ); + const filename = "node_modules/foo.js"; const configWithStatus = configs.getConfigWithStatus(filename); @@ -675,10 +706,7 @@ describe("ConfigArray", () => { assert.strictEqual(configWithStatus.config, undefined); assert.strictEqual(configWithStatus.status, "ignored"); - const newFilename = path.resolve( - basePath, - "node_modules/bar.js", - ); + const newFilename = "node_modules/bar.js"; const newConfigWithStatus = configs.getConfigWithStatus(newFilename); @@ -687,7 +715,7 @@ describe("ConfigArray", () => { }); it("for a file ignored based on file pattern", () => { - const filename = path.resolve(basePath, ".gitignore"); + const filename = ".gitignore"; const configWithStatus = configs.getConfigWithStatus(filename); @@ -695,10 +723,7 @@ describe("ConfigArray", () => { assert.strictEqual(configWithStatus.config, undefined); assert.strictEqual(configWithStatus.status, "ignored"); - const newFilename = path.resolve( - basePath, - "dir/.gitignore", - ); + const newFilename = "dir/.gitignore"; const newConfigWithStatus = configs.getConfigWithStatus(newFilename); @@ -717,7 +742,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve(basePath, "foo.bar"); + const filename = "foo.bar"; const configWithStatus = configs.getConfigWithStatus(filename); @@ -725,7 +750,7 @@ describe("ConfigArray", () => { assert.strictEqual(configWithStatus.config, undefined); assert.strictEqual(configWithStatus.status, "unconfigured"); - const newFilename = path.resolve(basePath, "foo.baz"); + const newFilename = "foo.baz"; const newConfigWithStatus = configs.getConfigWithStatus(newFilename); @@ -734,7 +759,7 @@ describe("ConfigArray", () => { }); it("for a file with a config", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; const configWithStatus = configs.getConfigWithStatus(filename); const { config } = configWithStatus; @@ -748,7 +773,7 @@ describe("ConfigArray", () => { describe("getConfig()", () => { it("should throw an error when not normalized", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; assert.throws(() => { unnormalizedConfigs.getConfig(filename); @@ -756,7 +781,7 @@ describe("ConfigArray", () => { }); it("should calculate correct config when passed JS filename", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; const config = configs.getConfig(filename); assert.strictEqual(config.language, JSLanguage); @@ -767,7 +792,7 @@ describe("ConfigArray", () => { }); it("should calculate correct config when passed XYZ filename", () => { - const filename = path.resolve(basePath, "tests/.bar/foo.xyz"); + const filename = "tests/.bar/foo.xyz"; const config = configs.getConfig(filename); @@ -778,7 +803,7 @@ describe("ConfigArray", () => { }); it("should calculate correct config when passed HTML filename", () => { - const filename = path.resolve(basePath, "foo.html"); + const filename = "foo.html"; const config = configs.getConfig(filename); @@ -788,7 +813,7 @@ describe("ConfigArray", () => { }); it("should return undefined when passed ignored .gitignore filename", () => { - const filename = path.resolve(basePath, ".gitignore"); + const filename = ".gitignore"; const config = configs.getConfig(filename); @@ -796,7 +821,7 @@ describe("ConfigArray", () => { }); it("should calculate correct config when passed JS filename that matches two configs", () => { - const filename = path.resolve(basePath, "foo.test.js"); + const filename = "foo.test.js"; const config = configs.getConfig(filename); @@ -808,7 +833,7 @@ describe("ConfigArray", () => { }); it("should calculate correct config when passed JS filename that matches a function config", () => { - const filename = path.resolve(basePath, "bar.test.js"); + const filename = "bar.test.js"; const config = configs.getConfig(filename); @@ -820,8 +845,8 @@ describe("ConfigArray", () => { }); it("should not match a filename that doesn't explicitly match a files pattern", () => { - const matchingFilename = path.resolve(basePath, "foo.js"); - const notMatchingFilename = path.resolve(basePath, "foo.md"); + const matchingFilename = "foo.js"; + const notMatchingFilename = "foo.md"; configs = new ConfigArray( [ {}, @@ -860,7 +885,7 @@ describe("ConfigArray", () => { }); it("should throw an error when passed JS filename that matches a async function config and normalizeSync() is called", async () => { - const filename = path.resolve(basePath, "async.test.js"); + const filename = "async.test.js"; const configsToTest = createConfigArray(); configsToTest.push(context => Promise.resolve([ @@ -900,21 +925,21 @@ describe("ConfigArray", () => { await configsToTest.normalize(); - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; assert.throws(() => { configsToTest.getConfig(filename); }, /Config "bar": Key "defs": Object expected./u); }); it("should calculate correct config when passed JS filename that matches a function config returning an array", () => { - const filename1 = path.resolve(basePath, "baz.test.js"); + const filename1 = "baz.test.js"; const config1 = configs.getConfig(filename1); assert.strictEqual(typeof config1.defs, "object"); assert.strictEqual(config1.language, JSLanguage); assert.strictEqual(config1.defs.name, "baz-from-context"); - const filename2 = path.resolve(basePath, "baz.test.js"); + const filename2 = "baz.test.js"; const config2 = configs.getConfig(filename2); assert.strictEqual(config2.language, JSLanguage); @@ -924,7 +949,7 @@ describe("ConfigArray", () => { }); it("should calculate correct config when passed CSS filename", () => { - const filename = path.resolve(basePath, "foo.css"); + const filename = "foo.css"; const config = configs.getConfig(filename); assert.strictEqual(config.language, CSSLanguage); @@ -934,7 +959,7 @@ describe("ConfigArray", () => { }); it("should calculate correct config when passed JS filename that matches AND pattern", () => { - const filename = path.resolve(basePath, "foo.and.js"); + const filename = "foo.and.js"; const config = configs.getConfig(filename); assert.strictEqual(config.language, JSLanguage); @@ -945,7 +970,7 @@ describe("ConfigArray", () => { }); it("should return the same config when called with the same filename twice (caching)", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; const config1 = configs.getConfig(filename); const config2 = configs.getConfig(filename); @@ -954,8 +979,8 @@ describe("ConfigArray", () => { }); it("should return the same config when called with two filenames that match the same configs (caching)", () => { - const filename1 = path.resolve(basePath, "foo1.js"); - const filename2 = path.resolve(basePath, "foo2.js"); + const filename1 = "foo1.js"; + const filename2 = "foo2.js"; const config1 = configs.getConfig(filename1); const config2 = configs.getConfig(filename2); @@ -964,7 +989,7 @@ describe("ConfigArray", () => { }); it("should return empty config when called with ignored node_modules filename", () => { - const filename = path.resolve(basePath, "node_modules/foo.js"); + const filename = "node_modules/foo.js"; const config = configs.getConfig(filename); assert.strictEqual(config, undefined); @@ -986,17 +1011,10 @@ describe("ConfigArray", () => { configs.normalizeSync(); + assert.strictEqual(configs.getConfig("src/a.js"), undefined); + assert.strictEqual(configs.getConfig("src/b.js"), undefined); assert.strictEqual( - configs.getConfig(path.resolve(basePath, "src", "a.js")), - undefined, - ); - assert.strictEqual( - configs.getConfig(path.resolve(basePath, "src", "b.js")), - undefined, - ); - assert.strictEqual( - configs.getConfig(path.resolve(basePath, "src", "{a,b}.js")) - .defs.severity, + configs.getConfig("src/{a,b}.js").defs.severity, "error", ); }); @@ -1018,19 +1036,15 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfig(path.resolve(basePath, "src", "a.js")) - .defs.severity, + configs.getConfig("src/a.js").defs.severity, "error", ); assert.strictEqual( - configs.getConfig(path.resolve(basePath, "src", "b.js")) - .defs.severity, + configs.getConfig("src/b.js").defs.severity, "error", ); assert.strictEqual( - configs.getConfig( - path.resolve(basePath, "src", "{a,b}.js"), - ), + configs.getConfig("src/{a,b}.js"), undefined, ); }); @@ -1054,19 +1068,15 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfig(path.resolve(basePath, "src", "a.js")) - .defs.severity, + configs.getConfig("src/a.js").defs.severity, "error", ); assert.strictEqual( - configs.getConfig(path.resolve(basePath, "src", "b.js")) - .defs.severity, + configs.getConfig("src/b.js").defs.severity, "error", ); assert.strictEqual( - configs.getConfig( - path.resolve(basePath, "src", "{a,b}.js"), - ), + configs.getConfig("src/{a,b}.js"), undefined, ); }); @@ -1091,15 +1101,12 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfig(path.resolve(basePath, "file.js")).defs - .severity, + configs.getConfig("file.js").defs.severity, "error", ); assert.strictEqual( - configs.getConfig( - path.resolve(basePath, "subdir", "file.js"), - ).defs.severity, + configs.getConfig("subdir/file.js").defs.severity, "error", ); }); @@ -1123,8 +1130,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfig(path.resolve(basePath, "file.js")).defs - .severity, + configs.getConfig("file.js").defs.severity, "error", ); }); @@ -1132,11 +1138,8 @@ describe("ConfigArray", () => { // https://github.com/eslint/eslint/issues/17103 describe("ignores patterns should be properly applied", () => { it("should return undefined when a filename matches an ignores pattern but not a files pattern", () => { - const matchingFilename = path.resolve(basePath, "foo.js"); - const notMatchingFilename = path.resolve( - basePath, - "foo.md", - ); + const matchingFilename = "foo.js"; + const notMatchingFilename = "foo.md"; configs = new ConfigArray( [ { @@ -1164,11 +1167,8 @@ describe("ConfigArray", () => { }); it("should apply config with only ignores when a filename matches a files pattern", () => { - const matchingFilename = path.resolve(basePath, "foo.js"); - const notMatchingFilename = path.resolve( - basePath, - "foo.md", - ); + const matchingFilename = "foo.js"; + const notMatchingFilename = "foo.md"; configs = new ConfigArray( [ { @@ -1198,8 +1198,8 @@ describe("ConfigArray", () => { }); it("should not apply config with only ignores when a filename should be ignored", () => { - const matchingFilename = path.resolve(basePath, "foo.js"); - const ignoredFilename = path.resolve(basePath, "bar.js"); + const matchingFilename = "foo.js"; + const ignoredFilename = "bar.js"; configs = new ConfigArray( [ { @@ -1233,14 +1233,23 @@ describe("ConfigArray", () => { describe("getConfigStatus()", () => { it("should throw an error when not normalized", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; assert.throws(() => { unnormalizedConfigs.getConfigStatus(filename); }, /normalized/u); }); it('should return "matched" when passed JS filename', () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; + + assert.strictEqual( + configs.getConfigStatus(filename), + "matched", + ); + }); + + it('should return "matched" when passed JS filename that starts with ".."', () => { + const filename = "..foo.js"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1249,7 +1258,7 @@ describe("ConfigArray", () => { }); it('should return "external" when passed JS filename in parent directory', () => { - const filename = path.resolve(basePath, "../foo.js"); + const filename = "../foo.js"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1258,7 +1267,7 @@ describe("ConfigArray", () => { }); it('should return "matched" when passed HTML filename', () => { - const filename = path.resolve(basePath, "foo.html"); + const filename = "foo.html"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1267,7 +1276,7 @@ describe("ConfigArray", () => { }); it('should return "ignored" when passed ignored .gitignore filename', () => { - const filename = path.resolve(basePath, ".gitignore"); + const filename = ".gitignore"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1276,7 +1285,7 @@ describe("ConfigArray", () => { }); it('should return "matched" when passed CSS filename', () => { - const filename = path.resolve(basePath, "foo.css"); + const filename = "foo.css"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1285,7 +1294,7 @@ describe("ConfigArray", () => { }); it('should return "matched" when passed docx filename', () => { - const filename = path.resolve(basePath, "sss.docx"); + const filename = "sss.docx"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1294,7 +1303,7 @@ describe("ConfigArray", () => { }); it('should return "ignored" when passed ignored node_modules filename', () => { - const filename = path.resolve(basePath, "node_modules/foo.js"); + const filename = "node_modules/foo.js"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1317,7 +1326,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve(basePath, "fixtures/test.xsl"); + const filename = "fixtures/test.xsl"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1341,11 +1350,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "bar.txt")), + configs.getConfigStatus("bar.txt"), "unconfigured", ); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo.txt")), + configs.getConfigStatus("foo.txt"), "matched", ); }); @@ -1365,11 +1374,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "bar.txt")), + configs.getConfigStatus("bar.txt"), "ignored", ); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo.txt")), + configs.getConfigStatus("foo.txt"), "ignored", ); }); @@ -1390,11 +1399,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "bar.test.js")), + configs.getConfigStatus("bar.test.js"), "unconfigured", ); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo.test.js")), + configs.getConfigStatus("foo.test.js"), "matched", ); }); @@ -1417,9 +1426,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus( - path.join(basePath, "ignoreme/foo.js"), - ), + configs.getConfigStatus("ignoreme/foo.js"), "ignored", ); }); @@ -1442,9 +1449,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus( - path.join(basePath, "foo/bar/a.js"), - ), + configs.getConfigStatus("foo/bar/a.js"), "matched", ); }); @@ -1466,10 +1471,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "a.js")), - "ignored", - ); + assert.strictEqual(configs.getConfigStatus("a.js"), "ignored"); }); it('should return "ignored" when the parent directory of a file is ignored', () => { @@ -1490,9 +1492,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus( - path.join(basePath, "foo/bar/a.js"), - ), + configs.getConfigStatus("foo/bar/a.js"), "ignored", ); }); @@ -1518,9 +1518,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus( - path.join(basePath, "node_modules/package/a.js"), - ), + configs.getConfigStatus("node_modules/package/a.js"), "ignored", ); }); @@ -1546,9 +1544,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus( - path.join(basePath, "node_modules/package/a.js"), - ), + configs.getConfigStatus("node_modules/package/a.js"), "ignored", ); }); @@ -1568,7 +1564,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "unconfigured", ); }); @@ -1588,7 +1584,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "unconfigured", ); }); @@ -1608,7 +1604,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "a.js")), + configs.getConfigStatus("a.js"), "unconfigured", ); }); @@ -1628,7 +1624,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "matched", ); }); @@ -1650,10 +1646,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo")), - "matched", - ); + assert.strictEqual(configs.getConfigStatus("foo"), "matched"); }); it('should return "matched" when file is in the parent directory of directories that are ignored by a pattern that ends with `/`', () => { @@ -1674,7 +1667,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "matched", ); }); @@ -1697,7 +1690,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "ignored", ); }); @@ -1720,7 +1713,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "ignored", ); }); @@ -1743,7 +1736,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "matched", ); }); @@ -1771,7 +1764,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "ignored", ); }); @@ -1799,7 +1792,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "ignored", ); }); @@ -1827,7 +1820,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "matched", ); }); @@ -1849,10 +1842,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo")), - "ignored", - ); + assert.strictEqual(configs.getConfigStatus("foo"), "ignored"); }); it('should return "ignored" when file is in a directory that is ignored even if an unignore pattern that ends with `/*` matches the file', () => { @@ -1873,7 +1863,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(path.join(basePath, "foo/a.js")), + configs.getConfigStatus("foo/a.js"), "ignored", ); }); @@ -1906,29 +1896,20 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus( - path.join(basePath, "tests/format/foo.js"), - ), + configs.getConfigStatus("tests/format/foo.js"), "ignored", ); assert.strictEqual( - configs.getConfigStatus( - path.join(basePath, "tests/format/jsfmt.spec.js"), - ), + configs.getConfigStatus("tests/format/jsfmt.spec.js"), "matched", ); assert.strictEqual( - configs.getConfigStatus( - path.join(basePath, "tests/format/subdir/foo.js"), - ), + configs.getConfigStatus("tests/format/subdir/foo.js"), "ignored", ); assert.strictEqual( configs.getConfigStatus( - path.join( - basePath, - "tests/format/subdir/jsfmt.spec.js", - ), + "tests/format/subdir/jsfmt.spec.js", ), "matched", ); @@ -1950,10 +1931,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve( - basePath, - "node_modules/foo/bar.js", - ); + const filename = "node_modules/foo/bar.js"; assert.strictEqual( configs.getConfigStatus(filename), @@ -1975,10 +1953,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve( - basePath, - "node_modules/foo/bar.js", - ); + const filename = "node_modules/foo/bar.js"; assert.strictEqual( configs.getConfigStatus(filename), @@ -2000,10 +1975,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve( - basePath, - "node_modules/foo/bar.js", - ); + const filename = "node_modules/foo/bar.js"; assert.strictEqual( configs.getConfigStatus(filename), @@ -2025,10 +1997,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve( - basePath, - "node_modules/foo/bar.js", - ); + const filename = "node_modules/foo/bar.js"; assert.strictEqual( configs.getConfigStatus(filename), @@ -2036,49 +2005,169 @@ describe("ConfigArray", () => { ); }); }); + + describe("Windows paths", () => { + it('should return "matched" for a file in the base directory with different capitalization', () => { + configs = new ConfigArray([{ files: ["**/*.js"] }], { + basePath: "C:\\DIR", + }); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("c:\\dir\\subdir\\file.js"), + "matched", + ); + }); + + it('should return "external" for a file on a different drive', () => { + configs = new ConfigArray([{ files: ["**/*.js"] }], { + basePath: "C:\\dir", + }); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("D:\\dir\\file.js"), + "external", + ); + }); + + it('should return "external" for a file with a UNC path on a different drive', () => { + configs = new ConfigArray([{ files: ["**/*.js"] }], { + basePath: "C:\\dir", + }); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("\\\\NAS\\Share\\file.js"), + "external", + ); + }); + + it('should return "matched" for a file with a UNC path in the base directory', () => { + configs = new ConfigArray([{ files: ["**/*.js"] }], { + basePath: "\\\\NAS\\Share", + }); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("\\\\NAS\\Share\\dir\\file.js"), + "matched", + ); + }); + + it('should return "matched" for a file with a namespaced path in the base directory', () => { + configs = new ConfigArray([{ files: ["**/*.js"] }], { + basePath: "C:\\dir", + }); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus("\\\\?\\c:\\dir\\file.js"), + "matched", + ); + }); + + it('should return "matched" for a file with a namespaced UNC path in the base directory', () => { + configs = new ConfigArray([{ files: ["**/*.js"] }], { + basePath: "\\\\NAS\\Share", + }); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus( + "\\\\?\\UNC\\NAS\\Share\\file.js", + ), + "matched", + ); + }); + + it('should return "ignored" for a file with a namespaced path in a directory matched by a global ignore pattern', () => { + configs = new ConfigArray( + [{ files: ["**/*.js"] }, { ignores: ["dist"] }], + { basePath: "C:\\dir" }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus( + "\\\\?\\C:\\dir\\dist\\file.js", + ), + "ignored", + ); + }); + + it('should return "unconfigured" for a file with a namespaced path matched by a non-global ignore pattern', () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + ignores: ["dist/**"], + }, + ], + { basePath: "C:\\dir" }, + ); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus( + "\\\\?\\C:\\dir\\dist\\file.js", + ), + "unconfigured", + ); + }); + }); }); describe("isIgnored()", () => { it("should throw an error when not normalized", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; assert.throws(() => { unnormalizedConfigs.isIgnored(filename); }, /normalized/u); }); + it("should return false when passed JS filename", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; assert.strictEqual(configs.isIgnored(filename), false); }); it("should return false when passed JS filename in parent directory", () => { - const filename = path.resolve(basePath, "../foo.js"); + const filename = "../foo.js"; assert.strictEqual(configs.isIgnored(filename), false); }); it("should return false when passed HTML filename", () => { - const filename = path.resolve(basePath, "foo.html"); + const filename = "foo.html"; assert.strictEqual(configs.isIgnored(filename), false); }); it("should return true when passed ignored .gitignore filename", () => { - const filename = path.resolve(basePath, ".gitignore"); + const filename = ".gitignore"; assert.strictEqual(configs.isIgnored(filename), true); }); it("should return false when passed CSS filename", () => { - const filename = path.resolve(basePath, "foo.css"); + const filename = "foo.css"; assert.strictEqual(configs.isIgnored(filename), false); }); it("should return false when passed docx filename", () => { - const filename = path.resolve(basePath, "sss.docx"); + const filename = "sss.docx"; assert.strictEqual(configs.isIgnored(filename), false); }); it("should return true when passed ignored node_modules filename", () => { - const filename = path.resolve(basePath, "node_modules/foo.js"); + const filename = "node_modules/foo.js"; assert.strictEqual(configs.isIgnored(filename), true); }); @@ -2097,63 +2186,57 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isIgnored(path.join(basePath, "bar.txt")), - true, - ); - assert.strictEqual( - configs.isIgnored(path.join(basePath, "foo.txt")), - true, - ); + assert.strictEqual(configs.isIgnored("bar.txt"), true); + assert.strictEqual(configs.isIgnored("foo.txt"), true); }); }); describe("isFileIgnored()", () => { it("should throw an error when not normalized", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; assert.throws(() => { unnormalizedConfigs.isFileIgnored(filename); }, /normalized/u); }); it("should return false when passed JS filename", () => { - const filename = path.resolve(basePath, "foo.js"); + const filename = "foo.js"; assert.strictEqual(configs.isFileIgnored(filename), false); }); it("should return false when passed JS filename in parent directory", () => { - const filename = path.resolve(basePath, "../foo.js"); + const filename = "../foo.js"; assert.strictEqual(configs.isFileIgnored(filename), false); }); it("should return false when passed HTML filename", () => { - const filename = path.resolve(basePath, "foo.html"); + const filename = "foo.html"; assert.strictEqual(configs.isFileIgnored(filename), false); }); it("should return true when passed ignored .gitignore filename", () => { - const filename = path.resolve(basePath, ".gitignore"); + const filename = ".gitignore"; assert.strictEqual(configs.isFileIgnored(filename), true); }); it("should return false when passed CSS filename", () => { - const filename = path.resolve(basePath, "foo.css"); + const filename = "foo.css"; assert.strictEqual(configs.isFileIgnored(filename), false); }); it("should return false when passed docx filename", () => { - const filename = path.resolve(basePath, "sss.docx"); + const filename = "sss.docx"; assert.strictEqual(configs.isFileIgnored(filename), false); }); it("should return true when passed ignored node_modules filename", () => { - const filename = path.resolve(basePath, "node_modules/foo.js"); + const filename = "node_modules/foo.js"; assert.strictEqual(configs.isFileIgnored(filename), true); }); @@ -2172,14 +2255,8 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "bar.txt")), - true, - ); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo.txt")), - true, - ); + assert.strictEqual(configs.isFileIgnored("bar.txt"), true); + assert.strictEqual(configs.isFileIgnored("foo.txt"), true); }); it("should return true when file is inside of ignored directory", () => { @@ -2200,9 +2277,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isFileIgnored( - path.join(basePath, "ignoreme/foo.js"), - ), + configs.isFileIgnored("ignoreme/foo.js"), true, ); }); @@ -2225,7 +2300,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/bar/a.js")), + configs.isFileIgnored("foo/bar/a.js"), false, ); }); @@ -2247,10 +2322,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "a.js")), - true, - ); + assert.strictEqual(configs.isFileIgnored("a.js"), true); }); it("should return true when the parent directory of a file is ignored", () => { @@ -2270,10 +2342,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/bar/a.js")), - true, - ); + assert.strictEqual(configs.isFileIgnored("foo/bar/a.js"), true); }); it("should return true when an ignored directory is later negated with **", () => { @@ -2297,9 +2366,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isFileIgnored( - path.join(basePath, "node_modules/package/a.js"), - ), + configs.isFileIgnored("node_modules/package/a.js"), true, ); }); @@ -2325,9 +2392,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isFileIgnored( - path.join(basePath, "node_modules/package/a.js"), - ), + configs.isFileIgnored("node_modules/package/a.js"), true, ); }); @@ -2349,10 +2414,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo")), - false, - ); + assert.strictEqual(configs.isFileIgnored("foo"), false); }); it("should return false when file is in the parent directory of directories that are ignored by a pattern that ends with `/`", () => { @@ -2372,10 +2434,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/a.js")), - false, - ); + assert.strictEqual(configs.isFileIgnored("foo/a.js"), false); }); it("should return true when file is in a directory that is ignored by a pattern that ends with `/`", () => { @@ -2395,10 +2454,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/a.js")), - true, - ); + assert.strictEqual(configs.isFileIgnored("foo/a.js"), true); }); it("should return true when file is in a directory that is ignored by a pattern that does not end with `/`", () => { @@ -2418,10 +2474,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/a.js")), - true, - ); + assert.strictEqual(configs.isFileIgnored("foo/a.js"), true); }); it("should return false when file is in a directory that is ignored and then unignored by pattern that ends with `/`", () => { @@ -2441,10 +2494,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/a.js")), - false, - ); + assert.strictEqual(configs.isFileIgnored("foo/a.js"), false); }); it("should return true when file is in a directory that is ignored along with its files by a pattern that ends with `/**` and then unignored by pattern that ends with `/`", () => { @@ -2469,10 +2519,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/a.js")), - true, - ); + assert.strictEqual(configs.isFileIgnored("foo/a.js"), true); }); it("should return true when file is in a directory that is ignored along with its files by a pattern that ends with `/**` and then unignored by pattern that does not end with `/`", () => { @@ -2497,10 +2544,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/a.js")), - true, - ); + assert.strictEqual(configs.isFileIgnored("foo/a.js"), true); }); it("should return false when file is in a directory that is ignored along its files by pattern that ends with `/**` and then unignored along its files by pattern that ends with `/**`", () => { @@ -2525,10 +2569,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/a.js")), - false, - ); + assert.strictEqual(configs.isFileIgnored("foo/a.js"), false); }); it("should return true when file is ignored by a pattern and there are unignore patterns that target files of a directory with the same name", () => { @@ -2548,10 +2589,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo")), - true, - ); + assert.strictEqual(configs.isFileIgnored("foo"), true); }); it("should return true when file is in a directory that is ignored even if an unignore pattern that ends with `/*` matches the file", () => { @@ -2571,10 +2609,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); - assert.strictEqual( - configs.isFileIgnored(path.join(basePath, "foo/a.js")), - true, - ); + assert.strictEqual(configs.isFileIgnored("foo/a.js"), true); }); // https://github.com/eslint/eslint/issues/17964#issuecomment-1879840650 @@ -2605,30 +2640,19 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isFileIgnored( - path.join(basePath, "tests/format/foo.js"), - ), + configs.isFileIgnored("tests/format/foo.js"), true, ); assert.strictEqual( - configs.isFileIgnored( - path.join(basePath, "tests/format/jsfmt.spec.js"), - ), + configs.isFileIgnored("tests/format/jsfmt.spec.js"), false, ); assert.strictEqual( - configs.isFileIgnored( - path.join(basePath, "tests/format/subdir/foo.js"), - ), + configs.isFileIgnored("tests/format/subdir/foo.js"), true, ); assert.strictEqual( - configs.isFileIgnored( - path.join( - basePath, - "tests/format/subdir/jsfmt.spec.js", - ), - ), + configs.isFileIgnored("tests/format/subdir/jsfmt.spec.js"), false, ); }); @@ -2649,10 +2673,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve( - basePath, - "node_modules/foo/bar.js", - ); + const filename = "node_modules/foo/bar.js"; assert.strictEqual(configs.isFileIgnored(filename), true); }); @@ -2671,10 +2692,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve( - basePath, - "node_modules/foo/bar.js", - ); + const filename = "node_modules/foo/bar.js"; assert.strictEqual(configs.isFileIgnored(filename), true); }); @@ -2693,10 +2711,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve( - basePath, - "node_modules/foo/bar.js", - ); + const filename = "node_modules/foo/bar.js"; assert.strictEqual(configs.isFileIgnored(filename), true); }); @@ -2715,10 +2730,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const filename = path.resolve( - basePath, - "node_modules/foo/bar.js", - ); + const filename = "node_modules/foo/bar.js"; assert.strictEqual(configs.isFileIgnored(filename), true); }); @@ -2744,17 +2756,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules"), - "No trailing slash", - ), + configs.isDirectoryIgnored("node_modules"), // No trailing slash true, ); assert.strictEqual( - configs.isDirectoryIgnored( - `${path.join(basePath, "node_modules")}/`, - "Trailing slash", - ), + configs.isDirectoryIgnored("node_modules/"), // Trailing slash true, ); }); @@ -2791,17 +2797,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules"), - "No trailing slash", - ), + configs.isDirectoryIgnored("node_modules"), // No trailing slash true, ); assert.strictEqual( - configs.isDirectoryIgnored( - `${path.join(basePath, "node_modules")}/`, - "Trailing slash", - ), + configs.isDirectoryIgnored("node_modules/"), // Trailing slash true, ); }); @@ -2821,16 +2821,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules"), - ), + configs.isDirectoryIgnored("node_modules"), true, ); assert.strictEqual( - configs.isDirectoryIgnored( - `${path.join(basePath, "node_modules")}/`, - "Trailing slash", - ), + configs.isDirectoryIgnored("node_modules/"), // Trailing slash true, ); }); @@ -2850,15 +2845,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules"), - ), + configs.isDirectoryIgnored("node_modules"), true, ); assert.strictEqual( - configs.isDirectoryIgnored( - `${path.join(basePath, "node_modules")}/`, - ), + configs.isDirectoryIgnored("node_modules/"), true, ); }); @@ -2879,16 +2870,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules"), - ), + configs.isDirectoryIgnored("node_modules"), false, ); assert.strictEqual( - configs.isDirectoryIgnored( - `${path.join(basePath, "node_modules")}/`, - "Trailing slash", - ), + configs.isDirectoryIgnored("node_modules/"), // Trailing slash false, ); }); @@ -2907,15 +2893,9 @@ describe("ConfigArray", () => { configs.normalizeSync(); + assert.strictEqual(configs.isDirectoryIgnored("foo"), true); assert.strictEqual( - configs.isDirectoryIgnored(path.join(basePath, "foo")), - true, - ); - assert.strictEqual( - configs.isDirectoryIgnored( - `${path.join(basePath, "foo")}/`, - "Trailing slash", - ), + configs.isDirectoryIgnored("foo/"), // Trailing slash true, ); }); @@ -2934,15 +2914,9 @@ describe("ConfigArray", () => { configs.normalizeSync(); + assert.strictEqual(configs.isDirectoryIgnored("bar"), false); assert.strictEqual( - configs.isDirectoryIgnored(path.join(basePath, "bar")), - false, - ); - assert.strictEqual( - configs.isDirectoryIgnored( - `${path.join(basePath, "bar")}/`, - "Trailing slash", - ), + configs.isDirectoryIgnored("bar/"), // Trailing slash false, ); }); @@ -2962,11 +2936,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored(path.join(basePath, "foo/bar")), + configs.isDirectoryIgnored("foo/bar"), false, ); assert.strictEqual( - configs.isDirectoryIgnored(path.join(basePath, "foo/bar/")), + configs.isDirectoryIgnored("foo/bar/"), false, ); }); @@ -2985,15 +2959,9 @@ describe("ConfigArray", () => { configs.normalizeSync(); + assert.strictEqual(configs.isDirectoryIgnored("foo/bar"), true); assert.strictEqual( - configs.isDirectoryIgnored(path.join(basePath, "foo/bar")), - true, - ); - assert.strictEqual( - configs.isDirectoryIgnored( - `${path.join(basePath, "foo/bar")}/`, - "Trailing slash", - ), + configs.isDirectoryIgnored("foo/bar/"), // Trailing slash true, ); }); @@ -3010,7 +2978,7 @@ describe("ConfigArray", () => { }, ); assert.throws(() => { - configs.isDirectoryIgnored("foo/bar"); + configs.isDirectoryIgnored("/foo/bar"); }, /normalized/u); }); @@ -3029,13 +2997,29 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.resolve(basePath, "../foo/bar"), - ), + configs.isDirectoryIgnored("../foo/bar"), true, ); }); + it("should return true when the directory is the parent of the base path", () => { + configs = new ConfigArray( + [ + { + files: ["**/*.js"], + }, + ], + { + basePath, + }, + ); + + configs.normalizeSync(); + + assert.strictEqual(configs.isDirectoryIgnored(".."), true); + assert.strictEqual(configs.isDirectoryIgnored("../"), true); + }); + it("should return true when the parent directory of a directory is ignored", () => { configs = new ConfigArray( [ @@ -3053,12 +3037,9 @@ describe("ConfigArray", () => { configs.normalizeSync(); + assert.strictEqual(configs.isDirectoryIgnored("foo/bar"), true); assert.strictEqual( - configs.isDirectoryIgnored(path.join(basePath, "foo/bar")), - true, - ); - assert.strictEqual( - configs.isDirectoryIgnored(path.join(basePath, "foo/bar/")), + configs.isDirectoryIgnored("foo/bar/"), true, ); }); @@ -3087,15 +3068,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules/package"), - ), + configs.isDirectoryIgnored("node_modules/package"), true, ); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules/package/"), - ), + configs.isDirectoryIgnored("node_modules/package/"), true, ); }); @@ -3121,15 +3098,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules"), - ), + configs.isDirectoryIgnored("node_modules"), false, ); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules/"), - ), + configs.isDirectoryIgnored("node_modules/"), false, ); }); @@ -3155,15 +3128,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules"), - ), + configs.isDirectoryIgnored("node_modules"), true, ); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules/"), - ), + configs.isDirectoryIgnored("node_modules/"), true, ); }); @@ -3189,15 +3158,11 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules/package"), - ), + configs.isDirectoryIgnored("node_modules/package"), true, ); assert.strictEqual( - configs.isDirectoryIgnored( - path.join(basePath, "node_modules/package/"), - ), + configs.isDirectoryIgnored("node_modules/package/"), true, ); }); @@ -3218,10 +3183,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const directoryPath = path.resolve( - basePath, - "node_modules/foo", - ); + const directoryPath = "node_modules/foo"; assert.strictEqual( configs.isDirectoryIgnored(directoryPath), @@ -3240,10 +3202,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const directoryPath = path.resolve( - basePath, - "node_modules/foo", - ); + const directoryPath = "node_modules/foo"; assert.strictEqual( configs.isDirectoryIgnored(directoryPath), @@ -3265,10 +3224,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const directoryPath = path.resolve( - basePath, - "node_modules/foo", - ); + const directoryPath = "node_modules/foo"; assert.strictEqual( configs.isDirectoryIgnored(directoryPath), @@ -3290,10 +3246,7 @@ describe("ConfigArray", () => { ); configs.normalizeSync(); - const directoryPath = path.resolve( - basePath, - "node_modules/foo", - ); + const directoryPath = "node_modules/foo"; assert.strictEqual( configs.isDirectoryIgnored(directoryPath), @@ -3358,9 +3311,7 @@ describe("ConfigArray", () => { configs.normalizeSync(); assert.strictEqual( - configs.isFileIgnored( - path.join(basePath, "ignoreme/foo.js"), - ), + configs.isFileIgnored("ignoreme/foo.js"), true, ); assert.deepStrictEqual(configs.ignores, ["ignoreme"]);