diff --git a/packages/compat/README.md b/packages/compat/README.md index 7f66e3a07..8d82e6403 100644 --- a/packages/compat/README.md +++ b/packages/compat/README.md @@ -33,7 +33,7 @@ This package exports the following functions in both ESM and CommonJS format: - `fixupRule(rule)` - wraps the given rule in a compatibility layer and returns the result - `fixupPluginRules(plugin)` - wraps each rule in the given plugin using `fixupRule()` and returns a new object that represents the plugin with the fixed-up rules - `fixupConfigRules(configs)` - wraps all plugins found in an array of config objects using `fixupPluginRules()` -- `includeIgnoreFile(path)` - reads an ignore file (like `.gitignore`) and converts the patterns into the correct format for the config file +- `includeIgnoreFile(path)` (deprecated) - reads an ignore file (like `.gitignore`) and converts the patterns into the correct format for the config file ### Fixing Rules @@ -149,6 +149,8 @@ module.exports = defineConfig([ ### Including Ignore Files +**Deprecated**: The `includeIgnoreFile()` exported by this package has been deprecated ([eslint/rewrite#329](https://github.com/eslint/rewrite/issues/329)). Use the `includeIgnoreFile()` function exported by `@eslint/config-helpers` instead (also available at `eslint/config`). This section is only preserved for historical reference. + If you were using an alternate ignore file in ESLint v8.x, such as using `--ignore-path .gitignore` on the command line, you can include those patterns programmatically in your config file using the `includeIgnoreFile()` function. The `includeIgnoreFile()` function also accepts a second optional `name` parameter that allows you to set a custom name for this configuration object. If not specified, it defaults to `"Imported .gitignore patterns"`. For example: diff --git a/packages/compat/src/ignore-file.js b/packages/compat/src/ignore-file.js index a3f3b4474..d8af09112 100644 --- a/packages/compat/src/ignore-file.js +++ b/packages/compat/src/ignore-file.js @@ -24,6 +24,9 @@ import path from "node:path"; * Converts an ESLint ignore pattern to a minimatch pattern. * @param {string} pattern The .eslintignore or .gitignore pattern to convert. * @returns {string} The converted pattern. + * + * @deprecated Use the `convertIgnorePatternToMinimatch()` function exported by + * `@eslint/config-helpers` instead. */ export function convertIgnorePatternToMinimatch(pattern) { const isNegated = pattern.startsWith("!"); @@ -72,6 +75,9 @@ export function convertIgnorePatternToMinimatch(pattern) { * @param {string} [name] The name of the ignore file config. * @returns {FlatConfig} An object with an `ignores` property that is an array of ignore patterns. * @throws {Error} If the ignore file path is not an absolute path. + * + * @deprecated Use the `includeIgnoreFile()` function exported by + * `@eslint/config-helpers` instead (also available at `eslint/config`). */ export function includeIgnoreFile(ignoreFilePath, name) { if (!path.isAbsolute(ignoreFilePath)) { diff --git a/packages/compat/tests/ignore-file.test.js b/packages/compat/tests/ignore-file.test.js index 60a48a8a6..407110103 100644 --- a/packages/compat/tests/ignore-file.test.js +++ b/packages/compat/tests/ignore-file.test.js @@ -1,5 +1,6 @@ /** - * @filedescription Fixup tests + * @fileoverview Tests for `includeIgnoreFile()` and `convertIgnorePatternToMinimatch()` + * @author Nicholas C. Zakas */ //----------------------------------------------------------------------------- diff --git a/packages/config-helpers/README.md b/packages/config-helpers/README.md index 8f214a757..59c6fe7c6 100644 --- a/packages/config-helpers/README.md +++ b/packages/config-helpers/README.md @@ -74,6 +74,59 @@ export default defineConfig([ ]); ``` +### `includeIgnoreFile()` + +The `includeIgnoreFile()` function reads a file with gitignore-style patterns (such as a `.gitignore`) and returns a config object with the patterns converted to a global ignores object. Pass the absolute path to the ignore file as the first argument: + +```js +// eslint.config.js + +import { defineConfig, includeIgnoreFile } from "@eslint/config-helpers"; +import path from "node:path"; + +const ignorePath = path.join(import.meta.dirname, ".gitignore"); + +export default defineConfig([ + includeIgnoreFile(ignorePath, { + gitignoreResolution: true, + }), + // ... +]); +``` + +#### Options + +The second argument is an optional options object: + +- **`gitignoreResolution`** (`boolean`): Controls how ignore patterns are interpreted. + - `false` (default) — patterns are resolved relative to the location of the configuration file. + - `true` — patterns are resolved relative to the location of the ignore file, matching the behavior of `.gitignore` files. +- **`name`** (`string`): A custom name for the resulting config object. + +For backwards compatibility with `includeIgnoreFile()` from `@eslint/compat`, passing a string instead of an object as the second argument is treated as equivalent to providing a value for `name`. + +#### Multiple files + +You can also pass an array of absolute paths to include multiple ignore files at once. In this case an array of config objects is returned: + +```js +// eslint.config.js + +import { defineConfig, includeIgnoreFile } from "@eslint/config-helpers"; +import path from "node:path"; + +export default defineConfig([ + includeIgnoreFile( + [ + path.join(import.meta.dirname, ".gitignore"), + path.join(import.meta.dirname, "packages/lib/.gitignore"), + ], + { gitignoreResolution: true }, + ), + // ... +]); +``` + ## License Apache 2.0 diff --git a/packages/config-helpers/src/ignore-file.js b/packages/config-helpers/src/ignore-file.js new file mode 100644 index 000000000..2e1674e3d --- /dev/null +++ b/packages/config-helpers/src/ignore-file.js @@ -0,0 +1,211 @@ +/** + * @fileoverview Ignore file utilities for the config-helpers package. + * This file was forked from the source code for the compat package. + * + * @author Nicholas C. Zakas + * @author Kirk Waiblinger + */ + +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import fs from "node:fs"; +import path from "node:path"; + +//----------------------------------------------------------------------------- +// Types +//----------------------------------------------------------------------------- + +/** @typedef {import("@eslint/core").ConfigObject} Config */ + +/** + * @typedef {object} IncludeIgnoreFileOptionsObject + * @property {boolean} [gitignoreResolution] Whether to interpret the contents of an ignore file relative to the config file or the ignore file. + * - gitignoreResolution: false (default): Interprets ignore patterns relative to the config file + * - gitignoreResolution: true: Interprets the ignore patterns in a file relative to the ignore file + * @property {string} [name] The name to give the output config object(s). + */ + +/** + * Options for `includeIgnoreFile()`. May be provided as an object or, for + * legacy compatibility with `@eslint/compat`, as a string which is treated as + * the `name` option. + * @typedef {IncludeIgnoreFileOptionsObject | string} IncludeIgnoreFileOptions + */ + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +/** + * Converts an ESLint ignore pattern to a minimatch pattern. + * @param {string} pattern The .eslintignore or .gitignore pattern to convert. + * @returns {string} The converted pattern. + */ +export function convertIgnorePatternToMinimatch(pattern) { + const isNegated = pattern.startsWith("!"); + const negatedPrefix = isNegated ? "!" : ""; + const patternToTest = (isNegated ? pattern.slice(1) : pattern).trimEnd(); + + // special cases + if (["", "**", "/**", "**/"].includes(patternToTest)) { + return `${negatedPrefix}${patternToTest}`; + } + + const firstIndexOfSlash = patternToTest.indexOf("/"); + + const matchEverywherePrefix = + firstIndexOfSlash < 0 || firstIndexOfSlash === patternToTest.length - 1 + ? "**/" + : ""; + + const patternWithoutLeadingSlash = + firstIndexOfSlash === 0 ? patternToTest.slice(1) : patternToTest; + + /* + * Escape `{` and `(` because in gitignore patterns they are just + * literal characters without any specific syntactic meaning, + * while in minimatch patterns they can form brace expansion or extglob syntax. + * + * For example, gitignore pattern `src/{a,b}.js` ignores file `src/{a,b}.js`. + * But, the same minimatch pattern `src/{a,b}.js` ignores files `src/a.js` and `src/b.js`. + * Minimatch pattern `src/\{a,b}.js` is equivalent to gitignore pattern `src/{a,b}.js`. + */ + const escapedPatternWithoutLeadingSlash = + patternWithoutLeadingSlash.replaceAll( + // eslint-disable-next-line regexp/no-empty-lookarounds-assertion -- False positive + /(?=((?:\\.|[^{(])*))\1([{(])/guy, + "$1\\$2", + ); + + const matchInsideSuffix = patternToTest.endsWith("/**") ? "/*" : ""; + + return `${negatedPrefix}${matchEverywherePrefix}${escapedPatternWithoutLeadingSlash}${matchInsideSuffix}`; +} + +/** + * @param {string} ignoreFilePath + * @returns {string[]} + */ +function ignoreFilePathToPatterns(ignoreFilePath) { + const ignoreFile = fs.readFileSync(ignoreFilePath, "utf8"); + const lines = ignoreFile.split(/\r?\n/u); + + return lines + .map(line => line.trim()) + .filter(line => line && !line.startsWith("#")) + .map(convertIgnorePatternToMinimatch); +} + +/** + * Helper to parse and validate the options to `includeIgnoreFile()` + * + * @param {string | { gitignoreResolution?: unknown, name?: unknown } | undefined} options + * @returns {{ gitignoreResolution: boolean, name: string }} + */ +function parseOptions(options) { + // legacy compatibility with @eslint/compat's `includeIgnoreFile` + if (typeof options === "string") { + return { gitignoreResolution: false, name: options }; + } + + const optionsObject = options ?? {}; + if (typeof optionsObject !== "object" || Array.isArray(optionsObject)) { + throw new TypeError( + "The options argument to `includeIgnoreFile()` should be an object or a string.", + ); + } + + const gitignoreResolution = optionsObject.gitignoreResolution ?? false; + if (typeof gitignoreResolution !== "boolean") { + throw new TypeError( + "The `gitignoreResolution` option must be specified a boolean or omitted", + ); + } + + const name = optionsObject.name ?? `Imported .gitignore patterns`; + if (typeof name !== "string") { + throw new TypeError( + "The `name` option must be specified as a string or omitted.", + ); + } + + return { gitignoreResolution, name }; +} + +/** + * @overload + * + * Reads ignore files and returns objects with the ignore patterns. + * + * @param {string[]} ignoreFilePathArg The paths of ignore files to include. + * @param {IncludeIgnoreFileOptions} [options] + * @returns {Config[]} + */ + +/** + * @overload + * + * Reads an ignore file and returns an object with the ignore patterns. + * + * @param {string} ignoreFilePathArg The path of the ignore file to include. + * @param {IncludeIgnoreFileOptions} [options] + * @returns {Config} + */ + +/** + * @overload + * + * Reads an ignore file(s) and returns an object(s) with the ignore patterns. + * + * @param {string[] | string} ignoreFilePathArg The path(s) of the ignore file(s) to include. + * @param {IncludeIgnoreFileOptions} [options] + * @returns {Config[] | Config} + */ + +/** + * Reads an ignore file(s) and returns an object(s) with the ignore patterns. + * + * @param {string[] | string} ignoreFilePathArg The path(s) of the ignore file(s) to include. + * @param {IncludeIgnoreFileOptions} [options] + * @returns {Config[] | Config} + */ +export function includeIgnoreFile(ignoreFilePathArg, options) { + const returnSingleObject = !Array.isArray(ignoreFilePathArg); + const ignoreFilePaths = Array.isArray(ignoreFilePathArg) + ? ignoreFilePathArg + : [ignoreFilePathArg]; + for (const ignorePath of ignoreFilePaths) { + if (typeof ignorePath !== "string") { + throw new TypeError( + "The first argument to `includeIgnoreFile()` should be a string or array of strings", + ); + } + if (!path.isAbsolute(ignorePath)) { + throw new Error( + `The ignore file location must be an absolute path. Received ${ignorePath}`, + ); + } + } + + const { gitignoreResolution, name } = parseOptions(options); + + if (returnSingleObject) { + return { + name, + ignores: ignoreFilePathToPatterns(ignoreFilePathArg), + ...(gitignoreResolution + ? { basePath: path.dirname(ignoreFilePathArg) } + : {}), + }; + } + + return ignoreFilePaths.map((ignoreFilePath, i) => ({ + name: `${name} (${i})`, + ignores: ignoreFilePathToPatterns(ignoreFilePath), + ...(gitignoreResolution + ? { basePath: path.dirname(ignoreFilePath) } + : {}), + })); +} diff --git a/packages/config-helpers/src/index.js b/packages/config-helpers/src/index.js index da8412aab..20d8d4e73 100644 --- a/packages/config-helpers/src/index.js +++ b/packages/config-helpers/src/index.js @@ -4,3 +4,7 @@ export { defineConfig } from "./define-config.js"; export { globalIgnores } from "./global-ignores.js"; +export { + includeIgnoreFile, + convertIgnorePatternToMinimatch, +} from "./ignore-file.js"; diff --git a/packages/config-helpers/tests/fixtures/ignore-files/gitignore1.txt b/packages/config-helpers/tests/fixtures/ignore-files/gitignore1.txt new file mode 100644 index 000000000..f6b5f5754 --- /dev/null +++ b/packages/config-helpers/tests/fixtures/ignore-files/gitignore1.txt @@ -0,0 +1,17 @@ +# Node.js +node_modules +!/fixtures/node_modules +/dist + +# Logs +*.log + +# Gatsby files +.cache/ + +# vuepress build output +.vuepress/dist + +# other +*/foo.js +dir/** diff --git a/packages/config-helpers/tests/ignore-file.test.js b/packages/config-helpers/tests/ignore-file.test.js new file mode 100644 index 000000000..3785295bf --- /dev/null +++ b/packages/config-helpers/tests/ignore-file.test.js @@ -0,0 +1,238 @@ +/** + * @fileoverview Tests for `includeIgnoreFile()` and `convertIgnorePatternToMinimatch()` + * @author Nicholas C. Zakas + * @author Kirk Waiblinger + */ + +//----------------------------------------------------------------------------- +// Imports +//----------------------------------------------------------------------------- + +import assert from "node:assert"; +import { + includeIgnoreFile, + convertIgnorePatternToMinimatch, +} from "../src/ignore-file.js"; +import { fileURLToPath } from "node:url"; + +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- + +const gitignore1FixturePathRelative = + "../tests/fixtures/ignore-files/gitignore1.txt"; +const gitignore1FixturePathAbsolute = fileURLToPath( + import.meta.resolve(gitignore1FixturePathRelative), +); +const gitignore1FixtureDir = fileURLToPath( + import.meta.resolve("../tests/fixtures/ignore-files"), +); + +describe("@eslint/config-helpers", () => { + describe("convertIgnorePatternToMinimatch", () => { + const tests = [ + ["", ""], + ["**", "**"], + ["/**", "/**"], + ["**/", "**/"], + ["src/", "**/src/"], + ["src", "**/src"], + ["src/**", "src/**/*"], + ["!src/", "!**/src/"], + ["!src", "!**/src"], + ["!src/**", "!src/**/*"], + ["*/foo.js", "*/foo.js"], + ["*/foo.js/", "*/foo.js/"], + ["src/{a,b}.js", "src/\\{a,b}.js"], + ["src/?(a)b.js", "src/?\\(a)b.js"], + ["{.js", "**/\\{.js"], + ["(.js", "**/\\(.js"], + ["(.js", "**/\\(.js"], + ["{(.js", "**/\\{\\(.js"], + ["{bar}/{baz}", "\\{bar}/\\{baz}"], + ["\\[foo]/{bar}/{baz}", "\\[foo]/\\{bar}/\\{baz}"], + ["src/\\{a}", "src/\\{a}"], + ["src/\\(a)", "src/\\(a)"], + ["src/\\{a}/{b}", "src/\\{a}/\\{b}"], + ["src/\\(a)/(b)", "src/\\(a)/\\(b)"], + ["a\\bc{de(f\\gh\\{i\\(j{(", "**/a\\bc\\{de\\(f\\gh\\{i\\(j\\{\\("], + ]; + + tests.forEach(([pattern, expected]) => { + it(`should convert "${pattern}" to "${expected}"`, () => { + assert.strictEqual( + convertIgnorePatternToMinimatch(pattern), + expected, + ); + }); + }); + }); + + describe("includeIgnoreFile", () => { + it("should throw an error when an array of relative paths is passed", () => { + assert.throws( + () => { + includeIgnoreFile([gitignore1FixturePathRelative]); + }, + { + name: "Error", + message: + /The ignore file location must be an absolute path. Received .*/u, + }, + ); + }); + + it("should return an object with an `ignores` property", () => { + const result = includeIgnoreFile(gitignore1FixturePathAbsolute, { + gitignoreResolution: true, + }); + + assert.deepStrictEqual(result, { + name: "Imported .gitignore patterns", + ignores: [ + "**/node_modules", + "!fixtures/node_modules", + "dist", + "**/*.log", + "**/.cache/", + ".vuepress/dist", + "*/foo.js", + "dir/**/*", + ], + basePath: gitignore1FixtureDir, + }); + }); + + it("should return an object with a custom name", () => { + const result = includeIgnoreFile( + gitignore1FixturePathAbsolute, + "Custom Name", + ); + assert.deepStrictEqual(result, { + name: "Custom Name", + ignores: [ + "**/node_modules", + "!fixtures/node_modules", + "dist", + "**/*.log", + "**/.cache/", + ".vuepress/dist", + "*/foo.js", + "dir/**/*", + ], + }); + }); + + it("should handle when both name and gitignoreResolution are specified", () => { + const result = includeIgnoreFile( + [gitignore1FixturePathAbsolute, gitignore1FixturePathAbsolute], + { + gitignoreResolution: true, + name: "Custom Name", + }, + ); + assert.deepStrictEqual(result, [ + { + name: "Custom Name (0)", + basePath: gitignore1FixtureDir, + ignores: [ + "**/node_modules", + "!fixtures/node_modules", + "dist", + "**/*.log", + "**/.cache/", + ".vuepress/dist", + "*/foo.js", + "dir/**/*", + ], + }, + { + name: "Custom Name (1)", + basePath: gitignore1FixtureDir, + ignores: [ + "**/node_modules", + "!fixtures/node_modules", + "dist", + "**/*.log", + "**/.cache/", + ".vuepress/dist", + "*/foo.js", + "dir/**/*", + ], + }, + ]); + }); + + for (const value of [true, 1, 123n, [1, 2, 3]]) { + it(`should throw an error when the second argument is ${value}`, () => { + assert.throws( + () => { + includeIgnoreFile(gitignore1FixturePathAbsolute, value); + }, + { + name: "TypeError", + message: + /The options argument to `includeIgnoreFile\(\)` should be an object or a string./u, + }, + ); + }); + } + }); +}); + +// These tests are copied from @eslint/compat and ensure that the `includeIgnoreFile` is a superset of the +// functionality of the `includeIgnoreFile` in `@eslint/compat`. +describe("`includeIgnoreFile` compat with @eslint/compat", () => { + it("should throw an error when a relative path is passed", () => { + const ignoreFilePath = "../tests/fixtures/ignore-files/gitignore1.txt"; + assert.throws(() => { + includeIgnoreFile(ignoreFilePath); + }, /The ignore file location must be an absolute path./u); + }); + + it("should return an object with an `ignores` property", () => { + const ignoreFilePath = fileURLToPath( + new URL( + "../tests/fixtures/ignore-files/gitignore1.txt", + import.meta.url, + ), + ); + const result = includeIgnoreFile(ignoreFilePath); + assert.deepStrictEqual(result, { + name: "Imported .gitignore patterns", + ignores: [ + "**/node_modules", + "!fixtures/node_modules", + "dist", + "**/*.log", + "**/.cache/", + ".vuepress/dist", + "*/foo.js", + "dir/**/*", + ], + }); + }); + + it("should return an object with a custom name", () => { + const ignoreFilePath = fileURLToPath( + new URL( + "../tests/fixtures/ignore-files/gitignore1.txt", + import.meta.url, + ), + ); + const result = includeIgnoreFile(ignoreFilePath, "Custom Name"); + assert.deepStrictEqual(result, { + name: "Custom Name", + ignores: [ + "**/node_modules", + "!fixtures/node_modules", + "dist", + "**/*.log", + "**/.cache/", + ".vuepress/dist", + "*/foo.js", + "dir/**/*", + ], + }); + }); +}); diff --git a/packages/config-helpers/tests/index.test.js b/packages/config-helpers/tests/index.test.js index 5aa842f0a..9bf2a3d2b 100644 --- a/packages/config-helpers/tests/index.test.js +++ b/packages/config-helpers/tests/index.test.js @@ -21,4 +21,15 @@ describe("index", () => { it("should export globalIgnores()", () => { assert.strictEqual(typeof api.globalIgnores, "function"); }); + + it("should export includeIgnoreFile()", () => { + assert.strictEqual(typeof api.includeIgnoreFile, "function"); + }); + + it("should export convertIgnorePatternToMinimatch()", () => { + assert.strictEqual( + typeof api.convertIgnorePatternToMinimatch, + "function", + ); + }); }); diff --git a/packages/config-helpers/tests/types/types.test.ts b/packages/config-helpers/tests/types/types.test.ts index c8f5c0e88..24f89ed59 100644 --- a/packages/config-helpers/tests/types/types.test.ts +++ b/packages/config-helpers/tests/types/types.test.ts @@ -13,6 +13,8 @@ import { type ConfigWithExtends, type ExtensionConfigObject, globalIgnores, + includeIgnoreFile, + convertIgnorePatternToMinimatch, } from "@eslint/config-helpers"; //----------------------------------------------------------------------------- @@ -229,3 +231,57 @@ globalIgnores([1]); globalIgnores(["node_modules"], 1); // #endregion globalIgnores + +//----------------------------------------------------------------------------- +// Tests for includeIgnoreFile() +//----------------------------------------------------------------------------- + +// #region includeIgnoreFile + +// string path should return a single config object + +includeIgnoreFile("some-string").ignores; +includeIgnoreFile("some-string", {}).ignores; +includeIgnoreFile("some-string", { gitignoreResolution: true, name: "falafel" }) + .ignores; + +// array of string paths should return an array of config objects +includeIgnoreFile(["some-string", "some-other-string"]).map( + config => config.ignores, +); + +declare const pathOrPaths: string | string[]; +includeIgnoreFile(pathOrPaths, { gitignoreResolution: true, name: "falafel" }); + +// prettier-ignore +includeIgnoreFile(pathOrPaths, { gitignoreResolution: true, name: "falafel" }) + // @ts-expect-error -- return type shouldn't be able to access field of config object + .ignores; +includeIgnoreFile(pathOrPaths, { + gitignoreResolution: true, + name: "falafel", +}) + // @ts-expect-error -- return type shouldn't be able to access array method + .map(config => config.ignores); + +// should be able to provide a string options argument for compatibility reasons. +includeIgnoreFile("foo", "string-name"); + +// @ts-expect-error -- options should not be a number. +includeIgnoreFile("bar", 22); + +// #endregion includeIgnoreFile + +//----------------------------------------------------------------------------- +// Tests for convertIgnorePatternToMinimatch() +//----------------------------------------------------------------------------- + +// #region convertIgnorePatternToMinimatch + +// should be string => string. +convertIgnorePatternToMinimatch("foo") satisfies string; + +// @ts-expect-error -- input should be a string. +convertIgnorePatternToMinimatch(12345); + +// #endregion convertIgnorePatternToMinimatch diff --git a/packages/migrate-config/package.json b/packages/migrate-config/package.json index cbd0abe2e..be05fcaae 100644 --- a/packages/migrate-config/package.json +++ b/packages/migrate-config/package.json @@ -40,14 +40,13 @@ }, "homepage": "https://github.com/eslint/rewrite/tree/main/packages/migrate-config#readme", "devDependencies": { - "@eslint/core": "^1.2.1", - "eslint": "^10.0.3" + "@eslint/core": "^1.2.1" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" }, "dependencies": { - "@eslint/compat": "^2.0.5", + "@eslint/config-helpers": "^0.5.5", "@eslint/eslintrc": "^3.3.5", "camelcase": "^8.0.0", "espree": "^11.2.0", diff --git a/packages/migrate-config/src/migrate-config.js b/packages/migrate-config/src/migrate-config.js index b1c33fd7b..acc3d55ce 100644 --- a/packages/migrate-config/src/migrate-config.js +++ b/packages/migrate-config/src/migrate-config.js @@ -13,7 +13,7 @@ import { Legacy } from "@eslint/eslintrc"; import camelCase from "camelcase"; import pluginsNeedingCompat from "./compat-plugins.js"; import configsNeedingCompat from "./compat-configs.js"; -import { convertIgnorePatternToMinimatch } from "@eslint/compat"; +import { convertIgnorePatternToMinimatch } from "@eslint/config-helpers"; import * as espree from "espree"; //-----------------------------------------------------------------------------