From 1c6d147b1e3b0695b6a4f0a11940be68e3c82704 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 16 Jun 2024 22:26:08 +0200 Subject: [PATCH 01/12] fix: correctly detect is file is outside base path on Windows --- packages/config-array/src/config-array.js | 62 +++--- .../config-array/tests/config-array.test.js | 176 +++++++++++++++++- 2 files changed, 204 insertions(+), 34 deletions(-) diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index 51a525b..3ac207f 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -89,6 +89,8 @@ const CONFIG_WITH_STATUS_UNCONFIGURED = Object.freeze({ status: "unconfigured", }); +const EXTERNAL_PATH_REGEX = new RegExp(`^\\.\\.($|\\${path.sep})`, "u"); + /** * Wrapper error for config validation errors that adds a name to the front of the * error message. @@ -352,11 +354,6 @@ function normalizeSync(items, context, extraConfigTypes) { * @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") { @@ -387,19 +384,12 @@ 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} relativeFilePath The relative path of the file to check. * @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) @@ -412,19 +402,12 @@ function pathMatchesIgnores(filePath, basePath, config) { * 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} relativeFilePath The relative path of the file to check. * @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)) { @@ -599,6 +582,13 @@ export class ConfigArray extends Array { } else { this.push(configs); } + + // 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 = path.toNamespacedPath( + basePath || path.resolve(), + ); } /** @@ -804,9 +794,12 @@ 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 = path.relative( + this.namespacedBasePath, + path.toNamespacedPath(filePath), + ); - if (relativeFilePath.startsWith("..")) { + if (EXTERNAL_PATH_REGEX.test(relativeFilePath)) { debug(`No config for file ${filePath} outside of base path`); // cache and return result @@ -847,7 +840,7 @@ export class ConfigArray extends Array { 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 +876,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 +890,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 +905,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; @@ -1020,19 +1013,22 @@ export class ConfigArray extends Array { isDirectoryIgnored(directoryPath) { assertNormalized(this); - const relativeDirectoryPath = path - .relative(this.basePath, directoryPath) - .replace(/\\/gu, "/"); + let relativeDirectoryPath = path.relative( + this.namespacedBasePath, + path.toNamespacedPath(directoryPath), + ); // basePath directory can never be ignored if (relativeDirectoryPath === "") { return false; } - if (relativeDirectoryPath.startsWith("..")) { + if (EXTERNAL_PATH_REGEX.test(relativeDirectoryPath)) { return true; } + relativeDirectoryPath = relativeDirectoryPath.replace(/\\/gu, "/"); + // first check the cache const cache = dataCache.get(this).directoryMatches; diff --git a/packages/config-array/tests/config-array.test.js b/packages/config-array/tests/config-array.test.js index 11a0961..736fca7 100644 --- a/packages/config-array/tests/config-array.test.js +++ b/packages/config-array/tests/config-array.test.js @@ -10,13 +10,14 @@ 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: { @@ -1248,6 +1249,15 @@ describe("ConfigArray", () => { ); }); + it('should return "matched" when passed JS filename that starts with ".."', () => { + const filename = path.resolve(basePath, "..foo.js"); + + assert.strictEqual( + configs.getConfigStatus(filename), + "matched", + ); + }); + it('should return "external" when passed JS filename in parent directory', () => { const filename = path.resolve(basePath, "../foo.js"); @@ -2036,6 +2046,146 @@ describe("ConfigArray", () => { ); }); }); + + describe("on Windows", () => { + // Windows only + if (process.platform !== "win32") { + return; + } + + 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 when a base path is specified', () => { + 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 on a different drive when no base path is specified', () => { + const currentDriveLetter = process.cwd()[0]; + const otherDriveLetter = + currentDriveLetter === "X" ? "Y" : "X"; + const filePath = `${otherDriveLetter}:\\dir\\file.js`; + + configs = new ConfigArray([{ files: ["**/*.js"] }]); + + configs.normalizeSync(); + + assert.strictEqual( + configs.getConfigStatus(filePath), + "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()", () => { @@ -3036,6 +3186,30 @@ describe("ConfigArray", () => { ); }); + 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(path.join(basePath, "..")), + true, + ); + assert.strictEqual( + configs.isDirectoryIgnored(path.join(basePath, "../")), + true, + ); + }); + it("should return true when the parent directory of a directory is ignored", () => { configs = new ConfigArray( [ From f39fc53baa40ee99516667a78426c698cc9987d2 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 23 Jun 2024 21:08:42 +0200 Subject: [PATCH 02/12] `toNamespacedPath` fallback to identity, use `process.cwd`, improve comments --- packages/config-array/src/config-array.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index 3ac207f..95583f3 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -89,8 +89,13 @@ const CONFIG_WITH_STATUS_UNCONFIGURED = Object.freeze({ status: "unconfigured", }); +// Match two leading dots followed by a path separator or the end of input. const EXTERNAL_PATH_REGEX = new RegExp(`^\\.\\.($|\\${path.sep})`, "u"); +// Some `node:path` polyfills do not provide an implementation for `toNamespacedPath`. +// In that case, return the argument unchanged like on POSIX systems. +const toNamespacedPath = path.toNamespacedPath ?? (arg => arg); + /** * Wrapper error for config validation errors that adds a name to the front of the * error message. @@ -350,7 +355,7 @@ function normalizeSync(items, context, extraConfigTypes) { * 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} relativeFilePath The path of the file to check relative to the base path. * @returns {boolean} True if the path should be ignored and false if not. */ function shouldIgnorePath(ignores, filePath, relativeFilePath) { @@ -384,7 +389,7 @@ 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} relativeFilePath The relative path of the file to check. + * @param {string} relativeFilePath The path of the file to check relative to the base path. * @param {Object} config The config object to check. * @returns {boolean} True if the file path is matched by the config, * false if not. @@ -402,7 +407,7 @@ function pathMatchesIgnores(filePath, relativeFilePath, config) { * 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} relativeFilePath The relative path of the file to check. + * @param {string} relativeFilePath The path of the file to check relative to the base path. * @param {Object} config The config object to check. * @returns {boolean} True if the file path is matched by the config, * false if not. @@ -586,9 +591,7 @@ export class ConfigArray extends Array { // 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 = path.toNamespacedPath( - basePath || path.resolve(), - ); + this.namespacedBasePath = toNamespacedPath(basePath || process.cwd()); } /** @@ -796,7 +799,7 @@ export class ConfigArray extends Array { const relativeFilePath = path.relative( this.namespacedBasePath, - path.toNamespacedPath(filePath), + toNamespacedPath(filePath), ); if (EXTERNAL_PATH_REGEX.test(relativeFilePath)) { @@ -1015,7 +1018,7 @@ export class ConfigArray extends Array { let relativeDirectoryPath = path.relative( this.namespacedBasePath, - path.toNamespacedPath(directoryPath), + toNamespacedPath(directoryPath), ); // basePath directory can never be ignored From 93fbcef4ff62e90ddec09fff63ff05499ca1f0ca Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 26 Jun 2024 14:32:37 +0200 Subject: [PATCH 03/12] Add generic `toNamespacedPath` polyfill --- packages/config-array/src/config-array.js | 5 +-- .../config-array/src/to-namespaced-path.js | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 packages/config-array/src/to-namespaced-path.js diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index 95583f3..b846b8c 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -14,6 +14,7 @@ import createDebug from "debug"; import { ObjectSchema } from "@eslint/object-schema"; import { baseSchema } from "./base-schema.js"; import { filesAndIgnoresSchema } from "./files-and-ignores-schema.js"; +import toNamespacedPath from "./to-namespaced-path.js"; //------------------------------------------------------------------------------ // Types @@ -92,10 +93,6 @@ const CONFIG_WITH_STATUS_UNCONFIGURED = Object.freeze({ // Match two leading dots followed by a path separator or the end of input. const EXTERNAL_PATH_REGEX = new RegExp(`^\\.\\.($|\\${path.sep})`, "u"); -// Some `node:path` polyfills do not provide an implementation for `toNamespacedPath`. -// In that case, return the argument unchanged like on POSIX systems. -const toNamespacedPath = path.toNamespacedPath ?? (arg => arg); - /** * Wrapper error for config validation errors that adds a name to the front of the * error message. diff --git a/packages/config-array/src/to-namespaced-path.js b/packages/config-array/src/to-namespaced-path.js new file mode 100644 index 0000000..429f5f8 --- /dev/null +++ b/packages/config-array/src/to-namespaced-path.js @@ -0,0 +1,34 @@ +/** + * @fileoverview Polyfill for Node.js `path.toNamespacedPath` + * @author Francesco Trotta + */ + +import { resolve, sep } from "node:path"; + +/** + * @param {string} path A path to be converted into a namespace-prefixed path. + * @returns {string} A namespace-prefixed path. + */ +function win32ToNamespacedPath(path) { + if (typeof path !== "string" || path.length === 0) { + return path; + } + + const resolvedPath = resolve(path); + if (resolvedPath.length <= 2) { + return path; + } + + if (/^\\\\[^.?]/u.test(resolvedPath)) { + // Matched non-long UNC root, convert the path to a long UNC path + return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; + } + if (/^[a-z]:\\/iu.test(resolvedPath)) { + // Matched device root, convert the path to a long UNC path + return `\\\\?\\${resolvedPath}`; + } + return resolvedPath; +} + +// On non-Windows systems, `toNamespacedPath` returns the argument as is. +export default sep === "\\" ? win32ToNamespacedPath : arg => arg; From 7df1675633c360b7b133130e46a53f7df8c13025 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Fri, 28 Jun 2024 17:17:14 +0200 Subject: [PATCH 04/12] link to Node.js license --- packages/config-array/src/to-namespaced-path.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/config-array/src/to-namespaced-path.js b/packages/config-array/src/to-namespaced-path.js index 429f5f8..eb3bc5b 100644 --- a/packages/config-array/src/to-namespaced-path.js +++ b/packages/config-array/src/to-namespaced-path.js @@ -1,6 +1,7 @@ /** * @fileoverview Polyfill for Node.js `path.toNamespacedPath` - * @author Francesco Trotta + * @license + * Copyright Node.js contributors: https://github.com/nodejs/node/blob/v22.3.0/LICENSE */ import { resolve, sep } from "node:path"; From 5d84435a6eab33b56143f1d52fafcf9f721cea52 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 18 Aug 2024 18:51:26 +0200 Subject: [PATCH 05/12] select `path` implementations depending on base path --- .npmrc | 1 + packages/config-array/package.json | 1 + packages/config-array/src/config-array.js | 86 ++++++++++++++----- .../config-array/src/to-namespaced-path.js | 35 -------- .../config-array/tests/config-array.test.js | 22 ++--- 5 files changed, 75 insertions(+), 70 deletions(-) delete mode 100644 packages/config-array/src/to-namespaced-path.js 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/package.json b/packages/config-array/package.json index 2dfdd5e..94497de 100644 --- a/packages/config-array/package.json +++ b/packages/config-array/package.json @@ -47,6 +47,7 @@ "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.4", + "@jsr/std__path": "^1.0.2", "debug": "^4.3.1", "minimatch": "^3.1.2" }, diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index b846b8c..9e7ad97 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -7,14 +7,14 @@ // 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"; import { ObjectSchema } from "@eslint/object-schema"; import { baseSchema } from "./base-schema.js"; import { filesAndIgnoresSchema } from "./files-and-ignores-schema.js"; -import toNamespacedPath from "./to-namespaced-path.js"; //------------------------------------------------------------------------------ // Types @@ -90,8 +90,8 @@ const CONFIG_WITH_STATUS_UNCONFIGURED = Object.freeze({ status: "unconfigured", }); -// Match two leading dots followed by a path separator or the end of input. -const EXTERNAL_PATH_REGEX = new RegExp(`^\\.\\.($|\\${path.sep})`, "u"); +// 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 @@ -352,7 +352,8 @@ function normalizeSync(items, context, extraConfigTypes) { * 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 path of the file to check relative to the base path. + * @param {string} relativeFilePath The path of the file to check relative to the base path, + * using slash ("/") as a separator. * @returns {boolean} True if the path should be ignored and false if not. */ function shouldIgnorePath(ignores, filePath, relativeFilePath) { @@ -386,7 +387,8 @@ 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} relativeFilePath The path of the file to check relative to the base path. + * @param {string} relativeFilePath The path of the file to check relative to the base path, + * using 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. @@ -484,6 +486,15 @@ function assertExtraConfigTypes(extraConfigTypes) { } } +/** + * Determines if an absolute path is a POSIX path. + * @param {string} path The absolute path to check. + * @returns {boolean} Whether the specified path is a POSIX path. + */ +function isPosixPath(path) { + return path.startsWith("/"); +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ @@ -509,7 +520,7 @@ export class ConfigArray extends Array { * @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. * @param {boolean} [options.normalized=false] Flag indicating if the * configs have already been normalized. * @param {Object} [options.schema] The additional schema @@ -553,6 +564,26 @@ export class ConfigArray extends Array { */ this.basePath = basePath; + /** + * The path-handling implementations depend on whether basePath is a Window or a Unix path, + * or unspecified. + * If the base path is not specified, files will never be considered "external" (outside the + * base path). This allows for some implementations to be heavily simplified. + */ + if (!basePath) { + this.path = { + dirname: null, + join: null, + relative: (from, to) => to, + SEPARATOR: "/", + toNamespacedPath: path => path, + }; + } else if (isPosixPath(basePath)) { + this.path = posixPath; + } else { + this.path = windowsPath; + } + assertExtraConfigTypes(extraConfigTypes); /** @@ -588,7 +619,8 @@ export class ConfigArray extends Array { // 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 = toNamespacedPath(basePath || process.cwd()); + this.namespacedBasePath = + basePath && this.path.toNamespacedPath(basePath); } /** @@ -794,10 +826,12 @@ export class ConfigArray extends Array { // check to see if the file is outside the base path - const relativeFilePath = path.relative( - this.namespacedBasePath, - toNamespacedPath(filePath), - ); + const relativeFilePath = this.path + .relative( + this.namespacedBasePath, + this.path.toNamespacedPath(filePath), + ) + .replaceAll(this.path.SEPARATOR, "/"); if (EXTERNAL_PATH_REGEX.test(relativeFilePath)) { debug(`No config for file ${filePath} outside of base path`); @@ -809,8 +843,13 @@ export class ConfigArray extends Array { // next check to see if the file should be ignored + // Use predetermined `dirname` implementation, or deduce from the argument. + const dirname = + this.path.dirname ?? + (isPosixPath(filePath) ? posixPath.dirname : windowsPath.dirname); + // check if this should be ignored due to its directory - if (this.isDirectoryIgnored(path.dirname(filePath))) { + if (this.isDirectoryIgnored(dirname(filePath))) { debug(`Ignoring ${filePath} based on directory pattern`); // cache and return result @@ -835,7 +874,7 @@ 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; } @@ -1013,10 +1052,12 @@ export class ConfigArray extends Array { isDirectoryIgnored(directoryPath) { assertNormalized(this); - let relativeDirectoryPath = path.relative( - this.namespacedBasePath, - toNamespacedPath(directoryPath), - ); + const relativeDirectoryPath = this.path + .relative( + this.namespacedBasePath, + this.path.toNamespacedPath(directoryPath), + ) + .replaceAll(this.path.SEPARATOR, "/"); // basePath directory can never be ignored if (relativeDirectoryPath === "") { @@ -1027,8 +1068,6 @@ export class ConfigArray extends Array { return true; } - relativeDirectoryPath = relativeDirectoryPath.replace(/\\/gu, "/"); - // first check the cache const cache = dataCache.get(this).directoryMatches; @@ -1040,6 +1079,11 @@ export class ConfigArray extends Array { let relativeDirectoryToCheck = ""; let result; + // Use predetermined `join` implementation, or deduce from the argument. + const join = + this.path.join ?? + (isPosixPath(directoryPath) ? posixPath.join : windowsPath.join); + /* * In order to get the correct gitignore-style ignores, where an * ignored parent directory cannot have any descendants unignored, @@ -1054,7 +1098,7 @@ export class ConfigArray extends Array { result = shouldIgnorePath( this.ignores, - path.join(this.basePath, relativeDirectoryToCheck), + join(this.basePath, relativeDirectoryToCheck), relativeDirectoryToCheck, ); diff --git a/packages/config-array/src/to-namespaced-path.js b/packages/config-array/src/to-namespaced-path.js deleted file mode 100644 index eb3bc5b..0000000 --- a/packages/config-array/src/to-namespaced-path.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * @fileoverview Polyfill for Node.js `path.toNamespacedPath` - * @license - * Copyright Node.js contributors: https://github.com/nodejs/node/blob/v22.3.0/LICENSE - */ - -import { resolve, sep } from "node:path"; - -/** - * @param {string} path A path to be converted into a namespace-prefixed path. - * @returns {string} A namespace-prefixed path. - */ -function win32ToNamespacedPath(path) { - if (typeof path !== "string" || path.length === 0) { - return path; - } - - const resolvedPath = resolve(path); - if (resolvedPath.length <= 2) { - return path; - } - - if (/^\\\\[^.?]/u.test(resolvedPath)) { - // Matched non-long UNC root, convert the path to a long UNC path - return `\\\\?\\UNC\\${resolvedPath.slice(2)}`; - } - if (/^[a-z]:\\/iu.test(resolvedPath)) { - // Matched device root, convert the path to a long UNC path - return `\\\\?\\${resolvedPath}`; - } - return resolvedPath; -} - -// On non-Windows systems, `toNamespacedPath` returns the argument as is. -export default sep === "\\" ? win32ToNamespacedPath : arg => arg; diff --git a/packages/config-array/tests/config-array.test.js b/packages/config-array/tests/config-array.test.js index 736fca7..96b349e 100644 --- a/packages/config-array/tests/config-array.test.js +++ b/packages/config-array/tests/config-array.test.js @@ -2047,12 +2047,7 @@ describe("ConfigArray", () => { }); }); - describe("on Windows", () => { - // Windows only - if (process.platform !== "win32") { - return; - } - + 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", @@ -2079,19 +2074,18 @@ describe("ConfigArray", () => { ); }); - it('should return "external" for a file on a different drive when no base path is specified', () => { - const currentDriveLetter = process.cwd()[0]; - const otherDriveLetter = - currentDriveLetter === "X" ? "Y" : "X"; - const filePath = `${otherDriveLetter}:\\dir\\file.js`; - + it('should return "matched" for files on different drives when no base path is specified', () => { configs = new ConfigArray([{ files: ["**/*.js"] }]); configs.normalizeSync(); assert.strictEqual( - configs.getConfigStatus(filePath), - "external", + configs.getConfigStatus("X:\\dir1\\file.js"), + "matched", + ); + assert.strictEqual( + configs.getConfigStatus("Y:\\dir2\\file.js"), + "matched", ); }); From e408f2b95adc8a3b174ef4f50c9d2072f04982f0 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 18 Aug 2024 22:23:25 +0200 Subject: [PATCH 06/12] fix for `isDirectoryIgnored` when no base path specified --- packages/config-array/src/config-array.js | 81 +++++++++---------- .../config-array/tests/config-array.test.js | 19 +++++ 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index 9e7ad97..75cf6cb 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -564,26 +564,6 @@ export class ConfigArray extends Array { */ this.basePath = basePath; - /** - * The path-handling implementations depend on whether basePath is a Window or a Unix path, - * or unspecified. - * If the base path is not specified, files will never be considered "external" (outside the - * base path). This allows for some implementations to be heavily simplified. - */ - if (!basePath) { - this.path = { - dirname: null, - join: null, - relative: (from, to) => to, - SEPARATOR: "/", - toNamespacedPath: path => path, - }; - } else if (isPosixPath(basePath)) { - this.path = posixPath; - } else { - this.path = windowsPath; - } - assertExtraConfigTypes(extraConfigTypes); /** @@ -620,7 +600,11 @@ export class ConfigArray extends Array { // 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 = - basePath && this.path.toNamespacedPath(basePath); + basePath && + (isPosixPath(this.basePath) + ? posixPath + : windowsPath + ).toNamespacedPath(basePath); } /** @@ -824,14 +808,21 @@ export class ConfigArray extends Array { return cache.get(filePath); } + // Select `path` implementations depending on base path. + // If base path is not specified, relative paths cannot be built. + // In this case, `path` implementations are selected depending on the specified argument. + const path = isPosixPath(this.basePath || filePath) + ? posixPath + : windowsPath; + // check to see if the file is outside the base path - const relativeFilePath = this.path - .relative( - this.namespacedBasePath, - this.path.toNamespacedPath(filePath), - ) - .replaceAll(this.path.SEPARATOR, "/"); + const namespacedFilePath = path.toNamespacedPath(filePath); + const relativeFilePath = ( + this.namespacedBasePath + ? path.relative(this.namespacedBasePath, namespacedFilePath) + : namespacedFilePath + ).replaceAll(path.SEPARATOR, "/"); if (EXTERNAL_PATH_REGEX.test(relativeFilePath)) { debug(`No config for file ${filePath} outside of base path`); @@ -843,13 +834,8 @@ export class ConfigArray extends Array { // next check to see if the file should be ignored - // Use predetermined `dirname` implementation, or deduce from the argument. - const dirname = - this.path.dirname ?? - (isPosixPath(filePath) ? posixPath.dirname : windowsPath.dirname); - // check if this should be ignored due to its directory - if (this.isDirectoryIgnored(dirname(filePath))) { + if (this.isDirectoryIgnored(path.dirname(filePath))) { debug(`Ignoring ${filePath} based on directory pattern`); // cache and return result @@ -1052,12 +1038,22 @@ export class ConfigArray extends Array { isDirectoryIgnored(directoryPath) { assertNormalized(this); - const relativeDirectoryPath = this.path - .relative( - this.namespacedBasePath, - this.path.toNamespacedPath(directoryPath), - ) - .replaceAll(this.path.SEPARATOR, "/"); + // Select `path` implementations depending on base path. + // If base path is not specified, relative paths cannot be built. + // In this case, `path` implementations are selected depending on the specified argument. + const path = isPosixPath(this.basePath || directoryPath) + ? posixPath + : windowsPath; + + const namespacedDirectoryPath = path.toNamespacedPath(directoryPath); + const relativeDirectoryPath = ( + this.namespacedBasePath + ? path.relative( + this.namespacedBasePath, + namespacedDirectoryPath, + ) + : namespacedDirectoryPath + ).replaceAll(path.SEPARATOR, "/"); // basePath directory can never be ignored if (relativeDirectoryPath === "") { @@ -1079,11 +1075,6 @@ export class ConfigArray extends Array { let relativeDirectoryToCheck = ""; let result; - // Use predetermined `join` implementation, or deduce from the argument. - const join = - this.path.join ?? - (isPosixPath(directoryPath) ? posixPath.join : windowsPath.join); - /* * In order to get the correct gitignore-style ignores, where an * ignored parent directory cannot have any descendants unignored, @@ -1098,7 +1089,7 @@ export class ConfigArray extends Array { result = shouldIgnorePath( this.ignores, - join(this.basePath, relativeDirectoryToCheck), + 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 96b349e..77a3ffb 100644 --- a/packages/config-array/tests/config-array.test.js +++ b/packages/config-array/tests/config-array.test.js @@ -3204,6 +3204,25 @@ describe("ConfigArray", () => { ); }); + it("should return false when no basePath is specified", () => { + configs = new ConfigArray([ + { + ignores: ["**/bar"], + }, + ]); + + configs.normalizeSync(); + + assert.strictEqual( + configs.isDirectoryIgnored("/foo/bar/baz"), + true, + ); + assert.strictEqual( + configs.isDirectoryIgnored("C:\\foo\\bar\\baz"), + true, + ); + }); + it("should return true when the parent directory of a directory is ignored", () => { configs = new ConfigArray( [ From aa2516753981f7350a637d63daf34bee755d9ca9 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Mon, 19 Aug 2024 09:48:38 +0200 Subject: [PATCH 07/12] simplify selection of path-handling implementations --- packages/config-array/src/config-array.js | 34 +++++++++-------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index 75cf6cb..4dfb74f 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -487,12 +487,12 @@ function assertExtraConfigTypes(extraConfigTypes) { } /** - * Determines if an absolute path is a POSIX path. + * Returns path-handling implementations for Unix or Windows, depending on a given absolute path. * @param {string} path The absolute path to check. - * @returns {boolean} Whether the specified path is a POSIX path. + * @returns {typeof import("@jsr/std__path")} Path-handling implementations for the specified path. */ -function isPosixPath(path) { - return path.startsWith("/"); +function getPathImpl(path) { + return path.startsWith("/") ? posixPath : windowsPath; } //------------------------------------------------------------------------------ @@ -600,11 +600,7 @@ export class ConfigArray extends Array { // 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 = - basePath && - (isPosixPath(this.basePath) - ? posixPath - : windowsPath - ).toNamespacedPath(basePath); + basePath && getPathImpl(this.basePath).toNamespacedPath(basePath); } /** @@ -808,16 +804,14 @@ export class ConfigArray extends Array { return cache.get(filePath); } - // Select `path` implementations depending on base path. - // If base path is not specified, relative paths cannot be built. - // In this case, `path` implementations are selected depending on the specified argument. - const path = isPosixPath(this.basePath || filePath) - ? posixPath - : windowsPath; + // Select path-handling implementations depending on the specified file path. + const path = getPathImpl(filePath); // check to see if the file is outside the base path const namespacedFilePath = path.toNamespacedPath(filePath); + + // If base path is not specified, relative paths cannot be built. const relativeFilePath = ( this.namespacedBasePath ? path.relative(this.namespacedBasePath, namespacedFilePath) @@ -1038,14 +1032,12 @@ export class ConfigArray extends Array { isDirectoryIgnored(directoryPath) { assertNormalized(this); - // Select `path` implementations depending on base path. - // If base path is not specified, relative paths cannot be built. - // In this case, `path` implementations are selected depending on the specified argument. - const path = isPosixPath(this.basePath || directoryPath) - ? posixPath - : windowsPath; + // Select path-handling implementations depending on the specified directory path. + const path = getPathImpl(directoryPath); const namespacedDirectoryPath = path.toNamespacedPath(directoryPath); + + // If base path is not specified, relative paths cannot be built. const relativeDirectoryPath = ( this.namespacedBasePath ? path.relative( From a73982e752f0d24d9fb665846e88c31475673790 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Wed, 4 Sep 2024 06:10:47 +0200 Subject: [PATCH 08/12] download `@std/path` package from tarball URL --- .npmrc | 1 - packages/config-array/package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.npmrc b/.npmrc index 1b8d617..c1ca392 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ package-lock = false -@jsr:registry=https://npm.jsr.io diff --git a/packages/config-array/package.json b/packages/config-array/package.json index 94497de..1a606cd 100644 --- a/packages/config-array/package.json +++ b/packages/config-array/package.json @@ -47,7 +47,7 @@ "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.4", - "@jsr/std__path": "^1.0.2", + "@jsr/std__path": "https://npm.jsr.io/~/11/@jsr/std__path/1.0.3.tgz", "debug": "^4.3.1", "minimatch": "^3.1.2" }, From 2d7af8a4147f553ab21d1311f21c1a067e517498 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 8 Sep 2024 15:38:14 +0200 Subject: [PATCH 09/12] use pre-bundled versions of `@jsr/std__path` --- .npmrc | 1 + .../config-array/fix-std__path-imports.js | 26 +++++++++++++++++ packages/config-array/package.json | 5 ++-- .../config-array/rollup.std__path-config.js | 28 +++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 packages/config-array/fix-std__path-imports.js create mode 100644 packages/config-array/rollup.std__path-config.js 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/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/package.json b/packages/config-array/package.json index 1a606cd..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/", @@ -47,11 +48,11 @@ "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.4", - "@jsr/std__path": "https://npm.jsr.io/~/11/@jsr/std__path/1.0.3.tgz", "debug": "^4.3.1", "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..533d7a0 --- /dev/null +++ b/packages/config-array/rollup.std__path-config.js @@ -0,0 +1,28 @@ +export default [ + { + input: "../../node_modules/@jsr/std__path/posix/mod.js", + output: [ + { + file: "./dist/cjs/std__path/posix.cjs", + format: "cjs", + }, + { + file: "./dist/esm/std__path/posix.js", + format: "esm", + }, + ], + }, + { + input: "../../node_modules/@jsr/std__path/windows/mod.js", + output: [ + { + file: "./dist/cjs/std__path/windows.cjs", + format: "cjs", + }, + { + file: "./dist/esm/std__path/windows.js", + format: "esm", + }, + ], + }, +]; From 133ee16fa9ac33e4353bcabfc6f12f4284702065 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 8 Sep 2024 15:50:06 +0200 Subject: [PATCH 10/12] update jsr.json in `config-array` --- packages/config-array/jsr.json | 2 ++ 1 file changed, 2 insertions(+) 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" From 9d129e77a6e35310663e55214f3c245c337ec803 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 8 Sep 2024 16:06:52 +0200 Subject: [PATCH 11/12] fix unit tests in `compat` --- packages/compat/tests/fixup-rules.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/compat/tests/fixup-rules.js b/packages/compat/tests/fixup-rules.js index 7132d21..d23b976 100644 --- a/packages/compat/tests/fixup-rules.js +++ b/packages/compat/tests/fixup-rules.js @@ -7,6 +7,7 @@ //----------------------------------------------------------------------------- import assert from "node:assert"; +import path from "node:path"; import { fixupRule, fixupPluginRules, @@ -133,7 +134,7 @@ describe("@eslint/backcompat", () => { const linter = new Linter(); const code = "var foo = () => 123; function bar() { return 123; }"; const messages = linter.verify(code, config, { - filename: "test.js", + filename: path.resolve("test.js"), }); assert.deepStrictEqual( @@ -203,7 +204,7 @@ describe("@eslint/backcompat", () => { const code = "var foo = () => 123; function bar() { return 123; }"; const messages = linter.verify(code, config, { - filename: "test.js", + filename: path.resolve("test.js"), }); assert.deepStrictEqual( @@ -283,7 +284,7 @@ describe("@eslint/backcompat", () => { const code = "var foo = () => 123; function bar() { for (const x of y) { foo(); } }"; const messages = linter.verify(code, config, { - filename: "test.js", + filename: path.resolve("test.js"), }); assert.deepStrictEqual( @@ -507,7 +508,7 @@ describe("@eslint/backcompat", () => { }, }, { - filename: "test.js", + filename: path.resolve("test.js"), }, ); @@ -641,7 +642,7 @@ describe("@eslint/backcompat", () => { const code = "var foo = () => 123; function bar() { return 123; }"; const messages = linter.verify(code, fixupConfigRules(config), { - filename: "test.js", + filename: path.resolve("test.js"), }); assert.deepStrictEqual( From a783ba1d19664f18c545bf86c2395f637c73ba98 Mon Sep 17 00:00:00 2001 From: Francesco Trotta Date: Sun, 15 Sep 2024 14:38:47 +0200 Subject: [PATCH 12/12] throw an error if a path is not absolute --- packages/config-array/src/config-array.js | 15 ++++- .../config-array/tests/config-array.test.js | 59 ++++++++++++++++++- 2 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js index 4dfb74f..61bf991 100644 --- a/packages/config-array/src/config-array.js +++ b/packages/config-array/src/config-array.js @@ -490,9 +490,22 @@ function assertExtraConfigTypes(extraConfigTypes) { * Returns path-handling implementations for Unix or Windows, depending on a given absolute path. * @param {string} path The absolute path to check. * @returns {typeof import("@jsr/std__path")} Path-handling implementations for the specified path. + * @throws An error is thrown if the specified argument is not an absolute path. */ function getPathImpl(path) { - return path.startsWith("/") ? posixPath : windowsPath; + // Posix absolute paths always start with a slash. + if (path.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(path)) { + return windowsPath; + } + + throw new Error(`Expected an absolute path but received "${path}"`); } //------------------------------------------------------------------------------ diff --git a/packages/config-array/tests/config-array.test.js b/packages/config-array/tests/config-array.test.js index 77a3ffb..847e291 100644 --- a/packages/config-array/tests/config-array.test.js +++ b/packages/config-array/tests/config-array.test.js @@ -495,6 +495,12 @@ describe("ConfigArray", () => { configs.normalizeSync(); }, "Config Error: Config (unnamed): Unexpected null config."); }); + + it("should throw an error when basePath is a relative path", async () => { + assert.throws(() => { + void new ConfigArray([{}], { basePath: "foo/bar" }); + }, /Expected an absolute path/u); + }); }); describe("ConfigArray members", () => { @@ -646,6 +652,12 @@ describe("ConfigArray", () => { }, /normalized/u); }); + it("should throw an error when passed a relative path", () => { + assert.throws(() => { + configs.getConfigWithStatus("./foo/bar.js"); + }, /Expected an absolute path/u); + }); + describe("should return expected results", () => { it("for a file outside the base path", () => { const filename = path.resolve(basePath, "../foo.js"); @@ -756,6 +768,12 @@ describe("ConfigArray", () => { }, /normalized/u); }); + it("should throw an error when passed a relative path", () => { + assert.throws(() => { + configs.getConfig("foo/bar.js"); + }, /Expected an absolute path/u); + }); + it("should calculate correct config when passed JS filename", () => { const filename = path.resolve(basePath, "foo.js"); const config = configs.getConfig(filename); @@ -1240,6 +1258,12 @@ describe("ConfigArray", () => { }, /normalized/u); }); + it("should throw an error when passed a relative path", () => { + assert.throws(() => { + configs.getConfigStatus("foo.js"); + }, /Expected an absolute path/u); + }); + it('should return "matched" when passed JS filename', () => { const filename = path.resolve(basePath, "foo.js"); @@ -2179,6 +2203,16 @@ describe("ConfigArray", () => { "unconfigured", ); }); + + it("should throw an error when passed a relative path with a drive letter", () => { + configs = new ConfigArray([], { basePath: "C:\\dir" }); + + configs.normalizeSync(); + + assert.throws(() => { + configs.getConfigStatus("C:file.js"); + }, /Expected an absolute path/u); + }); }); }); @@ -2189,6 +2223,13 @@ describe("ConfigArray", () => { unnormalizedConfigs.isIgnored(filename); }, /normalized/u); }); + + it("should throw an error when passed a relative path", () => { + assert.throws(() => { + configs.isIgnored("foo.js"); + }, /Expected an absolute path/u); + }); + it("should return false when passed JS filename", () => { const filename = path.resolve(basePath, "foo.js"); assert.strictEqual(configs.isIgnored(filename), false); @@ -2260,6 +2301,12 @@ describe("ConfigArray", () => { }, /normalized/u); }); + it("should throw an error when passed a relative path", () => { + assert.throws(() => { + configs.isFileIgnored("foo.js"); + }, /Expected an absolute path/u); + }); + it("should return false when passed JS filename", () => { const filename = path.resolve(basePath, "foo.js"); @@ -3154,10 +3201,20 @@ describe("ConfigArray", () => { }, ); assert.throws(() => { - configs.isDirectoryIgnored("foo/bar"); + configs.isDirectoryIgnored("/foo/bar"); }, /normalized/u); }); + it("should throw an error when a relative path is specified", () => { + configs = new ConfigArray([]); + + configs.normalizeSync(); + + assert.throws(() => { + configs.isDirectoryIgnored("foo/bar"); + }, /Expected an absolute path/u); + }); + it("should return true when the directory is outside of the basePath", () => { configs = new ConfigArray( [