Skip to content

Commit

Permalink
select path implementations depending on base path
Browse files Browse the repository at this point in the history
  • Loading branch information
fasttime committed Aug 18, 2024
1 parent e5526a4 commit 77d7e88
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 70 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
package-lock = false
@jsr:registry=https://npm.jsr.io
1 change: 1 addition & 0 deletions packages/config-array/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
86 changes: 65 additions & 21 deletions packages/config-array/src/config-array.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -352,7 +352,8 @@ function normalizeSync(items, context, extraConfigTypes) {
* matcher.
* @param {Array<string|((string) => 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) {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
//------------------------------------------------------------------------------
Expand All @@ -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
Expand Down Expand Up @@ -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);

/**
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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`);
Expand All @@ -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
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 === "") {
Expand All @@ -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;

Expand All @@ -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,
Expand All @@ -1054,7 +1098,7 @@ export class ConfigArray extends Array {

result = shouldIgnorePath(
this.ignores,
path.join(this.basePath, relativeDirectoryToCheck),
join(this.basePath, relativeDirectoryToCheck),
relativeDirectoryToCheck,
);

Expand Down
35 changes: 0 additions & 35 deletions packages/config-array/src/to-namespaced-path.js

This file was deleted.

22 changes: 8 additions & 14 deletions packages/config-array/tests/config-array.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
);
});

Expand Down

0 comments on commit 77d7e88

Please sign in to comment.