Repository: eslint/rewrite
Pull request #162: feat: normalize patterns to handle "./" prefix in files and ignores
Author: login: Pixel998
Labels: name: bug
description: Something isn't working
name: feature
name: contributor pool
Created: 2025-03-11T13:18:39Z | Merged: 2025-03-23T21:05:18Z

<!--
    Thank you for contributing!

    ESLint adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct).
-->

#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md).

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/latest/contribute/pull-requests)
    - Update or create tests
    - If performance-related, include a benchmark
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What is the purpose of this pull request?

Addresses issue where patterns starting with "./" were not correctly matched in `files` and `ignores`.

#### What changes did you make? (Give an overview)

Strip "./" from the start of strings in `files` and `ignores` so that they match as expected.

#### Related Issues

https://github.com/eslint/eslint/issues/18757

<!-- include tags like "fixes #123" or "refs #123" -->

#### Is there anything you'd like reviewers to focus on?

<!-- markdownlint-disable-file MD004 -->



Diff:
diff --git a/packages/config-array/src/config-array.js b/packages/config-array/src/config-array.js
index 7d55640da..de4e063c6 100644
--- a/packages/config-array/src/config-array.js
+++ b/packages/config-array/src/config-array.js
@@ -244,6 +244,85 @@ function doMatch(filepath, pattern, options = {}) {
 	return matcher.match(filepath);
 }
 
+/**
+ * Normalizes a pattern by removing the leading "./" if present.
+ * @param {string} pattern The pattern to normalize.
+ * @returns {string} The normalized pattern.
+ */
+function normalizePattern(pattern) {
+	if (isString(pattern)) {
+		if (pattern.startsWith("./")) {
+			return pattern.slice(2);
+		}
+
+		if (pattern.startsWith("!./")) {
+			return `!${pattern.slice(3)}`;
+		}
+	}
+
+	return pattern;
+}
+
+/**
+ * Checks if a given pattern requires normalization.
+ * @param {any} pattern The pattern to check.
+ * @returns {boolean} True if the pattern needs normalization, false otherwise.
+ *
+ */
+function needsPatternNormalization(pattern) {
+	return (
+		isString(pattern) &&
+		(pattern.startsWith("./") || pattern.startsWith("!./"))
+	);
+}
+
+/**
+ * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes.
+ * @param {Object} config The config object to normalize patterns in.
+ * @returns {Object} The normalized config object.
+ */
+function normalizeConfigPatterns(config) {
+	if (!config) {
+		return config;
+	}
+
+	let needsNormalization = false;
+
+	if (Array.isArray(config.files)) {
+		needsNormalization = config.files.some(pattern => {
+			if (Array.isArray(pattern)) {
+				return pattern.some(needsPatternNormalization);
+			}
+			return needsPatternNormalization(pattern);
+		});
+	}
+
+	if (!needsNormalization && Array.isArray(config.ignores)) {
+		needsNormalization = config.ignores.some(needsPatternNormalization);
+	}
+
+	if (!needsNormalization) {
+		return config;
+	}
+
+	const newConfig = { ...config };
+
+	if (Array.isArray(newConfig.files)) {
+		newConfig.files = newConfig.files.map(pattern => {
+			if (Array.isArray(pattern)) {
+				return pattern.map(normalizePattern);
+			}
+			return normalizePattern(pattern);
+		});
+	}
+
+	if (Array.isArray(newConfig.ignores)) {
+		newConfig.ignores = newConfig.ignores.map(normalizePattern);
+	}
+
+	return newConfig;
+}
+
 /**
  * Normalizes a `ConfigArray` by flattening it and executing any functions
  * that are found inside.
@@ -294,7 +373,7 @@ async function normalize(items, context, extraConfigTypes) {
 	const configs = [];
 
 	for await (const config of asyncIterable) {
-		configs.push(config);
+		configs.push(normalizeConfigPatterns(config));
 	}
 
 	return configs;
@@ -345,7 +424,13 @@ function normalizeSync(items, context, extraConfigTypes) {
 		}
 	}
 
-	return [...flatTraverse(items)];
+	const configs = [];
+
+	for (const config of flatTraverse(items)) {
+		configs.push(normalizeConfigPatterns(config));
+	}
+
+	return configs;
 }
 
 /**
diff --git a/packages/config-array/tests/config-array.test.js b/packages/config-array/tests/config-array.test.js
index fbfb0c248..3a3676694 100644
--- a/packages/config-array/tests/config-array.test.js
+++ b/packages/config-array/tests/config-array.test.js
@@ -426,7 +426,7 @@ describe("ConfigArray", () => {
 
 			assert.throws(() => {
 				configs.normalizeSync();
-			}, "Config Error: Config (unnamed): Unexpected non-object config.");
+			}, /ConfigError: Config \(unnamed\): Unexpected non-object config./u);
 		});
 
 		it("should throw an error when base config name is not a string", async () => {
@@ -444,7 +444,7 @@ describe("ConfigArray", () => {
 
 			assert.throws(() => {
 				configs.getConfig("foo.js");
-			}, 'Config Error: Config (unnamed): Key "name": Property must be a string.');
+			}, /ConfigError: Config \(unnamed\): Key "name": Property must be a string./u);
 		});
 
 		it("should throw an error when additional config name is not a string", async () => {
@@ -458,7 +458,7 @@ describe("ConfigArray", () => {
 
 			assert.throws(() => {
 				configs.getConfig("foo.js");
-			}, 'Config Error: Config (unnamed): Key "name": Property must be a string.');
+			}, /ConfigError: Config \(unnamed\): Key "name": Property must be a string./u);
 		});
 
 		it("should throw an error when base config is undefined", () => {
@@ -466,7 +466,7 @@ describe("ConfigArray", () => {
 
 			assert.throws(() => {
 				configs.normalizeSync();
-			}, "ConfigError: Config (unnamed): Unexpected undefined config.");
+			}, /ConfigError: Config \(unnamed\): Unexpected undefined config./u);
 		});
 
 		it("should throw an error when base config is null", () => {
@@ -474,7 +474,7 @@ describe("ConfigArray", () => {
 
 			assert.throws(() => {
 				configs.normalizeSync();
-			}, "Config Error: Config (unnamed): Unexpected null config.");
+			}, /ConfigError: Config \(unnamed\): Unexpected null config./u);
 		});
 
 		it("should throw an error when additional config is undefined", () => {
@@ -483,7 +483,7 @@ describe("ConfigArray", () => {
 
 			assert.throws(() => {
 				configs.normalizeSync();
-			}, "Config Error: Config (unnamed): Unexpected undefined config.");
+			}, /ConfigError: Config \(unnamed\): Unexpected undefined config./u);
 		});
 
 		it("should throw an error when additional config is null", () => {
@@ -492,7 +492,7 @@ describe("ConfigArray", () => {
 
 			assert.throws(() => {
 				configs.normalizeSync();
-			}, "Config Error: Config (unnamed): Unexpected null config.");
+			}, /ConfigError: Config \(unnamed\): Unexpected null config./u);
 		});
 
 		it("should throw an error when basePath is a relative path", () => {
@@ -526,6 +526,36 @@ describe("ConfigArray", () => {
 		});
 	});
 
+	describe("Config Pattern Normalization", () => {
+		it("should create a new object when normalizing config patterns with ./", () => {
+			const config = {
+				files: ["./foo.js"],
+			};
+
+			configs = new ConfigArray([config], {
+				basePath,
+			});
+
+			configs.normalizeSync();
+
+			assert.notStrictEqual(configs[0], config);
+		});
+
+		it("should create a new object when normalizing config patterns with !./", async () => {
+			const config = {
+				ignores: ["!./foo.js"],
+			};
+
+			configs = new ConfigArray([config], {
+				basePath,
+			});
+
+			await configs.normalize();
+
+			assert.notStrictEqual(configs[0], config);
+		});
+	});
+
 	describe("ConfigArray members", () => {
 		beforeEach(() => {
 			configs = createConfigArray();
@@ -1229,6 +1259,193 @@ describe("ConfigArray", () => {
 					assert.strictEqual(config2.defs.severity, "error");
 				});
 			});
+
+			// https://github.com/eslint/eslint/issues/18757
+			describe("patterns with './' prefix", () => {
+				it("should match patterns with './' prefix in `files` patterns", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["./src/*.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js").defs.severity,
+						"error",
+					);
+				});
+
+				it("should match patterns with './' prefix in `ignores` patterns", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["**/*.js"],
+								ignores: ["./src/*.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js"),
+						undefined,
+					);
+				});
+
+				it("should match patterns with './' prefix in global `ignores` patterns", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["**/*.js"],
+								defs: { severity: "error" },
+							},
+							{
+								ignores: ["./src/*.js"],
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js"),
+						undefined,
+					);
+				});
+
+				it("should match negated `files` patterns with './' prefix", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["!./src/*.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js"),
+						undefined,
+					);
+				});
+
+				it("should match negated `ignores` patterns with './' prefix", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["**/*.js"],
+								ignores: ["**/*.js", "!./src/foo.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js").defs.severity,
+						"error",
+					);
+				});
+
+				it("should match negated global `ignores` patterns with './' prefix", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["**/*.js"],
+								defs: { severity: "error" },
+							},
+							{
+								ignores: ["**/*.js", "!./src/*.js"],
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js").defs.severity,
+						"error",
+					);
+				});
+
+				it("should match nested `files` patterns with './' prefix", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: [["./src/*.js"]],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js").defs.severity,
+						"error",
+					);
+				});
+
+				it("should match patterns with './' prefix in `files` patterns using normalize()", async () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["./src/*.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					await configs.normalize();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js").defs.severity,
+						"error",
+					);
+				});
+			});
 		});
 
 		describe("getConfigStatus()", () => {


Reviews:
user: login: nzakas
body: Thanks for taking a look at this. Overall, it looks good.

I think we have a question here: Is it safe to overwrite `files` in an existing object or should we be creating a new object? I think we may need to create a new object to be safe, but that would also increase the number of objects we create. And if we do that, perhaps we should only do so when `files` has at least one pattern starting with `"./"` and reuse every other config as-is?

@eslint/eslint-tsc what do you think?
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-11T15:01:40Z
user: login: mdjermanovic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T18:59:33Z
user: login: mdjermanovic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-17T19:05:27Z
user: login: nzakas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T14:28:00Z
user: login: mdjermanovic
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:38:57Z
user: login: Pixel998
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T17:10:15Z
user: login: nzakas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T14:54:00Z
user: login: nzakas
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-19T14:55:05Z
user: login: Pixel998
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T17:31:35Z
user: login: nzakas
body: LGTM, thanks!

Would like @mdjermanovic to approve before merging.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T15:22:45Z
user: login: mdjermanovic
body: LGTM, thanks!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-23T21:05:06Z

Review comments:
diff_hunk: @@ -1229,6 +1229,149 @@ describe("ConfigArray", () => {
 					assert.strictEqual(config2.defs.severity, "error");
 				});
 			});
+
+			// https://github.com/eslint/eslint/issues/18757
+			describe("patterns with './' prefix", () => {
+				it("should match patterns with './' prefix in `files` patterns", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["./src/*.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js").defs.severity,
+						"error",
+					);
+				});
+
+				it("should match patterns with './' prefix in `ignores` patterns", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["**/*.js"],
+								ignores: ["./src/*.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js"),
+						undefined,
+					);
+				});
+
+				it("should match patterns with './' prefix in global `ignores` patterns", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["**/*.js"],
+								defs: { severity: "error" },
+							},
+							{
+								ignores: ["./src/*.js"],
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js"),
+						undefined,
+					);
+				});
+
+				it("should match negated `files` patterns with './' prefix", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["!./src/*.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js"),
+						undefined,
+					);
+				});
+
+				it("should match negated `ignores` patterns with './' prefix", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["**/*.js"],
+								ignores: ["**/*.js", "!./src/foo.js"],
+								defs: { severity: "error" },
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js").defs.severity,
+						"error",
+					);
+				});
+
+				it("should match negated global `ignores` patterns with './' prefix", () => {
+					configs = new ConfigArray(
+						[
+							{
+								files: ["**/*.js"],
+								defs: { severity: "error" },
+							},
+							{
+								ignores: ["**/*.js", "!./src/*.js"],
+							},
+						],
+						{
+							basePath,
+							schema,
+						},
+					);
+
+					configs.normalizeSync();
+
+					assert.strictEqual(
+						configs.getConfig("src/foo.js").defs.severity,
+						"error",
+					);
+				});
+			});
path: packages/config-array/tests/config-array.test.js
user: login: mdjermanovic
body: Can you add a test with `configs.normalize()` to ensure patterns will be normalized when it's used? (currently, all tests are with `configs.normalizeSync()`)
created_at: 2025-03-17T18:59:33Z
author_association: MEMBER
diff_hunk: @@ -244,6 +244,44 @@ function doMatch(filepath, pattern, options = {}) {
 	return matcher.match(filepath);
 }
 
+/**
+ * Normalizes a pattern by removing the leading "./" if present.
+ * @param {string} pattern The pattern to normalize.
+ * @returns {string} The normalized pattern.
+ */
+function normalizePattern(pattern) {
+	if (isString(pattern)) {
+		if (pattern.startsWith("./")) {
+			return pattern.slice(2);
+		}
+
+		if (pattern.startsWith("!./")) {
+			return `!${pattern.slice(3)}`;
+		}
+	}
+
+	return pattern;
+}
+
+/**
+ * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes.
+ * @param {Object} config The config object to normalize patterns in.
+ */
+function normalizeConfigPatterns(config) {
+	if (Array.isArray(config.files)) {
+		config.files = config.files.map(pattern => {
+			if (Array.isArray(pattern)) {
+				return pattern.map(normalizePattern);
+			}
path: packages/config-array/src/config-array.js
user: login: mdjermanovic
body: Can you also add a test for this case?
created_at: 2025-03-17T19:05:27Z
author_association: MEMBER
path: packages/config-array/tests/config-array.test.js
user: login: nzakas
body: Can you add a test that verifies a different object is created when replacing `./` and `!./`?
created_at: 2025-03-18T14:28:00Z
author_association: MEMBER
diff_hunk: @@ -244,6 +244,80 @@ function doMatch(filepath, pattern, options = {}) {
 	return matcher.match(filepath);
 }
 
+/**
+ * Normalizes a pattern by removing the leading "./" if present.
+ * @param {string} pattern The pattern to normalize.
+ * @returns {string} The normalized pattern.
+ */
+function normalizePattern(pattern) {
+	if (isString(pattern)) {
+		if (pattern.startsWith("./")) {
+			return pattern.slice(2);
+		}
+
+		if (pattern.startsWith("!./")) {
+			return `!${pattern.slice(3)}`;
+		}
+	}
+
+	return pattern;
+}
+
+/**
+ * Checks if a given pattern requires normalization.
+ * @param {any} pattern The pattern to check.
+ * @returns {boolean} True if the pattern needs normalization, false otherwise.
+ *
+ */
+function needsPatternNormalization(pattern) {
+	return (
+		isString(pattern) &&
+		(pattern.startsWith("./") || pattern.startsWith("!./"))
+	);
+}
+
+/**
+ * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes.
+ * @param {Object} config The config object to normalize patterns in.
+ */
+function normalizeConfigPatterns(config) {
path: packages/config-array/src/config-array.js
user: login: mdjermanovic
body: ```suggestion
function normalizeConfigPatterns(config) {
	if (!config) {
		return config;
	}
```

I've just tried this version of config-array with eslint/eslint, and several tests that check how nullish config elements are reported are failing with errors like `Cannot read properties of undefined (reading 'files')`. I think we can address this by adding an early exit for that case.
created_at: 2025-03-18T16:38:56Z
author_association: MEMBER
diff_hunk: @@ -244,6 +244,80 @@ function doMatch(filepath, pattern, options = {}) {
 	return matcher.match(filepath);
 }
 
+/**
+ * Normalizes a pattern by removing the leading "./" if present.
+ * @param {string} pattern The pattern to normalize.
+ * @returns {string} The normalized pattern.
+ */
+function normalizePattern(pattern) {
+	if (isString(pattern)) {
+		if (pattern.startsWith("./")) {
+			return pattern.slice(2);
+		}
+
+		if (pattern.startsWith("!./")) {
+			return `!${pattern.slice(3)}`;
+		}
+	}
+
+	return pattern;
+}
+
+/**
+ * Checks if a given pattern requires normalization.
+ * @param {any} pattern The pattern to check.
+ * @returns {boolean} True if the pattern needs normalization, false otherwise.
+ *
+ */
+function needsPatternNormalization(pattern) {
+	return (
+		isString(pattern) &&
+		(pattern.startsWith("./") || pattern.startsWith("!./"))
+	);
+}
+
+/**
+ * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes.
+ * @param {Object} config The config object to normalize patterns in.
+ */
+function normalizeConfigPatterns(config) {
path: packages/config-array/src/config-array.js
user: login: Pixel998
body: Confirmed. This fix passes all ESLint tests.
created_at: 2025-03-18T17:10:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -244,6 +244,84 @@ function doMatch(filepath, pattern, options = {}) {
 	return matcher.match(filepath);
 }
 
+/**
+ * Normalizes a pattern by removing the leading "./" if present.
+ * @param {string} pattern The pattern to normalize.
+ * @returns {string} The normalized pattern.
+ */
+function normalizePattern(pattern) {
+	if (isString(pattern)) {
+		if (pattern.startsWith("./")) {
+			return pattern.slice(2);
+		}
+
+		if (pattern.startsWith("!./")) {
+			return `!${pattern.slice(3)}`;
+		}
+	}
+
+	return pattern;
+}
+
+/**
+ * Checks if a given pattern requires normalization.
+ * @param {any} pattern The pattern to check.
+ * @returns {boolean} True if the pattern needs normalization, false otherwise.
+ *
+ */
+function needsPatternNormalization(pattern) {
+	return (
+		isString(pattern) &&
+		(pattern.startsWith("./") || pattern.startsWith("!./"))
+	);
+}
+
+/**
+ * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes.
+ * @param {Object} config The config object to normalize patterns in.
+ */
path: packages/config-array/src/config-array.js
user: login: nzakas
body: ```suggestion
 * @returns {Object} The normalized config object.
 */
```
created_at: 2025-03-19T14:54:00Z
author_association: MEMBER
diff_hunk: @@ -244,6 +244,80 @@ function doMatch(filepath, pattern, options = {}) {
 	return matcher.match(filepath);
 }
 
+/**
+ * Normalizes a pattern by removing the leading "./" if present.
+ * @param {string} pattern The pattern to normalize.
+ * @returns {string} The normalized pattern.
+ */
+function normalizePattern(pattern) {
+	if (isString(pattern)) {
+		if (pattern.startsWith("./")) {
+			return pattern.slice(2);
+		}
+
+		if (pattern.startsWith("!./")) {
+			return `!${pattern.slice(3)}`;
+		}
+	}
+
+	return pattern;
+}
+
+/**
+ * Checks if a given pattern requires normalization.
+ * @param {any} pattern The pattern to check.
+ * @returns {boolean} True if the pattern needs normalization, false otherwise.
+ *
+ */
+function needsPatternNormalization(pattern) {
+	return (
+		isString(pattern) &&
+		(pattern.startsWith("./") || pattern.startsWith("!./"))
+	);
+}
+
+/**
+ * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes.
+ * @param {Object} config The config object to normalize patterns in.
+ */
+function normalizeConfigPatterns(config) {
path: packages/config-array/src/config-array.js
user: login: nzakas
body: Can you also add a test with `null` and `undefined` configs in this PR?
created_at: 2025-03-19T14:55:05Z
author_association: MEMBER
diff_hunk: @@ -244,6 +244,80 @@ function doMatch(filepath, pattern, options = {}) {
 	return matcher.match(filepath);
 }
 
+/**
+ * Normalizes a pattern by removing the leading "./" if present.
+ * @param {string} pattern The pattern to normalize.
+ * @returns {string} The normalized pattern.
+ */
+function normalizePattern(pattern) {
+	if (isString(pattern)) {
+		if (pattern.startsWith("./")) {
+			return pattern.slice(2);
+		}
+
+		if (pattern.startsWith("!./")) {
+			return `!${pattern.slice(3)}`;
+		}
+	}
+
+	return pattern;
+}
+
+/**
+ * Checks if a given pattern requires normalization.
+ * @param {any} pattern The pattern to check.
+ * @returns {boolean} True if the pattern needs normalization, false otherwise.
+ *
+ */
+function needsPatternNormalization(pattern) {
+	return (
+		isString(pattern) &&
+		(pattern.startsWith("./") || pattern.startsWith("!./"))
+	);
+}
+
+/**
+ * Normalizes `files` and `ignores` patterns in a config by removing "./" prefixes.
+ * @param {Object} config The config object to normalize patterns in.
+ */
+function normalizeConfigPatterns(config) {
path: packages/config-array/src/config-array.js
user: login: Pixel998
body: It appears we did have tests for this, but they were broken due to an incorrect use of assert.throws. We were using a string as the second argument, which [doesn't perform error message matching as intended](https://nodejs.org/api/assert.html#assertthrowsfn-error-message). I've corrected all instances of this.
created_at: 2025-03-19T17:31:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: mdjermanovic
created_at: 2025-03-17T17:35:47Z
body: I think we should create a new object when we need to modify its content. In this case, where at least one of its `files` / `ignores` patterns starts with `"./"` or `"!./"`.
author_association: MEMBER
user: login: mdjermanovic
created_at: 2025-03-18T17:02:40Z
body: I changed the tag to `feat` as this enables patterns that are currently no-op.
author_association: MEMBER