From cadfbcd468737fc9447243edd1d15058efb6d3d8 Mon Sep 17 00:00:00 2001 From: "Nicholas C. Zakas" Date: Fri, 29 Dec 2023 05:31:41 -0700 Subject: [PATCH] feat!: Rename FlatESLint to ESLint (#17914) * feat!: Rename FlatESLint to ESLint Refs # 13481 * Rename supporting types * Update docs/src/use/migrate-to-9.0.0.md --------- Co-authored-by: Milos Djermanovic --- .../example-eslint-integration.js | 49 +- docs/src/integrate/integration-tutorial.md | 162 +- docs/src/integrate/nodejs-api.md | 29 +- docs/src/use/migrate-to-9.0.0.md | 15 + lib/api.js | 4 +- lib/cli.js | 10 +- lib/eslint/eslint.js | 1241 ++- lib/eslint/flat-eslint.js | 1183 --- lib/eslint/index.js | 4 +- lib/eslint/legacy-eslint.js | 722 ++ lib/unsupported-api.js | 6 +- tests/lib/cli.js | 62 +- tests/lib/eslint/eslint.js | 7644 +++++++---------- tests/lib/eslint/flat-eslint.js | 6660 -------------- tests/lib/eslint/legacy-eslint.js | 7638 ++++++++++++++++ 15 files changed, 12729 insertions(+), 12700 deletions(-) delete mode 100644 lib/eslint/flat-eslint.js create mode 100644 lib/eslint/legacy-eslint.js delete mode 100644 tests/lib/eslint/flat-eslint.js create mode 100644 tests/lib/eslint/legacy-eslint.js diff --git a/docs/_examples/integration-tutorial-code/example-eslint-integration.js b/docs/_examples/integration-tutorial-code/example-eslint-integration.js index f36b4e46e76..7c7ecb3ed00 100644 --- a/docs/_examples/integration-tutorial-code/example-eslint-integration.js +++ b/docs/_examples/integration-tutorial-code/example-eslint-integration.js @@ -1,4 +1,4 @@ -/** +/** * @fileoverview An example of how to integrate ESLint into your own tool * @author Ben Perlmutter */ @@ -6,46 +6,47 @@ const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function -function createESLintInstance(overrideConfig){ - return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +function createESLintInstance(overrideConfig) { + return new ESLint({ + overrideConfigFile: true, + overrideConfig, + fix: true + }); } // Lint the specified files and return the error results async function lintAndFix(eslint, filePaths) { - const results = await eslint.lintFiles(filePaths); + const results = await eslint.lintFiles(filePaths); - // Apply automatic fixes and output fixed code - await ESLint.outputFixes(results); + // Apply automatic fixes and output fixed code + await ESLint.outputFixes(results); - return results; + return results; } // Log results to console if there are any problems function outputLintingResults(results) { - // Identify the number of problems found - const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); - - if (problems > 0) { - console.log("Linting errors found!"); - console.log(results); - } else { - console.log("No linting errors found."); - } - return results; + // Identify the number of problems found + const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); + + if (problems > 0) { + console.log("Linting errors found!"); + console.log(results); + } else { + console.log("No linting errors found."); + } + return results; } // Put previous functions all together async function lintFiles(filePaths) { // The ESLint configuration. Alternatively, you could load the configuration - // from a .eslintrc file or just use the default config. + // from an eslint.config.js file or just use the default config. const overrideConfig = { - env: { - es6: true, - node: true, - }, - parserOptions: { + languageOptions: { ecmaVersion: 2018, + sourceType: "commonjs" }, rules: { "no-console": "error", @@ -59,4 +60,4 @@ async function lintFiles(filePaths) { } // Export integration -module.exports = { lintFiles } \ No newline at end of file +module.exports = { lintFiles } diff --git a/docs/src/integrate/integration-tutorial.md b/docs/src/integrate/integration-tutorial.md index 403582f19fe..1644b91528a 100644 --- a/docs/src/integrate/integration-tutorial.md +++ b/docs/src/integrate/integration-tutorial.md @@ -7,25 +7,42 @@ eleventyNavigation: order: 1 --- -This guide walks you through integrating the `ESLint` class to lint files and retrieve results, which can be useful for creating integrations with other projects. +This guide walks you through integrating the `ESLint` class to lint files and +retrieve results, which can be useful for creating integrations with other +projects. ## Why Create an Integration? -You might want to create an ESLint integration if you're creating developer tooling, such as the following: +You might want to create an ESLint integration if you're creating developer +tooling, such as the following: -* **Code editors and IDEs**: Integrating ESLint with code editors and IDEs can provide real-time feedback on code quality and automatically highlight potential issues as you type. Many editors already have ESLint plugins available, but you may need to create a custom integration if the existing plugins do not meet your specific requirements. +* **Code editors and IDEs**: Integrating ESLint with code editors and IDEs can + provide real-time feedback on code quality and automatically highlight + potential issues as you type. Many editors already have ESLint plugins + available, but you may need to create a custom integration if the existing + plugins do not meet your specific requirements. -* **Custom linter tools**: If you're building a custom linter tool that combines multiple linters or adds specific functionality, you may want to integrate ESLint into your tool to provide JavaScript linting capabilities. +* **Custom linter tools**: If you're building a custom linter tool that combines + multiple linters or adds specific functionality, you may want to integrate + ESLint into your tool to provide JavaScript linting capabilities. -* **Code review tools**: Integrating ESLint with code review tools can help automate the process of identifying potential issues in the codebase. +* **Code review tools**: Integrating ESLint with code review tools can help + automate the process of identifying potential issues in the codebase. -* **Learning platforms**: If you are developing a learning platform or coding tutorial, integrating ESLint can provide real-time feedback to users as they learn JavaScript, helping them improve their coding skills and learn best practices. +* **Learning platforms**: If you are developing a learning platform or coding + tutorial, integrating ESLint can provide real-time feedback to users as they + learn JavaScript, helping them improve their coding skills and learn best + practices. -* **Developer tool integration**: If you're creating or extending a developer tool, such as a bundler or testing framework, you may want to integrate ESLint to provide linting capabilities. You can integrate ESLint directly into the tool or as a plugin. +* **Developer tool integration**: If you're creating or extending a developer + tool, such as a bundler or testing framework, you may want to integrate ESLint + to provide linting capabilities. You can integrate ESLint directly into the + tool or as a plugin. ## What You'll Build -In this guide, you'll create a simple Node.js project that uses the `ESLint` class to lint files and retrieve results. +In this guide, you'll create a simple Node.js project that uses the `ESLint` +class to lint files and retrieve results. ## Requirements @@ -68,7 +85,8 @@ touch example-eslint-integration.js Import the `ESLint` class from the `eslint` package and create a new instance. -You can customize the ESLint configuration by passing an options object to the `ESLint` constructor: +You can customize the ESLint configuration by passing an options object to the +`ESLint` constructor: ```javascript // example-eslint-integration.js @@ -76,16 +94,25 @@ You can customize the ESLint configuration by passing an options object to the ` const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function -function createESLintInstance(overrideConfig){ - return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +function createESLintInstance(overrideConfig) { + return new ESLint({ + overrideConfigFile: true, + overrideConfig, + fix: true, + }); } ``` ## Step 3: Lint and Fix Files -To lint a file, use the `lintFiles` method of the `ESLint` instance. The `filePaths` argument passed to `ESLint#lintFiles()` can be a string or an array of strings, representing the file path(s) you want to lint. The file paths can be globs or filenames. +To lint a file, use the `lintFiles` method of the `ESLint` instance. The +`filePaths` argument passed to `ESLint#lintFiles()` can be a string or an array +of strings, representing the file path(s) you want to lint. The file paths can +be globs or filenames. -The static method `ESLint.outputFixes()` takes the linting results from the call to `ESLint#lintFiles()`, and then writes the fixed code back to the source files. +The static method `ESLint.outputFixes()` takes the linting results from the call +to `ESLint#lintFiles()`, and then writes the fixed code back to the source +files. ```javascript // example-eslint-integration.js @@ -105,7 +132,9 @@ async function lintAndFix(eslint, filePaths) { ## Step 4: Output Results -Define a function to output the linting results to the console. This should be specific to your integration's needs. For example, you could report the linting results to a user interface. +Define a function to output the linting results to the console. This should be +specific to your integration's needs. For example, you could report the linting +results to a user interface. In this example, we'll simply log the results to the console: @@ -118,7 +147,10 @@ In this example, we'll simply log the results to the console: // Log results to console if there are any problems function outputLintingResults(results) { // Identify the number of problems found - const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); + const problems = results.reduce( + (acc, result) => acc + result.errorCount + result.warningCount, + 0, + ); if (problems > 0) { console.log("Linting errors found!"); @@ -132,37 +164,34 @@ function outputLintingResults(results) { ## Step 5: Put It All Together -Put the above functions together in a new function called `lintFiles`. This function will be the main entry point for your integration: +Put the above functions together in a new function called `lintFiles`. This +function will be the main entry point for your integration: ```javascript // example-eslint-integration.js // Put previous functions all together async function lintFiles(filePaths) { - - // The ESLint configuration. Alternatively, you could load the configuration - // from a .eslintrc file or just use the default config. - const overrideConfig = { - env: { - es6: true, - node: true, - }, - parserOptions: { - ecmaVersion: 2018, - }, - rules: { - "no-console": "error", - "no-unused-vars": "warn", - }, - }; - - const eslint = createESLintInstance(overrideConfig); - const results = await lintAndFix(eslint, filePaths); - return outputLintingResults(results); + // The ESLint configuration. Alternatively, you could load the configuration + // from a .eslintrc file or just use the default config. + const overrideConfig = { + languageOptions: { + ecmaVersion: 2018, + sourceType: "commonjs", + }, + rules: { + "no-console": "error", + "no-unused-vars": "warn", + }, + }; + + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); } // Export integration -module.exports = { lintFiles } +module.exports = { lintFiles }; ``` Here's the complete code example for `example-eslint-integration.js`: @@ -171,8 +200,12 @@ Here's the complete code example for `example-eslint-integration.js`: const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function -function createESLintInstance(overrideConfig){ - return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +function createESLintInstance(overrideConfig) { + return new ESLint({ + overrideConfigFile: true, + overrideConfig, + fix: true, + }); } // Lint the specified files and return the results @@ -188,7 +221,10 @@ async function lintAndFix(eslint, filePaths) { // Log results to console if there are any problems function outputLintingResults(results) { // Identify the number of problems found - const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); + const problems = results.reduce( + (acc, result) => acc + result.errorCount + result.warningCount, + 0, + ); if (problems > 0) { console.log("Linting errors found!"); @@ -201,36 +237,36 @@ function outputLintingResults(results) { // Put previous functions all together async function lintFiles(filePaths) { - - // The ESLint configuration. Alternatively, you could load the configuration - // from a .eslintrc file or just use the default config. - const overrideConfig = { - env: { - es6: true, - node: true, - }, - parserOptions: { - ecmaVersion: 2018, - }, - rules: { - "no-console": "error", - "no-unused-vars": "warn", - }, - }; - - const eslint = createESLintInstance(overrideConfig); - const results = await lintAndFix(eslint, filePaths); - return outputLintingResults(results); + // The ESLint configuration. Alternatively, you could load the configuration + // from an eslint.config.js file or just use the default config. + const overrideConfig = { + languageOptions: { + ecmaVersion: 2018, + sourceType: "commonjs", + }, + rules: { + "no-console": "error", + "no-unused-vars": "warn", + }, + }; + + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); } // Export integration -module.exports = { lintFiles } +module.exports = { lintFiles }; ``` ## Conclusion -In this tutorial, we have covered the essentials of using the `ESLint` class to lint files and retrieve results in your projects. This knowledge can be applied to create custom integrations, such as code editor plugins, to provide real-time feedback on code quality. +In this tutorial, we have covered the essentials of using the `ESLint` class to +lint files and retrieve results in your projects. This knowledge can be applied +to create custom integrations, such as code editor plugins, to provide real-time +feedback on code quality. ## View the Tutorial Code -You can view the annotated source code for the tutorial [here](https://github.com/eslint/eslint/tree/main/docs/_examples/integration-tutorial-code). +You can view the annotated source code for the tutorial +[here](https://github.com/eslint/eslint/tree/main/docs/_examples/integration-tutorial-code). diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index 87fd52aae4b..89ac7e3f823 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -85,15 +85,10 @@ const testCode = ` const eslint = new ESLint({ useEslintrc: false, overrideConfig: { - extends: ["eslint:recommended"], parserOptions: { - sourceType: "module", - ecmaVersion: "latest", - }, - env: { - es2022: true, - node: true, - }, + ecmaVersion: 2018, + sourceType: "commonjs" + } }, }); @@ -130,8 +125,6 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob Default is `process.cwd()`. The working directory. This must be an absolute path. * `options.errorOnUnmatchedPattern` (`boolean`)
Default is `true`. Unless set to `false`, the [`eslint.lintFiles()`][eslint-lintfiles] method will throw an error when no target files are found. -* `options.extensions` (`string[] | null`)
- Default is `null`. If you pass directory paths to the [`eslint.lintFiles()`][eslint-lintfiles] method, ESLint checks the files in those directories that have the given extensions. For example, when passing the `src/` directory and `extensions` is `[".js", ".ts"]`, ESLint will lint `*.js` and `*.ts` files in `src/`. If `extensions` is `null`, ESLint checks `*.js` files and files that match `overrides[].files` patterns in your configuration.
**Note:** This option only applies when you pass directory paths to the [`eslint.lintFiles()`][eslint-lintfiles] method. If you pass glob patterns like `lib/**/*`, ESLint will lint all files matching the glob pattern regardless of extension. * `options.globInputPaths` (`boolean`)
Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't interpret glob patterns. * `options.ignore` (`boolean`)
@@ -147,18 +140,12 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob Default is `null`. [Configuration object], extended by all configurations used with this instance. You can use this option to define the default settings that will be used if your configuration files don't configure it. * `options.overrideConfig` (`ConfigData | null`)
Default is `null`. [Configuration object], overrides all configurations used with this instance. You can use this option to define the settings that will be used even if your configuration files configure it. -* `options.overrideConfigFile` (`string | null`)
- Default is `null`. The path to a configuration file, overrides all configurations used with this instance. The `options.overrideConfig` option is applied after this option is applied. +* `options.overrideConfigFile` (`string | boolean`)
+ Default is `false`. The path to a configuration file, overrides all configurations used with this instance. The `options.overrideConfig` option is applied after this option is applied. * `options.plugins` (`Record | null`)
Default is `null`. The plugin implementations that ESLint uses for the `plugins` setting of your configuration. This is a map-like object. Those keys are plugin IDs and each value is implementation. * `options.reportUnusedDisableDirectives` (`"error" | "warn" | "off" | null`)
Default is `null`. The severity to report unused eslint-disable and eslint-enable directives. If this option is a severity, it overrides the `reportUnusedDisableDirectives` setting in your configurations. -* `options.resolvePluginsRelativeTo` (`string` | `null`)
- Default is `null`. The path to a directory where plugins should be resolved from. If `null` is present, ESLint loads plugins from the location of the configuration file that contains the plugin setting. If a path is present, ESLint loads all plugins from there. -* `options.rulePaths` (`string[]`)
- Default is `[]`. An array of paths to directories to load custom rules from. -* `options.useEslintrc` (`boolean`)
- Default is `true`. If `false` is present, ESLint doesn't load configuration files (`.eslintrc.*` files). Only the configuration of the constructor options is valid. * `options.ruleFilter` (`({ruleId: string, severity: number}) => boolean`)
Default is `() => true`. A predicate function that filters rules to be run. This function is called with an object containing `ruleId` and `severity`, and returns `true` if the rule should be run. @@ -251,12 +238,6 @@ const config = await eslint.calculateConfigForFile(filePath); This method calculates the configuration for a given file, which can be useful for debugging purposes. -* It resolves and merges `extends` and `overrides` settings into the top level configuration. -* It resolves the `parser` setting to absolute paths. -* It normalizes the `plugins` setting to align short names. (e.g., `eslint-plugin-foo` → `foo`) -* It adds the `processor` setting if a legacy file extension processor is matched. -* It doesn't interpret the `env` setting to the `globals` and `parserOptions` settings, so the result object contains the `env` setting as is. - #### Parameters * `filePath` (`string`)
diff --git a/docs/src/use/migrate-to-9.0.0.md b/docs/src/use/migrate-to-9.0.0.md index d82ec3d8fad..7d9adb24eea 100644 --- a/docs/src/use/migrate-to-9.0.0.md +++ b/docs/src/use/migrate-to-9.0.0.md @@ -30,6 +30,7 @@ The lists below are ordered roughly by the number of users each change is expect ### Breaking changes for integration developers * [Node.js < v18.18, v19 are no longer supported](#drop-old-node) +* [`FlatESLint` is now `ESLint`](#flat-eslint) --- @@ -130,3 +131,17 @@ ESLint v9.0.0 removes the deprecated `sourceCode.getComments()` method. **To address:** Replace with `sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, or `sourceCode.getCommentsInside()`. **Related Issues(s):** [#14744](https://github.com/eslint/eslint/issues/14744) + +## `FlatESLint` is now `ESLint` + +As announced in our [blog post](/blog/2023/10/flat-config-rollout-plans/), the temporary `FlatESLint` class has been renamed to `ESLint`, while the `ESLint` class from v8.x has been renamed to `LegacyESLint`. + +**To address:** If you are currently using the `ESLint` class, verify that your tests pass using the new `ESLint` class. Not all of the old options are supported, so you may need to update the arguments passed to the constructor. See the [Node.js API Reference](../integrate/nodejs-api) for details. + +If you still need the v8.x `ESLint` functionality, use the `LegacyESLint` class like this: + +```js +const { LegacyESLint } = require("eslint/use-at-your-own-risk"); +``` + +**Related Issues(s):** [#13481](https://github.com/eslint/eslint/issues/13481) diff --git a/lib/api.js b/lib/api.js index fd43d9ef332..22d6a32fd2f 100644 --- a/lib/api.js +++ b/lib/api.js @@ -9,7 +9,7 @@ // Requirements //----------------------------------------------------------------------------- -const { FlatESLint } = require("./eslint/flat-eslint"); +const { ESLint } = require("./eslint/eslint"); const { Linter } = require("./linter"); const { FlatRuleTester } = require("./rule-tester"); const { SourceCode } = require("./source-code"); @@ -20,7 +20,7 @@ const { SourceCode } = require("./source-code"); module.exports = { Linter, - ESLint: FlatESLint, + ESLint, RuleTester: FlatRuleTester, SourceCode }; diff --git a/lib/cli.js b/lib/cli.js index 4203ac1fa49..5100cb17d40 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -18,8 +18,8 @@ const fs = require("fs"), path = require("path"), { promisify } = require("util"), - { ESLint } = require("./eslint"), - { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"), + { LegacyESLint } = require("./eslint"), + { ESLint, shouldUseFlatConfig } = require("./eslint/eslint"), createCLIOptions = require("./options"), log = require("./shared/logging"), RuntimeInfo = require("./shared/runtime-info"), @@ -399,8 +399,8 @@ const cli = { } const engine = usingFlatConfig - ? new FlatESLint(await translateOptions(options, "flat")) - : new ESLint(await translateOptions(options)); + ? new ESLint(await translateOptions(options, "flat")) + : new LegacyESLint(await translateOptions(options)); const fileConfig = await engine.calculateConfigForFile(options.printConfig); @@ -428,7 +428,7 @@ const cli = { return 2; } - const ActiveESLint = usingFlatConfig ? FlatESLint : ESLint; + const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint; const engine = new ActiveESLint(await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc")); let results; diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 0a136add77c..566220c5b60 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -1,7 +1,6 @@ /** - * @fileoverview Main API Class - * @author Kai Cataldo - * @author Toru Nagashima + * @fileoverview Main class using flat config + * @author Nicholas C. Zakas */ "use strict"; @@ -10,39 +9,60 @@ // Requirements //------------------------------------------------------------------------------ +// Note: Node.js 12 does not support fs/promises. +const fs = require("fs").promises; +const { existsSync } = require("fs"); const path = require("path"); -const fs = require("fs"); -const { promisify } = require("util"); -const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); -const BuiltinRules = require("../rules"); +const findUp = require("find-up"); +const { version } = require("../../package.json"); +const { Linter } = require("../linter"); +const { getRuleFromConfig } = require("../config/flat-config-helpers"); const { Legacy: { ConfigOps: { getRuleSeverity - } + }, + ModuleResolver, + naming } } = require("@eslint/eslintrc"); -const { version } = require("../../package.json"); + +const { + findFiles, + getCacheFile, + + isNonEmptyString, + isArrayOfNonEmptyString, + + createIgnoreResult, + isErrorMessage, + + processOptions +} = require("./eslint-helpers"); +const { pathToFileURL } = require("url"); +const { FlatConfigArray } = require("../config/flat-config-array"); +const LintResultCache = require("../cli-engine/lint-result-cache"); + +/* + * This is necessary to allow overwriting writeFile for testing purposes. + * We can just use fs/promises once we drop Node.js 12 support. + */ //------------------------------------------------------------------------------ // Typedefs //------------------------------------------------------------------------------ -/** @typedef {import("../cli-engine/cli-engine").LintReport} CLIEngineLintReport */ -/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */ +// For VSCode IntelliSense /** @typedef {import("../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */ /** @typedef {import("../shared/types").LintMessage} LintMessage */ -/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */ -/** @typedef {import("../shared/types").Plugin} Plugin */ -/** @typedef {import("../shared/types").Rule} Rule */ /** @typedef {import("../shared/types").LintResult} LintResult */ +/** @typedef {import("../shared/types").ParserOptions} ParserOptions */ +/** @typedef {import("../shared/types").Plugin} Plugin */ /** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */ - -/** - * The main formatter object. - * @typedef LoadedFormatter - * @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise} format format function. - */ +/** @typedef {import("../shared/types").RuleConf} RuleConf */ +/** @typedef {import("../shared/types").Rule} Rule */ +/** @typedef {ReturnType} ExtractedConfig */ /** * The options with which to configure the ESLint instance. @@ -54,287 +74,79 @@ const { version } = require("../../package.json"); * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files. * @property {string} [cwd] The value to use for the current working directory. * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`. - * @property {string[]} [extensions] An array of file extensions to check. * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean. * @property {string[]} [fixTypes] Array of rule types to apply fixes for. * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. - * @property {boolean} [ignore] False disables use of .eslintignore. - * @property {string} [ignorePath] The ignore file to use instead of .eslintignore. + * @property {boolean} [ignore] False disables all ignore patterns except for the default ones. + * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`. * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance - * @property {string} [overrideConfigFile] The configuration file to use. - * @property {Record|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation. - * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives. - * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD. - * @property {string[]} [rulePaths] An array of directories to load custom rules from. - * @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files. + * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy; + * doesn't do any config file lookup when `true`; considered to be a config filename + * when a string. + * @property {Record} [plugins] An array of plugin implementations. + * @property {boolean} warnIgnored Show warnings when the file list includes ignored files * @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause * the linting operation to short circuit and not report any failures. */ -/** - * A rules metadata object. - * @typedef {Object} RulesMeta - * @property {string} id The plugin ID. - * @property {Object} definition The plugin definition. - */ - -/** - * Private members for the `ESLint` instance. - * @typedef {Object} ESLintPrivateMembers - * @property {CLIEngine} cliEngine The wrapped CLIEngine instance. - * @property {ESLintOptions} options The options used to instantiate the ESLint instance. - */ - //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const writeFile = promisify(fs.writeFile); - -/** - * The map with which to store private class members. - * @type {WeakMap} - */ -const privateMembersMap = new WeakMap(); - -/** - * Check if a given value is a non-empty string or not. - * @param {any} value The value to check. - * @returns {boolean} `true` if `value` is a non-empty string. - */ -function isNonEmptyString(value) { - return typeof value === "string" && value.trim() !== ""; -} - -/** - * Check if a given value is an array of non-empty strings or not. - * @param {any} value The value to check. - * @returns {boolean} `true` if `value` is an array of non-empty strings. - */ -function isArrayOfNonEmptyString(value) { - return Array.isArray(value) && value.length && value.every(isNonEmptyString); -} - -/** - * Check if a given value is an empty array or an array of non-empty strings. - * @param {any} value The value to check. - * @returns {boolean} `true` if `value` is an empty array or an array of non-empty - * strings. - */ -function isEmptyArrayOrArrayOfNonEmptyString(value) { - return Array.isArray(value) && value.every(isNonEmptyString); -} +const FLAT_CONFIG_FILENAMES = [ + "eslint.config.js", + "eslint.config.mjs", + "eslint.config.cjs" +]; +const debug = require("debug")("eslint:flat-eslint"); +const privateMembers = new WeakMap(); +const importedConfigFileModificationTime = new Map(); +const removedFormatters = new Set([ + "checkstyle", + "codeframe", + "compact", + "jslint-xml", + "junit", + "table", + "tap", + "unix", + "visualstudio" +]); /** - * Check if a given value is a valid fix type or not. - * @param {any} value The value to check. - * @returns {boolean} `true` if `value` is valid fix type. + * It will calculate the error and warning count for collection of messages per file + * @param {LintMessage[]} messages Collection of messages + * @returns {Object} Contains the stats + * @private */ -function isFixType(value) { - return value === "directive" || value === "problem" || value === "suggestion" || value === "layout"; -} - -/** - * Check if a given value is an array of fix types or not. - * @param {any} value The value to check. - * @returns {boolean} `true` if `value` is an array of fix types. - */ -function isFixTypeArray(value) { - return Array.isArray(value) && value.every(isFixType); -} - -/** - * The error for invalid options. - */ -class ESLintInvalidOptionsError extends Error { - constructor(messages) { - super(`Invalid Options:\n- ${messages.join("\n- ")}`); - this.code = "ESLINT_INVALID_OPTIONS"; - Error.captureStackTrace(this, ESLintInvalidOptionsError); - } -} - -/** - * Validates and normalizes options for the wrapped CLIEngine instance. - * @param {ESLintOptions} options The options to process. - * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors. - * @returns {ESLintOptions} The normalized options. - */ -function processOptions({ - allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. - baseConfig = null, - cache = false, - cacheLocation = ".eslintcache", - cacheStrategy = "metadata", - cwd = process.cwd(), - errorOnUnmatchedPattern = true, - extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature. - fix = false, - fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. - globInputPaths = true, - ignore = true, - ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT. - overrideConfig = null, - overrideConfigFile = null, - plugins = {}, - reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that. - resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature. - rulePaths = [], - useEslintrc = true, - passOnNoPatterns = false, - ...unknownOptions -}) { - const errors = []; - const unknownOptionKeys = Object.keys(unknownOptions); - - if (unknownOptionKeys.length >= 1) { - errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`); - if (unknownOptionKeys.includes("cacheFile")) { - errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead."); - } - if (unknownOptionKeys.includes("configFile")) { - errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead."); - } - if (unknownOptionKeys.includes("envs")) { - errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead."); - } - if (unknownOptionKeys.includes("globals")) { - errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead."); - } - if (unknownOptionKeys.includes("ignorePattern")) { - errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead."); - } - if (unknownOptionKeys.includes("parser")) { - errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead."); - } - if (unknownOptionKeys.includes("parserOptions")) { - errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead."); - } - if (unknownOptionKeys.includes("rules")) { - errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead."); - } - } - if (typeof allowInlineConfig !== "boolean") { - errors.push("'allowInlineConfig' must be a boolean."); - } - if (typeof baseConfig !== "object") { - errors.push("'baseConfig' must be an object or null."); - } - if (typeof cache !== "boolean") { - errors.push("'cache' must be a boolean."); - } - if (!isNonEmptyString(cacheLocation)) { - errors.push("'cacheLocation' must be a non-empty string."); - } - if ( - cacheStrategy !== "metadata" && - cacheStrategy !== "content" - ) { - errors.push("'cacheStrategy' must be any of \"metadata\", \"content\"."); - } - if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) { - errors.push("'cwd' must be an absolute path."); - } - if (typeof errorOnUnmatchedPattern !== "boolean") { - errors.push("'errorOnUnmatchedPattern' must be a boolean."); - } - if (!isEmptyArrayOrArrayOfNonEmptyString(extensions) && extensions !== null) { - errors.push("'extensions' must be an array of non-empty strings or null."); - } - if (typeof fix !== "boolean" && typeof fix !== "function") { - errors.push("'fix' must be a boolean or a function."); - } - if (fixTypes !== null && !isFixTypeArray(fixTypes)) { - errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\"."); - } - if (typeof globInputPaths !== "boolean") { - errors.push("'globInputPaths' must be a boolean."); - } - if (typeof ignore !== "boolean") { - errors.push("'ignore' must be a boolean."); - } - if (!isNonEmptyString(ignorePath) && ignorePath !== null) { - errors.push("'ignorePath' must be a non-empty string or null."); - } - if (typeof overrideConfig !== "object") { - errors.push("'overrideConfig' must be an object or null."); - } - if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) { - errors.push("'overrideConfigFile' must be a non-empty string or null."); - } - if (typeof plugins !== "object") { - errors.push("'plugins' must be an object or null."); - } else if (plugins !== null && Object.keys(plugins).includes("")) { - errors.push("'plugins' must not include an empty string."); - } - if (Array.isArray(plugins)) { - errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead."); - } - if ( - reportUnusedDisableDirectives !== "error" && - reportUnusedDisableDirectives !== "warn" && - reportUnusedDisableDirectives !== "off" && - reportUnusedDisableDirectives !== null - ) { - errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null."); - } - if ( - !isNonEmptyString(resolvePluginsRelativeTo) && - resolvePluginsRelativeTo !== null - ) { - errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null."); - } - if (!isEmptyArrayOrArrayOfNonEmptyString(rulePaths)) { - errors.push("'rulePaths' must be an array of non-empty strings."); - } - if (typeof useEslintrc !== "boolean") { - errors.push("'useEslintrc' must be a boolean."); - } - if (typeof passOnNoPatterns !== "boolean") { - errors.push("'passOnNoPatterns' must be a boolean."); - } - - if (errors.length > 0) { - throw new ESLintInvalidOptionsError(errors); - } - - return { - allowInlineConfig, - baseConfig, - cache, - cacheLocation, - cacheStrategy, - configFile: overrideConfigFile, - cwd: path.normalize(cwd), - errorOnUnmatchedPattern, - extensions, - fix, - fixTypes, - globInputPaths, - ignore, - ignorePath, - reportUnusedDisableDirectives, - resolvePluginsRelativeTo, - rulePaths, - useEslintrc, - passOnNoPatterns +function calculateStatsPerFile(messages) { + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0 }; -} -/** - * Check if a value has one or more properties and that value is not undefined. - * @param {any} obj The value to check. - * @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined. - */ -function hasDefinedProperty(obj) { - if (typeof obj === "object" && obj !== null) { - for (const key in obj) { - if (typeof obj[key] !== "undefined") { - return true; + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + + if (message.fatal || message.severity === 2) { + stat.errorCount++; + if (message.fatal) { + stat.fatalErrorCount++; + } + if (message.fix) { + stat.fixableErrorCount++; + } + } else { + stat.warningCount++; + if (message.fix) { + stat.fixableWarningCount++; } } } - return false; + return stat; } /** @@ -349,62 +161,73 @@ function createRulesMeta(rules) { }, {}); } +/** + * Return the absolute path of a file named `"__placeholder__.js"` in a given directory. + * This is used as a replacement for a missing file path. + * @param {string} cwd An absolute directory path. + * @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory. + */ +function getPlaceholderPath(cwd) { + return path.join(cwd, "__placeholder__.js"); +} + /** @type {WeakMap} */ const usedDeprecatedRulesCache = new WeakMap(); /** * Create used deprecated rule list. - * @param {CLIEngine} cliEngine The CLIEngine instance. + * @param {CLIEngine} eslint The CLIEngine instance. * @param {string} maybeFilePath The absolute path to a lint target file or `""`. * @returns {DeprecatedRuleInfo[]} The used deprecated rule list. */ -function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) { +function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) { const { - configArrayFactory, + configs, options: { cwd } - } = getCLIEngineInternalSlots(cliEngine); + } = privateMembers.get(eslint); const filePath = path.isAbsolute(maybeFilePath) ? maybeFilePath - : path.join(cwd, "__placeholder__.js"); - const configArray = configArrayFactory.getConfigArrayForFile(filePath); - const config = configArray.extractConfig(filePath); + : getPlaceholderPath(cwd); + const config = configs.getConfig(filePath); // Most files use the same config, so cache it. - if (!usedDeprecatedRulesCache.has(config)) { - const pluginRules = configArray.pluginRules; + if (config && !usedDeprecatedRulesCache.has(config)) { const retv = []; - for (const [ruleId, ruleConf] of Object.entries(config.rules)) { - if (getRuleSeverity(ruleConf) === 0) { - continue; - } - const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId); - const meta = rule && rule.meta; - - if (meta && meta.deprecated) { - retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); + if (config.rules) { + for (const [ruleId, ruleConf] of Object.entries(config.rules)) { + if (getRuleSeverity(ruleConf) === 0) { + continue; + } + const rule = getRuleFromConfig(ruleId, config); + const meta = rule && rule.meta; + + if (meta && meta.deprecated) { + retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); + } } } + usedDeprecatedRulesCache.set(config, Object.freeze(retv)); } - return usedDeprecatedRulesCache.get(config); + return config ? usedDeprecatedRulesCache.get(config) : Object.freeze([]); } /** * Processes the linting results generated by a CLIEngine linting report to * match the ESLint class's API. - * @param {CLIEngine} cliEngine The CLIEngine instance. + * @param {CLIEngine} eslint The CLIEngine instance. * @param {CLIEngineLintReport} report The CLIEngine linting report to process. * @returns {LintResult[]} The processed linting results. */ -function processCLIEngineLintReport(cliEngine, { results }) { +function processLintReport(eslint, { results }) { const descriptor = { configurable: true, enumerable: true, get() { - return getOrFindUsedDeprecatedRules(cliEngine, this.filePath); + return getOrFindUsedDeprecatedRules(eslint, this.filePath); } }; @@ -434,7 +257,311 @@ function compareResultsByFilePath(a, b) { } /** - * Main API. + * Searches from the current working directory up until finding the + * given flat config filename. + * @param {string} cwd The current working directory to search from. + * @returns {Promise} The filename if found or `undefined` if not. + */ +function findFlatConfigFile(cwd) { + return findUp( + FLAT_CONFIG_FILENAMES, + { cwd } + ); +} + +/** + * Load the config array from the given filename. + * @param {string} filePath The filename to load from. + * @returns {Promise} The config loaded from the config file. + */ +async function loadFlatConfigFile(filePath) { + debug(`Loading config from ${filePath}`); + + const fileURL = pathToFileURL(filePath); + + debug(`Config file URL is ${fileURL}`); + + const mtime = (await fs.stat(filePath)).mtime.getTime(); + + /* + * Append a query with the config file's modification time (`mtime`) in order + * to import the current version of the config file. Without the query, `import()` would + * cache the config file module by the pathname only, and then always return + * the same version (the one that was actual when the module was imported for the first time). + * + * This ensures that the config file module is loaded and executed again + * if it has been changed since the last time it was imported. + * If it hasn't been changed, `import()` will just return the cached version. + * + * Note that we should not overuse queries (e.g., by appending the current time + * to always reload the config file module) as that could cause memory leaks + * because entries are never removed from the import cache. + */ + fileURL.searchParams.append("mtime", mtime); + + /* + * With queries, we can bypass the import cache. However, when import-ing a CJS module, + * Node.js uses the require infrastructure under the hood. That includes the require cache, + * which caches the config file module by its file path (queries have no effect). + * Therefore, we also need to clear the require cache before importing the config file module. + * In order to get the same behavior with ESM and CJS config files, in particular - to reload + * the config file only if it has been changed, we track file modification times and clear + * the require cache only if the file has been changed. + */ + if (importedConfigFileModificationTime.get(filePath) !== mtime) { + delete require.cache[filePath]; + } + + const config = (await import(fileURL)).default; + + importedConfigFileModificationTime.set(filePath, mtime); + + return config; +} + +/** + * Determines which config file to use. This is determined by seeing if an + * override config file was passed, and if so, using it; otherwise, as long + * as override config file is not explicitly set to `false`, it will search + * upwards from the cwd for a file named `eslint.config.js`. + * @param {import("./eslint").ESLintOptions} options The ESLint instance options. + * @returns {{configFilePath:string|undefined,basePath:string,error:Error|null}} Location information for + * the config file. + */ +async function locateConfigFileToUse({ configFile, cwd }) { + + // determine where to load config file from + let configFilePath; + let basePath = cwd; + let error = null; + + if (typeof configFile === "string") { + debug(`Override config file path is ${configFile}`); + configFilePath = path.resolve(cwd, configFile); + } else if (configFile !== false) { + debug("Searching for eslint.config.js"); + configFilePath = await findFlatConfigFile(cwd); + + if (configFilePath) { + basePath = path.resolve(path.dirname(configFilePath)); + } else { + error = new Error("Could not find config file."); + } + + } + + return { + configFilePath, + basePath, + error + }; + +} + +/** + * Calculates the config array for this run based on inputs. + * @param {FlatESLint} eslint The instance to create the config array for. + * @param {import("./eslint").ESLintOptions} options The ESLint instance options. + * @returns {FlatConfigArray} The config array for `eslint``. + */ +async function calculateConfigArray(eslint, { + cwd, + baseConfig, + overrideConfig, + configFile, + ignore: shouldIgnore, + ignorePatterns +}) { + + // check for cached instance + const slots = privateMembers.get(eslint); + + if (slots.configs) { + return slots.configs; + } + + const { configFilePath, basePath, error } = await locateConfigFileToUse({ configFile, cwd }); + + // config file is required to calculate config + if (error) { + throw error; + } + + const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore }); + + // load config file + if (configFilePath) { + const fileConfig = await loadFlatConfigFile(configFilePath); + + if (Array.isArray(fileConfig)) { + configs.push(...fileConfig); + } else { + configs.push(fileConfig); + } + } + + // add in any configured defaults + configs.push(...slots.defaultConfigs); + + // append command line ignore patterns + if (ignorePatterns && ignorePatterns.length > 0) { + + let relativeIgnorePatterns; + + /* + * If the config file basePath is different than the cwd, then + * the ignore patterns won't work correctly. Here, we adjust the + * ignore pattern to include the correct relative path. Patterns + * passed as `ignorePatterns` are relative to the cwd, whereas + * the config file basePath can be an ancestor of the cwd. + */ + if (basePath === cwd) { + relativeIgnorePatterns = ignorePatterns; + } else { + + const relativeIgnorePath = path.relative(basePath, cwd); + + relativeIgnorePatterns = ignorePatterns.map(pattern => { + const negated = pattern.startsWith("!"); + const basePattern = negated ? pattern.slice(1) : pattern; + + return (negated ? "!" : "") + + path.posix.join(relativeIgnorePath, basePattern); + }); + } + + /* + * Ignore patterns are added to the end of the config array + * so they can override default ignores. + */ + configs.push({ + ignores: relativeIgnorePatterns + }); + } + + if (overrideConfig) { + if (Array.isArray(overrideConfig)) { + configs.push(...overrideConfig); + } else { + configs.push(overrideConfig); + } + } + + await configs.normalize(); + + // cache the config array for this instance + slots.configs = configs; + + return configs; +} + +/** + * Processes an source code using ESLint. + * @param {Object} config The config object. + * @param {string} config.text The source code to verify. + * @param {string} config.cwd The path to the current working directory. + * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses ``. + * @param {FlatConfigArray} config.configs The config. + * @param {boolean} config.fix If `true` then it does fix. + * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments. + * @param {Function} config.ruleFilter A predicate function to filter which rules should be run. + * @param {Linter} config.linter The linter instance to verify. + * @returns {LintResult} The result of linting. + * @private + */ +function verifyText({ + text, + cwd, + filePath: providedFilePath, + configs, + fix, + allowInlineConfig, + ruleFilter, + linter +}) { + const filePath = providedFilePath || ""; + + debug(`Lint ${filePath}`); + + /* + * Verify. + * `config.extractConfig(filePath)` requires an absolute path, but `linter` + * doesn't know CWD, so it gives `linter` an absolute path always. + */ + const filePathToVerify = filePath === "" ? getPlaceholderPath(cwd) : filePath; + const { fixed, messages, output } = linter.verifyAndFix( + text, + configs, + { + allowInlineConfig, + filename: filePathToVerify, + fix, + ruleFilter, + + /** + * Check if the linter should adopt a given code block or not. + * @param {string} blockFilename The virtual filename of a code block. + * @returns {boolean} `true` if the linter should adopt the code block. + */ + filterCodeBlock(blockFilename) { + return configs.isExplicitMatch(blockFilename); + } + } + ); + + // Tweak and return. + const result = { + filePath: filePath === "" ? filePath : path.resolve(filePath), + messages, + suppressedMessages: linter.getSuppressedMessages(), + ...calculateStatsPerFile(messages) + }; + + if (fixed) { + result.output = output; + } + + if ( + result.errorCount + result.warningCount > 0 && + typeof result.output === "undefined" + ) { + result.source = text; + } + + return result; +} + +/** + * Checks whether a message's rule type should be fixed. + * @param {LintMessage} message The message to check. + * @param {FlatConfig} config The config for the file that generated the message. + * @param {string[]} fixTypes An array of fix types to check. + * @returns {boolean} Whether the message should be fixed. + */ +function shouldMessageBeFixed(message, config, fixTypes) { + if (!message.ruleId) { + return fixTypes.has("directive"); + } + + const rule = message.ruleId && getRuleFromConfig(message.ruleId, config); + + return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); +} + +/** + * Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine. + * @returns {TypeError} An error object. + */ +function createExtraneousResultsError() { + return new TypeError("Results object was not created from this ESLint instance."); +} + +//----------------------------------------------------------------------------- +// Main API +//----------------------------------------------------------------------------- + +/** + * Primary Node.js API for ESLint. */ class ESLint { @@ -443,35 +570,49 @@ class ESLint { * @param {ESLintOptions} options The options for this instance. */ constructor(options = {}) { + + const defaultConfigs = []; const processedOptions = processOptions(options); - const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins }); - const { - configArrayFactory, - lastConfigArrays - } = getCLIEngineInternalSlots(cliEngine); - let updated = false; + const linter = new Linter({ + cwd: processedOptions.cwd, + configType: "flat" + }); - /* - * Address `overrideConfig` to set override config. - * Operate the `configArrayFactory` internal slot directly because this - * functionality doesn't exist as the public API of CLIEngine. + const cacheFilePath = getCacheFile( + processedOptions.cacheLocation, + processedOptions.cwd + ); + + const lintResultCache = processedOptions.cache + ? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy) + : null; + + privateMembers.set(this, { + options: processedOptions, + linter, + cacheFilePath, + lintResultCache, + defaultConfigs, + configs: null + }); + + /** + * If additional plugins are passed in, add that to the default + * configs for this instance. */ - if (hasDefinedProperty(options.overrideConfig)) { - configArrayFactory.setOverrideConfig(options.overrideConfig); - updated = true; - } + if (options.plugins) { - // Update caches. - if (updated) { - configArrayFactory.clearCache(); - lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile(); + const plugins = {}; + + for (const [pluginName, plugin] of Object.entries(options.plugins)) { + plugins[naming.getShorthandName(pluginName, "eslint-plugin")] = plugin; + } + + defaultConfigs.push({ + plugins + }); } - // Initialize private properties. - privateMembersMap.set(this, { - cliEngine, - options: processedOptions - }); } /** @@ -503,7 +644,7 @@ class ESLint { path.isAbsolute(result.filePath) ); }) - .map(r => writeFile(r.filePath, r.output)) + .map(r => fs.writeFile(r.filePath, r.output)) ); } @@ -513,65 +654,286 @@ class ESLint { * @returns {LintResult[]} The filtered results. */ static getErrorResults(results) { - return CLIEngine.getErrorResults(results); + const filtered = []; + + results.forEach(result => { + const filteredMessages = result.messages.filter(isErrorMessage); + const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage); + + if (filteredMessages.length > 0) { + filtered.push({ + ...result, + messages: filteredMessages, + suppressedMessages: filteredSuppressedMessages, + errorCount: filteredMessages.length, + warningCount: 0, + fixableErrorCount: result.fixableErrorCount, + fixableWarningCount: 0 + }); + } + }); + + return filtered; } /** * Returns meta objects for each rule represented in the lint results. * @param {LintResult[]} results The results to fetch rules meta for. * @returns {Object} A mapping of ruleIds to rule meta objects. + * @throws {TypeError} When the results object wasn't created from this ESLint instance. + * @throws {TypeError} When a plugin or rule is missing. */ getRulesMetaForResults(results) { - const resultRuleIds = new Set(); + // short-circuit simple case + if (results.length === 0) { + return {}; + } - // first gather all ruleIds from all results + const resultRules = new Map(); + const { + configs, + options: { cwd } + } = privateMembers.get(this); - for (const result of results) { - for (const { ruleId } of result.messages) { - resultRuleIds.add(ruleId); - } - for (const { ruleId } of result.suppressedMessages) { - resultRuleIds.add(ruleId); - } + /* + * We can only accurately return rules meta information for linting results if the + * results were created by this instance. Otherwise, the necessary rules data is + * not available. So if the config array doesn't already exist, just throw an error + * to let the user know we can't do anything here. + */ + if (!configs) { + throw createExtraneousResultsError(); } - // create a map of all rules in the results - - const { cliEngine } = privateMembersMap.get(this); - const rules = cliEngine.getRules(); - const resultRules = new Map(); + for (const result of results) { - for (const [ruleId, rule] of rules) { - if (resultRuleIds.has(ruleId)) { - resultRules.set(ruleId, rule); + /* + * Normalize filename for . + */ + const filePath = result.filePath === "" + ? getPlaceholderPath(cwd) : result.filePath; + const allMessages = result.messages.concat(result.suppressedMessages); + + for (const { ruleId } of allMessages) { + if (!ruleId) { + continue; + } + + /* + * All of the plugin and rule information is contained within the + * calculated config for the given file. + */ + const config = configs.getConfig(filePath); + + if (!config) { + throw createExtraneousResultsError(); + } + const rule = getRuleFromConfig(ruleId, config); + + // ignore unknown rules + if (rule) { + resultRules.set(ruleId, rule); + } } } return createRulesMeta(resultRules); - } /** * Executes the current configuration on an array of file and directory names. - * @param {string[]} patterns An array of file and directory names. + * @param {string|string[]} patterns An array of file and directory names. * @returns {Promise} The results of linting the file patterns given. */ async lintFiles(patterns) { - const { cliEngine, options } = privateMembersMap.get(this); - if (options.passOnNoPatterns && (patterns === "" || (Array.isArray(patterns) && patterns.length === 0))) { - return []; + let normalizedPatterns = patterns; + const { + cacheFilePath, + lintResultCache, + linter, + options: eslintOptions + } = privateMembers.get(this); + + /* + * Special cases: + * 1. `patterns` is an empty string + * 2. `patterns` is an empty array + * + * In both cases, we use the cwd as the directory to lint. + */ + if (patterns === "" || Array.isArray(patterns) && patterns.length === 0) { + + /* + * Special case: If `passOnNoPatterns` is true, then we just exit + * without doing any work. + */ + if (eslintOptions.passOnNoPatterns) { + return []; + } + + normalizedPatterns = ["."]; + } else { + + if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { + throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); + } + + if (typeof patterns === "string") { + normalizedPatterns = [patterns]; + } } - if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { - throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); + debug(`Using file patterns: ${normalizedPatterns}`); + + const configs = await calculateConfigArray(this, eslintOptions); + const { + allowInlineConfig, + cache, + cwd, + fix, + fixTypes, + ruleFilter, + globInputPaths, + errorOnUnmatchedPattern, + warnIgnored + } = eslintOptions; + const startTime = Date.now(); + const fixTypesSet = fixTypes ? new Set(fixTypes) : null; + + // Delete cache file; should this be done here? + if (!cache && cacheFilePath) { + debug(`Deleting cache file at ${cacheFilePath}`); + + try { + await fs.unlink(cacheFilePath); + } catch (error) { + const errorCode = error && error.code; + + // Ignore errors when no such file exists or file system is read only (and cache file does not exist) + if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !existsSync(cacheFilePath))) { + throw error; + } + } } - return processCLIEngineLintReport( - cliEngine, - cliEngine.executeOnFiles(patterns) + const filePaths = await findFiles({ + patterns: normalizedPatterns, + cwd, + globInputPaths, + configs, + errorOnUnmatchedPattern + }); + + debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`); + + /* + * Because we need to process multiple files, including reading from disk, + * it is most efficient to start by reading each file via promises so that + * they can be done in parallel. Then, we can lint the returned text. This + * ensures we are waiting the minimum amount of time in between lints. + */ + const results = await Promise.all( + + filePaths.map(({ filePath, ignored }) => { + + /* + * If a filename was entered that matches an ignore + * pattern, then notify the user. + */ + if (ignored) { + if (warnIgnored) { + return createIgnoreResult(filePath, cwd); + } + + return void 0; + } + + const config = configs.getConfig(filePath); + + /* + * Sometimes a file found through a glob pattern will + * be ignored. In this case, `config` will be undefined + * and we just silently ignore the file. + */ + if (!config) { + return void 0; + } + + // Skip if there is cached result. + if (lintResultCache) { + const cachedResult = + lintResultCache.getCachedLintResults(filePath, config); + + if (cachedResult) { + const hadMessages = + cachedResult.messages && + cachedResult.messages.length > 0; + + if (hadMessages && fix) { + debug(`Reprocessing cached file to allow autofix: ${filePath}`); + } else { + debug(`Skipping file since it hasn't changed: ${filePath}`); + return cachedResult; + } + } + } + + + // set up fixer for fixTypes if necessary + let fixer = fix; + + if (fix && fixTypesSet) { + + // save original value of options.fix in case it's a function + const originalFix = (typeof fix === "function") + ? fix : () => true; + + fixer = message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message); + } + + return fs.readFile(filePath, "utf8") + .then(text => { + + // do the linting + const result = verifyText({ + text, + filePath, + configs, + cwd, + fix: fixer, + allowInlineConfig, + ruleFilter, + linter + }); + + /* + * Store the lint result in the LintResultCache. + * NOTE: The LintResultCache will remove the file source and any + * other properties that are difficult to serialize, and will + * hydrate those properties back in on future lint runs. + */ + if (lintResultCache) { + lintResultCache.setCachedLintResults(filePath, config, result); + } + + return result; + }); + + }) ); + + // Persist the cache to disk. + if (lintResultCache) { + lintResultCache.reconcile(); + } + + const finalResults = results.filter(result => !!result); + + return processLintReport(this, { + results: finalResults + }); } /** @@ -583,15 +945,22 @@ class ESLint { * @returns {Promise} The results of linting the string of code given. */ async lintText(code, options = {}) { + + // Parameter validation + if (typeof code !== "string") { throw new Error("'code' must be a string"); } + if (typeof options !== "object") { throw new Error("'options' must be an object, null, or undefined"); } + + // Options validation + const { filePath, - warnIgnored = false, + warnIgnored, ...unknownOptions } = options || {}; @@ -604,16 +973,57 @@ class ESLint { if (filePath !== void 0 && !isNonEmptyString(filePath)) { throw new Error("'options.filePath' must be a non-empty string or undefined"); } - if (typeof warnIgnored !== "boolean") { + + if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") { throw new Error("'options.warnIgnored' must be a boolean or undefined"); } - const { cliEngine } = privateMembersMap.get(this); + // Now we can get down to linting + + const { + linter, + options: eslintOptions + } = privateMembers.get(this); + const configs = await calculateConfigArray(this, eslintOptions); + const { + allowInlineConfig, + cwd, + fix, + warnIgnored: constructorWarnIgnored, + ruleFilter + } = eslintOptions; + const results = []; + const startTime = Date.now(); + const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js"); + + // Clear the last used config arrays. + if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) { + const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored; + + if (shouldWarnIgnored) { + results.push(createIgnoreResult(resolvedFilename, cwd)); + } + } else { + + // Do lint. + results.push(verifyText({ + text: code, + filePath: resolvedFilename.endsWith("__placeholder__.js") ? "" : resolvedFilename, + configs, + cwd, + fix, + allowInlineConfig, + ruleFilter, + linter + })); + } + + debug(`Linting complete in: ${Date.now() - startTime}ms`); + + return processLintReport(this, { + results + }); - return processCLIEngineLintReport( - cliEngine, - cliEngine.executeOnText(code, filePath, warnIgnored) - ); } /** @@ -627,7 +1037,7 @@ class ESLint { * - `@foo` → `@foo/eslint-formatter` * - `@foo/bar` → `@foo/eslint-formatter-bar` * - A file path ... Load the file. - * @returns {Promise} A promise resolving to the formatter object. + * @returns {Promise} A promise resolving to the formatter object. * This promise will be rejected if the given formatter was not found or not * a function. */ @@ -636,20 +1046,60 @@ class ESLint { throw new Error("'name' must be a string"); } - const { cliEngine, options } = privateMembersMap.get(this); - const formatter = cliEngine.getFormatter(name); + // replace \ with / for Windows compatibility + const normalizedFormatName = name.replace(/\\/gu, "/"); + const namespace = naming.getNamespaceFromTerm(normalizedFormatName); + + // grab our options + const { cwd } = privateMembers.get(this).options; + + + let formatterPath; + + // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) + if (!namespace && normalizedFormatName.includes("/")) { + formatterPath = path.resolve(cwd, normalizedFormatName); + } else { + try { + const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter"); + + // TODO: This is pretty dirty...would be nice to clean up at some point. + formatterPath = ModuleResolver.resolve(npmFormat, getPlaceholderPath(cwd)); + } catch { + formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`); + } + } + + let formatter; + + try { + formatter = (await import(pathToFileURL(formatterPath))).default; + } catch (ex) { + + // check for formatters that have been removed + if (removedFormatters.has(name)) { + ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``; + } else { + ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; + } + + throw ex; + } + if (typeof formatter !== "function") { - throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`); + throw new TypeError(`Formatter must be a function, but got a ${typeof formatter}.`); } + const eslint = this; + return { /** * The main formatter method. - * @param {LintResult[]} results The lint results to format. + * @param {LintResults[]} results The lint results to format. * @param {ResultsMeta} resultsMeta Warning count and max threshold. - * @returns {string | Promise} The formatted lint results. + * @returns {string} The formatted lint results. */ format(results, resultsMeta) { let rulesMeta = null; @@ -658,12 +1108,10 @@ class ESLint { return formatter(results, { ...resultsMeta, - get cwd() { - return options.cwd; - }, + cwd, get rulesMeta() { if (!rulesMeta) { - rulesMeta = createRulesMeta(cliEngine.getRules()); + rulesMeta = eslint.getRulesMetaForResults(results); } return rulesMeta; @@ -678,15 +1126,31 @@ class ESLint { * This is the same logic used by the ESLint CLI executable to determine * configuration for each file it processes. * @param {string} filePath The path of the file to retrieve a config object for. - * @returns {Promise} A configuration object for the file. + * @returns {Promise} A configuration object for the file + * or `undefined` if there is no configuration data for the object. */ async calculateConfigForFile(filePath) { if (!isNonEmptyString(filePath)) { throw new Error("'filePath' must be a non-empty string"); } - const { cliEngine } = privateMembersMap.get(this); + const options = privateMembers.get(this).options; + const absolutePath = path.resolve(options.cwd, filePath); + const configs = await calculateConfigArray(this, options); + + return configs.getConfig(absolutePath); + } - return cliEngine.getConfigForFile(filePath); + /** + * Finds the config file being used by this instance based on the options + * passed to the constructor. + * @returns {string|undefined} The path to the config file being used or + * `undefined` if no config file is being used. + */ + async findConfigFile() { + const options = privateMembers.get(this).options; + const { configFilePath } = await locateConfigFileToUse(options); + + return configFilePath; } /** @@ -695,28 +1159,25 @@ class ESLint { * @returns {Promise} Whether or not the given path is ignored. */ async isPathIgnored(filePath) { - if (!isNonEmptyString(filePath)) { - throw new Error("'filePath' must be a non-empty string"); - } - const { cliEngine } = privateMembersMap.get(this); + const config = await this.calculateConfigForFile(filePath); - return cliEngine.isPathIgnored(filePath); + return config === void 0; } } +/** + * Returns whether flat config should be used. + * @returns {Promise} Whether flat config should be used. + */ +async function shouldUseFlatConfig() { + return (process.env.ESLINT_USE_FLAT_CONFIG !== "false"); +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ module.exports = { ESLint, - - /** - * Get the private class members of a given ESLint instance for tests. - * @param {ESLint} instance The ESLint instance to get. - * @returns {ESLintPrivateMembers} The instance's private class members. - */ - getESLintPrivateMembers(instance) { - return privateMembersMap.get(instance); - } + shouldUseFlatConfig }; diff --git a/lib/eslint/flat-eslint.js b/lib/eslint/flat-eslint.js deleted file mode 100644 index 8f86705f778..00000000000 --- a/lib/eslint/flat-eslint.js +++ /dev/null @@ -1,1183 +0,0 @@ -/** - * @fileoverview Main class using flat config - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -// Note: Node.js 12 does not support fs/promises. -const fs = require("fs").promises; -const { existsSync } = require("fs"); -const path = require("path"); -const findUp = require("find-up"); -const { version } = require("../../package.json"); -const { Linter } = require("../linter"); -const { getRuleFromConfig } = require("../config/flat-config-helpers"); -const { - Legacy: { - ConfigOps: { - getRuleSeverity - }, - ModuleResolver, - naming - } -} = require("@eslint/eslintrc"); - -const { - findFiles, - getCacheFile, - - isNonEmptyString, - isArrayOfNonEmptyString, - - createIgnoreResult, - isErrorMessage, - - processOptions -} = require("./eslint-helpers"); -const { pathToFileURL } = require("url"); -const { FlatConfigArray } = require("../config/flat-config-array"); -const LintResultCache = require("../cli-engine/lint-result-cache"); - -/* - * This is necessary to allow overwriting writeFile for testing purposes. - * We can just use fs/promises once we drop Node.js 12 support. - */ - -//------------------------------------------------------------------------------ -// Typedefs -//------------------------------------------------------------------------------ - -// For VSCode IntelliSense -/** @typedef {import("../shared/types").ConfigData} ConfigData */ -/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */ -/** @typedef {import("../shared/types").LintMessage} LintMessage */ -/** @typedef {import("../shared/types").LintResult} LintResult */ -/** @typedef {import("../shared/types").ParserOptions} ParserOptions */ -/** @typedef {import("../shared/types").Plugin} Plugin */ -/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */ -/** @typedef {import("../shared/types").RuleConf} RuleConf */ -/** @typedef {import("../shared/types").Rule} Rule */ -/** @typedef {ReturnType} ExtractedConfig */ - -/** - * The options with which to configure the ESLint instance. - * @typedef {Object} FlatESLintOptions - * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments. - * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance - * @property {boolean} [cache] Enable result caching. - * @property {string} [cacheLocation] The cache file to use instead of .eslintcache. - * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files. - * @property {string} [cwd] The value to use for the current working directory. - * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`. - * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean. - * @property {string[]} [fixTypes] Array of rule types to apply fixes for. - * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. - * @property {boolean} [ignore] False disables all ignore patterns except for the default ones. - * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`. - * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance - * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy; - * doesn't do any config file lookup when `true`; considered to be a config filename - * when a string. - * @property {Record} [plugins] An array of plugin implementations. - * @property {boolean} warnIgnored Show warnings when the file list includes ignored files - * @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause - * the linting operation to short circuit and not report any failures. - */ - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -const FLAT_CONFIG_FILENAMES = [ - "eslint.config.js", - "eslint.config.mjs", - "eslint.config.cjs" -]; -const debug = require("debug")("eslint:flat-eslint"); -const privateMembers = new WeakMap(); -const importedConfigFileModificationTime = new Map(); -const removedFormatters = new Set([ - "checkstyle", - "codeframe", - "compact", - "jslint-xml", - "junit", - "table", - "tap", - "unix", - "visualstudio" -]); - -/** - * It will calculate the error and warning count for collection of messages per file - * @param {LintMessage[]} messages Collection of messages - * @returns {Object} Contains the stats - * @private - */ -function calculateStatsPerFile(messages) { - const stat = { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }; - - for (let i = 0; i < messages.length; i++) { - const message = messages[i]; - - if (message.fatal || message.severity === 2) { - stat.errorCount++; - if (message.fatal) { - stat.fatalErrorCount++; - } - if (message.fix) { - stat.fixableErrorCount++; - } - } else { - stat.warningCount++; - if (message.fix) { - stat.fixableWarningCount++; - } - } - } - return stat; -} - -/** - * Create rulesMeta object. - * @param {Map} rules a map of rules from which to generate the object. - * @returns {Object} metadata for all enabled rules. - */ -function createRulesMeta(rules) { - return Array.from(rules).reduce((retVal, [id, rule]) => { - retVal[id] = rule.meta; - return retVal; - }, {}); -} - -/** - * Return the absolute path of a file named `"__placeholder__.js"` in a given directory. - * This is used as a replacement for a missing file path. - * @param {string} cwd An absolute directory path. - * @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory. - */ -function getPlaceholderPath(cwd) { - return path.join(cwd, "__placeholder__.js"); -} - -/** @type {WeakMap} */ -const usedDeprecatedRulesCache = new WeakMap(); - -/** - * Create used deprecated rule list. - * @param {CLIEngine} eslint The CLIEngine instance. - * @param {string} maybeFilePath The absolute path to a lint target file or `""`. - * @returns {DeprecatedRuleInfo[]} The used deprecated rule list. - */ -function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) { - const { - configs, - options: { cwd } - } = privateMembers.get(eslint); - const filePath = path.isAbsolute(maybeFilePath) - ? maybeFilePath - : getPlaceholderPath(cwd); - const config = configs.getConfig(filePath); - - // Most files use the same config, so cache it. - if (config && !usedDeprecatedRulesCache.has(config)) { - const retv = []; - - if (config.rules) { - for (const [ruleId, ruleConf] of Object.entries(config.rules)) { - if (getRuleSeverity(ruleConf) === 0) { - continue; - } - const rule = getRuleFromConfig(ruleId, config); - const meta = rule && rule.meta; - - if (meta && meta.deprecated) { - retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); - } - } - } - - - usedDeprecatedRulesCache.set(config, Object.freeze(retv)); - } - - return config ? usedDeprecatedRulesCache.get(config) : Object.freeze([]); -} - -/** - * Processes the linting results generated by a CLIEngine linting report to - * match the ESLint class's API. - * @param {CLIEngine} eslint The CLIEngine instance. - * @param {CLIEngineLintReport} report The CLIEngine linting report to process. - * @returns {LintResult[]} The processed linting results. - */ -function processLintReport(eslint, { results }) { - const descriptor = { - configurable: true, - enumerable: true, - get() { - return getOrFindUsedDeprecatedRules(eslint, this.filePath); - } - }; - - for (const result of results) { - Object.defineProperty(result, "usedDeprecatedRules", descriptor); - } - - return results; -} - -/** - * An Array.prototype.sort() compatible compare function to order results by their file path. - * @param {LintResult} a The first lint result. - * @param {LintResult} b The second lint result. - * @returns {number} An integer representing the order in which the two results should occur. - */ -function compareResultsByFilePath(a, b) { - if (a.filePath < b.filePath) { - return -1; - } - - if (a.filePath > b.filePath) { - return 1; - } - - return 0; -} - -/** - * Searches from the current working directory up until finding the - * given flat config filename. - * @param {string} cwd The current working directory to search from. - * @returns {Promise} The filename if found or `undefined` if not. - */ -function findFlatConfigFile(cwd) { - return findUp( - FLAT_CONFIG_FILENAMES, - { cwd } - ); -} - -/** - * Load the config array from the given filename. - * @param {string} filePath The filename to load from. - * @returns {Promise} The config loaded from the config file. - */ -async function loadFlatConfigFile(filePath) { - debug(`Loading config from ${filePath}`); - - const fileURL = pathToFileURL(filePath); - - debug(`Config file URL is ${fileURL}`); - - const mtime = (await fs.stat(filePath)).mtime.getTime(); - - /* - * Append a query with the config file's modification time (`mtime`) in order - * to import the current version of the config file. Without the query, `import()` would - * cache the config file module by the pathname only, and then always return - * the same version (the one that was actual when the module was imported for the first time). - * - * This ensures that the config file module is loaded and executed again - * if it has been changed since the last time it was imported. - * If it hasn't been changed, `import()` will just return the cached version. - * - * Note that we should not overuse queries (e.g., by appending the current time - * to always reload the config file module) as that could cause memory leaks - * because entries are never removed from the import cache. - */ - fileURL.searchParams.append("mtime", mtime); - - /* - * With queries, we can bypass the import cache. However, when import-ing a CJS module, - * Node.js uses the require infrastructure under the hood. That includes the require cache, - * which caches the config file module by its file path (queries have no effect). - * Therefore, we also need to clear the require cache before importing the config file module. - * In order to get the same behavior with ESM and CJS config files, in particular - to reload - * the config file only if it has been changed, we track file modification times and clear - * the require cache only if the file has been changed. - */ - if (importedConfigFileModificationTime.get(filePath) !== mtime) { - delete require.cache[filePath]; - } - - const config = (await import(fileURL)).default; - - importedConfigFileModificationTime.set(filePath, mtime); - - return config; -} - -/** - * Determines which config file to use. This is determined by seeing if an - * override config file was passed, and if so, using it; otherwise, as long - * as override config file is not explicitly set to `false`, it will search - * upwards from the cwd for a file named `eslint.config.js`. - * @param {import("./eslint").ESLintOptions} options The ESLint instance options. - * @returns {{configFilePath:string|undefined,basePath:string,error:Error|null}} Location information for - * the config file. - */ -async function locateConfigFileToUse({ configFile, cwd }) { - - // determine where to load config file from - let configFilePath; - let basePath = cwd; - let error = null; - - if (typeof configFile === "string") { - debug(`Override config file path is ${configFile}`); - configFilePath = path.resolve(cwd, configFile); - } else if (configFile !== false) { - debug("Searching for eslint.config.js"); - configFilePath = await findFlatConfigFile(cwd); - - if (configFilePath) { - basePath = path.resolve(path.dirname(configFilePath)); - } else { - error = new Error("Could not find config file."); - } - - } - - return { - configFilePath, - basePath, - error - }; - -} - -/** - * Calculates the config array for this run based on inputs. - * @param {FlatESLint} eslint The instance to create the config array for. - * @param {import("./eslint").ESLintOptions} options The ESLint instance options. - * @returns {FlatConfigArray} The config array for `eslint``. - */ -async function calculateConfigArray(eslint, { - cwd, - baseConfig, - overrideConfig, - configFile, - ignore: shouldIgnore, - ignorePatterns -}) { - - // check for cached instance - const slots = privateMembers.get(eslint); - - if (slots.configs) { - return slots.configs; - } - - const { configFilePath, basePath, error } = await locateConfigFileToUse({ configFile, cwd }); - - // config file is required to calculate config - if (error) { - throw error; - } - - const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore }); - - // load config file - if (configFilePath) { - const fileConfig = await loadFlatConfigFile(configFilePath); - - if (Array.isArray(fileConfig)) { - configs.push(...fileConfig); - } else { - configs.push(fileConfig); - } - } - - // add in any configured defaults - configs.push(...slots.defaultConfigs); - - // append command line ignore patterns - if (ignorePatterns && ignorePatterns.length > 0) { - - let relativeIgnorePatterns; - - /* - * If the config file basePath is different than the cwd, then - * the ignore patterns won't work correctly. Here, we adjust the - * ignore pattern to include the correct relative path. Patterns - * passed as `ignorePatterns` are relative to the cwd, whereas - * the config file basePath can be an ancestor of the cwd. - */ - if (basePath === cwd) { - relativeIgnorePatterns = ignorePatterns; - } else { - - const relativeIgnorePath = path.relative(basePath, cwd); - - relativeIgnorePatterns = ignorePatterns.map(pattern => { - const negated = pattern.startsWith("!"); - const basePattern = negated ? pattern.slice(1) : pattern; - - return (negated ? "!" : "") + - path.posix.join(relativeIgnorePath, basePattern); - }); - } - - /* - * Ignore patterns are added to the end of the config array - * so they can override default ignores. - */ - configs.push({ - ignores: relativeIgnorePatterns - }); - } - - if (overrideConfig) { - if (Array.isArray(overrideConfig)) { - configs.push(...overrideConfig); - } else { - configs.push(overrideConfig); - } - } - - await configs.normalize(); - - // cache the config array for this instance - slots.configs = configs; - - return configs; -} - -/** - * Processes an source code using ESLint. - * @param {Object} config The config object. - * @param {string} config.text The source code to verify. - * @param {string} config.cwd The path to the current working directory. - * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses ``. - * @param {FlatConfigArray} config.configs The config. - * @param {boolean} config.fix If `true` then it does fix. - * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments. - * @param {Function} config.ruleFilter A predicate function to filter which rules should be run. - * @param {Linter} config.linter The linter instance to verify. - * @returns {LintResult} The result of linting. - * @private - */ -function verifyText({ - text, - cwd, - filePath: providedFilePath, - configs, - fix, - allowInlineConfig, - ruleFilter, - linter -}) { - const filePath = providedFilePath || ""; - - debug(`Lint ${filePath}`); - - /* - * Verify. - * `config.extractConfig(filePath)` requires an absolute path, but `linter` - * doesn't know CWD, so it gives `linter` an absolute path always. - */ - const filePathToVerify = filePath === "" ? getPlaceholderPath(cwd) : filePath; - const { fixed, messages, output } = linter.verifyAndFix( - text, - configs, - { - allowInlineConfig, - filename: filePathToVerify, - fix, - ruleFilter, - - /** - * Check if the linter should adopt a given code block or not. - * @param {string} blockFilename The virtual filename of a code block. - * @returns {boolean} `true` if the linter should adopt the code block. - */ - filterCodeBlock(blockFilename) { - return configs.isExplicitMatch(blockFilename); - } - } - ); - - // Tweak and return. - const result = { - filePath: filePath === "" ? filePath : path.resolve(filePath), - messages, - suppressedMessages: linter.getSuppressedMessages(), - ...calculateStatsPerFile(messages) - }; - - if (fixed) { - result.output = output; - } - - if ( - result.errorCount + result.warningCount > 0 && - typeof result.output === "undefined" - ) { - result.source = text; - } - - return result; -} - -/** - * Checks whether a message's rule type should be fixed. - * @param {LintMessage} message The message to check. - * @param {FlatConfig} config The config for the file that generated the message. - * @param {string[]} fixTypes An array of fix types to check. - * @returns {boolean} Whether the message should be fixed. - */ -function shouldMessageBeFixed(message, config, fixTypes) { - if (!message.ruleId) { - return fixTypes.has("directive"); - } - - const rule = message.ruleId && getRuleFromConfig(message.ruleId, config); - - return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); -} - -/** - * Creates an error to be thrown when an array of results passed to `getRulesMetaForResults` was not created by the current engine. - * @returns {TypeError} An error object. - */ -function createExtraneousResultsError() { - return new TypeError("Results object was not created from this ESLint instance."); -} - -//----------------------------------------------------------------------------- -// Main API -//----------------------------------------------------------------------------- - -/** - * Primary Node.js API for ESLint. - */ -class FlatESLint { - - /** - * Creates a new instance of the main ESLint API. - * @param {FlatESLintOptions} options The options for this instance. - */ - constructor(options = {}) { - - const defaultConfigs = []; - const processedOptions = processOptions(options); - const linter = new Linter({ - cwd: processedOptions.cwd, - configType: "flat" - }); - - const cacheFilePath = getCacheFile( - processedOptions.cacheLocation, - processedOptions.cwd - ); - - const lintResultCache = processedOptions.cache - ? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy) - : null; - - privateMembers.set(this, { - options: processedOptions, - linter, - cacheFilePath, - lintResultCache, - defaultConfigs, - configs: null - }); - - /** - * If additional plugins are passed in, add that to the default - * configs for this instance. - */ - if (options.plugins) { - - const plugins = {}; - - for (const [pluginName, plugin] of Object.entries(options.plugins)) { - plugins[naming.getShorthandName(pluginName, "eslint-plugin")] = plugin; - } - - defaultConfigs.push({ - plugins - }); - } - - } - - /** - * The version text. - * @type {string} - */ - static get version() { - return version; - } - - /** - * Outputs fixes from the given results to files. - * @param {LintResult[]} results The lint results. - * @returns {Promise} Returns a promise that is used to track side effects. - */ - static async outputFixes(results) { - if (!Array.isArray(results)) { - throw new Error("'results' must be an array"); - } - - await Promise.all( - results - .filter(result => { - if (typeof result !== "object" || result === null) { - throw new Error("'results' must include only objects"); - } - return ( - typeof result.output === "string" && - path.isAbsolute(result.filePath) - ); - }) - .map(r => fs.writeFile(r.filePath, r.output)) - ); - } - - /** - * Returns results that only contains errors. - * @param {LintResult[]} results The results to filter. - * @returns {LintResult[]} The filtered results. - */ - static getErrorResults(results) { - const filtered = []; - - results.forEach(result => { - const filteredMessages = result.messages.filter(isErrorMessage); - const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage); - - if (filteredMessages.length > 0) { - filtered.push({ - ...result, - messages: filteredMessages, - suppressedMessages: filteredSuppressedMessages, - errorCount: filteredMessages.length, - warningCount: 0, - fixableErrorCount: result.fixableErrorCount, - fixableWarningCount: 0 - }); - } - }); - - return filtered; - } - - /** - * Returns meta objects for each rule represented in the lint results. - * @param {LintResult[]} results The results to fetch rules meta for. - * @returns {Object} A mapping of ruleIds to rule meta objects. - * @throws {TypeError} When the results object wasn't created from this ESLint instance. - * @throws {TypeError} When a plugin or rule is missing. - */ - getRulesMetaForResults(results) { - - // short-circuit simple case - if (results.length === 0) { - return {}; - } - - const resultRules = new Map(); - const { - configs, - options: { cwd } - } = privateMembers.get(this); - - /* - * We can only accurately return rules meta information for linting results if the - * results were created by this instance. Otherwise, the necessary rules data is - * not available. So if the config array doesn't already exist, just throw an error - * to let the user know we can't do anything here. - */ - if (!configs) { - throw createExtraneousResultsError(); - } - - for (const result of results) { - - /* - * Normalize filename for . - */ - const filePath = result.filePath === "" - ? getPlaceholderPath(cwd) : result.filePath; - const allMessages = result.messages.concat(result.suppressedMessages); - - for (const { ruleId } of allMessages) { - if (!ruleId) { - continue; - } - - /* - * All of the plugin and rule information is contained within the - * calculated config for the given file. - */ - const config = configs.getConfig(filePath); - - if (!config) { - throw createExtraneousResultsError(); - } - const rule = getRuleFromConfig(ruleId, config); - - // ignore unknown rules - if (rule) { - resultRules.set(ruleId, rule); - } - } - } - - return createRulesMeta(resultRules); - } - - /** - * Executes the current configuration on an array of file and directory names. - * @param {string|string[]} patterns An array of file and directory names. - * @returns {Promise} The results of linting the file patterns given. - */ - async lintFiles(patterns) { - - let normalizedPatterns = patterns; - const { - cacheFilePath, - lintResultCache, - linter, - options: eslintOptions - } = privateMembers.get(this); - - /* - * Special cases: - * 1. `patterns` is an empty string - * 2. `patterns` is an empty array - * - * In both cases, we use the cwd as the directory to lint. - */ - if (patterns === "" || Array.isArray(patterns) && patterns.length === 0) { - - /* - * Special case: If `passOnNoPatterns` is true, then we just exit - * without doing any work. - */ - if (eslintOptions.passOnNoPatterns) { - return []; - } - - normalizedPatterns = ["."]; - } else { - - if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { - throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); - } - - if (typeof patterns === "string") { - normalizedPatterns = [patterns]; - } - } - - debug(`Using file patterns: ${normalizedPatterns}`); - - const configs = await calculateConfigArray(this, eslintOptions); - const { - allowInlineConfig, - cache, - cwd, - fix, - fixTypes, - ruleFilter, - globInputPaths, - errorOnUnmatchedPattern, - warnIgnored - } = eslintOptions; - const startTime = Date.now(); - const fixTypesSet = fixTypes ? new Set(fixTypes) : null; - - // Delete cache file; should this be done here? - if (!cache && cacheFilePath) { - debug(`Deleting cache file at ${cacheFilePath}`); - - try { - await fs.unlink(cacheFilePath); - } catch (error) { - const errorCode = error && error.code; - - // Ignore errors when no such file exists or file system is read only (and cache file does not exist) - if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !existsSync(cacheFilePath))) { - throw error; - } - } - } - - const filePaths = await findFiles({ - patterns: normalizedPatterns, - cwd, - globInputPaths, - configs, - errorOnUnmatchedPattern - }); - - debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`); - - /* - * Because we need to process multiple files, including reading from disk, - * it is most efficient to start by reading each file via promises so that - * they can be done in parallel. Then, we can lint the returned text. This - * ensures we are waiting the minimum amount of time in between lints. - */ - const results = await Promise.all( - - filePaths.map(({ filePath, ignored }) => { - - /* - * If a filename was entered that matches an ignore - * pattern, then notify the user. - */ - if (ignored) { - if (warnIgnored) { - return createIgnoreResult(filePath, cwd); - } - - return void 0; - } - - const config = configs.getConfig(filePath); - - /* - * Sometimes a file found through a glob pattern will - * be ignored. In this case, `config` will be undefined - * and we just silently ignore the file. - */ - if (!config) { - return void 0; - } - - // Skip if there is cached result. - if (lintResultCache) { - const cachedResult = - lintResultCache.getCachedLintResults(filePath, config); - - if (cachedResult) { - const hadMessages = - cachedResult.messages && - cachedResult.messages.length > 0; - - if (hadMessages && fix) { - debug(`Reprocessing cached file to allow autofix: ${filePath}`); - } else { - debug(`Skipping file since it hasn't changed: ${filePath}`); - return cachedResult; - } - } - } - - - // set up fixer for fixTypes if necessary - let fixer = fix; - - if (fix && fixTypesSet) { - - // save original value of options.fix in case it's a function - const originalFix = (typeof fix === "function") - ? fix : () => true; - - fixer = message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message); - } - - return fs.readFile(filePath, "utf8") - .then(text => { - - // do the linting - const result = verifyText({ - text, - filePath, - configs, - cwd, - fix: fixer, - allowInlineConfig, - ruleFilter, - linter - }); - - /* - * Store the lint result in the LintResultCache. - * NOTE: The LintResultCache will remove the file source and any - * other properties that are difficult to serialize, and will - * hydrate those properties back in on future lint runs. - */ - if (lintResultCache) { - lintResultCache.setCachedLintResults(filePath, config, result); - } - - return result; - }); - - }) - ); - - // Persist the cache to disk. - if (lintResultCache) { - lintResultCache.reconcile(); - } - - const finalResults = results.filter(result => !!result); - - return processLintReport(this, { - results: finalResults - }); - } - - /** - * Executes the current configuration on text. - * @param {string} code A string of JavaScript code to lint. - * @param {Object} [options] The options. - * @param {string} [options.filePath] The path to the file of the source code. - * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path. - * @returns {Promise} The results of linting the string of code given. - */ - async lintText(code, options = {}) { - - // Parameter validation - - if (typeof code !== "string") { - throw new Error("'code' must be a string"); - } - - if (typeof options !== "object") { - throw new Error("'options' must be an object, null, or undefined"); - } - - // Options validation - - const { - filePath, - warnIgnored, - ...unknownOptions - } = options || {}; - - const unknownOptionKeys = Object.keys(unknownOptions); - - if (unknownOptionKeys.length > 0) { - throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`); - } - - if (filePath !== void 0 && !isNonEmptyString(filePath)) { - throw new Error("'options.filePath' must be a non-empty string or undefined"); - } - - if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") { - throw new Error("'options.warnIgnored' must be a boolean or undefined"); - } - - // Now we can get down to linting - - const { - linter, - options: eslintOptions - } = privateMembers.get(this); - const configs = await calculateConfigArray(this, eslintOptions); - const { - allowInlineConfig, - cwd, - fix, - warnIgnored: constructorWarnIgnored, - ruleFilter - } = eslintOptions; - const results = []; - const startTime = Date.now(); - const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js"); - - // Clear the last used config arrays. - if (resolvedFilename && await this.isPathIgnored(resolvedFilename)) { - const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored; - - if (shouldWarnIgnored) { - results.push(createIgnoreResult(resolvedFilename, cwd)); - } - } else { - - // Do lint. - results.push(verifyText({ - text: code, - filePath: resolvedFilename.endsWith("__placeholder__.js") ? "" : resolvedFilename, - configs, - cwd, - fix, - allowInlineConfig, - ruleFilter, - linter - })); - } - - debug(`Linting complete in: ${Date.now() - startTime}ms`); - - return processLintReport(this, { - results - }); - - } - - /** - * Returns the formatter representing the given formatter name. - * @param {string} [name] The name of the formatter to load. - * The following values are allowed: - * - `undefined` ... Load `stylish` builtin formatter. - * - A builtin formatter name ... Load the builtin formatter. - * - A third-party formatter name: - * - `foo` → `eslint-formatter-foo` - * - `@foo` → `@foo/eslint-formatter` - * - `@foo/bar` → `@foo/eslint-formatter-bar` - * - A file path ... Load the file. - * @returns {Promise} A promise resolving to the formatter object. - * This promise will be rejected if the given formatter was not found or not - * a function. - */ - async loadFormatter(name = "stylish") { - if (typeof name !== "string") { - throw new Error("'name' must be a string"); - } - - // replace \ with / for Windows compatibility - const normalizedFormatName = name.replace(/\\/gu, "/"); - const namespace = naming.getNamespaceFromTerm(normalizedFormatName); - - // grab our options - const { cwd } = privateMembers.get(this).options; - - - let formatterPath; - - // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) - if (!namespace && normalizedFormatName.includes("/")) { - formatterPath = path.resolve(cwd, normalizedFormatName); - } else { - try { - const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter"); - - // TODO: This is pretty dirty...would be nice to clean up at some point. - formatterPath = ModuleResolver.resolve(npmFormat, getPlaceholderPath(cwd)); - } catch { - formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`); - } - } - - let formatter; - - try { - formatter = (await import(pathToFileURL(formatterPath))).default; - } catch (ex) { - - // check for formatters that have been removed - if (removedFormatters.has(name)) { - ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``; - } else { - ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; - } - - throw ex; - } - - - if (typeof formatter !== "function") { - throw new TypeError(`Formatter must be a function, but got a ${typeof formatter}.`); - } - - const eslint = this; - - return { - - /** - * The main formatter method. - * @param {LintResults[]} results The lint results to format. - * @param {ResultsMeta} resultsMeta Warning count and max threshold. - * @returns {string} The formatted lint results. - */ - format(results, resultsMeta) { - let rulesMeta = null; - - results.sort(compareResultsByFilePath); - - return formatter(results, { - ...resultsMeta, - cwd, - get rulesMeta() { - if (!rulesMeta) { - rulesMeta = eslint.getRulesMetaForResults(results); - } - - return rulesMeta; - } - }); - } - }; - } - - /** - * Returns a configuration object for the given file based on the CLI options. - * This is the same logic used by the ESLint CLI executable to determine - * configuration for each file it processes. - * @param {string} filePath The path of the file to retrieve a config object for. - * @returns {Promise} A configuration object for the file - * or `undefined` if there is no configuration data for the object. - */ - async calculateConfigForFile(filePath) { - if (!isNonEmptyString(filePath)) { - throw new Error("'filePath' must be a non-empty string"); - } - const options = privateMembers.get(this).options; - const absolutePath = path.resolve(options.cwd, filePath); - const configs = await calculateConfigArray(this, options); - - return configs.getConfig(absolutePath); - } - - /** - * Finds the config file being used by this instance based on the options - * passed to the constructor. - * @returns {string|undefined} The path to the config file being used or - * `undefined` if no config file is being used. - */ - async findConfigFile() { - const options = privateMembers.get(this).options; - const { configFilePath } = await locateConfigFileToUse(options); - - return configFilePath; - } - - /** - * Checks if a given path is ignored by ESLint. - * @param {string} filePath The path of the file to check. - * @returns {Promise} Whether or not the given path is ignored. - */ - async isPathIgnored(filePath) { - const config = await this.calculateConfigForFile(filePath); - - return config === void 0; - } -} - -/** - * Returns whether flat config should be used. - * @returns {Promise} Whether flat config should be used. - */ -async function shouldUseFlatConfig() { - return (process.env.ESLINT_USE_FLAT_CONFIG !== "false"); -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = { - FlatESLint, - shouldUseFlatConfig -}; diff --git a/lib/eslint/index.js b/lib/eslint/index.js index 017b768ecd0..b7c52a4ea70 100644 --- a/lib/eslint/index.js +++ b/lib/eslint/index.js @@ -1,9 +1,9 @@ "use strict"; const { ESLint } = require("./eslint"); -const { FlatESLint } = require("./flat-eslint"); +const { LegacyESLint } = require("./legacy-eslint"); module.exports = { ESLint, - FlatESLint + LegacyESLint }; diff --git a/lib/eslint/legacy-eslint.js b/lib/eslint/legacy-eslint.js new file mode 100644 index 00000000000..251a3890db8 --- /dev/null +++ b/lib/eslint/legacy-eslint.js @@ -0,0 +1,722 @@ +/** + * @fileoverview Main API Class + * @author Kai Cataldo + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const path = require("path"); +const fs = require("fs"); +const { promisify } = require("util"); +const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); +const BuiltinRules = require("../rules"); +const { + Legacy: { + ConfigOps: { + getRuleSeverity + } + } +} = require("@eslint/eslintrc"); +const { version } = require("../../package.json"); + +//------------------------------------------------------------------------------ +// Typedefs +//------------------------------------------------------------------------------ + +/** @typedef {import("../cli-engine/cli-engine").LintReport} CLIEngineLintReport */ +/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */ +/** @typedef {import("../shared/types").ConfigData} ConfigData */ +/** @typedef {import("../shared/types").LintMessage} LintMessage */ +/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */ +/** @typedef {import("../shared/types").Plugin} Plugin */ +/** @typedef {import("../shared/types").Rule} Rule */ +/** @typedef {import("../shared/types").LintResult} LintResult */ +/** @typedef {import("../shared/types").ResultsMeta} ResultsMeta */ + +/** + * The main formatter object. + * @typedef LoadedFormatter + * @property {(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise} format format function. + */ + +/** + * The options with which to configure the LegacyESLint instance. + * @typedef {Object} LegacyESLintOptions + * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments. + * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance + * @property {boolean} [cache] Enable result caching. + * @property {string} [cacheLocation] The cache file to use instead of .eslintcache. + * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files. + * @property {string} [cwd] The value to use for the current working directory. + * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`. + * @property {string[]} [extensions] An array of file extensions to check. + * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean. + * @property {string[]} [fixTypes] Array of rule types to apply fixes for. + * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file. + * @property {boolean} [ignore] False disables use of .eslintignore. + * @property {string} [ignorePath] The ignore file to use instead of .eslintignore. + * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance + * @property {string} [overrideConfigFile] The configuration file to use. + * @property {Record|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation. + * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives. + * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD. + * @property {string[]} [rulePaths] An array of directories to load custom rules from. + * @property {boolean} [useEslintrc] False disables looking for .eslintrc.* files. + * @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause + * the linting operation to short circuit and not report any failures. + */ + +/** + * A rules metadata object. + * @typedef {Object} RulesMeta + * @property {string} id The plugin ID. + * @property {Object} definition The plugin definition. + */ + +/** + * Private members for the `ESLint` instance. + * @typedef {Object} ESLintPrivateMembers + * @property {CLIEngine} cliEngine The wrapped CLIEngine instance. + * @property {LegacyESLintOptions} options The options used to instantiate the ESLint instance. + */ + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const writeFile = promisify(fs.writeFile); + +/** + * The map with which to store private class members. + * @type {WeakMap} + */ +const privateMembersMap = new WeakMap(); + +/** + * Check if a given value is a non-empty string or not. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is a non-empty string. + */ +function isNonEmptyString(value) { + return typeof value === "string" && value.trim() !== ""; +} + +/** + * Check if a given value is an array of non-empty strings or not. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is an array of non-empty strings. + */ +function isArrayOfNonEmptyString(value) { + return Array.isArray(value) && value.length && value.every(isNonEmptyString); +} + +/** + * Check if a given value is an empty array or an array of non-empty strings. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is an empty array or an array of non-empty + * strings. + */ +function isEmptyArrayOrArrayOfNonEmptyString(value) { + return Array.isArray(value) && value.every(isNonEmptyString); +} + +/** + * Check if a given value is a valid fix type or not. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is valid fix type. + */ +function isFixType(value) { + return value === "directive" || value === "problem" || value === "suggestion" || value === "layout"; +} + +/** + * Check if a given value is an array of fix types or not. + * @param {any} value The value to check. + * @returns {boolean} `true` if `value` is an array of fix types. + */ +function isFixTypeArray(value) { + return Array.isArray(value) && value.every(isFixType); +} + +/** + * The error for invalid options. + */ +class ESLintInvalidOptionsError extends Error { + constructor(messages) { + super(`Invalid Options:\n- ${messages.join("\n- ")}`); + this.code = "ESLINT_INVALID_OPTIONS"; + Error.captureStackTrace(this, ESLintInvalidOptionsError); + } +} + +/** + * Validates and normalizes options for the wrapped CLIEngine instance. + * @param {LegacyESLintOptions} options The options to process. + * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors. + * @returns {LegacyESLintOptions} The normalized options. + */ +function processOptions({ + allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. + baseConfig = null, + cache = false, + cacheLocation = ".eslintcache", + cacheStrategy = "metadata", + cwd = process.cwd(), + errorOnUnmatchedPattern = true, + extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature. + fix = false, + fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. + globInputPaths = true, + ignore = true, + ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT. + overrideConfig = null, + overrideConfigFile = null, + plugins = {}, + reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that. + resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature. + rulePaths = [], + useEslintrc = true, + passOnNoPatterns = false, + ...unknownOptions +}) { + const errors = []; + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length >= 1) { + errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`); + if (unknownOptionKeys.includes("cacheFile")) { + errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead."); + } + if (unknownOptionKeys.includes("configFile")) { + errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead."); + } + if (unknownOptionKeys.includes("envs")) { + errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead."); + } + if (unknownOptionKeys.includes("globals")) { + errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead."); + } + if (unknownOptionKeys.includes("ignorePattern")) { + errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead."); + } + if (unknownOptionKeys.includes("parser")) { + errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead."); + } + if (unknownOptionKeys.includes("parserOptions")) { + errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead."); + } + if (unknownOptionKeys.includes("rules")) { + errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead."); + } + } + if (typeof allowInlineConfig !== "boolean") { + errors.push("'allowInlineConfig' must be a boolean."); + } + if (typeof baseConfig !== "object") { + errors.push("'baseConfig' must be an object or null."); + } + if (typeof cache !== "boolean") { + errors.push("'cache' must be a boolean."); + } + if (!isNonEmptyString(cacheLocation)) { + errors.push("'cacheLocation' must be a non-empty string."); + } + if ( + cacheStrategy !== "metadata" && + cacheStrategy !== "content" + ) { + errors.push("'cacheStrategy' must be any of \"metadata\", \"content\"."); + } + if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) { + errors.push("'cwd' must be an absolute path."); + } + if (typeof errorOnUnmatchedPattern !== "boolean") { + errors.push("'errorOnUnmatchedPattern' must be a boolean."); + } + if (!isEmptyArrayOrArrayOfNonEmptyString(extensions) && extensions !== null) { + errors.push("'extensions' must be an array of non-empty strings or null."); + } + if (typeof fix !== "boolean" && typeof fix !== "function") { + errors.push("'fix' must be a boolean or a function."); + } + if (fixTypes !== null && !isFixTypeArray(fixTypes)) { + errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\"."); + } + if (typeof globInputPaths !== "boolean") { + errors.push("'globInputPaths' must be a boolean."); + } + if (typeof ignore !== "boolean") { + errors.push("'ignore' must be a boolean."); + } + if (!isNonEmptyString(ignorePath) && ignorePath !== null) { + errors.push("'ignorePath' must be a non-empty string or null."); + } + if (typeof overrideConfig !== "object") { + errors.push("'overrideConfig' must be an object or null."); + } + if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) { + errors.push("'overrideConfigFile' must be a non-empty string or null."); + } + if (typeof plugins !== "object") { + errors.push("'plugins' must be an object or null."); + } else if (plugins !== null && Object.keys(plugins).includes("")) { + errors.push("'plugins' must not include an empty string."); + } + if (Array.isArray(plugins)) { + errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead."); + } + if ( + reportUnusedDisableDirectives !== "error" && + reportUnusedDisableDirectives !== "warn" && + reportUnusedDisableDirectives !== "off" && + reportUnusedDisableDirectives !== null + ) { + errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null."); + } + if ( + !isNonEmptyString(resolvePluginsRelativeTo) && + resolvePluginsRelativeTo !== null + ) { + errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null."); + } + if (!isEmptyArrayOrArrayOfNonEmptyString(rulePaths)) { + errors.push("'rulePaths' must be an array of non-empty strings."); + } + if (typeof useEslintrc !== "boolean") { + errors.push("'useEslintrc' must be a boolean."); + } + if (typeof passOnNoPatterns !== "boolean") { + errors.push("'passOnNoPatterns' must be a boolean."); + } + + if (errors.length > 0) { + throw new ESLintInvalidOptionsError(errors); + } + + return { + allowInlineConfig, + baseConfig, + cache, + cacheLocation, + cacheStrategy, + configFile: overrideConfigFile, + cwd: path.normalize(cwd), + errorOnUnmatchedPattern, + extensions, + fix, + fixTypes, + globInputPaths, + ignore, + ignorePath, + reportUnusedDisableDirectives, + resolvePluginsRelativeTo, + rulePaths, + useEslintrc, + passOnNoPatterns + }; +} + +/** + * Check if a value has one or more properties and that value is not undefined. + * @param {any} obj The value to check. + * @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined. + */ +function hasDefinedProperty(obj) { + if (typeof obj === "object" && obj !== null) { + for (const key in obj) { + if (typeof obj[key] !== "undefined") { + return true; + } + } + } + return false; +} + +/** + * Create rulesMeta object. + * @param {Map} rules a map of rules from which to generate the object. + * @returns {Object} metadata for all enabled rules. + */ +function createRulesMeta(rules) { + return Array.from(rules).reduce((retVal, [id, rule]) => { + retVal[id] = rule.meta; + return retVal; + }, {}); +} + +/** @type {WeakMap} */ +const usedDeprecatedRulesCache = new WeakMap(); + +/** + * Create used deprecated rule list. + * @param {CLIEngine} cliEngine The CLIEngine instance. + * @param {string} maybeFilePath The absolute path to a lint target file or `""`. + * @returns {DeprecatedRuleInfo[]} The used deprecated rule list. + */ +function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) { + const { + configArrayFactory, + options: { cwd } + } = getCLIEngineInternalSlots(cliEngine); + const filePath = path.isAbsolute(maybeFilePath) + ? maybeFilePath + : path.join(cwd, "__placeholder__.js"); + const configArray = configArrayFactory.getConfigArrayForFile(filePath); + const config = configArray.extractConfig(filePath); + + // Most files use the same config, so cache it. + if (!usedDeprecatedRulesCache.has(config)) { + const pluginRules = configArray.pluginRules; + const retv = []; + + for (const [ruleId, ruleConf] of Object.entries(config.rules)) { + if (getRuleSeverity(ruleConf) === 0) { + continue; + } + const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId); + const meta = rule && rule.meta; + + if (meta && meta.deprecated) { + retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); + } + } + + usedDeprecatedRulesCache.set(config, Object.freeze(retv)); + } + + return usedDeprecatedRulesCache.get(config); +} + +/** + * Processes the linting results generated by a CLIEngine linting report to + * match the ESLint class's API. + * @param {CLIEngine} cliEngine The CLIEngine instance. + * @param {CLIEngineLintReport} report The CLIEngine linting report to process. + * @returns {LintResult[]} The processed linting results. + */ +function processCLIEngineLintReport(cliEngine, { results }) { + const descriptor = { + configurable: true, + enumerable: true, + get() { + return getOrFindUsedDeprecatedRules(cliEngine, this.filePath); + } + }; + + for (const result of results) { + Object.defineProperty(result, "usedDeprecatedRules", descriptor); + } + + return results; +} + +/** + * An Array.prototype.sort() compatible compare function to order results by their file path. + * @param {LintResult} a The first lint result. + * @param {LintResult} b The second lint result. + * @returns {number} An integer representing the order in which the two results should occur. + */ +function compareResultsByFilePath(a, b) { + if (a.filePath < b.filePath) { + return -1; + } + + if (a.filePath > b.filePath) { + return 1; + } + + return 0; +} + +/** + * Main API. + */ +class LegacyESLint { + + /** + * Creates a new instance of the main ESLint API. + * @param {LegacyESLintOptions} options The options for this instance. + */ + constructor(options = {}) { + const processedOptions = processOptions(options); + const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins }); + const { + configArrayFactory, + lastConfigArrays + } = getCLIEngineInternalSlots(cliEngine); + let updated = false; + + /* + * Address `overrideConfig` to set override config. + * Operate the `configArrayFactory` internal slot directly because this + * functionality doesn't exist as the public API of CLIEngine. + */ + if (hasDefinedProperty(options.overrideConfig)) { + configArrayFactory.setOverrideConfig(options.overrideConfig); + updated = true; + } + + // Update caches. + if (updated) { + configArrayFactory.clearCache(); + lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile(); + } + + // Initialize private properties. + privateMembersMap.set(this, { + cliEngine, + options: processedOptions + }); + } + + /** + * The version text. + * @type {string} + */ + static get version() { + return version; + } + + /** + * Outputs fixes from the given results to files. + * @param {LintResult[]} results The lint results. + * @returns {Promise} Returns a promise that is used to track side effects. + */ + static async outputFixes(results) { + if (!Array.isArray(results)) { + throw new Error("'results' must be an array"); + } + + await Promise.all( + results + .filter(result => { + if (typeof result !== "object" || result === null) { + throw new Error("'results' must include only objects"); + } + return ( + typeof result.output === "string" && + path.isAbsolute(result.filePath) + ); + }) + .map(r => writeFile(r.filePath, r.output)) + ); + } + + /** + * Returns results that only contains errors. + * @param {LintResult[]} results The results to filter. + * @returns {LintResult[]} The filtered results. + */ + static getErrorResults(results) { + return CLIEngine.getErrorResults(results); + } + + /** + * Returns meta objects for each rule represented in the lint results. + * @param {LintResult[]} results The results to fetch rules meta for. + * @returns {Object} A mapping of ruleIds to rule meta objects. + */ + getRulesMetaForResults(results) { + + const resultRuleIds = new Set(); + + // first gather all ruleIds from all results + + for (const result of results) { + for (const { ruleId } of result.messages) { + resultRuleIds.add(ruleId); + } + for (const { ruleId } of result.suppressedMessages) { + resultRuleIds.add(ruleId); + } + } + + // create a map of all rules in the results + + const { cliEngine } = privateMembersMap.get(this); + const rules = cliEngine.getRules(); + const resultRules = new Map(); + + for (const [ruleId, rule] of rules) { + if (resultRuleIds.has(ruleId)) { + resultRules.set(ruleId, rule); + } + } + + return createRulesMeta(resultRules); + + } + + /** + * Executes the current configuration on an array of file and directory names. + * @param {string[]} patterns An array of file and directory names. + * @returns {Promise} The results of linting the file patterns given. + */ + async lintFiles(patterns) { + const { cliEngine, options } = privateMembersMap.get(this); + + if (options.passOnNoPatterns && (patterns === "" || (Array.isArray(patterns) && patterns.length === 0))) { + return []; + } + + if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { + throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); + } + + return processCLIEngineLintReport( + cliEngine, + cliEngine.executeOnFiles(patterns) + ); + } + + /** + * Executes the current configuration on text. + * @param {string} code A string of JavaScript code to lint. + * @param {Object} [options] The options. + * @param {string} [options.filePath] The path to the file of the source code. + * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path. + * @returns {Promise} The results of linting the string of code given. + */ + async lintText(code, options = {}) { + if (typeof code !== "string") { + throw new Error("'code' must be a string"); + } + if (typeof options !== "object") { + throw new Error("'options' must be an object, null, or undefined"); + } + const { + filePath, + warnIgnored = false, + ...unknownOptions + } = options || {}; + + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length > 0) { + throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`); + } + + if (filePath !== void 0 && !isNonEmptyString(filePath)) { + throw new Error("'options.filePath' must be a non-empty string or undefined"); + } + if (typeof warnIgnored !== "boolean") { + throw new Error("'options.warnIgnored' must be a boolean or undefined"); + } + + const { cliEngine } = privateMembersMap.get(this); + + return processCLIEngineLintReport( + cliEngine, + cliEngine.executeOnText(code, filePath, warnIgnored) + ); + } + + /** + * Returns the formatter representing the given formatter name. + * @param {string} [name] The name of the formatter to load. + * The following values are allowed: + * - `undefined` ... Load `stylish` builtin formatter. + * - A builtin formatter name ... Load the builtin formatter. + * - A third-party formatter name: + * - `foo` → `eslint-formatter-foo` + * - `@foo` → `@foo/eslint-formatter` + * - `@foo/bar` → `@foo/eslint-formatter-bar` + * - A file path ... Load the file. + * @returns {Promise} A promise resolving to the formatter object. + * This promise will be rejected if the given formatter was not found or not + * a function. + */ + async loadFormatter(name = "stylish") { + if (typeof name !== "string") { + throw new Error("'name' must be a string"); + } + + const { cliEngine, options } = privateMembersMap.get(this); + const formatter = cliEngine.getFormatter(name); + + if (typeof formatter !== "function") { + throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`); + } + + return { + + /** + * The main formatter method. + * @param {LintResult[]} results The lint results to format. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. + * @returns {string | Promise} The formatted lint results. + */ + format(results, resultsMeta) { + let rulesMeta = null; + + results.sort(compareResultsByFilePath); + + return formatter(results, { + ...resultsMeta, + get cwd() { + return options.cwd; + }, + get rulesMeta() { + if (!rulesMeta) { + rulesMeta = createRulesMeta(cliEngine.getRules()); + } + + return rulesMeta; + } + }); + } + }; + } + + /** + * Returns a configuration object for the given file based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} filePath The path of the file to retrieve a config object for. + * @returns {Promise} A configuration object for the file. + */ + async calculateConfigForFile(filePath) { + if (!isNonEmptyString(filePath)) { + throw new Error("'filePath' must be a non-empty string"); + } + const { cliEngine } = privateMembersMap.get(this); + + return cliEngine.getConfigForFile(filePath); + } + + /** + * Checks if a given path is ignored by ESLint. + * @param {string} filePath The path of the file to check. + * @returns {Promise} Whether or not the given path is ignored. + */ + async isPathIgnored(filePath) { + if (!isNonEmptyString(filePath)) { + throw new Error("'filePath' must be a non-empty string"); + } + const { cliEngine } = privateMembersMap.get(this); + + return cliEngine.isPathIgnored(filePath); + } +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + LegacyESLint, + + /** + * Get the private class members of a given ESLint instance for tests. + * @param {ESLint} instance The ESLint instance to get. + * @returns {ESLintPrivateMembers} The instance's private class members. + */ + getESLintPrivateMembers(instance) { + return privateMembersMap.get(instance); + } +}; diff --git a/lib/unsupported-api.js b/lib/unsupported-api.js index 8a2e147aabe..6f13c44d3c7 100644 --- a/lib/unsupported-api.js +++ b/lib/unsupported-api.js @@ -12,9 +12,9 @@ //----------------------------------------------------------------------------- const { FileEnumerator } = require("./cli-engine/file-enumerator"); -const { FlatESLint, shouldUseFlatConfig } = require("./eslint/flat-eslint"); +const { ESLint: FlatESLint, shouldUseFlatConfig } = require("./eslint/eslint"); const FlatRuleTester = require("./rule-tester/flat-rule-tester"); -const { ESLint } = require("./eslint/eslint"); +const { LegacyESLint } = require("./eslint/legacy-eslint"); //----------------------------------------------------------------------------- // Exports @@ -26,5 +26,5 @@ module.exports = { shouldUseFlatConfig, FlatRuleTester, FileEnumerator, - LegacyESLint: ESLint + LegacyESLint }; diff --git a/tests/lib/cli.js b/tests/lib/cli.js index c7a0ccf51af..04bb8081ae5 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -16,7 +16,7 @@ const assert = require("chai").assert, stdAssert = require("assert"), - { ESLint, FlatESLint } = require("../../lib/eslint"), + { ESLint, LegacyESLint } = require("../../lib/eslint"), BuiltinRules = require("../../lib/rules"), path = require("path"), sinon = require("sinon"), @@ -54,7 +54,7 @@ describe("cli", () => { */ async function verifyESLintOpts(cmd, opts, configType) { - const ActiveESLint = configType === "flat" ? FlatESLint : ESLint; + const ActiveESLint = configType === "flat" ? ESLint : LegacyESLint; // create a fake ESLint class to test with const fakeESLint = sinon.mock().withExactArgs(sinon.match(opts)); @@ -64,8 +64,8 @@ describe("cli", () => { sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: sinon.spy() }); const localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(configType === "flat") }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(configType === "flat") }, "./shared/logging": log }); @@ -112,7 +112,7 @@ describe("cli", () => { ["eslintrc", "flat"].forEach(configType => { const useFlatConfig = configType === "flat"; - const ActiveESLint = configType === "flat" ? FlatESLint : ESLint; + const ActiveESLint = configType === "flat" ? ESLint : LegacyESLint; describe("execute()", () => { @@ -1062,8 +1062,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.stub(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1081,8 +1081,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.stub(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1112,8 +1112,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.mock().once(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1148,9 +1148,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.mock().withExactArgs(report); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1185,9 +1184,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.mock().withExactArgs(report); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1203,9 +1201,8 @@ describe("cli", () => { const fakeESLint = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1234,8 +1231,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1262,8 +1259,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.stub(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1297,8 +1294,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1334,8 +1331,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1370,8 +1367,8 @@ describe("cli", () => { fakeESLint.outputFixes = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); @@ -1387,9 +1384,8 @@ describe("cli", () => { const fakeESLint = sinon.mock().never(); localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { ESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, "./shared/logging": log }); diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index cde34e27623..6936e1c3052 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -11,7 +11,9 @@ //------------------------------------------------------------------------------ const assert = require("assert"); +const util = require("util"); const fs = require("fs"); +const fsp = fs.promises; const os = require("os"); const path = require("path"); const escapeStringRegExp = require("escape-string-regexp"); @@ -19,15 +21,36 @@ const fCache = require("file-entry-cache"); const sinon = require("sinon"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); const shell = require("shelljs"); -const { - Legacy: { - CascadingConfigArrayFactory - } -} = require("@eslint/eslintrc"); const hash = require("../../../lib/cli-engine/hash"); const { unIndent, createCustomTeardown } = require("../../_utils"); +const { shouldUseFlatConfig } = require("../../../lib/eslint/eslint"); const coreRules = require("../../../lib/rules"); -const childProcess = require("child_process"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Creates a directory if it doesn't already exist. + * @param {string} dirPath The path to the directory that should exist. + * @returns {void} + */ +function ensureDirectoryExists(dirPath) { + try { + fs.statSync(dirPath); + } catch { + fs.mkdirSync(dirPath); + } +} + +/** + * Does nothing for a given time. + * @param {number} time Time in ms. + * @returns {void} + */ +async function sleep(time) { + await util.promisify(setTimeout)(time); +} //------------------------------------------------------------------------------ // Tests @@ -82,15 +105,6 @@ describe("ESLint", () => { }); } - /** - * Call the last argument. - * @param {any[]} args Arguments - * @returns {void} - */ - function callLastArgument(...args) { - process.nextTick(args[args.length - 1], null); - } - // copy into clean area so as not to get "infected" by this project's .eslintrc files before(function() { @@ -117,7 +131,7 @@ describe("ESLint", () => { it("the default value of 'options.cwd' should be the current working directory.", async () => { process.chdir(__dirname); try { - const engine = new ESLint({ useEslintrc: false }); + const engine = new ESLint(); const results = await engine.lintFiles("eslint.js"); assert.strictEqual(path.dirname(results[0].filePath), __dirname); @@ -130,9 +144,11 @@ describe("ESLint", () => { const cwd = getFixturePath("example-app3"); const engine = new ESLint({ cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { - plugins: ["test"], + plugins: { + test: require(path.join(cwd, "node_modules", "eslint-plugin-test")) + }, rules: { "test/report-cwd": "error" } @@ -148,13 +164,6 @@ describe("ESLint", () => { assert.strictEqual(formatter.format(results), cwd); }); - it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { - assert.throws(() => { - // eslint-disable-next-line no-new -- Check for throwing - new ESLint({ ignorePath: fixtureDir }); - }, new RegExp(escapeStringRegExp(`Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`), "u")); - }); - // https://github.com/eslint/eslint/issues/2380 it("should not modify baseConfig when format is specified", () => { const customBaseConfig = { root: true }; @@ -171,24 +180,17 @@ describe("ESLint", () => { configFile: "", envs: [], globals: [], + ignorePath: ".gitignore", ignorePattern: [], parser: "", parserOptions: {}, rules: {}, - plugins: [] + plugins: [], + reportUnusedDisableDirectives: "error" }), new RegExp(escapeStringRegExp([ "Invalid Options:", - "- Unknown options: cacheFile, configFile, envs, globals, ignorePattern, parser, parserOptions, rules", - "- 'cacheFile' has been removed. Please use the 'cacheLocation' option instead.", - "- 'configFile' has been removed. Please use the 'overrideConfigFile' option instead.", - "- 'envs' has been removed. Please use the 'overrideConfig.env' option instead.", - "- 'globals' has been removed. Please use the 'overrideConfig.globals' option instead.", - "- 'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.", - "- 'parser' has been removed. Please use the 'overrideConfig.parser' option instead.", - "- 'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.", - "- 'rules' has been removed. Please use the 'overrideConfig.rules' option instead.", - "- 'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead." + "- Unknown options: cacheFile, configFile, envs, globals, ignorePath, ignorePattern, parser, parserOptions, rules, reportUnusedDisableDirectives" ].join("\n")), "u") ); }); @@ -202,19 +204,16 @@ describe("ESLint", () => { cacheLocation: "", cwd: "foo", errorOnUnmatchedPattern: "", - extensions: "", fix: "", fixTypes: ["xyz"], globInputPaths: "", ignore: "", - ignorePath: "", + ignorePatterns: "", overrideConfig: "", overrideConfigFile: "", plugins: "", - reportUnusedDisableDirectives: "", - resolvePluginsRelativeTo: "", - rulePaths: "", - useEslintrc: "" + warnIgnored: "", + ruleFilter: "" }), new RegExp(escapeStringRegExp([ "Invalid Options:", @@ -224,23 +223,48 @@ describe("ESLint", () => { "- 'cacheLocation' must be a non-empty string.", "- 'cwd' must be an absolute path.", "- 'errorOnUnmatchedPattern' must be a boolean.", - "- 'extensions' must be an array of non-empty strings or null.", "- 'fix' must be a boolean or a function.", "- 'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".", "- 'globInputPaths' must be a boolean.", "- 'ignore' must be a boolean.", - "- 'ignorePath' must be a non-empty string or null.", + "- 'ignorePatterns' must be an array of non-empty strings or null.", "- 'overrideConfig' must be an object or null.", - "- 'overrideConfigFile' must be a non-empty string or null.", + "- 'overrideConfigFile' must be a non-empty string, null, or true.", "- 'plugins' must be an object or null.", - "- 'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.", - "- 'resolvePluginsRelativeTo' must be a non-empty string or null.", - "- 'rulePaths' must be an array of non-empty strings.", - "- 'useEslintrc' must be a boolean." + "- 'warnIgnored' must be a boolean.", + "- 'ruleFilter' must be a function." ].join("\n")), "u") ); }); + it("should throw readable messages if 'ignorePatterns' is not an array of non-empty strings.", () => { + const invalidIgnorePatterns = [ + () => {}, + false, + {}, + "", + "foo", + [[]], + [() => {}], + [false], + [{}], + [""], + ["foo", ""], + ["foo", "", "bar"], + ["foo", false, "bar"] + ]; + + invalidIgnorePatterns.forEach(ignorePatterns => { + assert.throws( + () => new ESLint({ ignorePatterns }), + new RegExp(escapeStringRegExp([ + "Invalid Options:", + "- 'ignorePatterns' must be an array of non-empty strings or null." + ].join("\n")), "u") + ); + }); + }); + it("should throw readable messages if 'plugins' option contains empty key", () => { assert.throws( () => new ESLint({ @@ -261,81 +285,58 @@ describe("ESLint", () => { describe("lintText()", () => { let eslint; - describe("when using local cwd .eslintrc", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "eslint/multiple-rules-config"), - files: { - ".eslintrc.json": { - root: true, - rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true - } - } - } + it("should report the total and per file errors when using local cwd eslint.config.js", async () => { + eslint = new ESLint({ + cwd: __dirname }); - beforeEach(prepare); - afterEach(cleanup); - - it("should report the total and per file errors", async () => { - eslint = new ESLint({ cwd: getPath() }); - const results = await eslint.lintText("var foo = 'bar';"); + const results = await eslint.lintText("var foo = 'bar';"); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 5); - assert.strictEqual(results[0].messages[0].ruleId, "strict"); - assert.strictEqual(results[0].messages[1].ruleId, "no-var"); - assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[3].ruleId, "quotes"); - assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 3); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 2); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); - }); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 4); + assert.strictEqual(results[0].messages[0].ruleId, "no-var"); + assert.strictEqual(results[0].messages[1].ruleId, "no-unused-vars"); + assert.strictEqual(results[0].messages[2].ruleId, "quotes"); + assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 3); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); - it("should report the total and per file warnings", async () => { - eslint = new ESLint({ - cwd: getPath(), - overrideConfig: { - rules: { - quotes: 1, - "no-var": 1, - "eol-last": 1, - strict: 1, - "no-unused-vars": 1 - } + it("should report the total and per file warnings when using local cwd .eslintrc", async () => { + eslint = new ESLint({ + overrideConfig: { + rules: { + quotes: 1, + "no-var": 1, + "eol-last": 1, + "no-unused-vars": 1 } - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 5); - assert.strictEqual(results[0].messages[0].ruleId, "strict"); - assert.strictEqual(results[0].messages[1].ruleId, "no-var"); - assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[3].ruleId, "quotes"); - assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 3); - assert.strictEqual(results[0].usedDeprecatedRules.length, 2); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); + }, + overrideConfigFile: true }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 4); + assert.strictEqual(results[0].messages[0].ruleId, "no-var"); + assert.strictEqual(results[0].messages[1].ruleId, "no-unused-vars"); + assert.strictEqual(results[0].messages[2].ruleId, "quotes"); + assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 3); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report one message when using specific config file", async () => { eslint = new ESLint({ - overrideConfigFile: "fixtures/configurations/quotes-error.json", - useEslintrc: false, + overrideConfigFile: "fixtures/configurations/quotes-error.js", cwd: getFixturePath("..") }); const results = await eslint.lintText("var foo = 'bar';"); @@ -347,8 +348,10 @@ describe("ESLint", () => { assert.strictEqual(results[0].errorCount, 1); assert.strictEqual(results[0].fixableErrorCount, 1); assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); assert.strictEqual(results[0].usedDeprecatedRules.length, 1); assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report the filename when passed in", async () => { @@ -364,16 +367,41 @@ describe("ESLint", () => { it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - cwd: getFixturePath("..") + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/eslint.config-with-ignores.js" + }); + + const options = { filePath: "fixtures/passing.js", warnIgnored: true }; + const results = await eslint.lintText("var bar = foo;", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false, but lintText warnIgnored is true", async () => { + eslint = new ESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/eslint.config-with-ignores.js", + warnIgnored: false }); + const options = { filePath: "fixtures/passing.js", warnIgnored: true }; const results = await eslint.lintText("var bar = foo;", options); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); assert.strictEqual(results[0].messages[0].output, void 0); assert.strictEqual(results[0].errorCount, 0); assert.strictEqual(results[0].warningCount, 1); @@ -381,12 +409,13 @@ describe("ESLint", () => { assert.strictEqual(results[0].fixableErrorCount, 0); assert.strictEqual(results[0].fixableWarningCount, 0); assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - cwd: getFixturePath("..") + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/eslint.config-with-ignores.js" }); const options = { filePath: "fixtures/passing.js", @@ -400,24 +429,36 @@ describe("ESLint", () => { assert.strictEqual(results.length, 0); }); - it("should suppress excluded file warnings by default", async () => { + it("should not return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false", async () => { eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - cwd: getFixturePath("..") + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/eslint.config-with-ignores.js", + warnIgnored: false }); const options = { filePath: "fixtures/passing.js" }; const results = await eslint.lintText("var bar = foo;", options); - // should not report anything because there are no errors + // should not report anything because the warning is suppressed assert.strictEqual(results.length, 0); }); + it("should show excluded file warnings by default", async () => { + eslint = new ESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/eslint.config-with-ignores.js" + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText("var bar = foo;", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); + }); + it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { eslint = new ESLint({ - ignorePath: "fixtures/.eslintignore", cwd: getFixturePath(".."), ignore: false, - useEslintrc: false, + overrideConfigFile: "fixtures/eslint.config-with-ignores.js", overrideConfig: { rules: { "no-undef": 2 @@ -432,11 +473,12 @@ describe("ESLint", () => { assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); assert.strictEqual(results[0].messages[0].severity, 2); assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should return a message and fixed text when in fix mode", async () => { eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, fix: true, overrideConfig: { rules: { @@ -460,225 +502,19 @@ describe("ESLint", () => { fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foo;", - usedDeprecatedRules: [{ - ruleId: "semi", - replacedBy: [] - }] + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: [] + } + ] } ]); }); - it("should use eslint:recommended rules when eslint:recommended configuration is specified", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - extends: ["eslint:recommended"] - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "file.js" }; - const results = await eslint.lintText("foo ()", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - }); - - it("should use eslint:all rules when eslint:all configuration is specified", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - extends: ["eslint:all"] - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "file.js" }; - const results = await eslint.lintText("if (true) { foo() }", options); - - assert.strictEqual(results.length, 1); - - const { messages } = results[0]; - - // Some rules that should report errors in the given code. Not all, as we don't want to update this test when we add new rules. - const expectedRules = ["no-undef", "no-constant-condition"]; - - expectedRules.forEach(ruleId => { - const messageFromRule = messages.find(message => message.ruleId === ruleId); - - assert.ok( - typeof messageFromRule === "object" && messageFromRule !== null, // LintMessage object - `Expected a message from rule '${ruleId}'` - ); - assert.strictEqual(messageFromRule.severity, 2); - }); - - }); - - it("correctly autofixes semicolon-conflicting-fixes", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true - }); - const inputPath = getFixturePath("autofix/semicolon-conflicting-fixes.js"); - const outputPath = getFixturePath("autofix/semicolon-conflicting-fixes.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("correctly autofixes return-conflicting-fixes", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true - }); - const inputPath = getFixturePath("autofix/return-conflicting-fixes.js"); - const outputPath = getFixturePath("autofix/return-conflicting-fixes.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - describe("Fix Types", () => { - it("should throw an error when an invalid fix type is specified", () => { - assert.throws(() => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layou"] - }); - }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); - }); - - it("should not fix any rules when fixTypes is used without fix", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: false, - fixTypes: ["layout"] - }); - const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - const results = await eslint.lintFiles([inputPath]); - - assert.strictEqual(results[0].output, void 0); - }); - - it("should not fix non-style rules when fixTypes has only 'layout'", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layout"] - }); - const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - const outputPath = getFixturePath("fix-types/fix-only-semi.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["suggestion"] - }); - const inputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.js"); - const outputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["suggestion", "layout"] - }); - const inputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.js"); - const outputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not throw an error when a rule doesn't have a 'meta' property", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layout"], - rulePaths: [getFixturePath("rules", "fix-types-test")] - }); - const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); - const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not throw an error when a rule is loaded after initialization with lintFiles()", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layout"], - plugins: { - test: { - rules: { - "no-program": require(getFixturePath("rules", "fix-types-test", "no-program.js")) - } - } - } - }); - const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); - const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not throw an error when a rule is loaded after initialization with lintText()", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layout"], - plugins: { - test: { - rules: { - "no-program": require(getFixturePath("rules", "fix-types-test", "no-program.js")) - } - } - } - }); - const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); - const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); - const results = await eslint.lintText(fs.readFileSync(inputPath, { encoding: "utf8" }), { filePath: inputPath }); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - }); - it("should return a message and omit fixed text when in fix mode and fixes aren't done", async () => { eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, fix: true, overrideConfig: { rules: { @@ -721,16 +557,15 @@ describe("ESLint", () => { it("should not delete code if there is a syntax error after trying to autofix.", async () => { eslint = eslintWithPlugins({ - useEslintrc: false, + overrideConfigFile: true, fix: true, overrideConfig: { - plugins: ["example"], rules: { "example/make-syntax-error": "error" } }, ignore: false, - cwd: getFixturePath() + cwd: getFixturePath(".") }); const options = { filePath: "test.js" }; const results = await eslint.lintText("var bar = foo", options); @@ -762,8 +597,8 @@ describe("ESLint", () => { }); it("should not crash even if there are any syntax error since the first time.", async () => { - eslint = new ESLint({ - useEslintrc: false, + eslint = eslintWithPlugins({ + overrideConfigFile: true, fix: true, overrideConfig: { rules: { @@ -804,7 +639,7 @@ describe("ESLint", () => { it("should return source code of file in `source` property when errors are present", async () => { eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { rules: { semi: 2 } } @@ -816,7 +651,7 @@ describe("ESLint", () => { it("should return source code of file in `source` property when warnings are present", async () => { eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { rules: { semi: 1 } } @@ -829,7 +664,7 @@ describe("ESLint", () => { it("should not return a `source` property when no errors or warnings are present", async () => { eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { rules: { semi: 2 } } @@ -838,11 +673,12 @@ describe("ESLint", () => { assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[0].source, void 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should not return a `source` property when fixes are applied", async () => { eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, fix: true, overrideConfig: { rules: { @@ -859,7 +695,7 @@ describe("ESLint", () => { it("should return a `source` property when a parsing error has occurred", async () => { eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { rules: { eqeqeq: 2 } } @@ -893,62 +729,24 @@ describe("ESLint", () => { }); // https://github.com/eslint/eslint/issues/5547 - it("should respect default ignore rules, even with --no-ignore", async () => { + it("should respect default ignore rules (ignoring node_modules), even with --no-ignore", async () => { eslint = new ESLint({ cwd: getFixturePath(), ignore: false }); const results = await eslint.lintText("var bar = foo;", { filePath: "node_modules/passing.js", warnIgnored: true }); - const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; assert.strictEqual(results.length, 1); assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js")); assert.strictEqual(results[0].messages[0].message, expectedMsg); - }); - - describe('plugin shorthand notation ("@scope" for "@scope/eslint-plugin")', () => { - const Module = require("module"); - let originalFindPath = null; - - /* eslint-disable no-underscore-dangle -- Override Node API */ - before(() => { - originalFindPath = Module._findPath; - Module._findPath = function(id, ...otherArgs) { - if (id === "@scope/eslint-plugin") { - return path.resolve(__dirname, "../../fixtures/plugin-shorthand/basic/node_modules/@scope/eslint-plugin/index.js"); - } - return originalFindPath.call(this, id, ...otherArgs); - }; - }); - after(() => { - Module._findPath = originalFindPath; - }); - /* eslint-enable no-underscore-dangle -- Override Node API */ - - it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { - eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/basic") }); - const [result] = await eslint.lintText("var x = 0", { filePath: "index.js" }); - - assert.strictEqual(result.filePath, getFixturePath("plugin-shorthand/basic/index.js")); - assert.strictEqual(result.messages[0].ruleId, "@scope/rule"); - assert.strictEqual(result.messages[0].message, "OK"); - }); - - it("should resolve 'extends:[\"plugin:@scope/recommended\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { - eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/extends") }); - const [result] = await eslint.lintText("var x = 0", { filePath: "index.js" }); - - assert.strictEqual(result.filePath, getFixturePath("plugin-shorthand/extends/index.js")); - assert.strictEqual(result.messages[0].ruleId, "@scope/rule"); - assert.strictEqual(result.messages[0].message, "OK"); - }); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should warn when deprecated rules are found in a config", async () => { eslint = new ESLint({ cwd: originalDir, - useEslintrc: false, - overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml" + overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/eslint.config.js" }); const [result] = await eslint.lintText("foo"); @@ -958,11 +756,42 @@ describe("ESLint", () => { ); }); - it("should throw if non-string value is given to 'code' parameter", async () => { - eslint = new ESLint(); - await assert.rejects(() => eslint.lintText(100), /'code' must be a string/u); - }); - + it("should throw if eslint.config.js file is not present", async () => { + eslint = new ESLint({ + cwd: getFixturePath("..") + }); + await assert.rejects(() => eslint.lintText("var foo = 'bar';"), /Could not find config file/u); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { + eslint = new ESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: true + }); + await eslint.lintText("var foo = 'bar';"); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { + eslint = new ESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/configurations/quotes-error.js" + }); + await eslint.lintText("var foo = 'bar';"); + }); + + it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { + eslint = new ESLint({ + cwd: getFixturePath(""), + overrideConfigFile: "does-not-exist.js" + }); + await assert.rejects(() => eslint.lintText("var foo = 'bar';"), { code: "ENOENT" }); + }); + + it("should throw if non-string value is given to 'code' parameter", async () => { + eslint = new ESLint(); + await assert.rejects(() => eslint.lintText(100), /'code' must be a string/u); + }); + it("should throw if non-object value is given to 'options' parameter", async () => { eslint = new ESLint(); await assert.rejects(() => eslint.lintText("var a = 0", "foo.js"), /'options' must be an object, null, or undefined/u); @@ -982,6 +811,85 @@ describe("ESLint", () => { eslint = new ESLint(); await assert.rejects(() => eslint.lintText("var a = 0", { warnIgnored: "" }), /'options.warnIgnored' must be a boolean or undefined/u); }); + + it("should work with config file that exports a promise", async () => { + eslint = new ESLint({ + cwd: getFixturePath("promise-config") + }); + const results = await eslint.lintText('var foo = "bar";'); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + describe("Alternate config files", () => { + + it("should find eslint.config.mjs when present", async () => { + + const cwd = getFixturePath("mjs-config"); + + eslint = new ESLint({ + cwd + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + + }); + + it("should find eslint.config.cjs when present", async () => { + + const cwd = getFixturePath("cjs-config"); + + eslint = new ESLint({ + cwd + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + + }); + + it("should favor eslint.config.js when eslint.config.mjs and eslint.config.cjs are present", async () => { + + const cwd = getFixturePath("js-mjs-cjs-config"); + + eslint = new ESLint({ + cwd + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should favor eslint.config.mjs when eslint.config.cjs is present", async () => { + + const cwd = getFixturePath("mjs-cjs-config"); + + eslint = new ESLint({ + cwd + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + }); + }); }); describe("lintFiles()", () => { @@ -990,38 +898,44 @@ describe("ESLint", () => { let eslint; it("should use correct parser when custom parser is specified", async () => { + const filePath = path.resolve(__dirname, "../../fixtures/configurations/parser/custom.js"); + eslint = new ESLint({ cwd: originalDir, - ignore: false + ignore: false, + overrideConfigFile: true, + overrideConfig: { + languageOptions: { + parser: require(filePath) + } + } }); - const filePath = path.resolve(__dirname, "../../fixtures/configurations/parser/custom.js"); + const results = await eslint.lintFiles([filePath]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 1); assert.strictEqual(results[0].messages[0].message, "Parsing error: Boom!"); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report zero messages when given a config file and a valid file", async () => { eslint = new ESLint({ cwd: originalDir, - useEslintrc: false, - ignore: false, - overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" + overrideConfigFile: "tests/fixtures/simple-valid-project/eslint.config.js" }); const results = await eslint.lintFiles(["tests/fixtures/simple-valid-project/**/foo*.js"]); assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should handle multiple patterns with overlapping files", async () => { eslint = new ESLint({ cwd: originalDir, - useEslintrc: false, - ignore: false, - overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" + overrideConfigFile: "tests/fixtures/simple-valid-project/eslint.config.js" }); const results = await eslint.lintFiles([ "tests/fixtures/simple-valid-project/**/foo*.js", @@ -1032,1687 +946,1454 @@ describe("ESLint", () => { assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report zero messages when given a config file and a valid file and espree as parser", async () => { eslint = new ESLint({ overrideConfig: { - parser: "espree", - parserOptions: { - ecmaVersion: 2021 + languageOptions: { + parser: require("espree"), + parserOptions: { + ecmaVersion: 2021 + } } }, - useEslintrc: false + overrideConfigFile: true }); const results = await eslint.lintFiles(["lib/cli.js"]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { eslint = new ESLint({ overrideConfig: { - parser: "esprima" + languageOptions: { + parser: require("esprima") + } }, - useEslintrc: false, + overrideConfigFile: true, ignore: false }); const results = await eslint.lintFiles(["tests/fixtures/passing.js"]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should throw if eslint.config.js file is not present", async () => { + eslint = new ESLint({ + cwd: getFixturePath("..") + }); + await assert.rejects(() => eslint.lintFiles("fixtures/undef*.js"), /Could not find config file/u); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { + eslint = new ESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: true + }); + await eslint.lintFiles("fixtures/undef*.js"); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { + eslint = new ESLint({ + cwd: getFixturePath(".."), + overrideConfigFile: "fixtures/configurations/quotes-error.js" + }); + await eslint.lintFiles("fixtures/undef*.js"); + }); + + it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { + eslint = new ESLint({ + cwd: getFixturePath(), + overrideConfigFile: "does-not-exist.js" + }); + await assert.rejects(() => eslint.lintFiles("undef*.js"), { code: "ENOENT" }); }); it("should throw an error when given a config file and a valid file and invalid parser", async () => { eslint = new ESLint({ overrideConfig: { - parser: "test11" + languageOptions: { + parser: "test11" + } }, - useEslintrc: false + overrideConfigFile: true }); - await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Cannot find module 'test11'/u); + await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); }); describe("Invalid inputs", () => { [ - ["an empty string", ""], - ["an empty array", []], ["a string with a single space", " "], ["an array with one empty string", [""]], - ["an array with two empty strings", ["", ""]] - + ["an array with two empty strings", ["", ""]], + ["undefined", void 0] ].forEach(([name, value]) => { it(`should throw an error when passed ${name}`, async () => { eslint = new ESLint({ - useEslintrc: false + overrideConfigFile: true }); await assert.rejects(async () => await eslint.lintFiles(value), /'patterns' must be a non-empty string or an array of non-empty strings/u); }); + }); - if (value === "" || Array.isArray(value) && value.length === 0) { - it(`should not throw an error when passed ${name} and passOnNoPatterns: true`, async () => { - eslint = new ESLint({ - useEslintrc: false, - passOnNoPatterns: true - }); + }); + + describe("Normalized inputs", () => { - const results = await eslint.lintFiles(value); + [ + ["an empty string", ""], + ["an empty array", []] + + ].forEach(([name, value]) => { - assert.strictEqual(results.length, 0); + it(`should normalize to '.' when ${name} is passed`, async () => { + eslint = new ESLint({ + ignore: false, + cwd: getFixturePath("files"), + overrideConfig: { files: ["**/*.js"] }, + overrideConfigFile: getFixturePath("eslint.config.js") }); - } + const results = await eslint.lintFiles(value); - }); + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].filePath, getFixturePath("files/.bar.js")); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("files/foo.js")); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it(`should return an empty array when ${name} is passed with passOnNoPatterns: true`, async () => { + eslint = new ESLint({ + ignore: false, + cwd: getFixturePath("files"), + overrideConfig: { files: ["**/*.js"] }, + overrideConfigFile: getFixturePath("eslint.config.js"), + passOnNoPatterns: true + }); + const results = await eslint.lintFiles(value); + assert.strictEqual(results.length, 0); + }); + }); }); it("should report zero messages when given a directory with a .js2 file", async () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - extensions: [".js2"] + overrideConfigFile: getFixturePath("eslint.config.js"), + overrideConfig: { + files: ["**/*.js2"] + } }); const results = await eslint.lintFiles([getFixturePath("files/foo.js2")]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); - }); - - it("should fall back to defaults when extensions is set to an empty array", async () => { - eslint = new ESLint({ - cwd: getFixturePath("configurations"), - overrideConfigFile: getFixturePath("configurations", "quotes-error.json"), - extensions: [] - }); - const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should report zero messages when given a directory with a .js and a .js2 file", async () => { eslint = new ESLint({ - extensions: [".js", ".js2"], ignore: false, - cwd: getFixturePath("..") + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js") }); const results = await eslint.lintFiles(["fixtures/files/"]); - assert.strictEqual(results.length, 2); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { + // https://github.com/eslint/eslint/issues/16413 + it("should find files and report zero messages when given a parent directory with a .js", async () => { eslint = new ESLint({ - extensions: [".js", ".js2"], ignore: false, - cwd: path.join(fixtureDir, "..") + cwd: getFixturePath("example-app/subdir") }); - const results = await eslint.lintFiles(["fixtures/files/*"]); + const results = await eslint.lintFiles(["../*.js"]); assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); }); - it("should resolve globs when 'globInputPaths' option is true", async () => { + // https://github.com/eslint/eslint/issues/16038 + it("should allow files patterns with '..' inside", async () => { eslint = new ESLint({ - extensions: [".js", ".js2"], ignore: false, - cwd: getFixturePath("..") + cwd: getFixturePath("dots-in-files") }); - const results = await eslint.lintFiles(["fixtures/files/*"]); + const results = await eslint.lintFiles(["."]); assert.strictEqual(results.length, 2); assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dots-in-files/a..b.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("should not resolve globs when 'globInputPaths' option is false", async () => { + + // https://github.com/eslint/eslint/issues/16299 + it("should only find files in the subdir1 directory when given a directory name", async () => { eslint = new ESLint({ - extensions: [".js", ".js2"], ignore: false, - cwd: getFixturePath(".."), - globInputPaths: false + cwd: getFixturePath("example-app2") }); + const results = await eslint.lintFiles(["subdir1"]); - await assert.rejects(async () => { - await eslint.lintFiles(["fixtures/files/*"]); - }, /No files matching 'fixtures\/files\/\*' were found \(glob was disabled\)\./u); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("example-app2/subdir1/a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("should report on all files passed explicitly, even if ignored by default", async () => { + // https://github.com/eslint/eslint/issues/14742 + it("should run", async () => { eslint = new ESLint({ - cwd: getFixturePath("cli-engine") + cwd: getFixturePath("{curly-path}", "server") }); - const results = await eslint.lintFiles(["node_modules/foo.js"]); - const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + const results = await eslint.lintFiles(["src/**/*.{js,json}"]); assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual( + results[0].filePath, + getFixturePath("{curly-path}/server/src/two.js") + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("should report on globs with explicit inclusion of dotfiles, even though ignored by default", async () => { + it("should work with config file that exports a promise", async () => { eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } + cwd: getFixturePath("promise-config") }); - const results = await eslint.lintFiles(["hidden/.hiddenfolder/*.js"]); + const results = await eslint.lintFiles(["a*.js"]); assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].filePath, getFixturePath("promise-config", "a.js")); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); }); - it("should not check default ignored files without --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine") - }); + // https://github.com/eslint/eslint/issues/16265 + describe("Dot files in searches", () => { - await assert.rejects(async () => { - await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules' are ignored\./u); - }); + it("should find dot files in current directory when a . pattern is used", async () => { + eslint = new ESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles(["."]); - // https://github.com/eslint/eslint/issues/5547 - it("should not check node_modules files even with --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - ignore: false + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); - await assert.rejects(async () => { - await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules' are ignored\./u); - }); + it("should find dot files in current directory when a *.js pattern is used", async () => { + eslint = new ESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles(["*.js"]); - it("should not check .hidden files if they are passed explicitly without --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath(".."), - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); - const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); - const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - }); + it("should find dot files in current directory when a .a.js pattern is used", async () => { + eslint = new ESLint({ + cwd: getFixturePath("dot-files") + }); + const results = await eslint.lintFiles([".a.js"]); - // https://github.com/eslint/eslint/issues/12873 - it("should not check files within a .hidden folder if they are passed explicitly without the --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - const results = await eslint.lintFiles(["hidden/.hiddenfolder/double-quotes.js"]); - const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].message, expectedMsg); }); - it("should check .hidden files if they are passed explicitly with --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath(".."), - ignore: false, - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } - }); - const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); + // https://github.com/eslint/eslint/issues/16275 + describe("Glob patterns without matches", () => { - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - }); + it("should throw an error for a missing pattern when combined with a found pattern", async () => { + eslint = new ESLint({ + ignore: false, + cwd: getFixturePath("example-app2") + }); - it("should check .hidden files if they are unignored with an --ignore-pattern", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - ignore: true, - useEslintrc: false, - overrideConfig: { - ignorePatterns: "!.hidden*", - rules: { - quotes: [2, "single"] - } - } + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1", "doesnotexist/*.js"]); + }, /No files matching 'doesnotexist\/\*\.js' were found/u); }); - const results = await eslint.lintFiles(["hidden/"]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - }); + it("should throw an error for an ignored directory pattern when combined with a found pattern", async () => { + eslint = new ESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2"] + } + }); - it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { - eslint = new ESLint({ - extensions: [".js", ".js2"], - ignore: false, - cwd: path.join(fixtureDir, "..") + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir2\/\*\.js' are ignored/u); }); - const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - }); + it("should throw an error for an ignored file pattern when combined with a found pattern", async () => { + eslint = new ESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2/*.js"] + } + }); - it("should return one error message when given a config with rules with options and severity level set to error", async () => { - eslint = new ESLint({ - cwd: getFixturePath("configurations"), - overrideConfigFile: getFixturePath("configurations", "quotes-error.json") + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir2\/\*\.js' are ignored/u); }); - const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); + it("should always throw an error for the first unmatched file pattern", async () => { + eslint = new ESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir1/*.js", "subdir2/*.js"] + } + }); - it("should return 3 messages when given a config file and a directory of 3 valid files", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "semi-error.json") + await assert.rejects(async () => { + await eslint.lintFiles(["doesnotexist1/*.js", "doesnotexist2/*.js"]); + }, /No files matching 'doesnotexist1\/\*\.js' were found/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["doesnotexist1/*.js", "subdir1/*.js"]); + }, /No files matching 'doesnotexist1\/\*\.js' were found/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "doesnotexist1/*.js"]); + }, /All files matched by 'subdir1\/\*\.js' are ignored/u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); + }, /All files matched by 'subdir1\/\*\.js' are ignored/u); }); - const fixturePath = getFixturePath("formatters"); - const results = await eslint.lintFiles([fixturePath]); - assert.strictEqual(results.length, 5); - assert.strictEqual(path.relative(fixturePath, results[0].filePath), "async.js"); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(path.relative(fixturePath, results[1].filePath), "broken.js"); - assert.strictEqual(results[1].errorCount, 0); - assert.strictEqual(results[1].warningCount, 0); - assert.strictEqual(results[1].fixableErrorCount, 0); - assert.strictEqual(results[1].fixableWarningCount, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(path.relative(fixturePath, results[2].filePath), "cwd.js"); - assert.strictEqual(results[2].errorCount, 0); - assert.strictEqual(results[2].warningCount, 0); - assert.strictEqual(results[2].fixableErrorCount, 0); - assert.strictEqual(results[2].fixableWarningCount, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(path.relative(fixturePath, results[3].filePath), "simple.js"); - assert.strictEqual(results[3].errorCount, 0); - assert.strictEqual(results[3].warningCount, 0); - assert.strictEqual(results[3].fixableErrorCount, 0); - assert.strictEqual(results[3].fixableWarningCount, 0); - assert.strictEqual(results[3].messages.length, 0); - assert.strictEqual(path.relative(fixturePath, results[4].filePath), path.join("test", "simple.js")); - assert.strictEqual(results[4].errorCount, 0); - assert.strictEqual(results[4].warningCount, 0); - assert.strictEqual(results[4].fixableErrorCount, 0); - assert.strictEqual(results[4].fixableWarningCount, 0); - assert.strictEqual(results[4].messages.length, 0); - }); + it("should not throw an error for an ignored file pattern when errorOnUnmatchedPattern is false", async () => { + eslint = new ESLint({ + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2/*.js"] + }, + errorOnUnmatchedPattern: false + }); - it("should process when file is given by not specifying extensions", async () => { - eslint = new ESLint({ - ignore: false, - cwd: path.join(fixtureDir, "..") + const results = await eslint.lintFiles(["subdir2/*.js"]); + + assert.strictEqual(results.length, 0); }); - const results = await eslint.lintFiles(["fixtures/files/foo.js2"]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); + it("should not throw an error for a non-existing file pattern when errorOnUnmatchedPattern is false", async () => { + eslint = new ESLint({ + cwd: getFixturePath("example-app2"), + errorOnUnmatchedPattern: false + }); + + const results = await eslint.lintFiles(["doesexist/*.js"]); + + assert.strictEqual(results.length, 0); + }); }); - it("should return zero messages when given a config with environment set to browser", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "env-browser.json") + // https://github.com/eslint/eslint/issues/16260 + describe("Globbing based on configs", () => { + it("should report zero messages when given a directory with a .js and config file specifying a subdirectory", async () => { + eslint = new ESLint({ + ignore: false, + cwd: getFixturePath("shallow-glob") + }); + const results = await eslint.lintFiles(["target-dir"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); + it("should glob for .jsx file in a subdirectory of the passed-in directory and not glob for any other patterns", async () => { + eslint = new ESLint({ + ignore: false, + overrideConfigFile: true, + overrideConfig: { + files: ["subdir/**/*.jsx", "target-dir/*.js"], + languageOptions: { + parserOptions: { + jsx: true + } + } + }, + cwd: getFixturePath("shallow-glob") + }); + const results = await eslint.lintFiles(["subdir/subsubdir"]); - it("should return zero messages when given an option to set environment to browser", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfig: { - env: { browser: true }, - rules: { - "no-alert": 0, - "no-undef": 2 - } - } + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("shallow-glob/subdir/subsubdir/broken.js")); + assert(results[0].messages[0].fatal, "Fatal error expected."); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].filePath, getFixturePath("shallow-glob/subdir/subsubdir/plain.jsx")); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); + it("should glob for all files in subdir when passed-in on the command line with a partial matching glob", async () => { + eslint = new ESLint({ + ignore: false, + overrideConfigFile: true, + overrideConfig: { + files: ["s*/subsubdir/*.jsx", "target-dir/*.js"], + languageOptions: { + parserOptions: { + jsx: true + } + } + }, + cwd: getFixturePath("shallow-glob") + }); + const results = await eslint.lintFiles(["subdir"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 1); + assert(results[0].messages[0].fatal, "Fatal error expected."); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 1); + assert(results[0].messages[0].fatal, "Fatal error expected."); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); }); - it("should return zero messages when given a config with environment set to Node.js", async () => { + it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { eslint = new ESLint({ + ignore: false, cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "env-node.json") + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js") + }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-node.js"))]); + const results = await eslint.lintFiles(["fixtures/files/*"]); - assert.strictEqual(results.length, 1); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); - it("should not return results from previous call when calling more than once", async () => { + it("should resolve globs when 'globInputPaths' option is true", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), ignore: false, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - const failFilePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); - const passFilePath = fs.realpathSync(getFixturePath("passing.js")); - - let results = await eslint.lintFiles([failFilePath]); + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js") - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, failFilePath); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].severity, 2); + }); + const results = await eslint.lintFiles(["fixtures/files/*"]); - results = await eslint.lintFiles([passFilePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, passFilePath); + assert.strictEqual(results.length, 3); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); - it("should throw an error when given a directory with all eslint excluded files in the directory", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore") - }); + // only works on a Windows machine + if (os.platform() === "win32") { - await assert.rejects(async () => { - await eslint.lintFiles([getFixturePath("./cli-engine/")]); - }, new RegExp(escapeStringRegExp(`All files matched by '${getFixturePath("./cli-engine/")}' are ignored.`), "u")); - }); + it("should resolve globs with Windows slashes when 'globInputPaths' option is true", async () => { + eslint = new ESLint({ + ignore: false, + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js") - it("should throw an error when all given files are ignored", async () => { - eslint = new ESLint({ - useEslintrc: false, - ignorePath: getFixturePath(".eslintignore") - }); - await assert.rejects(async () => { - await eslint.lintFiles(["tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); - }); + }); + const results = await eslint.lintFiles(["fixtures\\files\\*"]); - it("should throw an error when all given files are ignored even with a `./` prefix", async () => { - eslint = new ESLint({ - useEslintrc: false, - ignorePath: getFixturePath(".eslintignore") + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); }); - await assert.rejects(async () => { - await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - // https://github.com/eslint/eslint/issues/3788 - it("should ignore one-level down node_modules when ignore file has 'node_modules/' in it", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath("cli-engine", "nested_node_modules", ".eslintignore"), - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "double"] - } - }, - cwd: getFixturePath("cli-engine", "nested_node_modules") - }); - const results = await eslint.lintFiles(["."]); + } - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - // https://github.com/eslint/eslint/issues/3812 - it("should ignore all files and throw an error when tests/fixtures/ is in ignore file", async () => { + it("should not resolve globs when 'globInputPaths' option is false", async () => { eslint = new ESLint({ - ignorePath: getFixturePath("cli-engine/.eslintignore2"), - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "double"] - } - } + ignore: false, + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: true, + globInputPaths: false }); await assert.rejects(async () => { - await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); + await eslint.lintFiles(["fixtures/files/*"]); + }, /No files matching 'fixtures\/files\/\*' were found \(glob was disabled\)\./u); }); - // https://github.com/eslint/eslint/issues/15642 - it("should ignore files that are ignored by patterns with escaped brackets", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithEscapedBrackets"), - useEslintrc: false, - cwd: getFixturePath("ignored-paths") - }); + describe("Ignoring Files", () => { - // Only `brackets/index.js` should be linted. Other files in `brackets/` should be ignored. - const results = await eslint.lintFiles(["brackets/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignored-paths", "brackets", "index.js")); - }); + it("should report on a file in the node_modules folder passed explicitly, even if ignored by default", async () => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine") + }); + const results = await eslint.lintFiles(["node_modules/foo.js"]); + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - it("should throw an error when all given files are ignored via ignore-pattern", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - ignorePatterns: "tests/fixtures/single-quoted.js" - } + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - await assert.rejects(async () => { - await eslint.lintFiles(["tests/fixtures/*-quoted.js"]); - }, /All files matched by 'tests\/fixtures\/\*-quoted\.js' are ignored\./u); - }); + it("should report on a file in a node_modules subfolder passed explicitly, even if ignored by default", async () => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine") + }); + const results = await eslint.lintFiles(["nested_node_modules/subdir/node_modules/text.js"]); + const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - it("should not throw an error when ignorePatterns is an empty array", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - ignorePatterns: [] - } + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - await assert.doesNotReject(async () => { - await eslint.lintFiles(["*.js"]); - }); - }); + it("should report on an ignored file with \"node_modules\" in its name", async () => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + ignorePatterns: ["*.js"] + }); + const results = await eslint.lintFiles(["node_modules_cleaner.js"]); + const expectedMsg = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - it("should return a warning when an explicitly given file is ignored", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - cwd: getFixturePath() + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - const filePath = getFixturePath("passing.js"); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); + it("should suppress the warning when a file in the node_modules folder passed explicitly and warnIgnored is false", async () => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + warnIgnored: false + }); + const results = await eslint.lintFiles(["node_modules/foo.js"]); - it("should return two messages when given a file in excluded files list while ignore is off", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - ignore: false, - overrideConfig: { - rules: { - "no-undef": 2 - } - } + assert.strictEqual(results.length, 0); }); - const filePath = fs.realpathSync(getFixturePath("undef.js")); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[1].severity, 2); - }); + it("should report on globs with explicit inclusion of dotfiles", async () => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + overrideConfigFile: true, + overrideConfig: { + rules: { + quotes: [2, "single"] + } + } + }); + const results = await eslint.lintFiles(["hidden/.hiddenfolder/*.js"]); - it("should return zero messages when executing a file with a shebang", async () => { - eslint = new ESLint({ - ignore: false + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); }); - const results = await eslint.lintFiles([getFixturePath("shebang.js")]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); + it("should ignore node_modules files when using ignore file", async () => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + overrideConfigFile: true + }); - it("should give a warning when loading a custom rule that doesn't exist", async () => { - eslint = new ESLint({ - ignore: false, - rulePaths: [getFixturePath("rules", "dir1")], - overrideConfigFile: getFixturePath("rules", "missing-rule.json") + await assert.rejects(async () => { + await eslint.lintFiles(["node_modules"]); + }, /All files matched by 'node_modules' are ignored\./u); }); - const results = await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "missing-rule"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].message, "Definition for rule 'missing-rule' was not found."); - }); + // https://github.com/eslint/eslint/issues/5547 + it("should ignore node_modules files even with ignore: false", async () => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + ignore: false + }); - it("should throw an error when loading a bad custom rule", async () => { - eslint = new ESLint({ - ignore: false, - rulePaths: [getFixturePath("rules", "wrong")], - overrideConfigFile: getFixturePath("rules", "eslint.json") + await assert.rejects(async () => { + await eslint.lintFiles(["node_modules"]); + }, /All files matched by 'node_modules' are ignored\./u); }); + it("should throw an error when all given files are ignored", async () => { + eslint = new ESLint({ + overrideConfigFile: getFixturePath("eslint.config-with-ignores.js") + }); - await assert.rejects(async () => { - await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); - }, /Error while loading rule 'custom-rule'/u); - }); - - it("should throw an error when loading a function-style custom rule", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - rulePaths: [getFixturePath("rules", "function-style")], - overrideConfig: { - rules: { - "no-strings": "error" - } - } + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/cli-engine/"]); + }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); }); - await assert.rejects(async () => { - await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); - }, /Error while loading rule 'no-strings': Rule must be an object with a `create` method/u); - }); + it("should throw an error when all given files are ignored even with a `./` prefix", async () => { + eslint = new ESLint({ + overrideConfigFile: getFixturePath("eslint.config-with-ignores.js") + }); - it("should return one message when a custom rule matches a file", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - rulePaths: [getFixturePath("rules/")], - overrideConfigFile: getFixturePath("rules", "eslint.json") + await assert.rejects(async () => { + await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); }); - const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - - it("should load custom rule from the provided cwd", async () => { - const cwd = path.resolve(getFixturePath("rules")); + // https://github.com/eslint/eslint/issues/3788 + it("should ignore one-level down node_modules by default", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: { + rules: { + quotes: [2, "double"] + } + }, + cwd: getFixturePath("cli-engine", "nested_node_modules") + }); + const results = await eslint.lintFiles(["."]); - eslint = new ESLint({ - ignore: false, - cwd, - rulePaths: ["./"], - overrideConfigFile: "eslint.json" + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); }); - const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); + // https://github.com/eslint/eslint/issues/3812 + it("should ignore all files and throw an error when **/fixtures/** is in `ignores` in the config file", async () => { + eslint = new ESLint({ + overrideConfigFile: getFixturePath("cli-engine/eslint.config-with-ignores2.js"), + overrideConfig: { + rules: { + quotes: [2, "double"] + } + } + }); - it("should return messages when multiple custom rules match a file", async () => { - eslint = new ESLint({ - ignore: false, - rulePaths: [ - getFixturePath("rules", "dir1"), - getFixturePath("rules", "dir2") - ], - overrideConfigFile: getFixturePath("rules", "multi-rulesdirs.json") + await assert.rejects(async () => { + await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); }); - const filePath = fs.realpathSync(getFixturePath("rules", "test-multi-rulesdirs.js")); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "no-literals"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-strings"); - assert.strictEqual(results[0].messages[1].severity, 2); - }); + it("should throw an error when all given files are ignored via ignorePatterns", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + ignorePatterns: ["tests/fixtures/single-quoted.js"] + }); - it("should return zero messages when executing without useEslintrc flag", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/*-quoted.js"]); + }, /All files matched by 'tests\/fixtures\/\*-quoted\.js' are ignored\./u); }); - const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - }); + it("should not throw an error when ignorePatterns is an empty array", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + ignorePatterns: [] + }); - it("should return zero messages when executing without useEslintrc flag in Node.js environment", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - overrideConfig: { - env: { node: true } - } + await assert.doesNotReject(async () => { + await eslint.lintFiles(["*.js"]); + }); }); - const filePath = fs.realpathSync(getFixturePath("process-exit.js")); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - }); - it("should return zero messages and ignore .eslintrc files when executing with no-eslintrc flag", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - overrideConfig: { - env: { node: true } - } + it("should return a warning when an explicitly given file is ignored", async () => { + eslint = new ESLint({ + overrideConfigFile: "eslint.config-with-ignores.js", + cwd: getFixturePath() + }); + const filePath = getFixturePath("passing.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - const filePath = fs.realpathSync(getFixturePath("eslintrc", "quotes.js")); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - }); + it("should suppress the warning when an explicitly given file is ignored and warnIgnored is false", async () => { + eslint = new ESLint({ + overrideConfigFile: "eslint.config-with-ignores.js", + cwd: getFixturePath(), + warnIgnored: false + }); + const filePath = getFixturePath("passing.js"); + const results = await eslint.lintFiles([filePath]); - it("should return zero messages and ignore package.json files when executing with no-eslintrc flag", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - overrideConfig: { - env: { node: true } - } + assert.strictEqual(results.length, 0); }); - const filePath = fs.realpathSync(getFixturePath("packagejson", "quotes.js")); - const results = await eslint.lintFiles([filePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - }); + it("should return a warning about matching ignore patterns when an explicitly given dotfile is ignored", async () => { + eslint = new ESLint({ + overrideConfigFile: "eslint.config-with-ignores.js", + cwd: getFixturePath() + }); + const filePath = getFixturePath("dot-files/.a.js"); + const results = await eslint.lintFiles([filePath]); - it("should warn when deprecated rules are configured", async () => { - eslint = new ESLint({ - cwd: originalDir, - useEslintrc: false, - overrideConfig: { - rules: { - "indent-legacy": 1, - "callback-return": 1 + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return two messages when given a file in excluded files list while ignore is off", async () => { + eslint = new ESLint({ + cwd: getFixturePath(), + ignore: false, + overrideConfigFile: getFixturePath("eslint.config-with-ignores.js"), + overrideConfig: { + rules: { + "no-undef": 2 + } } - } + }); + const filePath = fs.realpathSync(getFixturePath("undef.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[1].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - assert.deepStrictEqual( - results[0].usedDeprecatedRules, - [ - { ruleId: "indent-legacy", replacedBy: ["indent"] }, - { ruleId: "callback-return", replacedBy: [] } - ] - ); - }); + // https://github.com/eslint/eslint/issues/16300 + it("should process ignore patterns relative to basePath not cwd", async () => { + eslint = new ESLint({ + cwd: getFixturePath("ignores-relative/subdir") + }); + const results = await eslint.lintFiles(["**/*.js"]); - it("should not warn when deprecated rules are not configured", async () => { - eslint = new ESLint({ - cwd: originalDir, - useEslintrc: false, - overrideConfig: { - rules: { eqeqeq: 1, "callback-return": 0 } - } + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/subdir/a.js")); }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - assert.deepStrictEqual(results[0].usedDeprecatedRules, []); - }); + // https://github.com/eslint/eslint/issues/16354 + it("should skip subdirectory files when ignore pattern matches deep subdirectory", async () => { + eslint = new ESLint({ + cwd: getFixturePath("ignores-directory") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/**"]); + }, /All files matched by 'subdir\/\*\*' are ignored\./u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/subsubdir/**"]); + }, /All files matched by 'subdir\/subsubdir\/\*\*' are ignored\./u); + + const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); + assert.strictEqual(results[0].warningCount, 1); + assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); - it("should warn when deprecated rules are found in a config", async () => { - eslint = new ESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", - useEslintrc: false }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - assert.deepStrictEqual( - results[0].usedDeprecatedRules, - [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] - ); - }); + // https://github.com/eslint/eslint/issues/16414 + it("should skip subdirectory files when ignore pattern matches subdirectory", async () => { + eslint = new ESLint({ + cwd: getFixturePath("ignores-subdirectory") + }); - describe("Fix Mode", () => { - it("should return fixed text on multiple files when in fix mode", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/**/*.js"]); + }, /All files matched by 'subdir\/\*\*\/\*\.js' are ignored\./u); - /** - * Converts CRLF to LF in output. - * This is a workaround for git's autocrlf option on Windows. - * @param {Object} result A result object to convert. - * @returns {void} - */ - function convertCRLF(result) { - if (result && result.output) { - result.output = result.output.replace(/\r\n/gu, "\n"); - } - } + const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-subdirectory/subdir/subsubdir/a.js")); + assert.strictEqual(results[0].warningCount, 1); + assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"], - eqeqeq: 2, - "no-undef": 2, - "space-infix-ops": 2 - } - } + cwd: getFixturePath("ignores-subdirectory/subdir") }); - const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - results.forEach(convertCRLF); - assert.deepStrictEqual(results, [ - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "true ? \"yes\" : \"no\";\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), - messages: [ - { - column: 9, - line: 2, - endColumn: 11, - endLine: 2, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), - messages: [ - { - column: 18, - line: 1, - endColumn: 21, - endLine: 1, - messageId: "undef", - message: "'foo' is not defined.", - nodeType: "Identifier", - ruleId: "no-undef", - severity: 2 - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\" + foo;\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - } - ]); - }); - - it("should run autofix even if files are cached without autofix results", async () => { - const baseOptions = { - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"], - eqeqeq: 2, - "no-undef": 2, - "space-infix-ops": 2 - } - } - }; - - eslint = new ESLint(Object.assign({}, baseOptions, { cache: true, fix: false })); - - // Do initial lint run and populate the cache file - await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - eslint = new ESLint(Object.assign({}, baseOptions, { cache: true, fix: true })); - const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - assert(results.some(result => result.output)); - }); - }); - - // These tests have to do with https://github.com/eslint/eslint/issues/963 - - describe("configuration hierarchy", () => { - - // Default configuration - blank - it("should return zero messages when executing with no .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // No default configuration rules - conf/environments.js (/*eslint-env node*/) - it("should return zero messages when executing with no .eslintrc in the Node.js environment", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes-node.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Project configuration - first level .eslintrc - it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Project configuration - first level .eslintrc - it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Project configuration - first level .eslintrc - it("should return one message when executing with .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - }); + await assert.rejects(async () => { + await eslint.lintFiles(["subsubdir/**/*.js"]); + }, /All files matched by 'subsubdir\/\*\*\/\*\.js' are ignored\./u); - // Project configuration - second level .eslintrc - it("should return one message when executing with local .eslintrc that overrides parent .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-console"); - assert.strictEqual(results[0].messages[0].severity, 1); }); - // Project configuration - third level .eslintrc - it("should return one message when executing with local .eslintrc that overrides parent and grandparent .eslintrc", async () => { + // https://github.com/eslint/eslint/issues/16340 + it("should lint files even when cwd directory name matches ignores pattern", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") + cwd: getFixturePath("ignores-self") }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/subsubbroken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - // Project configuration - first level package.json - it("should return one message when executing with package.json", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/wrong-quotes.js`)]); + const results = await eslint.lintFiles(["*.js"]); assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-self/eslint.config.js")); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); - // Project configuration - second level package.json - it("should return zero messages when executing with local package.json that overrides parent package.json", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); }); - // Project configuration - third level package.json - it("should return one message when executing with local package.json that overrides parent and grandparent package.json", async () => { + // https://github.com/eslint/eslint/issues/16416 + it("should allow reignoring of previously ignored files", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - }); - - // Project configuration - .eslintrc overrides package.json in same directory - it("should return one message when executing with .eslintrc that overrides a package.json in the same directory", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") + cwd: getFixturePath("ignores-relative"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + "*.js", + "!a*.js", + "a.js" + ] + } }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/wrong-quotes.js`)]); + const results = await eslint.lintFiles(["a.js"]); assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/a.js")); }); - // Command line configuration - --config with first level .eslintrc - it("should return two messages when executing with config file that adds to local .eslintrc", async () => { + // https://github.com/eslint/eslint/issues/16415 + it("should allow directories to be unignored", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` + cwd: getFixturePath("ignores-directory"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + "subdir/*", + "!subdir/subsubdir" + ] + } }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + const results = await eslint.lintFiles(["subdir/**/*.js"]); assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); - assert.strictEqual(results[0].messages[1].severity, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); }); - // Command line configuration - --config with first level .eslintrc - it("should return no messages when executing with config file that overrides local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Command line configuration - --config with second level .eslintrc - it("should return two messages when executing with config file that adds to local and parent .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); + }); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "no-console"); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); - assert.strictEqual(results[0].messages[1].severity, 1); - }); - // Command line configuration - --config with second level .eslintrc - it("should return one message when executing with config file that overrides local and parent .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("config-hierarchy/broken/override-conf.yaml") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-console"); - assert.strictEqual(results[0].messages[0].severity, 1); + it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { + eslint = new ESLint({ + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + ignore: false, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true }); + const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); - // Command line configuration - --config with first level .eslintrc - it("should return no messages when executing with config file that overrides local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); - // Command line configuration - --rule with --config and first level .eslintrc - it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("config-hierarchy/broken/override-conf.yaml"), - overrideConfig: { - rules: { - quotes: [1, "double"] - } + it("should return one error message when given a config with rules with options and severity level set to error", async () => { + eslint = new ESLint({ + cwd: getFixturePath(), + overrideConfigFile: true, + overrideConfig: { + rules: { + quotes: ["error", "double"] } - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 1); + }, + ignore: false }); + const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); - // Command line configuration - --rule with --config and first level .eslintrc - it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("/config-hierarchy/broken/override-conf.yaml"), - overrideConfig: { - rules: { - quotes: [1, "double"] - } - } - }); - const results = await eslint.lintFiles([getFixturePath("config-hierarchy/broken/console-wrong-quotes.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - describe("plugins", () => { - it("should return two messages when executing with config file that specifies a plugin", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix.json"), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test/test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + it("should return 5 results when given a config and a directory of 5 valid files", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 1, + strict: 0 + } + } }); - it("should return two messages when executing with config file that specifies a plugin with namespace", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix-and-namespace.json"), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + const formattersDir = getFixturePath("formatters"); + const results = await eslint.lintFiles([formattersDir]); + + assert.strictEqual(results.length, 5); + assert.strictEqual(path.relative(formattersDir, results[0].filePath), "async.js"); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(path.relative(formattersDir, results[1].filePath), "broken.js"); + assert.strictEqual(results[1].errorCount, 0); + assert.strictEqual(results[1].warningCount, 0); + assert.strictEqual(results[1].fatalErrorCount, 0); + assert.strictEqual(results[1].fixableErrorCount, 0); + assert.strictEqual(results[1].fixableWarningCount, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(path.relative(formattersDir, results[2].filePath), "cwd.js"); + assert.strictEqual(results[2].errorCount, 0); + assert.strictEqual(results[2].warningCount, 0); + assert.strictEqual(results[2].fatalErrorCount, 0); + assert.strictEqual(results[2].fixableErrorCount, 0); + assert.strictEqual(results[2].fixableWarningCount, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + assert.strictEqual(path.relative(formattersDir, results[3].filePath), "simple.js"); + assert.strictEqual(results[3].errorCount, 0); + assert.strictEqual(results[3].warningCount, 0); + assert.strictEqual(results[3].fatalErrorCount, 0); + assert.strictEqual(results[3].fixableErrorCount, 0); + assert.strictEqual(results[3].fixableWarningCount, 0); + assert.strictEqual(results[3].messages.length, 0); + assert.strictEqual(results[3].suppressedMessages.length, 0); + assert.strictEqual(path.relative(formattersDir, results[4].filePath), path.join("test", "simple.js")); + assert.strictEqual(results[4].errorCount, 0); + assert.strictEqual(results[4].warningCount, 0); + assert.strictEqual(results[4].fatalErrorCount, 0); + assert.strictEqual(results[4].fixableErrorCount, 0); + assert.strictEqual(results[4].fixableWarningCount, 0); + assert.strictEqual(results[4].messages.length, 0); + assert.strictEqual(results[4].suppressedMessages.length, 0); + }); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "@eslint/example/example-rule"); + it("should return zero messages when given a config with browser globals", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "env-browser.js") }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - it("should return two messages when executing with config file that specifies a plugin without prefix", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-without-prefix.json"), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0, "Should have no messages."); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + it("should return zero messages when given an option to add browser globals", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + languageOptions: { + globals: { + window: false + } + }, + rules: { + "no-alert": 0, + "no-undef": 2 + } + } }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - it("should return two messages when executing with config file that specifies a plugin without prefix and with namespace", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-without-prefix-with-namespace.json"), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "@eslint/example/example-rule"); + it("should return zero messages when given a config with sourceType set to commonjs and Node.js globals", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "env-node.js") }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-node.js"))]); - it("should return two messages when executing with cli option that specifies a plugin", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - plugins: ["example"], - rules: { "example/example-rule": 1 } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); - }); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0, "Should have no messages."); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); - it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - plugins: ["test"], - rules: { "test/example-rule": 1 } - }, - plugins: { - "eslint-plugin-test": { rules: { "example-rule": require("../../fixtures/rules/custom-rule") } } + it("should not return results from previous call when calling more than once", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("eslint.config.js"), + ignore: false, + overrideConfig: { + rules: { + semi: 2 } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); + } }); + const failFilePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); + const passFilePath = fs.realpathSync(getFixturePath("passing.js")); - it("should throw an error when executing with a function-style rule from a preloaded plugin", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - plugins: ["test"], - rules: { "test/example-rule": 1 } - }, - plugins: { - "eslint-plugin-test": { rules: { "example-rule": () => ({}) } } - } - }); + let results = await eslint.lintFiles([failFilePath]); - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Error while loading rule 'test\/example-rule': Rule must be an object with a `create` method/u); - }); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, failFilePath); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[0].messages[0].severity, 2); - it("should return two messages when executing with `baseConfig` that extends preloaded plugin config", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - baseConfig: { - extends: ["plugin:test/preset"] - }, - plugins: { - test: { - rules: { - "example-rule": require("../../fixtures/rules/custom-rule") - }, - configs: { - preset: { - rules: { - "test/example-rule": 1 - }, - plugins: ["test"] - } - } - } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + results = await eslint.lintFiles([passFilePath]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, passFilePath); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); + it("should return zero messages when executing a file with a shebang", async () => { + eslint = new ESLint({ + ignore: false, + cwd: getFixturePath(), + overrideConfigFile: getFixturePath("eslint.config.js") }); + const results = await eslint.lintFiles([getFixturePath("shebang.js")]); - it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", async () => { - eslint = new ESLint({ - resolvePluginsRelativeTo: getFixturePath("plugins"), - baseConfig: { - plugins: ["with-rules"], - rules: { "with-rules/rule1": "error" } - }, - useEslintrc: false - }); - const results = await eslint.lintText("foo"); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0, "Should have lint messages."); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "with-rules/rule1"); - assert.strictEqual(results[0].messages[0].message, "Rule report from plugin"); + it("should return zero messages when executing without a config file", async () => { + eslint = new ESLint({ + cwd: getFixturePath(), + ignore: false, + overrideConfigFile: true }); + const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); + const results = await eslint.lintFiles([filePath]); - it("should throw an error when executing with a function-style rule from a plugin", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["with-function-style-rules"], - rules: { "with-function-style-rules/rule1": "error" } - }, - useEslintrc: false - }); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Error while loading rule 'with-function-style-rules\/rule1': Rule must be an object with a `create` method/u); - }); + // working + describe("Deprecated Rules", () => { - it("should throw an error when executing with a rule with `schema:true` from a plugin", async () => { + it("should warn when deprecated rules are configured", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-true"], - rules: { "schema-true/rule1": "error" } - }, - useEslintrc: false + cwd: originalDir, + overrideConfigFile: true, + overrideConfig: { + rules: { + "indent-legacy": 1, + "callback-return": 1 + } + } }); + const results = await eslint.lintFiles(["lib/cli*.js"]); - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Error while processing options validation schema of rule 'schema-true\/rule1': Rule's `meta.schema` must be an array or object/u); + assert.deepStrictEqual( + results[0].usedDeprecatedRules, + [ + { ruleId: "indent-legacy", replacedBy: ["indent"] }, + { ruleId: "callback-return", replacedBy: [] } + ] + ); }); - it("should throw an error when executing with a rule with `schema:null` from a plugin", async () => { + it("should not warn when deprecated rules are not configured", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-null"], - rules: { "schema-null/rule1": "error" } - }, - useEslintrc: false + cwd: originalDir, + overrideConfigFile: true, + overrideConfig: { + rules: { eqeqeq: 1, "callback-return": 0 } + } }); + const results = await eslint.lintFiles(["lib/cli*.js"]); - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Error while processing options validation schema of rule 'schema-null\/rule1': Rule's `meta.schema` must be an array or object/u); + assert.deepStrictEqual(results[0].usedDeprecatedRules, []); }); - it("should throw an error when executing with a rule with invalid JSON schema type from a plugin", async () => { + it("should warn when deprecated rules are found in a config", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-invalid"], - rules: { "schema-invalid/rule1": "error" } - }, - useEslintrc: false + cwd: originalDir, + overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/eslint.config.js" }); + const results = await eslint.lintFiles(["lib/cli*.js"]); - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Error while processing options validation schema of rule 'schema-invalid\/rule1': minItems must be number/u); + assert.deepStrictEqual( + results[0].usedDeprecatedRules, + [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] + ); }); + }); - it("should succesfully execute with a rule with `schema:false` from a plugin when no options were passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-false"], - rules: { "schema-false/rule1": "error" } - }, - useEslintrc: false - }); - - const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].ruleId, "schema-false/rule1"); - assert.strictEqual(result.messages[0].message, "No options were passed"); - }); + // working + describe("Fix Mode", () => { - it("should succesfully execute with a rule with `schema:false` from a plugin when an option is passed", async () => { + it("correctly autofixes semicolon-conflicting-fixes", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-false"], - rules: { "schema-false/rule1": ["error", "always"] } - }, - useEslintrc: false + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true }); + const inputPath = getFixturePath("autofix/semicolon-conflicting-fixes.js"); + const outputPath = getFixturePath("autofix/semicolon-conflicting-fixes.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); - const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].ruleId, "schema-false/rule1"); - assert.strictEqual(result.messages[0].message, "Option 'always' was passed"); + assert.strictEqual(results[0].output, expectedOutput); }); - it("should succesfully execute with a rule with `schema:[]` from a plugin when no options were passed", async () => { + it("correctly autofixes return-conflicting-fixes", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-empty-array"], - rules: { "schema-empty-array/rule1": "error" } - }, - useEslintrc: false + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true }); + const inputPath = getFixturePath("autofix/return-conflicting-fixes.js"); + const outputPath = getFixturePath("autofix/return-conflicting-fixes.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); - const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].ruleId, "schema-empty-array/rule1"); - assert.strictEqual(result.messages[0].message, "Hello"); + assert.strictEqual(results[0].output, expectedOutput); }); - it("should throw when executing with a rule with `schema:[]` from a plugin when an option is passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-empty-array"], - rules: { "schema-empty-array/rule1": ["error", "always"] } - }, - useEslintrc: false - }); + it("should return fixed text on multiple files when in fix mode", async () => { - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Configuration for rule "schema-empty-array\/rule1" is invalid.*should NOT have more than 0 items/us); - }); + /** + * Converts CRLF to LF in output. + * This is a workaround for git's autocrlf option on Windows. + * @param {Object} result A result object to convert. + * @returns {void} + */ + function convertCRLF(result) { + if (result && result.output) { + result.output = result.output.replace(/\r\n/gu, "\n"); + } + } - it("should succesfully execute with a rule with no schema from a plugin when no options were passed", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-missing"], - rules: { "schema-missing/rule1": "error" } - }, - useEslintrc: false + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2 + } + } }); + const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].ruleId, "schema-missing/rule1"); - assert.strictEqual(result.messages[0].message, "Hello"); - }); - - it("should throw when executing with a rule with no schema from a plugin when an option is passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-missing"], - rules: { "schema-missing/rule1": ["error", "always"] } + results.forEach(convertCRLF); + assert.deepStrictEqual(results, [ + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "true ? \"yes\" : \"no\";\n", + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] + }, + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] }, - useEslintrc: false - }); - - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Configuration for rule "schema-missing\/rule1" is invalid.*should NOT have more than 0 items/us); - }); - - it("should succesfully execute with a rule with an array schema from a plugin when no options were passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-array"], - rules: { "schema-array/rule1": "error" } + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), + messages: [ + { + column: 9, + line: 2, + endColumn: 11, + endLine: 2, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2 + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] }, - useEslintrc: false - }); - - const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].ruleId, "schema-array/rule1"); - assert.strictEqual(result.messages[0].message, "No options were passed"); + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), + messages: [ + { + column: 18, + line: 1, + endColumn: 21, + endLine: 1, + messageId: "undef", + message: "'foo' is not defined.", + nodeType: "Identifier", + ruleId: "no-undef", + severity: 2 + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var msg = \"hi\" + foo;\n", + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] + } + ]); }); - it("should succesfully execute with a rule with an array schema from a plugin when a correct option was passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-array"], - rules: { "schema-array/rule1": ["error", "always"] } - }, - useEslintrc: false - }); - - const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].ruleId, "schema-array/rule1"); - assert.strictEqual(result.messages[0].message, "Option 'always' was passed"); - }); + // Cannot be run properly until cache is implemented + it("should run autofix even if files are cached without autofix results", async () => { + const baseOptions = { + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2 + } + } + }; - it("should throw when executing with a rule with an array schema from a plugin when an incorrect option was passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-array"], - rules: { "schema-array/rule1": ["error", 5] } - }, - useEslintrc: false - }); + eslint = new ESLint(Object.assign({}, baseOptions, { cache: true, fix: false })); - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Configuration for rule "schema-array\/rule1" is invalid.*Value 5 should be string/us); - }); + // Do initial lint run and populate the cache file + await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - it("should throw when executing with a rule with an array schema from a plugin when an extra option was passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-array"], - rules: { "schema-array/rule1": ["error", "always", "never"] } - }, - useEslintrc: false - }); + eslint = new ESLint(Object.assign({}, baseOptions, { cache: true, fix: true })); + const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Configuration for rule "schema-array\/rule1" is invalid.*should NOT have more than 1 items/us); + assert(results.some(result => result.output)); }); + }); - it("should succesfully execute with a rule with an object schema from a plugin when no options were passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-object"], - rules: { "schema-object/rule1": "error" } - }, - useEslintrc: false + describe("plugins", () => { + it("should return two messages when executing with config file that specifies a plugin", async () => { + eslint = eslintWithPlugins({ + cwd: path.resolve(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix.js") }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test/test-custom-rule.js"))]); - const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2, "Expected two messages."); + assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].ruleId, "schema-object/rule1"); - assert.strictEqual(result.messages[0].message, "No options were passed"); }); - it("should succesfully execute with a rule with an object schema from a plugin when a correct option was passed", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-object"], - rules: { "schema-object/rule1": ["error", "always"] } - }, - useEslintrc: false + it("should return two messages when executing with cli option that specifies a plugin", async () => { + eslint = eslintWithPlugins({ + cwd: path.resolve(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { "example/example-rule": 1 } + } }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].ruleId, "schema-object/rule1"); - assert.strictEqual(result.messages[0].message, "Option 'always' was passed"); }); - it("should throw when executing with a rule with an object schema from a plugin when an incorrect option was passed", async () => { + it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { eslint = new ESLint({ - cwd: path.join(fixtureDir, "plugins"), - baseConfig: { - plugins: ["schema-object"], - rules: { "schema-object/rule1": ["error", 5] } + cwd: path.resolve(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { "test/example-rule": 1 } }, - useEslintrc: false + plugins: { + "eslint-plugin-test": { rules: { "example-rule": require("../../fixtures/rules/custom-rule") } } + } }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); + assert.strictEqual(results[0].suppressedMessages.length, 0); - await assert.rejects(async () => { - await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - }, /Configuration for rule "schema-object\/rule1" is invalid.*Value 5 should be string/us); }); }); @@ -2753,7 +2434,7 @@ describe("ESLint", () => { const cwd = getFixturePath(); /** - * helper method to delete the cache files created during testing + * helper method to delete the directory used in testing * @returns {void} */ function deleteCacheDir() { @@ -2779,7 +2460,7 @@ describe("ESLint", () => { assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, cwd, // specifying cache true the cache will be created @@ -2791,7 +2472,6 @@ describe("ESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); const file = getFixturePath("cache/src", "test-file.js"); @@ -2807,7 +2487,7 @@ describe("ESLint", () => { fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, cwd, // specifying cache true the cache will be created @@ -2834,7 +2514,7 @@ describe("ESLint", () => { fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, cwd, // specifying cache true the cache will be created @@ -2864,7 +2544,7 @@ describe("ESLint", () => { assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, cache: true, cwd, overrideConfig: { @@ -2872,7 +2552,6 @@ describe("ESLint", () => { "no-console": 0 } }, - extensions: ["js"], ignore: false }); const file = getFixturePath("cli-engine", "console.js"); @@ -2882,7 +2561,7 @@ describe("ESLint", () => { assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created at provided cwd"); }); - it("should invalidate the cache if the configuration changed between executions", async () => { + it("should invalidate the cache if the overrideConfig changed between executions", async () => { const cwd = getFixturePath("cache/src"); cacheFilePath = path.resolve(cwd, ".eslintcache"); @@ -2890,7 +2569,7 @@ describe("ESLint", () => { assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, cwd, // specifying cache true the cache will be created @@ -2901,13 +2580,12 @@ describe("ESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); - let spy = sinon.spy(fs, "readFileSync"); + let spy = sinon.spy(fs.promises, "readFile"); - let file = getFixturePath("cache/src", "test-file.js"); + let file = path.join(cwd, "test-file.js"); file = fs.realpathSync(file); const results = await eslint.lintFiles([file]); @@ -2915,6 +2593,7 @@ describe("ESLint", () => { for (const { errorCount, warningCount } of results) { assert.strictEqual(errorCount + warningCount, 0, "the file should have passed linting without errors or warnings"); } + assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); @@ -2922,7 +2601,7 @@ describe("ESLint", () => { sinon.restore(); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, cwd, // specifying cache true the cache will be created @@ -2933,12 +2612,11 @@ describe("ESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); // create a new spy - spy = sinon.spy(fs, "readFileSync"); + spy = sinon.spy(fs.promises, "readFile"); const [newResult] = await eslint.lintFiles([file]); @@ -2956,7 +2634,7 @@ describe("ESLint", () => { assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, cwd, // specifying cache true the cache will be created @@ -2967,11 +2645,10 @@ describe("ESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); - let spy = sinon.spy(fs, "readFileSync"); + let spy = sinon.spy(fs.promises, "readFile"); let file = getFixturePath("cache/src", "test-file.js"); @@ -2986,7 +2663,7 @@ describe("ESLint", () => { sinon.restore(); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, cwd, // specifying cache true the cache will be created @@ -2997,12 +2674,11 @@ describe("ESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], ignore: false }); // create a new spy - spy = sinon.spy(fs, "readFileSync"); + spy = sinon.spy(fs.promises, "readFile"); const cachedResult = await eslint.lintFiles([file]); @@ -3018,7 +2694,7 @@ describe("ESLint", () => { assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); const eslintOptions = { - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3029,7 +2705,6 @@ describe("ESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], cwd: path.join(fixtureDir, "..") }; @@ -3057,14 +2732,14 @@ describe("ESLint", () => { assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); // Simulate a read-only file system. - sinon.stub(fs, "unlinkSync").throws( + sinon.stub(fsp, "unlink").rejects( Object.assign(new Error("read-only file system"), { code: "EROFS" }) ); const eslintOptions = { - useEslintrc: false, + overrideConfigFile: true, - // specifying cache true the cache will be created + // specifying cache false the cache will be deleted cache: false, cacheLocation: cacheFilePath, overrideConfig: { @@ -3073,7 +2748,6 @@ describe("ESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], cwd: path.join(fixtureDir, "..") }; @@ -3083,7 +2757,7 @@ describe("ESLint", () => { await eslint.lintFiles([file]); - assert(fs.unlinkSync.calledWithExactly(cacheFilePath), "Expected attempt to delete the cache was not made."); + assert(fsp.unlink.calledWithExactly(cacheFilePath), "Expected attempt to delete the cache was not made."); }); it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { @@ -3093,7 +2767,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3103,8 +2777,7 @@ describe("ESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -3133,7 +2806,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3143,8 +2816,7 @@ describe("ESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -3181,7 +2853,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3191,8 +2863,7 @@ describe("ESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -3206,7 +2877,7 @@ describe("ESLint", () => { assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); /* - * we pass a different set of files minus test-file2 + * we pass a different set of files (minus test-file2) * previous version of file-entry-cache would remove the non visited * entries. 2.0.0 version will keep them unless they don't exist */ @@ -3227,15 +2898,14 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); @@ -3254,15 +2924,14 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); @@ -3281,7 +2950,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, cache: true, cacheLocation: cacheFilePath, overrideConfig: { @@ -3289,8 +2958,7 @@ describe("ESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const file = getFixturePath("cli-engine", "console.js"); @@ -3310,15 +2978,14 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, cacheLocation: cacheFilePath, overrideConfig: { rules: { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } }); const file = getFixturePath("cli-engine", "console.js"); @@ -3335,7 +3002,7 @@ describe("ESLint", () => { assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, // specify a custom cache file cacheLocation: cacheFilePath, @@ -3348,7 +3015,7 @@ describe("ESLint", () => { "no-unused-vars": 2 } }, - extensions: ["js"], + cwd: path.join(fixtureDir, "..") }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); @@ -3378,7 +3045,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3427,7 +3094,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3475,7 +3142,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3486,8 +3153,8 @@ describe("ESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } + }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -3514,7 +3181,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3525,8 +3192,8 @@ describe("ESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } + }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -3555,7 +3222,7 @@ describe("ESLint", () => { eslint = new ESLint({ cwd: path.join(fixtureDir, ".."), - useEslintrc: false, + overrideConfigFile: true, // specifying cache true the cache will be created cache: true, @@ -3566,8 +3233,8 @@ describe("ESLint", () => { "no-console": 0, "no-unused-vars": 2 } - }, - extensions: ["js"] + } + }); const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); @@ -3593,192 +3260,177 @@ describe("ESLint", () => { }); describe("processors", () => { - it("should return two messages when executing with config file that specifies a processor", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: getFixturePath("configurations", "processors.json"), - useEslintrc: false, - extensions: ["js", "txt"], - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - }); it("should return two messages when executing with config file that specifies preloaded processor", async () => { eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - extensions: ["js", "txt"], - cwd: path.join(fixtureDir, ".."), - plugins: { - "test-processor": { - processors: { - ".txt": { - preprocess(text) { - return [text]; - }, - postprocess(messages) { - return messages[0]; + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(text) { + return [text]; + }, + postprocess(messages) { + return messages[0]; + } + } } } + }, + processor: "test/txt", + rules: { + "no-console": 2, + "no-unused-vars": 2 } + }, + { + files: ["**/*.txt", "**/*.txt/*.txt"] } - } + ], + cwd: path.join(fixtureDir, "..") }); const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 2); - }); + assert.strictEqual(results[0].suppressedMessages.length, 0); - it("should run processors when calling lintFiles with config file that specifies a processor", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: getFixturePath("configurations", "processors.json"), - useEslintrc: false, - extensions: ["js", "txt"], - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); }); it("should run processors when calling lintFiles with config file that specifies preloaded processor", async () => { eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - extensions: ["js", "txt"], - cwd: path.join(fixtureDir, ".."), - plugins: { - "test-processor": { - processors: { - ".txt": { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = "post-processed"; + return messages[0]; + } + } } } + }, + processor: "test/txt", + rules: { + "no-console": 2, + "no-unused-vars": 2 } + }, + { + files: ["**/*.txt", "**/*.txt/*.txt"] } - } + ], + cwd: path.join(fixtureDir, "..") }); const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - }); - - it("should run processors when calling lintText with config file that specifies a processor", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: getFixturePath("configurations", "processors.json"), - useEslintrc: false, - extensions: ["js", "txt"], - ignore: false - }); - const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - }); - - it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - extensions: ["js", "txt"], - ignore: false, - plugins: { - "test-processor": { - processors: { - ".txt": { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + + }); + + it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = "post-processed"; + return messages[0]; + } + } } } + }, + processor: "test/txt", + rules: { + "no-console": 2, + "no-unused-vars": 2 } + }, + { + files: ["**/*.txt", "**/*.txt/*.txt"] } - } + ], + ignore: false }); const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => { let count = 0; eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - overrides: [{ + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(text) { + count++; + return [ + { + + // it will be run twice, and text will be as-is at the second time, then it will not run third time + text: text.replace("a()", "b()"), + filename: ".txt" + } + ]; + }, + postprocess(messages) { + messages[0][0].ruleId = "post-processed"; + return messages[0]; + } + } + } + } + }, + processor: "test/txt" + }, + { files: ["**/*.txt/*.txt"], rules: { "no-console": 2, "no-unused-vars": 2 } - }] - }, - extensions: ["txt"], - ignore: false, - plugins: { - "test-processor": { - processors: { - ".txt": { - preprocess(text) { - count++; - return [ - { - - // it will be run twice, and text will be as-is at the second time, then it will not run third time - text: text.replace("a()", "b()"), - filename: ".txt" - } - ]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; - } - } - } + }, + { + files: ["**/*.txt"] } - } + ], + ignore: false }); const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); assert.strictEqual(count, 2); assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); describe("autofixing with processors", () => { @@ -3803,74 +3455,79 @@ describe("ESLint", () => { it("should run in autofix mode when using a processor that supports autofixing", async () => { eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - semi: 2 - } - }, - extensions: ["js", "txt"], - ignore: false, - fix: true, - plugins: { - "test-processor": { - processors: { - ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) + overrideConfigFile: true, + overrideConfig: [ + { + files: ["**/*.html"], + plugins: { + test: { processors: { html: Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } } + }, + processor: "test/html", + rules: { + semi: 2 } + }, + { + files: ["**/*.txt"] } - } + ], + ignore: false, + fix: true }); const results = await eslint.lintText("", { filePath: "foo.html" }); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[0].output, ""); }); it("should not run in autofix mode when using a processor that does not support autofixing", async () => { eslint = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { - plugins: ["test-processor"], + files: ["**/*.html"], + plugins: { + test: { processors: { html: HTML_PROCESSOR } } + }, + processor: "test/html", rules: { semi: 2 } }, - extensions: ["js", "txt"], ignore: false, - fix: true, - plugins: { - "test-processor": { processors: { ".html": HTML_PROCESSOR } } - } + fix: true }); const results = await eslint.lintText("", { filePath: "foo.html" }); assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); }); it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - semi: 2 - } - }, - extensions: ["js", "txt"], - ignore: false, - plugins: { - "test-processor": { - processors: { - ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) + overrideConfigFile: true, + overrideConfig: [ + { + files: ["**/*.html"], + plugins: { + test: { processors: { html: Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } } + }, + processor: "test/html", + rules: { + semi: 2 } + }, + { + files: ["**/*.txt"] } - } + ], + ignore: false }); const results = await eslint.lintText("", { filePath: "foo.html" }); assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); }); }); @@ -3880,7 +3537,7 @@ describe("ESLint", () => { beforeEach(() => { eslint = new ESLint({ cwd: getFixturePath("cli-engine"), - useEslintrc: false + overrideConfigFile: true }); }); @@ -3891,6 +3548,7 @@ describe("ESLint", () => { }); it("should throw if the directory exists and is empty", async () => { + ensureDirectoryExists(getFixturePath("cli-engine/empty")); await assert.rejects(async () => { await eslint.lintFiles(["empty"]); }, /No files matching 'empty' were found\./u); @@ -3913,152 +3571,12 @@ describe("ESLint", () => { await eslint.lintFiles(["console.js", "non-exist.js"]); }, /No files matching 'non-exist\.js' were found\./u); }); - }); - - describe("overrides", () => { - beforeEach(() => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine/overrides-with-dot"), - ignore: false - }); - }); - - it("should recognize dotfiles", async () => { - const ret = await eslint.lintFiles([".test-target.js"]); - - assert.strictEqual(ret.length, 1); - assert.strictEqual(ret[0].messages.length, 1); - assert.strictEqual(ret[0].messages[0].ruleId, "no-unused-vars"); - }); - }); - - describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "eslint/11510"), - files: { - "no-console-error-in-overrides.json": JSON.stringify({ - overrides: [{ - files: ["*.js"], - rules: { "no-console": "error" } - }] - }), - ".eslintrc.json": JSON.stringify({ - extends: "./no-console-error-in-overrides.json", - rules: { "no-console": "off" } - }), - "a.js": "console.log();" - } - }); - - beforeEach(() => { - eslint = new ESLint({ cwd: getPath() }); - return prepare(); - }); - - afterEach(cleanup); - - it("should not report 'no-console' error.", async () => { - const results = await eslint.lintFiles("a.js"); - - assert.strictEqual(results.length, 1); - assert.deepStrictEqual(results[0].messages, []); - }); - }); - - describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "eslint/11559"), - files: { - "node_modules/eslint-plugin-test/index.js": ` - exports.configs = { - recommended: { plugins: ["test"] } - }; - exports.rules = { - foo: { - meta: { schema: [{ type: "number" }] }, - create() { return {}; } - } - }; - `, - ".eslintrc.json": JSON.stringify({ - - // Import via the recommended config. - extends: "plugin:test/recommended", - - // Has invalid option. - rules: { "test/foo": ["error", "invalid-option"] } - }), - "a.js": "console.log();" - } - }); - - beforeEach(() => { - eslint = new ESLint({ cwd: getPath() }); - return prepare(); - }); - - afterEach(cleanup); - - it("should throw fatal error.", async () => { + // https://github.com/eslint/eslint/issues/16275 + it("a mix of an existing glob pattern and a non-existing glob pattern", async () => { await assert.rejects(async () => { - await eslint.lintFiles("a.js"); - }, /invalid-option/u); - }); - }); - - describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "cli-engine/11586"), - files: { - "node_modules/eslint-plugin-test/index.js": ` - exports.rules = { - "no-example": { - meta: { type: "problem", fixable: "code" }, - create(context) { - return { - Identifier(node) { - if (node.name === "example") { - context.report({ - node, - message: "fix", - fix: fixer => fixer.replaceText(node, "fixed") - }) - } - } - }; - } - } - }; - `, - ".eslintrc.json": { - plugins: ["test"], - rules: { "test/no-example": "error" } - }, - "a.js": "example;" - } - }); - - beforeEach(() => { - eslint = new ESLint({ - cwd: getPath(), - fix: true, - fixTypes: ["problem"] - }); - - return prepare(); - }); - - afterEach(cleanup); - - it("should not crash.", async () => { - const results = await eslint.lintFiles("a.js"); - - assert.strictEqual(results.length, 1); - assert.deepStrictEqual(results[0].messages, []); - assert.deepStrictEqual(results[0].output, "fixed;"); + await eslint.lintFiles(["*.js", "non-exist/*.js"]); + }, /No files matching 'non-exist\/\*\.js' were found\./u); }); }); @@ -4073,7 +3591,7 @@ describe("ESLint", () => { const { defineProcessor } = require("pattern-processor"); const processor = defineProcessor(${/```(\w+)\n([\s\S]+?)\n```/gu}); exports.processors = { - ".md": { ...processor, supportsAutofix: true }, + "markdown": { ...processor, supportsAutofix: true }, "non-fixable": processor }; `, @@ -4082,7 +3600,7 @@ describe("ESLint", () => { const processor = defineProcessor(${/ - - \`\`\` - `); }); - it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + it("should lint HTML blocks as well with multiple processors if represented in config.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: path.join(root, id), files: { ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") + } + }, + { + files: ["**/*.js"], + rules: { semi: "error" } + }, + { + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/html" + } + ];` } }); await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + eslint = new ESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); const results = await eslint.lintFiles(["test.md"]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results.length, 1, "Should have one result."); + assert.strictEqual(results[0].messages.length, 2, "Should have two messages."); assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block - assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[0].line, 2, "First error should be on line 2"); assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block - assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].messages[1].line, 7, "Second error should be on line 7."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + it("should fix HTML blocks as well with multiple processors if represented in config.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: path.join(root, id), files: { ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") + } + }, + { + files: ["**/*.js"], + rules: { semi: "error" } + }, + { + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/html" + } + ];` } }); await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true }); + eslint = new ESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] }, fix: true }); const results = await eslint.lintFiles(["test.md"]); assert.strictEqual(results.length, 1); assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); assert.strictEqual(results[0].output, unIndent` \`\`\`js console.log("hello");${/* ← fixed */""} @@ -4229,83 +3765,45 @@ describe("ESLint", () => { `); }); - it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ - { - files: "*.html", - processor: "html/non-fixable" // supportsAutofix: false - } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS Block in HTML Block - assert.strictEqual(results[0].messages[0].line, 7); - assert.strictEqual(results[0].messages[0].fix, void 0); - assert.strictEqual(results[0].output, unIndent` - \`\`\`js - console.log("hello");${/* ← fixed */""} - \`\`\` - \`\`\`html -
Hello
- - - \`\`\` - `); - }); - it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: path.join(root, id), files: { ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ - { - files: "*.html", - - // this rules are not used because ESLint re-resolve configs if a code block had a different file extension. - rules: { - semi: "error", - "no-console": "off" - } - }, - { - files: "**/*.html/*.js", - rules: { - semi: "off", - "no-console": "error" - } + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") + } + }, + { + files: ["**/*.js"], + rules: { semi: "error" } + }, + { + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/html" + }, + { + files: ["**/*.html/*.js"], + rules: { + semi: "off", + "no-console": "error" } - ] - } + } + + ];` + } }); await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + eslint = new ESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); const results = await eslint.lintFiles(["test.md"]); assert.strictEqual(results.length, 1); @@ -4314,40 +3812,49 @@ describe("ESLint", () => { assert.strictEqual(results[0].messages[0].line, 2); assert.strictEqual(results[0].messages[1].ruleId, "no-console"); assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: path.join(root, id), files: { ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ - { - files: "*.html", - processor: "html/legacy", // this processor returns strings rather than `{text, filename}` - rules: { - semi: "off", - "no-console": "error" - } + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") }, - { - files: "**/*.html/*.js", - rules: { - semi: "error", - "no-console": "off" - } + rules: { semi: "error" } + }, + { + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/legacy", // this processor returns strings rather than '{ text, filename }' + rules: { + semi: "off", + "no-console": "error" } - ] - } + }, + { + files: ["**/*.html/*.js"], + rules: { + semi: "error", + "no-console": "off" + } + } + + ];` } }); await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + eslint = new ESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); const results = await eslint.lintFiles(["test.md"]); assert.strictEqual(results.length, 1); @@ -4358,203 +3865,38 @@ describe("ESLint", () => { assert.strictEqual(results[0].messages[1].line, 7); assert.strictEqual(results[0].messages[2].ruleId, "no-console"); assert.strictEqual(results[0].messages[2].line, 10); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); it("should throw an error if invalid processor was specified.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: path.join(root, id), files: { ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - processor: "markdown/unknown" - } + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") + } + }, + { + files: ["**/*.md"], + processor: "markdown/unknown" + } + + ];` } }); await teardown.prepare(); - cleanup = teardown.cleanup; eslint = new ESLint({ cwd: teardown.getPath() }); await assert.rejects(async () => { await eslint.lintFiles(["test.md"]); - }, /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u); - }); - - it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ - { - files: "*.html", - processor: "html/.html" - }, - { - files: "*.md", - processor: "markdown/.md" - } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block - assert.strictEqual(results[0].messages[1].line, 7); - }); - }); - - describe("MODULE_NOT_FOUND error handling", () => { - const cwd = getFixturePath("module-not-found"); - - beforeEach(() => { - eslint = new ESLint({ cwd }); - }); - - it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", async () => { - try { - await eslint.lintText("test", { filePath: "extends-js/test.js" }); - } catch (err) { - assert.strictEqual(err.messageTemplate, "extend-config-missing"); - assert.deepStrictEqual(err.messageData, { - configName: "nonexistent-config", - importerName: getFixturePath("module-not-found", "extends-js", ".eslintrc.yml") - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with a message template when 'extends' property has a non-existence plugin config.", async () => { - try { - await eslint.lintText("test", { filePath: "extends-plugin/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, "plugin-missing"); - assert.deepStrictEqual(err.messageData, { - importerName: `extends-plugin${path.sep}.eslintrc.yml`, - pluginName: "eslint-plugin-nonexistent-plugin", - resolvePluginsRelativeTo: path.join(cwd, "extends-plugin") // the directory of the config file. - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with a message template when 'plugins' property has a non-existence plugin.", async () => { - try { - await eslint.lintText("test", { filePath: "plugins/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, "plugin-missing"); - assert.deepStrictEqual(err.messageData, { - importerName: `plugins${path.sep}.eslintrc.yml`, - pluginName: "eslint-plugin-nonexistent-plugin", - resolvePluginsRelativeTo: path.join(cwd, "plugins") // the directory of the config file. - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when a JavaScript config threw a 'MODULE_NOT_FOUND' error.", async () => { - try { - await eslint.lintText("test", { filePath: "throw-in-config-itself/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'extends' property has a JavaScript config that throws a 'MODULE_NOT_FOUND' error.", async () => { - try { - await eslint.lintText("test", { filePath: "throw-in-extends-js/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'extends' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { - try { - await eslint.lintText("test", { filePath: "throw-in-extends-plugin/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'plugins' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { - try { - await eslint.lintText("test", { filePath: "throw-in-plugins/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - }); - - describe("with '--rulesdir' option", () => { - - const rootPath = getFixturePath("cli-engine/with-rulesdir"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: rootPath, - files: { - "internal-rules/test.js": ` - module.exports = { - create(context) { - return { - ExpressionStatement(node) { - context.report({ node, message: "ok" }); - }, - }; - }, - }; - `, - ".eslintrc.json": { - root: true, - rules: { test: "error" } - }, - "test.js": "console.log('hello')" - } + }, /Key "processor": Could not find "unknown" in plugin "markdown"/u); }); - beforeEach(prepare); - afterEach(cleanup); - - - it("should use the configured rules which are defined by '--rulesdir' option.", async () => { - eslint = new ESLint({ - cwd: getPath(), - rulePaths: ["internal-rules"] - }); - const results = await eslint.lintFiles(["test.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].message, "ok"); - }); }); describe("glob pattern '[ab].js'", () => { @@ -4577,7 +3919,7 @@ describe("ESLint", () => { "b.js": "", "ab.js": "", "[ab].js": "", - ".eslintrc.yml": "root: true" + "eslint.config.js": "module.exports = [];" } }); @@ -4598,7 +3940,7 @@ describe("ESLint", () => { "a.js": "", "b.js": "", "ab.js": "", - ".eslintrc.yml": "root: true" + "eslint.config.js": "module.exports = [];" } }); @@ -4628,7 +3970,7 @@ describe("ESLint", () => { cwd: root, files: { "test.js": "/* globals foo */", - ".eslintrc.yml": "noInlineConfig: true" + "eslint.config.js": "module.exports = [{ linterOptions: { noInlineConfig: true } }];" } }); @@ -4640,48 +3982,30 @@ describe("ESLint", () => { const messages = results[0].messages; assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml)."); + assert.strictEqual(messages[0].message, "'/* globals foo */' has no effect because you have 'noInlineConfig' setting in your config."); }); - it("should show the config file what the 'noInlineConfig' came from.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}", - "test.js": "/* globals foo */", - ".eslintrc.yml": "extends: foo" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml » eslint-config-foo)."); - }); }); describe("with 'reportUnusedDisableDirectives' setting", () => { const root = getFixturePath("cli-engine/reportUnusedDisableDirectives"); let cleanup; + let i = 0; beforeEach(() => { cleanup = () => { }; + i++; }); afterEach(() => cleanup()); - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => { + it("should error unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = error'.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: `${root}${i}`, files: { "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" + "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'error' } }" } }); @@ -4694,721 +4018,583 @@ describe("ESLint", () => { const messages = results[0].messages; assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].severity, 2); assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - describe("the runtime option overrides config files.", () => { - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new ESLint({ - cwd: teardown.getPath(), - reportUnusedDisableDirectives: "off" - }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new ESLint({ - cwd: teardown.getPath(), - reportUnusedDisableDirectives: "error" - }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + it("should error unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 2'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 2 } }" + } }); - }); - }); - describe("with 'overrides[*].extends' setting on deep locations", () => { - const root = getFixturePath("cli-engine/deeply-overrides-i-extends"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - overrides: [{ files: ["*test*"], extends: "two" }] - })}`, - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - overrides: [{ files: ["*.js"], extends: "three" }] - })}`, - "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({ - rules: { "no-console": "error" } - })}`, - "test.js": "console.log('hello')", - ".eslintrc.yml": "extends: one" - } - }); - beforeEach(prepare); - afterEach(cleanup); + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ cwd: teardown.getPath() }); - it("should not throw.", async () => { - eslint = new ESLint({ cwd: getPath() }); const results = await eslint.lintFiles(["test.js"]); const messages = results[0].messages; assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - }); - describe("don't ignore the entry directory.", () => { - const root = getFixturePath("cli-engine/dont-ignore-entry-dir"); - - let cleanup; + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = warn'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'warn' } }" + } + }); - beforeEach(() => { - cleanup = () => { }; - }); - afterEach(async () => { - await cleanup(); + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ cwd: teardown.getPath() }); - const configFilePath = path.resolve(root, "../.eslintrc.json"); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; - if (shell.test("-e", configFilePath)) { - shell.rm(configFilePath); - } + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("'lintFiles(\".\")' should not load config files from outside of \".\".", async () => { + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 1'.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: `${root}${i}`, files: { - "../.eslintrc.json": "BROKEN FILE", - ".eslintrc.json": JSON.stringify({ root: true }), - "index.js": "console.log(\"hello\")" + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 1 } }" } }); + await teardown.prepare(); cleanup = teardown.cleanup; eslint = new ESLint({ cwd: teardown.getPath() }); - // Don't throw "failed to load config file" error. - await eslint.lintFiles("."); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("'lintFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => { + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = true'.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: `${root}${i}`, files: { - "../.eslintrc.json": { ignorePatterns: ["/dont-ignore-entry-dir"] }, - ".eslintrc.json": { root: true }, - "index.js": "console.log(\"hello\")" + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: true } }" } }); + await teardown.prepare(); cleanup = teardown.cleanup; eslint = new ESLint({ cwd: teardown.getPath() }); - // Don't throw "file not found" error. - await eslint.lintFiles("."); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - it("'lintFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => { + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = false'.", async () => { const teardown = createCustomTeardown({ - cwd: root, + cwd: `${root}${i}`, files: { - ".eslintrc.json": { ignorePatterns: ["/subdir"] }, - "subdir/.eslintrc.json": { root: true }, - "subdir/index.js": "console.log(\"hello\")" + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: false } }" } }); + await teardown.prepare(); cleanup = teardown.cleanup; eslint = new ESLint({ cwd: teardown.getPath() }); - // Don't throw "file not found" error. - await eslint.lintFiles("subdir"); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); }); - }); - it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { - eslint = new ESLint(); - await assert.rejects(() => eslint.lintFiles(777), /'patterns' must be a non-empty string or an array of non-empty strings/u); - await assert.rejects(() => eslint.lintFiles([null]), /'patterns' must be a non-empty string or an array of non-empty strings/u); - }); - }); + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = off'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'off' } }" + } + }); - describe("calculateConfigForFile", () => { - it("should return the info from Config#getConfig when called", async () => { - const options = { - overrideConfigFile: getFixturePath("configurations", "quotes-error.json") - }; - const engine = new ESLint(options); - const filePath = getFixturePath("single-quoted.js"); - const actualConfig = await engine.calculateConfigForFile(filePath); - const expectedConfig = new CascadingConfigArrayFactory({ specificConfigPath: options.overrideConfigFile }) - .getConfigArrayForFile(filePath) - .extractConfig(filePath) - .toCompatibleObjectAsConfigFileContent(); - assert.deepStrictEqual(actualConfig, expectedConfig); - }); + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ cwd: teardown.getPath() }); - it("should return the config for a file that doesn't exist", async () => { - const engine = new ESLint(); - const filePath = getFixturePath("does_not_exist.js"); - const existingSiblingFilePath = getFixturePath("single-quoted.js"); - const actualConfig = await engine.calculateConfigForFile(filePath); - const expectedConfig = await engine.calculateConfigForFile(existingSiblingFilePath); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; - assert.deepStrictEqual(actualConfig, expectedConfig); - }); + assert.strictEqual(messages.length, 0); + }); - it("should return the config for a virtual file that is a child of an existing file", async () => { - const engine = new ESLint(); - const parentFileName = "single-quoted.js"; - const filePath = getFixturePath(parentFileName, "virtual.js"); // single-quoted.js/virtual.js - const parentFilePath = getFixturePath(parentFileName); - const actualConfig = await engine.calculateConfigForFile(filePath); - const expectedConfig = await engine.calculateConfigForFile(parentFilePath); + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 0'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 0 } }" + } + }); - assert.deepStrictEqual(actualConfig, expectedConfig); - }); - it("should return the config when run from within a subdir", async () => { - const options = { - cwd: getFixturePath("config-hierarchy", "root-true", "parent", "root", "subdir") - }; - const engine = new ESLint(options); - const filePath = getFixturePath("config-hierarchy", "root-true", "parent", "root", ".eslintrc"); - const actualConfig = await engine.calculateConfigForFile("./.eslintrc"); - const expectedConfig = new CascadingConfigArrayFactory(options) - .getConfigArrayForFile(filePath) - .extractConfig(filePath) - .toCompatibleObjectAsConfigFileContent(); + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ cwd: teardown.getPath() }); - assert.deepStrictEqual(actualConfig, expectedConfig); - }); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; - it("should throw an error if a directory path was given.", async () => { - const engine = new ESLint(); + assert.strictEqual(messages.length, 0); + }); - try { - await engine.calculateConfigForFile("."); - } catch (error) { - assert.strictEqual(error.messageTemplate, "print-config-with-directory-path"); - return; - } - assert.fail("should throw an error"); - }); + describe("the runtime option overrides config files.", () => { + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = [{ linterOptions: { reportUnusedDisableDirectives: true } }]" + } + }); - it("should throw if non-string value is given to 'filePath' parameter", async () => { - const eslint = new ESLint(); + await teardown.prepare(); + cleanup = teardown.cleanup; - await assert.rejects(() => eslint.calculateConfigForFile(null), /'filePath' must be a non-empty string/u); - }); + eslint = new ESLint({ + cwd: teardown.getPath(), + overrideConfig: { + linterOptions: { reportUnusedDisableDirectives: "off" } + } + }); - // https://github.com/eslint/eslint/issues/13793 - it("should throw with an invalid built-in rule config", async () => { - const options = { - baseConfig: { - rules: { - "no-alert": ["error", { - thisDoesNotExist: true - }] - } - } - }; - const engine = new ESLint(options); - const filePath = getFixturePath("single-quoted.js"); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; - await assert.rejects( - () => engine.calculateConfigForFile(filePath), - /Configuration for rule "no-alert" is invalid:/u - ); - }); - }); + assert.strictEqual(messages.length, 0); + }); - describe("isPathIgnored", () => { - it("should check if the given path is ignored", async () => { - const engine = new ESLint({ - ignorePath: getFixturePath(".eslintignore2"), - cwd: getFixturePath() - }); + it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": "module.exports = [{ linterOptions: { reportUnusedDisableDirectives: true } }]" + } + }); - assert(await engine.isPathIgnored("undef.js")); - assert(!await engine.isPathIgnored("passing.js")); - }); + await teardown.prepare(); + cleanup = teardown.cleanup; - it("should return false if ignoring is disabled", async () => { - const engine = new ESLint({ - ignore: false, - ignorePath: getFixturePath(".eslintignore2"), - cwd: getFixturePath() - }); + eslint = new ESLint({ + cwd: teardown.getPath(), + overrideConfig: { + linterOptions: { reportUnusedDisableDirectives: "error" } + } + }); - assert(!await engine.isPathIgnored("undef.js")); - }); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; - // https://github.com/eslint/eslint/issues/5547 - it("should return true for default ignores even if ignoring is disabled", async () => { - const engine = new ESLint({ - ignore: false, - cwd: getFixturePath("cli-engine") + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); }); - - assert(await engine.isPathIgnored("node_modules/foo.js")); }); - describe("about the default ignore patterns", () => { - it("should always apply defaultPatterns if ignore option is true", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); + it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { + eslint = new ESLint(); + await assert.rejects(() => eslint.lintFiles(777), /'patterns' must be a non-empty string or an array of non-empty strings/u); + await assert.rejects(() => eslint.lintFiles([null]), /'patterns' must be a non-empty string or an array of non-empty strings/u); + }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); + describe("Alternate config files", () => { - it("should still apply defaultPatterns if ignore option is is false", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ ignore: false, cwd }); + it("should find eslint.config.mjs when present", async () => { - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); + const cwd = getFixturePath("mjs-config"); - it("should allow subfolders of defaultPatterns to be unignored by ignorePattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ - cwd, - overrideConfig: { - ignorePatterns: "!/node_modules/package" - } + eslint = new ESLint({ + cwd }); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignorePath", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd, ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithUnignoredDefaults") }); + const results = await eslint.lintFiles("foo.js"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); - }); - - it("should ignore dotfiles", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); }); - it("should ignore directories beginning with a dot", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); + it("should find eslint.config.cjs when present", async () => { - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); - }); + const cwd = getFixturePath("cjs-config"); - it("should still ignore dotfiles when ignore option disabled", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ ignore: false, cwd }); + eslint = new ESLint({ + cwd + }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); - }); + const results = await eslint.lintFiles("foo.js"); - it("should still ignore directories beginning with a dot when ignore option disabled", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ ignore: false, cwd }); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); }); - it("should not ignore absolute paths containing '..'", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); - - assert(!await engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); - }); + it("should favor eslint.config.js when eslint.config.mjs and eslint.config.cjs are present", async () => { - it("should ignore /node_modules/ relative to .eslintignore when loaded", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ ignorePath: getFixturePath("ignored-paths", ".eslintignore"), cwd }); + const cwd = getFixturePath("js-mjs-cjs-config"); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "existing.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo", "node_modules", "existing.js"))); - }); + eslint = new ESLint({ + cwd + }); - it("should ignore /node_modules/ relative to cwd without an .eslintignore", async () => { - const cwd = getFixturePath("ignored-paths", "no-ignore-file"); - const engine = new ESLint({ cwd }); + const results = await eslint.lintFiles("foo.js"); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); }); - }); - describe("with no .eslintignore file", () => { - it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", async () => { - const cwd = getFixturePath("ignored-paths", "configurations"); - const engine = new ESLint({ cwd }); + it("should favor eslint.config.mjs when eslint.config.cjs is present", async () => { - // an .eslintignore in parent directories includes `*.js`, but don't load it. - assert(!await engine.isPathIgnored("foo.js")); - assert(await engine.isPathIgnored("node_modules/foo.js")); - }); + const cwd = getFixturePath("mjs-cjs-config"); - it("should return false for files outside of the cwd (with no ignore file provided)", async () => { + eslint = new ESLint({ + cwd + }); - // Default ignore patterns should not inadvertently ignore files in parent directories - const engine = new ESLint({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); + const results = await eslint.lintFiles("foo.js"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); }); }); - describe("with .eslintignore file or package.json file", () => { - it("should load .eslintignore from cwd when explicitly passed", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); - - // `${cwd}/.eslintignore` includes `sampleignorepattern`. - assert(await engine.isPathIgnored("sampleignorepattern")); - }); - it("should use package.json's eslintIgnore files if no specified .eslintignore file", async () => { - const cwd = getFixturePath("ignored-paths", "package-json-ignore"); - const engine = new ESLint({ cwd }); + }); - assert(await engine.isPathIgnored("hello.js")); - assert(await engine.isPathIgnored("world.js")); - }); + describe("Fix Types", () => { - it("should use correct message template if failed to parse package.json", () => { - const cwd = getFixturePath("ignored-paths", "broken-package-json"); + let eslint; - assert.throws(() => { - try { - // eslint-disable-next-line no-new -- Check for error - new ESLint({ cwd }); - } catch (error) { - assert.strictEqual(error.messageTemplate, "failed-to-read-json"); - throw error; - } + it("should throw an error when an invalid fix type is specified", () => { + assert.throws(() => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["layou"] }); - }); - - it("should not use package.json's eslintIgnore files if specified .eslintignore file", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); + }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); + }); - /* - * package.json includes `hello.js` and `world.js`. - * .eslintignore includes `sampleignorepattern`. - */ - assert(!await engine.isPathIgnored("hello.js")); - assert(!await engine.isPathIgnored("world.js")); - assert(await engine.isPathIgnored("sampleignorepattern")); + it("should not fix any rules when fixTypes is used without fix", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: false, + fixTypes: ["layout"] }); + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const results = await eslint.lintFiles([inputPath]); - it("should error if package.json's eslintIgnore is not an array of file paths", () => { - const cwd = getFixturePath("ignored-paths", "bad-package-json-ignore"); + assert.strictEqual(results[0].output, void 0); + }); - assert.throws(() => { - // eslint-disable-next-line no-new -- Check for throwing - new ESLint({ cwd }); - }, /Package\.json eslintIgnore property requires an array of paths/u); + it("should not fix non-style rules when fixTypes has only 'layout'", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["layout"] }); - }); + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const outputPath = getFixturePath("fix-types/fix-only-semi.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); - describe("with --ignore-pattern option", () => { - it("should accept a string for options.ignorePattern", async () => { - const cwd = getFixturePath("ignored-paths", "ignore-pattern"); - const engine = new ESLint({ - overrideConfig: { - ignorePatterns: "ignore-me.txt" - }, - cwd - }); + assert.strictEqual(results[0].output, expectedOutput); + }); - assert(await engine.isPathIgnored("ignore-me.txt")); + it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["suggestion"] }); + const inputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.js"); + const outputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); - it("should accept an array for options.ignorePattern", async () => { - const engine = new ESLint({ - overrideConfig: { - ignorePatterns: ["a", "b"] - }, - useEslintrc: false - }); + assert.strictEqual(results[0].output, expectedOutput); + }); - assert(await engine.isPathIgnored("a")); - assert(await engine.isPathIgnored("b")); - assert(!await engine.isPathIgnored("c")); + it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["suggestion", "layout"] }); + const inputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.js"); + const outputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); - it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ - overrideConfig: { - ignorePatterns: "not-a-file" - }, - cwd - }); + assert.strictEqual(results[0].output, expectedOutput); + }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "not-a-file"))); + }); + + describe("isPathIgnored", () => { + it("should check if the given path is ignored", async () => { + const engine = new ESLint({ + overrideConfigFile: getFixturePath("eslint.config-with-ignores2.js"), + cwd: getFixturePath() }); - it("should return true for file matching an ignore pattern exactly", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "undef.js" }, cwd }); + assert(await engine.isPathIgnored("undef.js")); + assert(!await engine.isPathIgnored("passing.js")); + }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + it("should return false if ignoring is disabled", async () => { + const engine = new ESLint({ + ignore: false, + overrideConfigFile: getFixturePath("eslint.config-with-ignores2.js"), + cwd: getFixturePath() }); - it("should return false for file matching an invalid ignore pattern with leading './'", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "./undef.js" }, cwd }); + assert(!await engine.isPathIgnored("undef.js")); + }); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + // https://github.com/eslint/eslint/issues/5547 + it("should return true for default ignores even if ignoring is disabled", async () => { + const engine = new ESLint({ + ignore: false, + cwd: getFixturePath("cli-engine") }); - it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "/undef.js" }, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir", "undef.js"))); - }); + assert(await engine.isPathIgnored("node_modules/foo.js")); + }); - it("should return true for file matching a child of an ignore pattern", async () => { + describe("about the default ignore patterns", () => { + it("should always apply default ignore patterns if ignore option is true", async () => { const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "ignore-pattern" }, cwd }); + const engine = new ESLint({ cwd }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); }); - it("should return true for file matching a grandchild of an ignore pattern", async () => { + it("should still apply default ignore patterns if ignore option is is false", async () => { const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "ignore-pattern" }, cwd }); + const engine = new ESLint({ ignore: false, cwd }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.txt"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); }); - it("should return false for file not matching any ignore pattern", async () => { + it("should allow subfolders of defaultPatterns to be unignored by ignorePattern constructor option", async () => { const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "failing.js" }, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "unignored.js"))); - }); + const engine = new ESLint({ + cwd, + overrideConfigFile: true, + ignorePatterns: ["!node_modules/", "node_modules/*", "!node_modules/package/"] + }); - it("two globstar '**' ignore pattern should ignore files in nested directories", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "**/*.js" }, cwd }); + const result = await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js")); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.js"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.j2"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.j2"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.j2"))); + assert(!result, "File should not be ignored"); }); - }); - describe("with --ignore-path option", () => { - it("initialization with ignorePath should work when cwd is a parent directory", async () => { + it("should allow subfolders of defaultPatterns to be unignored by ignores in overrideConfig", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new ESLint({ ignorePath, cwd }); + const engine = new ESLint({ + cwd, + overrideConfigFile: true, + overrideConfig: { + ignores: ["!node_modules/", "node_modules/*", "!node_modules/package/"] + } + }); - assert(await engine.isPathIgnored("custom-name/foo.js")); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); }); - it("initialization with ignorePath should work when the file is in the cwd", async () => { - const cwd = getFixturePath("ignored-paths", "custom-name"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new ESLint({ ignorePath, cwd }); + it("should ignore .git directory", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ cwd }); - assert(await engine.isPathIgnored("foo.js")); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".git/bar"))); }); - it("initialization with ignorePath should work when cwd is a subdirectory", async () => { - const cwd = getFixturePath("ignored-paths", "custom-name", "subdirectory"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new ESLint({ ignorePath, cwd }); + it("should still ignore .git directory when ignore option disabled", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ ignore: false, cwd }); - assert(await engine.isPathIgnored("../custom-name/foo.js")); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".git/bar"))); }); - it("initialization with invalid file should throw error", () => { + it("should not ignore absolute paths containing '..'", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz"); + const engine = new ESLint({ cwd }); - assert.throws(() => { - // eslint-disable-next-line no-new -- Check for throwing - new ESLint({ ignorePath, cwd }); - }, /Cannot read \.eslintignore file/u); + assert(!await engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); }); - it("should return false for files outside of ignorePath's directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new ESLint({ ignorePath, cwd }); + it("should ignore /node_modules/ relative to cwd without any configured ignore patterns", async () => { + const cwd = getFixturePath("ignored-paths", "no-ignore-file"); + const engine = new ESLint({ cwd }); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); }); - it("should resolve relative paths from CWD", async () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new ESLint({ ignorePath, cwd }); + it("should not inadvertently ignore all files in parent directories", async () => { + const engine = new ESLint({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); + }); - it("should resolve relative paths from CWD when it's in a child directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/foo.js"))); + describe("with ignorePatterns option", () => { + it("should accept a string for options.ignorePatterns", async () => { + const cwd = getFixturePath("ignored-paths", "ignore-pattern"); + const engine = new ESLint({ + ignorePatterns: ["ignore-me.txt"], + cwd + }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/bar.js"))); + assert(await engine.isPathIgnored("ignore-me.txt")); }); - it("should resolve relative paths from CWD when it contains negated globs", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new ESLint({ ignorePath, cwd }); + it("should accept an array for options.ignorePattern", async () => { + const engine = new ESLint({ + ignorePatterns: ["a.js", "b.js"], + overrideConfigFile: true + }); - assert(await engine.isPathIgnored("subdir/blah.txt")); - assert(await engine.isPathIgnored("blah.txt")); - assert(await engine.isPathIgnored("subdir/bar.txt")); - assert(!await engine.isPathIgnored("bar.txt")); - assert(!await engine.isPathIgnored("subdir/baz.txt")); - assert(!await engine.isPathIgnored("baz.txt")); + assert(await engine.isPathIgnored("a.js"), "a.js should be ignored"); + assert(await engine.isPathIgnored("b.js"), "b.js should be ignored"); + assert(!await engine.isPathIgnored("c.js"), "c.js should not be ignored"); }); - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", async () => { + it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new ESLint({ ignorePath, cwd }); + const engine = new ESLint({ + ignorePatterns: ["not-a-file"], + cwd + }); - assert(await engine.isPathIgnored("node_modules/blah.js")); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "not-a-file"))); }); - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", async () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new ESLint({ ignorePath, cwd }); + it("should return true for file matching an ignore pattern exactly", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + ignorePatterns: ["undef.js"], + cwd, + overrideConfigFile: true + }); - assert(await engine.isPathIgnored("node_modules/blah.js")); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); - it("should handle .eslintignore which contains CRLF correctly.", async () => { - const ignoreFileContent = fs.readFileSync(getFixturePath("ignored-paths", "crlf/.eslintignore"), "utf8"); - - assert(ignoreFileContent.includes("\r"), "crlf/.eslintignore should contains CR."); + it("should return false for file in subfolder of cwd matching an ignore pattern with a base filename", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "crlf/.eslintignore"); - const engine = new ESLint({ ignorePath, cwd }); + const filePath = getFixturePath("ignored-paths", "subdir", "undef.js"); + const engine = new ESLint({ + ignorePatterns: ["undef.js"], + overrideConfigFile: true, + cwd + }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide1/a.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide2/a.js"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide3/a.js"))); + assert(!await engine.isPathIgnored(filePath)); }); - it("should not include comments in ignore rules", async () => { + it("should return true for file matching a child of an ignore pattern", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithComments"); - const engine = new ESLint({ ignorePath, cwd }); + const engine = new ESLint({ ignorePatterns: ["ignore-pattern"], cwd }); - assert(!await engine.isPathIgnored("# should be ignored")); - assert(await engine.isPathIgnored("this_one_not")); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); }); - it("should ignore a non-negated pattern", async () => { + it("should return true for file matching a grandchild of a directory when the pattern is directory/**", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new ESLint({ ignorePath, cwd }); + const engine = new ESLint({ ignorePatterns: ["ignore-pattern/**"], cwd }); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "ignore.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.js"))); }); - it("should not ignore a negated pattern", async () => { + it("should return false for file not matching any ignore pattern", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new ESLint({ ignorePath, cwd }); + const engine = new ESLint({ ignorePatterns: ["failing.js"], cwd }); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "unignore.js"))); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "unignored.js"))); }); - // https://github.com/eslint/eslint/issues/15642 - it("should correctly handle patterns with escaped brackets", async () => { + it("two globstar '**' ignore pattern should ignore files in nested directories", async () => { const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithEscapedBrackets"); - const engine = new ESLint({ ignorePath, cwd }); - - const subdir = "brackets"; - - assert( - !await engine.isPathIgnored(getFixturePath("ignored-paths", subdir, "index.js")), - `'${subdir}/index.js' should not be ignored` - ); - - for (const filename of ["[index.js", "index].js", "[index].js"]) { - assert( - await engine.isPathIgnored(getFixturePath("ignored-paths", subdir, filename)), - `'${subdir}/${filename}' should be ignored` - ); - } + const engine = new ESLint({ + overrideConfigFile: true, + ignorePatterns: ["**/*.js"], + cwd + }); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js")), "foo.js should be ignored"); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.js")), "foo/bar.js should be ignored"); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.js")), "foo/bar/baz.js"); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.cjs")), "foo.cjs should not be ignored"); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.cjs")), "foo/bar.cjs should not be ignored"); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.cjs")), "foo/bar/baz.cjs should not be ignored"); }); }); - describe("with --ignore-path option and --ignore-pattern option", () => { + describe("with config ignores ignorePatterns option", () => { it("should return false for ignored file when unignored with ignore pattern", async () => { const cwd = getFixturePath("ignored-paths"); const engine = new ESLint({ - ignorePath: getFixturePath("ignored-paths", ".eslintignore"), - overrideConfig: { - ignorePatterns: "!sampleignorepattern" - }, + overrideConfigFile: getFixturePath("eslint.config-with-ignores2.js"), + ignorePatterns: ["!undef.js"], cwd }); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "sampleignorepattern"))); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); }); @@ -5510,7 +4696,7 @@ describe("ESLint", () => { await assert.rejects(async () => { await engine.loadFormatter("special"); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`), "u")); + }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}.js\nError: Cannot find module '${fullFormatterPath}.js'`), "u")); }); it("should throw if the required formatter exists but has an error", async () => { @@ -5519,7 +4705,9 @@ describe("ESLint", () => { await assert.rejects(async () => { await engine.loadFormatter(formatterPath); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${formatterPath}\nError: Cannot find module 'this-module-does-not-exist'`), "u")); + + // for some reason, the error here contains multiple "there was a problem loading formatter" lines, so omitting + }, new RegExp(escapeStringRegExp("Error: Cannot find module 'this-module-does-not-exist'"), "u")); }); it("should throw if a non-string formatter name is passed", async () => { @@ -5529,94 +4717,71 @@ describe("ESLint", () => { await engine.loadFormatter(5); }, /'name' must be a string/u); }); - - it("should pass cwd to the `cwd` property of the second argument.", async () => { - const cwd = getFixturePath(); - const engine = new ESLint({ cwd }); - const formatterPath = getFixturePath("formatters", "cwd.js"); - const formatter = await engine.loadFormatter(formatterPath); - - assert.strictEqual(formatter.format([]), cwd); - }); }); describe("getErrorResults()", () => { + it("should report 5 error messages when looking for errors only", async () => { process.chdir(originalDir); const engine = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true + quotes: "error", + "no-var": "error", + "eol-last": "error", + "no-unused-vars": "error" } } }); const results = await engine.lintText("var foo = 'bar';"); const errorResults = ESLint.getErrorResults(results); - assert.strictEqual(errorResults[0].messages.length, 5); - assert.strictEqual(errorResults[0].errorCount, 5); - assert.strictEqual(errorResults[0].fixableErrorCount, 3); - assert.strictEqual(errorResults[0].fixableWarningCount, 0); - assert.strictEqual(errorResults[0].messages[0].ruleId, "strict"); + assert.strictEqual(errorResults[0].messages.length, 4, "messages.length is wrong"); + assert.strictEqual(errorResults[0].errorCount, 4, "errorCount is wrong"); + assert.strictEqual(errorResults[0].fixableErrorCount, 3, "fixableErrorCount is wrong"); + assert.strictEqual(errorResults[0].fixableWarningCount, 0, "fixableWarningCount is wrong"); + assert.strictEqual(errorResults[0].messages[0].ruleId, "no-var"); assert.strictEqual(errorResults[0].messages[0].severity, 2); - assert.strictEqual(errorResults[0].messages[1].ruleId, "no-var"); + assert.strictEqual(errorResults[0].messages[1].ruleId, "no-unused-vars"); assert.strictEqual(errorResults[0].messages[1].severity, 2); - assert.strictEqual(errorResults[0].messages[2].ruleId, "no-unused-vars"); + assert.strictEqual(errorResults[0].messages[2].ruleId, "quotes"); assert.strictEqual(errorResults[0].messages[2].severity, 2); - assert.strictEqual(errorResults[0].messages[3].ruleId, "quotes"); + assert.strictEqual(errorResults[0].messages[3].ruleId, "eol-last"); assert.strictEqual(errorResults[0].messages[3].severity, 2); - assert.strictEqual(errorResults[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(errorResults[0].messages[4].severity, 2); }); it("should not mutate passed report parameter", async () => { process.chdir(originalDir); const engine = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { - rules: { - quotes: [1, "double"], - "no-var": 2 - } + rules: { quotes: [1, "double"] } } }); const results = await engine.lintText("var foo = 'bar';"); const reportResultsLength = results[0].messages.length; - assert.strictEqual(results[0].messages.length, 2); - ESLint.getErrorResults(results); assert.strictEqual(results[0].messages.length, reportResultsLength); }); it("should report a warningCount of 0 when looking for errors only", async () => { - process.chdir(originalDir); const engine = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true + strict: ["error", "global"], + quotes: "error", + "no-var": "error", + "eol-last": "error", + "no-unused-vars": "error" } } }); - const results = await engine.lintText("var foo = 'bar';"); - const errorResults = ESLint.getErrorResults(results); + const lintResults = await engine.lintText("var foo = 'bar';"); + const errorResults = ESLint.getErrorResults(lintResults); assert.strictEqual(errorResults[0].warningCount, 0); assert.strictEqual(errorResults[0].fixableWarningCount, 0); @@ -5624,7 +4789,7 @@ describe("ESLint", () => { it("should return 0 error or warning messages even when the file has warnings", async () => { const engine = new ESLint({ - ignorePath: path.join(fixtureDir, ".eslintignore"), + overrideConfigFile: getFixturePath("eslint.config-with-ignores.js"), cwd: path.join(fixtureDir, "..") }); const options = { @@ -5638,15 +4803,12 @@ describe("ESLint", () => { assert.strictEqual(results.length, 1); assert.strictEqual(results[0].errorCount, 0); assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); }); it("should return source code of file in the `source` property", async () => { process.chdir(originalDir); const engine = new ESLint({ - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { rules: { quotes: [2, "double"] } } @@ -5654,754 +4816,699 @@ describe("ESLint", () => { const results = await engine.lintText("var foo = 'bar';"); const errorResults = ESLint.getErrorResults(results); - assert.strictEqual(errorResults[0].messages.length, 1); - assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); - }); - - it("should contain `output` property after fixes", async () => { - process.chdir(originalDir); - const engine = new ESLint({ - useEslintrc: false, - fix: true, - overrideConfig: { - rules: { - semi: 2, - "no-console": 2 - } - } - }); - const results = await engine.lintText("console.log('foo')"); - const errorResults = ESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 1); - assert.strictEqual(errorResults[0].output, "console.log('foo');"); - }); - }); - - describe("getRulesMetaForResults()", () => { - it("should return empty object when there are no linting errors", async () => { - const engine = new ESLint({ - useEslintrc: false - }); - - const rulesMeta = engine.getRulesMetaForResults([]); - - assert.deepStrictEqual(rulesMeta, {}); - }); - - it("should return one rule meta when there is a linting error", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results = await engine.lintText("a"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 1); - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return one rule meta when there is a suppressed linting error", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results = await engine.lintText("a // eslint-disable-line semi"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 1); - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return multiple rule meta when there are multiple linting errors", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"] - } - } - }); - - const results = await engine.lintText("'a'"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); - }); - - it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { - const customPlugin = { - rules: { - "no-var": require("../../../lib/rules/no-var") - } - }; - - const engine = new ESLint({ - useEslintrc: false, - plugins: { - "custom-plugin": customPlugin - }, - overrideConfig: { - plugins: ["custom-plugin"], - rules: { - "custom-plugin/no-var": 2, - semi: 2, - quotes: [2, "double"] - } - } - }); - - const results = await engine.lintText("var foo = 0; var bar = '1'"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); - assert.strictEqual( - rulesMeta["custom-plugin/no-var"], - customPlugin.rules["no-var"].meta - ); - }); - - it("should ignore messages not related to a rule", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - ignorePatterns: "ignored.js", - rules: { - "no-var": "warn" - } - }, - reportUnusedDisableDirectives: "warn" - }); - - { - const results = await engine.lintText("syntax error"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - { - const results = await engine.lintText("// eslint-disable-line no-var"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - { - const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } + assert.strictEqual(errorResults[0].messages.length, 1); + assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); }); - it("should return a non-empty value if some of the messages are related to a rule", async () => { + it("should contain `output` property after fixes", async () => { + process.chdir(originalDir); const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { rules: { "no-var": "warn" } }, - reportUnusedDisableDirectives: "warn" + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + semi: 2, + "no-console": 2 + } + } }); + const results = await engine.lintText("console.log('foo')"); + const errorResults = ESLint.getErrorResults(results); - const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); + assert.strictEqual(errorResults[0].messages.length, 1); + assert.strictEqual(errorResults[0].output, "console.log('foo');"); }); }); - describe("outputFixes()", () => { - afterEach(() => { - sinon.verifyAndRestore(); - }); + describe("findConfigFile()", () => { - it("should call fs.writeFile() for each result with output", async () => { - const fakeFS = { - writeFile: sinon.spy(callLastArgument) - }; - const spy = fakeFS.writeFile; - const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - fs: fakeFS + it("should return undefined when overrideConfigFile is true", async () => { + const engine = new ESLint({ + overrideConfigFile: true }); - const results = [ - { - filePath: path.resolve("foo.js"), - output: "bar" - }, - { - filePath: path.resolve("bar.js"), - output: "baz" - } - ]; - - await localESLint.outputFixes(results); - - assert.strictEqual(spy.callCount, 2); - assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar", sinon.match.func), "First call was incorrect."); - assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz", sinon.match.func), "Second call was incorrect."); + assert.strictEqual(await engine.findConfigFile(), void 0); }); - it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { - const fakeFS = { - writeFile: sinon.spy(callLastArgument) - }; - const spy = fakeFS.writeFile; - const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - fs: fakeFS + it("should return undefined when a config file isn't found", async () => { + const engine = new ESLint({ + cwd: path.resolve(__dirname, "../../../../") }); - const results = [ - { - filePath: path.resolve("foo.js"), - output: "bar" - }, - { - filePath: path.resolve("abc.js") - }, - { - filePath: path.resolve("bar.js"), - output: "baz" - } - ]; - - await localESLint.outputFixes(results); - assert.strictEqual(spy.callCount, 2); - assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar", sinon.match.func), "First call was incorrect."); - assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz", sinon.match.func), "Second call was incorrect."); + assert.strictEqual(await engine.findConfigFile(), void 0); }); - it("should throw if non object array is given to 'results' parameter", async () => { - await assert.rejects(() => ESLint.outputFixes(null), /'results' must be an array/u); - await assert.rejects(() => ESLint.outputFixes([null]), /'results' must include only objects/u); + it("should return custom config file path when overrideConfigFile is a nonempty string", async () => { + const engine = new ESLint({ + overrideConfigFile: "my-config.js" + }); + const configFilePath = path.resolve(__dirname, "../../../my-config.js"); + + assert.strictEqual(await engine.findConfigFile(), configFilePath); }); - }); - describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { - it("should report a violation for disabling rules", async () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - ignore: true, - useEslintrc: false, - allowInlineConfig: false, - overrideConfig: { - env: { browser: true }, - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - } - }; - const eslintCLI = new ESLint(config); - const results = await eslintCLI.lintText(code); - const messages = results[0].messages; + it("should return root level eslint.config.js when overrideConfigFile is null", async () => { + const engine = new ESLint({ + overrideConfigFile: null + }); + const configFilePath = path.resolve(__dirname, "../../../eslint.config.js"); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(await engine.findConfigFile(), configFilePath); }); - it("should not report a violation by default", async () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - ignore: true, - useEslintrc: false, - allowInlineConfig: true, - overrideConfig: { - env: { browser: true }, - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - } - }; - const eslintCLI = new ESLint(config); - const results = await eslintCLI.lintText(code); - const messages = results[0].messages; + it("should return root level eslint.config.js when overrideConfigFile is not specified", async () => { + const engine = new ESLint(); + const configFilePath = path.resolve(__dirname, "../../../eslint.config.js"); - assert.strictEqual(messages.length, 0); + assert.strictEqual(await engine.findConfigFile(), configFilePath); }); + }); - describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { - it("should report problems for unused eslint-disable directives", async () => { - const eslint = new ESLint({ useEslintrc: false, reportUnusedDisableDirectives: "error" }); + describe("getRulesMetaForResults()", () => { - assert.deepStrictEqual( - await eslint.lintText("/* eslint-disable */"), - [ + it("should throw an error when this instance did not lint any files", async () => { + const engine = new ESLint({ + overrideConfigFile: true + }); + + assert.throws(() => { + engine.getRulesMetaForResults([ { - filePath: "", + filePath: "path/to/file.js", messages: [ { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, + ruleId: "curly", + severity: 2, + message: "Expected { after 'if' condition.", + line: 2, column: 1, - fix: { - range: [0, 20], - text: " " - }, + nodeType: "IfStatement" + }, + { + ruleId: "no-process-exit", severity: 2, - nodeType: null + message: "Don't use process.exit(); throw an error instead.", + line: 3, + column: 1, + nodeType: "CallExpression" } ], suppressedMessages: [], - errorCount: 1, + errorCount: 2, warningCount: 0, fatalErrorCount: 0, - fixableErrorCount: 1, + fixableErrorCount: 0, fixableWarningCount: 0, - source: "/* eslint-disable */", - usedDeprecatedRules: [] + source: + "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n" } - ] - ); + ]); + }, { + constructor: TypeError, + message: "Results object was not created from this ESLint instance." + }); }); - }); - describe("when retrieving version number", () => { - it("should return current version number", () => { - const eslintCLI = require("../../../lib/eslint").ESLint; - const version = eslintCLI.version; + it("should throw an error when results were created from a different instance", async () => { + const engine1 = new ESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo"), + overrideConfig: { + rules: { + semi: 2 + } + } + }); + const engine2 = new ESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "bar"), + overrideConfig: { + rules: { + semi: 2 + } + } + }); - assert.strictEqual(typeof version, "string"); - assert(parseInt(version[0], 10) >= 3); + const results1 = await engine1.lintText("1", { filePath: "file.js" }); + const results2 = await engine2.lintText("2", { filePath: "file.js" }); + + engine1.getRulesMetaForResults(results1); // should not throw an error + assert.throws(() => { + engine1.getRulesMetaForResults(results2); + }, { + constructor: TypeError, + message: "Results object was not created from this ESLint instance." + }); }); - }); - describe("mutability", () => { - describe("plugins", () => { - it("Loading plugin in one instance doesn't mutate to another instance", async () => { - const filePath = getFixturePath("single-quoted.js"); - const engine1 = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - plugins: ["example"], - rules: { "example/example-rule": 1 } + it("should treat a result without `filePath` as if the file was located in `cwd`", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo", "bar"), + ignorePatterns: ["*/**"], // ignore all subdirectories of `cwd` + overrideConfig: { + rules: { + eqeqeq: "warn" } - }); - const engine2 = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const fileConfig1 = await engine1.calculateConfigForFile(filePath); - const fileConfig2 = await engine2.calculateConfigForFile(filePath); + } + }); - // plugin - assert.deepStrictEqual(fileConfig1.plugins, ["example"], "Plugin is present for engine 1"); - assert.deepStrictEqual(fileConfig2.plugins, [], "Plugin is not present for engine 2"); + const results = await engine.lintText("a==b"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta.eqeqeq, coreRules.get("eqeqeq").meta); + }); + + it("should not throw an error if a result without `filePath` contains an ignored file warning", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo", "bar"), + ignorePatterns: ["**"] }); + + const results = await engine.lintText("", { warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); }); - describe("rules", () => { - it("Loading rules in one instance doesn't mutate to another instance", async () => { - const filePath = getFixturePath("single-quoted.js"); - const engine1 = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { rules: { "example/example-rule": 1 } } - }); - const engine2 = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const fileConfig1 = await engine1.calculateConfigForFile(filePath); - const fileConfig2 = await engine2.calculateConfigForFile(filePath); + it("should not throw an error if results contain linted files and one ignored file", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + cwd: getFixturePath(), + ignorePatterns: ["passing*"], + overrideConfig: { + rules: { + "no-undef": 2, + semi: 1 + } + } + }); - // plugin - assert.deepStrictEqual(fileConfig1.rules["example/example-rule"], [1], "example is present for engine 1"); - assert.strictEqual(fileConfig2.rules["example/example-rule"], void 0, "example is not present for engine 2"); + const results = await engine.lintFiles(["missing-semicolon.js", "passing.js", "undef.js"]); + + assert( + results.some(({ messages }) => messages.some(({ message, ruleId }) => !ruleId && message.startsWith("File ignored"))), + "At least one file should be ignored but none is." + ); + + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta["no-undef"], coreRules.get("no-undef").meta); + assert.deepStrictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return empty object when there are no linting errors", async () => { + const engine = new ESLint({ + overrideConfigFile: true }); + + const rulesMeta = engine.getRulesMetaForResults([]); + + assert.deepStrictEqual(rulesMeta, {}); }); - }); - describe("with ignorePatterns config", () => { - const root = getFixturePath("cli-engine/ignore-patterns"); + it("should return one rule meta when there is a linting error", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2 + } + } + }); - describe("ignorePatterns can add an ignore pattern ('foo.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: "foo.js" - }, - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" + const results = await engine.lintText("a", { filePath: "foo.js" }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return one rule meta when there is a suppressed linting error", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2 + } } }); - beforeEach(prepare); - afterEach(cleanup); + const results = await engine.lintText("a // eslint-disable-line semi"); + const rulesMeta = engine.getRulesMetaForResults(results); - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); + it("should return multiple rule meta when there are multiple linting errors", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"] + } + } }); - it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + const results = await engine.lintText("'a'"); + const rulesMeta = engine.getRulesMetaForResults(results); - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); + }); + + it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { + const customPlugin = { + rules: { + "no-var": require("../../../lib/rules/no-var") + } + }; + const engine = new ESLint({ + overrideConfigFile: true, + overrideConfig: { + plugins: { + "custom-plugin": customPlugin + }, + rules: { + "custom-plugin/no-var": 2, + semi: 2, + quotes: [2, "double"] + } + } }); - it("'lintFiles()' should not verify 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); + const results = await engine.lintText("var foo = 0; var bar = '1'"); + const rulesMeta = engine.getRulesMetaForResults(results); - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js"), - path.join(root, "subdir/bar.js") - ]); - }); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); + assert.strictEqual( + rulesMeta["custom-plugin/no-var"], + customPlugin.rules["no-var"].meta + ); }); - describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: ["foo.js", "/bar.js"] + it("should ignore messages not related to a rule", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + ignorePatterns: ["ignored.js"], + overrideConfig: { + rules: { + "no-var": "warn" }, - "foo.js": "", - "bar.js": "", - "baz.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "", - "subdir/baz.js": "" + linterOptions: { + reportUnusedDisableDirectives: "warn" + } } }); - beforeEach(prepare); - afterEach(cleanup); + { + const results = await engine.lintText("syntax error"); + const rulesMeta = engine.getRulesMetaForResults(results); - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("// eslint-disable-line no-var"); + const rulesMeta = engine.getRulesMetaForResults(results); - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - }); + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); - it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + assert.deepStrictEqual(rulesMeta, {}); + } + }); - assert.strictEqual(await engine.isPathIgnored("bar.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); + it("should return a non-empty value if some of the messages are related to a rule", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + overrideConfig: { rules: { "no-var": "warn" }, linterOptions: { reportUnusedDisableDirectives: "warn" } } }); - it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); + const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); + const rulesMeta = engine.getRulesMetaForResults(results); - assert.deepStrictEqual(filePaths, [ - path.join(root, "baz.js"), - path.join(root, "subdir/bar.js"), - path.join(root, "subdir/baz.js") - ]); - }); + assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); }); - describe("ignorePatterns can unignore '/node_modules/foo'.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: "!/node_modules/foo" - }, - "node_modules/foo/index.js": "", - "node_modules/foo/.dot.js": "", - "node_modules/bar/index.js": "", - "foo.js": "" - } + it("should return empty object if all messages are related to unknown rules", async () => { + const engine = new ESLint({ + overrideConfigFile: true }); - beforeEach(prepare); - afterEach(cleanup); + const results = await engine.lintText("// eslint-disable-line foo, bar/baz, bar/baz/qux"); - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + assert.strictEqual(results[0].messages.length, 3); + assert.strictEqual(results[0].messages[0].ruleId, "foo"); + assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); + assert.strictEqual(results[0].messages[2].ruleId, "bar/baz/qux"); - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); - }); + const rulesMeta = engine.getRulesMetaForResults(results); - it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + assert.strictEqual(Object.keys(rulesMeta).length, 0); + }); - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), true); + it("should return object with meta of known rules if some messages are related to unknown rules", async () => { + const engine = new ESLint({ + overrideConfigFile: true, + overrideConfig: { rules: { "no-var": "warn" } } }); - it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + const results = await engine.lintText("// eslint-disable-line foo, bar/baz, bar/baz/qux\nvar x;"); - assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); - }); + assert.strictEqual(results[0].messages.length, 4); + assert.strictEqual(results[0].messages[0].ruleId, "foo"); + assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); + assert.strictEqual(results[0].messages[2].ruleId, "bar/baz/qux"); + assert.strictEqual(results[0].messages[3].ruleId, "no-var"); - it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); + const rulesMeta = engine.getRulesMetaForResults(results); - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js"), - path.join(root, "node_modules/foo/index.js") - ]); - }); + assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); }); + }); - describe("ignorePatterns can unignore '.eslintrc.js'.", () => { + describe("outputFixes()", () => { + afterEach(() => { + sinon.verifyAndRestore(); + }); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "!.eslintrc.js" - })}`, - "foo.js": "" + it("should call fs.writeFile() for each result with output", async () => { + const fakeFS = { + writeFile: sinon.spy(() => Promise.resolve()) + }; + const spy = fakeFS.writeFile; + const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { + fs: { + promises: fakeFS } }); - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".eslintrc.js"), false); - }); + const results = [ + { + filePath: path.resolve("foo.js"), + output: "bar" + }, + { + filePath: path.resolve("bar.js"), + output: "baz" + } + ]; - it("'lintFiles()' should verify '.eslintrc.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); + await localESLint.outputFixes(results); - assert.deepStrictEqual(filePaths, [ - path.join(root, ".eslintrc.js"), - path.join(root, "foo.js") - ]); - }); + assert.strictEqual(spy.callCount, 2); + assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar"), "First call was incorrect."); + assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz"), "Second call was incorrect."); }); - describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "!.*" - })}`, - ".eslintignore": ".foo*", - ".foo.js": "", - ".bar.js": "" + it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { + const fakeFS = { + writeFile: sinon.spy(() => Promise.resolve()) + }; + const spy = fakeFS.writeFile; + const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { + fs: { + promises: fakeFS } }); + const results = [ + { + filePath: path.resolve("foo.js"), + output: "bar" + }, + { + filePath: path.resolve("abc.js") + }, + { + filePath: path.resolve("bar.js"), + output: "baz" + } + ]; - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".foo.js"), true); - }); + await localESLint.outputFixes(results); - it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + assert.strictEqual(spy.callCount, 2, "Call count was wrong"); + assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar"), "First call was incorrect."); + assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz"), "Second call was incorrect."); + }); - assert.strictEqual(await engine.isPathIgnored(".bar.js"), false); - }); + it("should throw if non object array is given to 'results' parameter", async () => { + await assert.rejects(() => ESLint.outputFixes(null), /'results' must be an array/u); + await assert.rejects(() => ESLint.outputFixes([null]), /'results' must include only objects/u); + }); + }); - it("'lintFiles()' should not verify re-ignored '.foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); + describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { + it("should report a violation for disabling rules", async () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert" + ].join("\n"); + const config = { + ignore: true, + overrideConfigFile: true, + allowInlineConfig: false, + overrideConfig: { + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0 + } + } + }; + const eslintCLI = new ESLint(config); + const results = await eslintCLI.lintText(code); + const messages = results[0].messages; - assert.deepStrictEqual(filePaths, [ - path.join(root, ".bar.js"), - path.join(root, ".eslintrc.js") - ]); - }); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(results[0].suppressedMessages.length, 0); }); - describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "*.js" - })}`, - ".eslintignore": "!foo.js", - "foo.js": "", - "bar.js": "" + it("should not report a violation by default", async () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert" + ].join("\n"); + const config = { + ignore: true, + overrideConfigFile: true, + allowInlineConfig: true, + overrideConfig: { + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0 + } } - }); + }; + const eslintCLI = new ESLint(config); + const results = await eslintCLI.lintText(code); + const messages = results[0].messages; - beforeEach(prepare); - afterEach(cleanup); + assert.strictEqual(messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 1); + assert.strictEqual(results[0].suppressedMessages[0].ruleId, "no-alert"); + }); + }); - it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { + it("should report problems for unused eslint-disable directives", async () => { + const eslint = new ESLint({ overrideConfigFile: true, overrideConfig: { linterOptions: { reportUnusedDisableDirectives: "error" } } }); - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - }); + assert.deepStrictEqual( + await eslint.lintText("/* eslint-disable */"), + [ + { + filePath: "", + messages: [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " " + }, + severity: 2, + nodeType: null + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + source: "/* eslint-disable */", + usedDeprecatedRules: [] + } + ] + ); + }); + }); - it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + describe("when retrieving version number", () => { + it("should return current version number", () => { + const eslintCLI = require("../../../lib/eslint/eslint").ESLint; + const version = eslintCLI.version; - assert.strictEqual(await engine.isPathIgnored("bar.js"), true); - }); + assert.strictEqual(typeof version, "string"); + assert(parseInt(version[0], 10) >= 3); + }); + }); - it("'lintFiles()' should verify unignored 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); + describe("mutability", () => { - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js") - ]); + describe("rules", () => { + it("Loading rules in one instance doesn't mutate to another instance", async () => { + const filePath = getFixturePath("single-quoted.js"); + const engine1 = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + plugins: { + example: { + rules: { + "example-rule"() { + return {}; + } + } + } + }, + rules: { "example/example-rule": 1 } + } + }); + const engine2 = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true + }); + const fileConfig1 = await engine1.calculateConfigForFile(filePath); + const fileConfig2 = await engine2.calculateConfigForFile(filePath); + + // plugin + assert.deepStrictEqual(fileConfig1.rules["example/example-rule"], [1], "example is present for engine 1"); + assert.strictEqual(fileConfig2.rules, void 0, "example is not present for engine 2"); }); }); + }); - describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => { + describe("configs with 'ignores' and without 'files'", () => { + + // https://github.com/eslint/eslint/issues/17103 + describe("config with ignores: ['error.js']", () => { + const cwd = getFixturePath("config-with-ignores-without-files"); const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd, files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "foo.js" - }), - "subdir/.eslintrc.json": JSON.stringify({ - ignorePatterns: "bar.js" - }), - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "", - "subdir/subsubdir/foo.js": "", - "subdir/subsubdir/bar.js": "" + "eslint.config.js": `module.exports = [ + { + rules: { + "no-unused-vars": "error", + }, + }, + { + ignores: ["error.js"], + rules: { + "no-unused-vars": "warn", + }, + }, + ];`, + "error.js": "let unusedVar;", + "warn.js": "let unusedVar;" } }); - beforeEach(prepare); afterEach(cleanup); - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/subsubdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + it("should apply to all files except for 'error.js'", async () => { + const engine = new ESLint({ + cwd + }); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/subsubdir/bar.js"), true); - }); + const results = await engine.lintFiles("{error,warn}.js"); - it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", async () => { - const engine = new ESLint({ cwd: getPath() }); + assert.strictEqual(results.length, 2); - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - }); + const [errorResult, warnResult] = results; - it("'lintFiles()' should verify 'bar.js' in the outside of 'subdir'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); + assert.strictEqual(errorResult.filePath, path.join(getPath(), "error.js")); + assert.strictEqual(errorResult.messages.length, 1); + assert.strictEqual(errorResult.messages[0].ruleId, "no-unused-vars"); + assert.strictEqual(errorResult.messages[0].severity, 2); - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); + assert.strictEqual(warnResult.filePath, path.join(getPath(), "warn.js")); + assert.strictEqual(warnResult.messages.length, 1); + assert.strictEqual(warnResult.messages[0].ruleId, "no-unused-vars"); + assert.strictEqual(warnResult.messages[0].severity, 1); }); }); - describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => { + describe("config with ignores: ['**/*.json']", () => { + const cwd = getFixturePath("config-with-ignores-without-files"); const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd, files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "foo.js" - }), - "subdir/.eslintrc.json": JSON.stringify({ - ignorePatterns: "!foo.js" - }), + "eslint.config.js": `module.exports = [ + { + rules: { + "no-undef": "error", + }, + }, + { + ignores: ["**/*.json"], + rules: { + "no-unused-vars": "error", + }, + }, + ];`, "foo.js": "", - "subdir/foo.js": "" + "foo.json": "" } }); beforeEach(prepare); afterEach(cleanup); - it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - }); + it("should not add json files as lint targets", async () => { + const engine = new ESLint({ + cwd + }); - it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); + const results = await engine.lintFiles("foo*"); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); + // should not lint `foo.json` + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, path.join(getPath(), "foo.js")); }); + }); - it("'lintFiles()' should verify 'foo.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); + }); - assert.deepStrictEqual(filePaths, [ - path.join(root, "subdir/foo.js") - ]); - }); - }); + describe("with ignores config", () => { + const root = getFixturePath("cli-engine/ignore-patterns"); - describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => { + describe("ignores can add an ignore pattern ('foo.js').", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: root, files: { - ".eslintrc.json": JSON.stringify({}), - "subdir/.eslintrc.json": JSON.stringify({ - ignorePatterns: "*.js" - }), - ".eslintignore": "!foo.js", + "eslint.config.js": `module.exports = { + ignores: ["**/foo.js"] + };`, "foo.js": "", + "bar.js": "", "subdir/foo.js": "", "subdir/bar.js": "" } @@ -6410,237 +5517,234 @@ describe("ESLint", () => { beforeEach(prepare); afterEach(cleanup); - it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); }); - it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { + it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); }); - it("'lintFiles()' should verify unignored 'foo.js'.", async () => { + it("'lintFiles()' should not verify 'foo.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); const filePaths = (await engine.lintFiles("**/*.js")) .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js"), - path.join(root, "subdir/foo.js") + path.join(root, "bar.js"), + path.join(root, "eslint.config.js"), + path.join(root, "subdir/bar.js") ]); }); }); - describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => { + describe("ignores can add ignore patterns ('**/foo.js', '/bar.js').", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: root + Date.now(), files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "foo.js" - }), - "subdir/.eslintrc.json": JSON.stringify({ - root: true, - ignorePatterns: "bar.js" - }), + "eslint.config.js": `module.exports = { + ignores: ["**/foo.js", "bar.js"] + };`, "foo.js": "", "bar.js": "", + "baz.js": "", "subdir/foo.js": "", - "subdir/bar.js": "" + "subdir/bar.js": "", + "subdir/baz.js": "" } }); beforeEach(prepare); afterEach(cleanup); - it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => { + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); }); - it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - }); - - it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => { + it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); + assert.strictEqual(await engine.isPathIgnored("bar.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); }); - it("'lintFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", async () => { + it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); const filePaths = (await engine.lintFiles("**/*.js")) .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js"), - path.join(root, "subdir/foo.js") + path.join(getPath(), "baz.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "subdir/bar.js"), + path.join(getPath(), "subdir/baz.js") ]); }); }); - describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => { + + describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/'].", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-unignores`, files: { - ".eslintrc.json": JSON.stringify({}), - "subdir/.eslintrc.json": JSON.stringify({ - root: true, - ignorePatterns: "bar.js" - }), - ".eslintignore": "foo.js", - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" + "eslint.config.js": `module.exports = { + ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/"] + };`, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "" } }); beforeEach(prepare); afterEach(cleanup); - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); }); - it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => { + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), false); }); - it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => { + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); + assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); }); - it("'lintFiles()' should verify 'bar.js' in the root directory.", async () => { + it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); const filePaths = (await engine.lintFiles("**/*.js")) .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo.js"), + path.join(getPath(), "node_modules/foo/.dot.js"), + path.join(getPath(), "node_modules/foo/index.js") ]); }); }); - describe("ignorePatterns in the shareable config should be used.", () => { + describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/**'].", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-unignores`, files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "foo.js" - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one" - }), - "foo.js": "", - "bar.js": "" + "eslint.config.js": `module.exports = { + ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/**"] + };`, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "" } }); beforeEach(prepare); afterEach(cleanup); - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); }); - it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), false); }); - it("'lintFiles()' should verify 'bar.js'.", async () => { + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) + + assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); + }); + + it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { + const engine = new ESLint({ cwd: getPath() }); + const result = (await engine.lintFiles("**/*.js")); + + const filePaths = result .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo.js"), + path.join(getPath(), "node_modules/foo/.dot.js"), + path.join(getPath(), "node_modules/foo/index.js") ]); }); }); - describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => { - + describe("ignore pattern can re-ignore files that are unignored by a previous pattern.", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-reignore`, files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "/foo.js" + "eslint.config.js": `module.exports = ${JSON.stringify({ + ignores: ["!.*", ".foo*"] })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one" - }), - "foo.js": "", - "subdir/foo.js": "" + ".foo.js": "", + ".bar.js": "" } }); beforeEach(prepare); afterEach(cleanup); - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored(".foo.js"), true); }); - it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", async () => { + it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); + assert.strictEqual(await engine.isPathIgnored(".bar.js"), false); }); - it("'lintFiles()' should verify 'subdir/foo.js'.", async () => { + it("'lintFiles()' should not lint re-ignored '.foo.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); const filePaths = (await engine.lintFiles("**/*.js")) .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "subdir/foo.js") + path.join(getPath(), ".bar.js"), + path.join(getPath(), "eslint.config.js") ]); }); }); - describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => { + describe("ignore pattern can unignore files that are ignored by a previous pattern.", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}-dignore`, files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "*.js" + "eslint.config.js": `module.exports = ${JSON.stringify({ + ignores: ["**/*.js", "!foo.js"] })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one", - ignorePatterns: "!bar.js" - }), "foo.js": "", "bar.js": "" } @@ -6649,38 +5753,38 @@ describe("ESLint", () => { beforeEach(prepare); afterEach(cleanup); - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("foo.js"), false); }); - it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + assert.strictEqual(await engine.isPathIgnored("bar.js"), true); }); - it("'lintFiles()' should verify 'bar.js'.", async () => { + it("'lintFiles()' should verify unignored 'foo.js'.", async () => { const engine = new ESLint({ cwd: getPath() }); const filePaths = (await engine.lintFiles("**/*.js")) .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") + path.join(getPath(), "foo.js") ]); }); }); - describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => { + describe("ignores in a config file should not be used if ignore: false.", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: root, files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "*.js" - }), + "eslint.config.js": `module.exports = { + ignores: ["*.js"] + }`, "foo.js": "" } }); @@ -6701,57 +5805,26 @@ describe("ESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ + path.join(root, "eslint.config.js"), path.join(root, "foo.js") ]); }); }); - describe("ignorePatterns in overrides section is not allowed.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - overrides: [ - { - files: "*.js", - ignorePatterns: "foo.js" - } - ] - })}`, - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should throw a configuration error.", async () => { - await assert.rejects(async () => { - const engine = new ESLint({ cwd: getPath() }); - - await engine.lintFiles("*.js"); - }, /Unexpected top-level property "overrides\[0\]\.ignorePatterns"/u); - }); - }); }); - describe("'overrides[].files' adds lint targets", () => { + describe("config.files adds lint targets", () => { const root = getFixturePath("cli-engine/additional-lint-targets"); - describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => { + describe("if { files: 'foo/*.txt', ignores: '**/ignore.txt' } is present,", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: root + 1, files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "foo/*.txt", - excludedFiles: "**/ignore.txt" - } - ] - }), + "eslint.config.js": `module.exports = [{ + files: ["foo/*.txt"], + ignores: ["**/ignore.txt"] + }];`, "foo/nested/test.txt": "", "foo/test.js": "", "foo/test.txt": "", @@ -6775,10 +5848,11 @@ describe("ESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") + path.join(getPath(), "bar/test.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "foo/test.txt"), + path.join(getPath(), "test.js") ]); }); @@ -6789,108 +5863,72 @@ describe("ESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "test.js") + path.join(getPath(), "bar/test.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "test.js") ]); }); }); - describe("if { files: 'foo/**/*.txt' } is present,", () => { - + describe("if { files: 'foo/*.txt', ignores: '**/ignore.txt' } is present and subdirectory is passed,", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: root + 2, files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "foo/**/*.txt" - } - ] - }), + "eslint.config.js": `module.exports = [{ + files: ["foo/*.txt"], + ignores: ["**/ignore.txt"] + }];`, "foo/nested/test.txt": "", "foo/test.js": "", "foo/test.txt": "", + "foo/ignore.txt": "", "bar/test.js": "", "bar/test.txt": "", + "bar/ignore.txt": "", "test.js": "", - "test.txt": "" + "test.txt": "", + "ignore.txt": "" } }); beforeEach(prepare); afterEach(cleanup); - it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) + const filePaths = (await engine.lintFiles("foo")) .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "foo/test.txt") ]); }); - }); - - describe("if { files: 'foo/**/*' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "foo/**/*" - } - ] - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) + const filePaths = (await engine.lintFiles("foo/*.js")) .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "test.js") + path.join(getPath(), "foo/test.js") ]); }); }); - describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => { + describe("if { files: 'foo/**/*.txt' } is present,", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: root + 3, files: { - "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({ - overrides: [ - { - files: "foo/**/*.txt" - } - ] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "foo" - }), + "eslint.config.js": `module.exports = [ + { + files: ["foo/**/*.txt"] + } + ]`, "foo/nested/test.txt": "", "foo/test.js": "", "foo/test.txt": "", @@ -6911,32 +5949,26 @@ describe("ESLint", () => { .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") + path.join(getPath(), "bar/test.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo/nested/test.txt"), + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "foo/test.txt"), + path.join(getPath(), "test.js") ]); }); }); - describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => { + describe("if { files: 'foo/**/*' } is present,", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: root + 4, files: { - "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({ - bar: { - overrides: [ - { - files: "foo/**/*.txt" - } - ] + "eslint.config.js": `module.exports = [ + { + files: ["foo/**/*"] } - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "plugin:foo/bar" - }), + ]`, "foo/nested/test.txt": "", "foo/test.js": "", "foo/test.txt": "", @@ -6950,40 +5982,38 @@ describe("ESLint", () => { beforeEach(prepare); afterEach(cleanup); - it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { const engine = new ESLint({ cwd: getPath() }); const filePaths = (await engine.lintFiles(".")) .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") + path.join(getPath(), "bar/test.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "test.js") ]); }); }); + }); - describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => { + describe("'ignores', 'files' of the configuration that the '--config' option provided should be resolved from CWD.", () => { const root = getFixturePath("cli-engine/config-and-overrides-files"); - describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}a1`, files: { - "node_modules/myconf/.eslintrc.json": { - overrides: [ - { - files: "foo/*.js", - rules: { - eqeqeq: "error" - } + "node_modules/myconf/eslint.config.js": `module.exports = [ + { + files: ["foo/*.js"], + rules: { + eqeqeq: "error" } - ] - }, + } + ];`, "node_modules/myconf/foo/test.js": "a == b", "foo/test.js": "a == b" } @@ -6992,18 +6022,18 @@ describe("ESLint", () => { beforeEach(prepare); afterEach(cleanup); - it("'lintFiles()' with 'foo/test.js' should use the override entry.", async () => { + it("'lintFiles()' with 'foo/test.js' should use the files entry.", async () => { const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", + overrideConfigFile: "node_modules/myconf/eslint.config.js", cwd: getPath(), - ignore: false, - useEslintrc: false + ignore: false }); const results = await engine.lintFiles("foo/test.js"); // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { + suppressedMessages: [], errorCount: 1, filePath: path.join(getPath(), "foo/test.js"), fixableErrorCount: 0, @@ -7021,7 +6051,6 @@ describe("ESLint", () => { severity: 2 } ], - suppressedMessages: [], source: "a == b", usedDeprecatedRules: [], warningCount: 0, @@ -7030,48 +6059,53 @@ describe("ESLint", () => { ]); }); - it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", async () => { + it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the files entry.", async () => { const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: root, - ignore: false, - useEslintrc: false + overrideConfigFile: "node_modules/myconf/eslint.config.js", + cwd: getPath(), + ignore: false }); const results = await engine.lintFiles("node_modules/myconf/foo/test.js"); // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { + suppressedMessages: [], errorCount: 0, filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), fixableErrorCount: 0, fixableWarningCount: 0, - messages: [], - suppressedMessages: [], + messages: [ + { + ruleId: null, + fatal: false, + message: "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.", + severity: 1, + nodeType: null + } + ], usedDeprecatedRules: [], - warningCount: 0, + warningCount: 1, fatalErrorCount: 0 } ]); }); }); - describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + describe("if { files: '*', ignores: 'foo/*.txt', ... } is present by '--config bar/myconf/eslint.config.js',", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, + cwd: `${root}a2`, files: { - "node_modules/myconf/.eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "*", - excludedFiles: "foo/*.js", - rules: { - eqeqeq: "error" - } + "bar/myconf/eslint.config.js": `module.exports = [ + { + files: ["**/*"], + ignores: ["foo/*.js"], + rules: { + eqeqeq: "error" } - ] - }), - "node_modules/myconf/foo/test.js": "a == b", + } + ]`, + "bar/myconf/foo/test.js": "a == b", "foo/test.js": "a == b" } }); @@ -7079,24 +6113,23 @@ describe("ESLint", () => { beforeEach(prepare); afterEach(cleanup); - it("'lintFiles()' with 'foo/test.js' should NOT use the override entry.", async () => { + it("'lintFiles()' with 'foo/test.js' should have no errors because no rules are enabled.", async () => { const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: root, - ignore: false, - useEslintrc: false + overrideConfigFile: "bar/myconf/eslint.config.js", + cwd: getPath(), + ignore: false }); const results = await engine.lintFiles("foo/test.js"); // Expected to be no errors because the file matches to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { + suppressedMessages: [], errorCount: 0, filePath: path.join(getPath(), "foo/test.js"), fixableErrorCount: 0, fixableWarningCount: 0, messages: [], - suppressedMessages: [], usedDeprecatedRules: [], warningCount: 0, fatalErrorCount: 0 @@ -7104,20 +6137,20 @@ describe("ESLint", () => { ]); }); - it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", async () => { + it("'lintFiles()' with 'bar/myconf/foo/test.js' should have an error because eqeqeq is enabled.", async () => { const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: root, - ignore: false, - useEslintrc: false + overrideConfigFile: "bar/myconf/eslint.config.js", + cwd: getPath(), + ignore: false }); - const results = await engine.lintFiles("node_modules/myconf/foo/test.js"); + const results = await engine.lintFiles("bar/myconf/foo/test.js"); // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { + suppressedMessages: [], errorCount: 1, - filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), + filePath: path.join(getPath(), "bar/myconf/foo/test.js"), fixableErrorCount: 0, fixableWarningCount: 0, messages: [ @@ -7126,467 +6159,313 @@ describe("ESLint", () => { endColumn: 5, endLine: 1, line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - suppressedMessages: [], - source: "a == b", - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/myconf/.eslintrc.json": JSON.stringify({ - ignorePatterns: ["!/node_modules/myconf", "foo/*.js"], - rules: { - eqeqeq: "error" - } - }), - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { - const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: getPath(), - useEslintrc: false - }); - const files = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(files, [ - path.join(root, "node_modules/myconf/foo/test.js") - ]); - }); - }); - }); - - describe("plugin conflicts", () => { - let uid = 0; - const root = getFixturePath("cli-engine/plugin-conflicts-"); - - /** - * Verify thrown errors. - * @param {() => Promise} f The function to run and throw. - * @param {Record} props The properties to verify. - * @returns {Promise} void - */ - async function assertThrows(f, props) { - try { - await f(); - } catch (error) { - for (const [key, value] of Object.entries(props)) { - assert.deepStrictEqual(error[key], value, key); - } - return; - } - - assert.fail("Function should throw an error, but not."); - } - - describe("between a config file and linear extendees.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - extends: ["two"], - plugins: ["foo"] - })}`, - "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: ["one"], - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => { - const engine = new ESLint({ cwd: getPath() }); - - await engine.lintFiles("test.js"); - }); - }); - - describe("between a config file and same-depth extendees.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: ["one", "two"], - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => { - const engine = new ESLint({ cwd: getPath() }); - - await engine.lintFiles("test.js"); + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2 + } + ], + source: "a == b", + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0 + } + ]); }); }); - describe("between two config files in different directories, with single node_modules.", () => { - + describe("if { ignores: 'foo/*.js', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, + cwd: `${root}a3`, files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" + "node_modules/myconf/eslint.config.js": `module.exports = [{ + ignores: ["!node_modules", "node_modules/*", "!node_modules/myconf", "foo/*.js"], + }, { + rules: { + eqeqeq: "error" + } + }]`, + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b" } }); beforeEach(prepare); afterEach(cleanup); - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => { - const engine = new ESLint({ cwd: getPath() }); + it("'lintFiles()' with '**/*.js' should lint 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { + const engine = new ESLint({ + overrideConfigFile: "node_modules/myconf/eslint.config.js", + cwd: getPath() + }); + const files = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); - await engine.lintFiles("subdir/test.js"); + assert.deepStrictEqual(files, [ + path.join(getPath(), "node_modules/myconf/eslint.config.js"), + path.join(getPath(), "node_modules/myconf/foo/test.js") + ]); }); }); + }); - describe("between two config files in different directories, with multiple node_modules.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" + describe("baseConfig", () => { + it("can be an object", async () => { + const eslint = new ESLint({ + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2 + } } }); - beforeEach(prepare); - afterEach(cleanup); + const [{ messages }] = await eslint.lintText("foo"); - it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => { - const engine = new ESLint({ cwd: getPath() }); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); - await assertThrows( - () => engine.lintFiles("subdir/test.js"), + it("can be an array", async () => { + const eslint = new ESLint({ + overrideConfigFile: true, + baseConfig: [ { - message: `Plugin "foo" was conflicted between "subdir${path.sep}.eslintrc.json" and ".eslintrc.json".`, - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"), - importerName: `subdir${path.sep}.eslintrc.json` - }, - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: ".eslintrc.json" - } - ] + rules: { + "no-var": 2 + } + }, + { + rules: { + semi: 2 } } - ); + ] }); - }); - describe("between '--config' option and a regular config file, with single node_modules.", () => { + const [{ messages }] = await eslint.lintText("var foo"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "test.js": "" + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-var"); + assert.strictEqual(messages[1].ruleId, "semi"); + }); + + it("should be inserted after default configs", async () => { + const eslint = new ESLint({ + overrideConfigFile: true, + baseConfig: { + languageOptions: { + ecmaVersion: 5, + sourceType: "script" + } } }); - beforeEach(prepare); - afterEach(cleanup); + const [{ messages }] = await eslint.lintText("let x"); - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - overrideConfigFile: "node_modules/mine/.eslintrc.json" - }); + /* + * if baseConfig was inserted before default configs, + * `ecmaVersion: "latest"` from default configs would overwrite + * `ecmaVersion: 5` from baseConfig, so this wouldn't be a parsing error. + */ - await engine.lintFiles("test.js"); - }); + assert.strictEqual(messages.length, 1); + assert(messages[0].fatal, "Fatal error expected."); }); - describe("between '--config' option and a regular config file, with multiple node_modules.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "test.js": "" + it("should be inserted before configs from the config file", async () => { + const eslint = new ESLint({ + cwd: getFixturePath(), + baseConfig: { + rules: { + strict: ["error", "global"] + }, + languageOptions: { + sourceType: "script" + } } }); - beforeEach(prepare); - afterEach(cleanup); + const [{ messages }] = await eslint.lintText("foo"); - it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - overrideConfigFile: "node_modules/mine/.eslintrc.json" - }); + /* + * if baseConfig was inserted after configs from the config file, + * `strict: 0` from eslint.config.js wouldn't overwrite `strict: ["error", "global"]` + * from baseConfig, so there would be an error message from the `strict` rule. + */ - await assertThrows( - () => engine.lintFiles("test.js"), - { - message: "Plugin \"foo\" was conflicted between \"--config\" and \".eslintrc.json\".", - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "node_modules/mine/node_modules/eslint-plugin-foo/index.js"), - importerName: "--config" - }, - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: ".eslintrc.json" - } - ] - } + assert.strictEqual(messages.length, 0); + }); + + it("should be inserted before overrideConfig", async () => { + const eslint = new ESLint({ + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2 } - ); + }, + overrideConfig: { + rules: { + semi: 1 + } + } }); - }); - describe("between '--plugin' option and a regular config file, with single node_modules.", () => { + const [{ messages }] = await eslint.lintText("foo"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("should be inserted before configs from the config file and overrideConfig", async () => { + const eslint = new ESLint({ + overrideConfigFile: getFixturePath("eslint.config-with-rules.js"), + baseConfig: { + rules: { + quotes: ["error", "double"], + semi: "error" + } + }, + overrideConfig: { + rules: { + quotes: "warn" + } } }); + const [{ messages }] = await eslint.lintText('const foo = "bar"'); - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - overrideConfig: { plugins: ["foo"] } - }); + /* + * baseConfig: { quotes: ["error", "double"], semi: "error" } + * eslint.config-with-rules.js: { quotes: ["error", "single"] } + * overrideConfig: { quotes: "warn" } + * + * Merged config: { quotes: ["warn", "single"], semi: "error" } + */ - await engine.lintFiles("subdir/test.js"); - }); + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[1].ruleId, "semi"); + assert.strictEqual(messages[1].severity, 2); }); - describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => { + it("when it has 'files' they should be interpreted as relative to the config file", async () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new ESLint({ + cwd: getFixturePath("plugins"), + baseConfig: { + files: ["plugins/a.js"], + rules: { + semi: 2 + } } }); - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - overrideConfig: { plugins: ["foo"] } - }); + const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js") }); - await assertThrows( - () => engine.lintFiles("subdir/test.js"), - { - message: `Plugin "foo" was conflicted between "CLIOptions" and "subdir${path.sep}.eslintrc.json".`, - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: "CLIOptions" - }, - { - filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"), - importerName: `subdir${path.sep}.eslintrc.json` - } - ] - } - } - ); - }); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); }); - describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => { + it("when it has 'ignores' they should be interpreted as relative to the config file", async () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new ESLint({ + cwd: getFixturePath("plugins"), + baseConfig: { + ignores: ["plugins/a.js"] } }); - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - resolvePluginsRelativeTo: getPath() - }); + const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js"), warnIgnored: true }); - await engine.lintFiles("subdir/test.js"); - }); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.match(messages[0].message, /ignored/u); }); + }); - describe("between two config files with different target files.", () => { + describe("config file", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, + it("new instance of ESLint should use the latest version of the config file (ESM)", async () => { + const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); + const configFileContent = "export default [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, files: { - "one/node_modules/eslint-plugin-foo/index.js": "", - "one/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "one/test.js": "", - "two/node_modules/eslint-plugin-foo/index.js": "", - "two/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "two/test.js": "" + "package.json": '{ "type": "module" }', + "eslint.config.js": configFileContent, + "a.js": "foo\nbar;" } }); - beforeEach(prepare); - afterEach(cleanup); + await teardown.prepare(); - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", async () => { - const engine = new ESLint({ cwd: getPath() }); - const results = await engine.lintFiles("*/test.js"); + let eslint = new ESLint({ cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); - assert.strictEqual(results.length, 2); - }); - }); - }); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); - describe("loading rules", () => { - it("should not load unused core rules", done => { - let calledDone = false; + await sleep(100); + await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); - const cwd = getFixturePath("lazy-loading-rules"); - const pattern = "foo.js"; - const usedRules = ["semi"]; + eslint = new ESLint({ cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); - const forkedProcess = childProcess.fork( - path.join(__dirname, "../../_utils/test-lazy-loading-rules.js"), - [cwd, pattern, String(usedRules)] - ); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); + }); - // this is an error message - forkedProcess.on("message", ({ message, stack }) => { - if (calledDone) { - return; + it("new instance of ESLint should use the latest version of the config file (CJS)", async () => { + const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); + const configFileContent = "module.exports = [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, + files: { + "eslint.config.js": configFileContent, + "a.js": "foo\nbar;" } - calledDone = true; + }); - const error = new Error(message); + await teardown.prepare(); - error.stack = stack; - done(error); - }); + let eslint = new ESLint({ cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); - forkedProcess.on("exit", exitCode => { - if (calledDone) { - return; - } - calledDone = true; + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); - if (exitCode === 0) { - done(); - } else { - done(new Error("Forked process exited with a non-zero exit code")); - } - }); + await sleep(100); + await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); + + eslint = new ESLint({ cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); }); }); @@ -7610,9 +6489,11 @@ describe("ESLint", () => { it("should pass cwd with backslashes to rules", async () => { const engine = new ESLint({ cwd: cwdForwardSlash, - useEslintrc: false, + overrideConfigFile: true, overrideConfig: { - plugins: ["test"], + plugins: { + test: require(path.join(cwd, "node_modules", "eslint-plugin-test")) + }, rules: { "test/report-cwd": "error" } @@ -7635,4 +6516,145 @@ describe("ESLint", () => { }); }); } + + describe("config with circular references", () => { + it("in 'settings'", async () => { + let resolvedSettings = null; + + const circular = {}; + + circular.self = circular; + + const eslint = new ESLint({ + overrideConfigFile: true, + baseConfig: { + settings: { + sharedData: circular + }, + rules: { + "test-plugin/test-rule": 1 + } + }, + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create(context) { + resolvedSettings = context.settings; + return { }; + } + } + } + } + } + }); + + await eslint.lintText("debugger;"); + + assert.deepStrictEqual(resolvedSettings.sharedData, circular); + }); + + it("in 'parserOptions'", async () => { + let resolvedParserOptions = null; + + const circular = {}; + + circular.self = circular; + + const eslint = new ESLint({ + overrideConfigFile: true, + baseConfig: { + languageOptions: { + parser: { + parse(text, parserOptions) { + resolvedParserOptions = parserOptions; + } + }, + parserOptions: { + testOption: circular + } + } + } + }); + + await eslint.lintText("debugger;"); + + assert.deepStrictEqual(resolvedParserOptions.testOption, circular); + }); + }); + +}); + +describe("shouldUseFlatConfig", () => { + + /** + * Check that `shouldUseFlatConfig` returns the expected value from a CWD + * with a flat config and one without a flat config. + * @param {boolean} expectedValueWithConfig the expected return value of + * `shouldUseFlatConfig` when in a directory with a flat config present + * @param {boolean} expectedValueWithoutConfig the expected return value of + * `shouldUseFlatConfig` when in a directory without any flat config present + * @returns {void} + */ + function testShouldUseFlatConfig(expectedValueWithConfig, expectedValueWithoutConfig) { + describe("when there is a flat config file present", () => { + const originalDir = process.cwd(); + + beforeEach(() => { + process.chdir(__dirname); + }); + + afterEach(() => { + process.chdir(originalDir); + }); + + it(`is \`${expectedValueWithConfig}\``, async () => { + assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithConfig); + }); + }); + + describe("when there is no flat config file present", () => { + const originalDir = process.cwd(); + + beforeEach(() => { + process.chdir(os.tmpdir()); + }); + + afterEach(() => { + process.chdir(originalDir); + }); + + it(`is \`${expectedValueWithoutConfig}\``, async () => { + assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithoutConfig); + }); + }); + } + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'true'`", () => { + beforeEach(() => { + process.env.ESLINT_USE_FLAT_CONFIG = true; + }); + + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + + testShouldUseFlatConfig(true, true); + }); + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'false'`", () => { + beforeEach(() => { + process.env.ESLINT_USE_FLAT_CONFIG = false; + }); + + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + + testShouldUseFlatConfig(false, false); + }); + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is unset", () => { + testShouldUseFlatConfig(true, true); + }); }); diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js deleted file mode 100644 index 72b7dfefebc..00000000000 --- a/tests/lib/eslint/flat-eslint.js +++ /dev/null @@ -1,6660 +0,0 @@ -/** - * @fileoverview Tests for the ESLint class. - * @author Kai Cataldo - * @author Toru Nagashima - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("assert"); -const util = require("util"); -const fs = require("fs"); -const fsp = fs.promises; -const os = require("os"); -const path = require("path"); -const escapeStringRegExp = require("escape-string-regexp"); -const fCache = require("file-entry-cache"); -const sinon = require("sinon"); -const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); -const shell = require("shelljs"); -const hash = require("../../../lib/cli-engine/hash"); -const { unIndent, createCustomTeardown } = require("../../_utils"); -const { shouldUseFlatConfig } = require("../../../lib/eslint/flat-eslint"); -const coreRules = require("../../../lib/rules"); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Creates a directory if it doesn't already exist. - * @param {string} dirPath The path to the directory that should exist. - * @returns {void} - */ -function ensureDirectoryExists(dirPath) { - try { - fs.statSync(dirPath); - } catch { - fs.mkdirSync(dirPath); - } -} - -/** - * Does nothing for a given time. - * @param {number} time Time in ms. - * @returns {void} - */ -async function sleep(time) { - await util.promisify(setTimeout)(time); -} - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("FlatESLint", () => { - const examplePluginName = "eslint-plugin-example"; - const examplePluginNameWithNamespace = "@eslint/eslint-plugin-example"; - const examplePlugin = { - rules: { - "example-rule": require("../../fixtures/rules/custom-rule"), - "make-syntax-error": require("../../fixtures/rules/make-syntax-error-rule") - } - }; - const examplePreprocessorName = "eslint-plugin-processor"; - const originalDir = process.cwd(); - const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); - - /** @type {import("../../../lib/eslint/flat-eslint").FlatESLint} */ - let FlatESLint; - - /** - * Returns the path inside of the fixture directory. - * @param {...string} args file path segments. - * @returns {string} The path inside the fixture directory. - * @private - */ - function getFixturePath(...args) { - const filepath = path.join(fixtureDir, ...args); - - try { - return fs.realpathSync(filepath); - } catch { - return filepath; - } - } - - /** - * Create the ESLint object by mocking some of the plugins - * @param {Object} options options for ESLint - * @returns {ESLint} engine object - * @private - */ - function eslintWithPlugins(options) { - return new FlatESLint({ - ...options, - plugins: { - [examplePluginName]: examplePlugin, - [examplePluginNameWithNamespace]: examplePlugin, - [examplePreprocessorName]: require("../../fixtures/processors/custom-processor") - } - }); - } - - // copy into clean area so as not to get "infected" by this project's .eslintrc files - before(function() { - - /* - * GitHub Actions Windows and macOS runners occasionally exhibit - * extremely slow filesystem operations, during which copying fixtures - * exceeds the default test timeout, so raise it just for this hook. - * Mocha uses `this` to set timeouts on an individual hook level. - */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API - shell.mkdir("-p", fixtureDir); - shell.cp("-r", "./tests/fixtures/.", fixtureDir); - }); - - beforeEach(() => { - ({ FlatESLint } = require("../../../lib/eslint/flat-eslint")); - }); - - after(() => { - shell.rm("-r", fixtureDir); - }); - - describe("ESLint constructor function", () => { - it("the default value of 'options.cwd' should be the current working directory.", async () => { - process.chdir(__dirname); - try { - const engine = new FlatESLint(); - const results = await engine.lintFiles("eslint.js"); - - assert.strictEqual(path.dirname(results[0].filePath), __dirname); - } finally { - process.chdir(originalDir); - } - }); - - it("should normalize 'options.cwd'.", async () => { - const cwd = getFixturePath("example-app3"); - const engine = new FlatESLint({ - cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` - overrideConfigFile: true, - overrideConfig: { - plugins: { - test: require(path.join(cwd, "node_modules", "eslint-plugin-test")) - }, - rules: { - "test/report-cwd": "error" - } - } - }); - const results = await engine.lintText(""); - - assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); - assert.strictEqual(results[0].messages[0].message, cwd); - - const formatter = await engine.loadFormatter("cwd"); - - assert.strictEqual(formatter.format(results), cwd); - }); - - // https://github.com/eslint/eslint/issues/2380 - it("should not modify baseConfig when format is specified", () => { - const customBaseConfig = { root: true }; - - new FlatESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new -- Check for argument side effects - - assert.deepStrictEqual(customBaseConfig, { root: true }); - }); - - it("should throw readable messages if removed options are present", () => { - assert.throws( - () => new FlatESLint({ - cacheFile: "", - configFile: "", - envs: [], - globals: [], - ignorePath: ".gitignore", - ignorePattern: [], - parser: "", - parserOptions: {}, - rules: {}, - plugins: [], - reportUnusedDisableDirectives: "error" - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- Unknown options: cacheFile, configFile, envs, globals, ignorePath, ignorePattern, parser, parserOptions, rules, reportUnusedDisableDirectives" - ].join("\n")), "u") - ); - }); - - it("should throw readable messages if wrong type values are given to options", () => { - assert.throws( - () => new FlatESLint({ - allowInlineConfig: "", - baseConfig: "", - cache: "", - cacheLocation: "", - cwd: "foo", - errorOnUnmatchedPattern: "", - fix: "", - fixTypes: ["xyz"], - globInputPaths: "", - ignore: "", - ignorePatterns: "", - overrideConfig: "", - overrideConfigFile: "", - plugins: "", - warnIgnored: "", - ruleFilter: "" - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- 'allowInlineConfig' must be a boolean.", - "- 'baseConfig' must be an object or null.", - "- 'cache' must be a boolean.", - "- 'cacheLocation' must be a non-empty string.", - "- 'cwd' must be an absolute path.", - "- 'errorOnUnmatchedPattern' must be a boolean.", - "- 'fix' must be a boolean or a function.", - "- 'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".", - "- 'globInputPaths' must be a boolean.", - "- 'ignore' must be a boolean.", - "- 'ignorePatterns' must be an array of non-empty strings or null.", - "- 'overrideConfig' must be an object or null.", - "- 'overrideConfigFile' must be a non-empty string, null, or true.", - "- 'plugins' must be an object or null.", - "- 'warnIgnored' must be a boolean.", - "- 'ruleFilter' must be a function." - ].join("\n")), "u") - ); - }); - - it("should throw readable messages if 'ignorePatterns' is not an array of non-empty strings.", () => { - const invalidIgnorePatterns = [ - () => {}, - false, - {}, - "", - "foo", - [[]], - [() => {}], - [false], - [{}], - [""], - ["foo", ""], - ["foo", "", "bar"], - ["foo", false, "bar"] - ]; - - invalidIgnorePatterns.forEach(ignorePatterns => { - assert.throws( - () => new FlatESLint({ ignorePatterns }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- 'ignorePatterns' must be an array of non-empty strings or null." - ].join("\n")), "u") - ); - }); - }); - - it("should throw readable messages if 'plugins' option contains empty key", () => { - assert.throws( - () => new FlatESLint({ - plugins: { - "eslint-plugin-foo": {}, - "eslint-plugin-bar": {}, - "": {} - } - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- 'plugins' must not include an empty string." - ].join("\n")), "u") - ); - }); - }); - - describe("lintText()", () => { - let eslint; - - it("should report the total and per file errors when using local cwd eslint.config.js", async () => { - eslint = new FlatESLint({ - cwd: __dirname - }); - - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 4); - assert.strictEqual(results[0].messages[0].ruleId, "no-var"); - assert.strictEqual(results[0].messages[1].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[2].ruleId, "quotes"); - assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 3); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 2); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report the total and per file warnings when using local cwd .eslintrc", async () => { - eslint = new FlatESLint({ - overrideConfig: { - rules: { - quotes: 1, - "no-var": 1, - "eol-last": 1, - "no-unused-vars": 1 - } - }, - overrideConfigFile: true - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 4); - assert.strictEqual(results[0].messages[0].ruleId, "no-var"); - assert.strictEqual(results[0].messages[1].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[2].ruleId, "quotes"); - assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 3); - assert.strictEqual(results[0].usedDeprecatedRules.length, 2); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report one message when using specific config file", async () => { - eslint = new FlatESLint({ - overrideConfigFile: "fixtures/configurations/quotes-error.js", - cwd: getFixturePath("..") - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 1); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report the filename when passed in", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var foo = 'bar';", options); - - assert.strictEqual(results[0].filePath, getFixturePath("test.js")); - }); - - it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config-with-ignores.js" - }); - - const options = { filePath: "fixtures/passing.js", warnIgnored: true }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false, but lintText warnIgnored is true", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config-with-ignores.js", - warnIgnored: false - }); - - const options = { filePath: "fixtures/passing.js", warnIgnored: true }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config-with-ignores.js" - }); - const options = { - filePath: "fixtures/passing.js", - warnIgnored: false - }; - - // intentional parsing error - const results = await eslint.lintText("va r bar = foo;", options); - - // should not report anything because the file is ignored - assert.strictEqual(results.length, 0); - }); - - it("should not return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config-with-ignores.js", - warnIgnored: false - }); - const options = { filePath: "fixtures/passing.js" }; - const results = await eslint.lintText("var bar = foo;", options); - - // should not report anything because the warning is suppressed - assert.strictEqual(results.length, 0); - }); - - it("should show excluded file warnings by default", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config-with-ignores.js" - }); - const options = { filePath: "fixtures/passing.js" }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - }); - - it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - ignore: false, - overrideConfigFile: "fixtures/eslint.config-with-ignores.js", - overrideConfig: { - rules: { - "no-undef": 2 - } - } - }); - const options = { filePath: "fixtures/passing.js" }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return a message and fixed text when in fix mode", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - semi: 2 - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "passing.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("passing.js"), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var bar = foo;", - usedDeprecatedRules: [ - { - ruleId: "semi", - replacedBy: [] - } - ] - } - ]); - }); - - it("should return a message and omit fixed text when in fix mode and fixes aren't done", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - "no-undef": 2 - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "passing.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("passing.js"), - messages: [ - { - ruleId: "no-undef", - severity: 2, - messageId: "undef", - message: "'foo' is not defined.", - line: 1, - column: 11, - endLine: 1, - endColumn: 14, - nodeType: "Identifier" - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar = foo", - usedDeprecatedRules: [] - } - ]); - }); - - it("should not delete code if there is a syntax error after trying to autofix.", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - "example/make-syntax-error": "error" - } - }, - ignore: false, - cwd: getFixturePath(".") - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("test.js"), - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token is", - line: 1, - column: 19, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var bar = foothis is a syntax error.", - usedDeprecatedRules: [] - } - ]); - }); - - it("should not crash even if there are any syntax error since the first time.", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - "example/make-syntax-error": "error" - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var bar =", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("test.js"), - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token", - line: 1, - column: 10, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar =", - usedDeprecatedRules: [] - } - ]); - }); - - it("should return source code of file in `source` property when errors are present", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { semi: 2 } - } - }); - const results = await eslint.lintText("var foo = 'bar'"); - - assert.strictEqual(results[0].source, "var foo = 'bar'"); - }); - - it("should return source code of file in `source` property when warnings are present", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { semi: 1 } - } - }); - const results = await eslint.lintText("var foo = 'bar'"); - - assert.strictEqual(results[0].source, "var foo = 'bar'"); - }); - - - it("should not return a `source` property when no errors or warnings are present", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { semi: 2 } - } - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].source, void 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not return a `source` property when fixes are applied", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - semi: 2, - "no-unused-vars": 2 - } - } - }); - const results = await eslint.lintText("var msg = 'hi' + foo\n"); - - assert.strictEqual(results[0].source, void 0); - assert.strictEqual(results[0].output, "var msg = 'hi' + foo;\n"); - }); - - it("should return a `source` property when a parsing error has occurred", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { eqeqeq: 2 } - } - }); - const results = await eslint.lintText("var bar = foothis is a syntax error.\n return bar;"); - - assert.deepStrictEqual(results, [ - { - filePath: "", - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token is", - line: 1, - column: 19, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar = foothis is a syntax error.\n return bar;", - usedDeprecatedRules: [] - } - ]); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should respect default ignore rules (ignoring node_modules), even with --no-ignore", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - ignore: false - }); - const results = await eslint.lintText("var bar = foo;", { filePath: "node_modules/passing.js", warnIgnored: true }); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js")); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn when deprecated rules are found in a config", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/eslint.config.js" - }); - const [result] = await eslint.lintText("foo"); - - assert.deepStrictEqual( - result.usedDeprecatedRules, - [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] - ); - }); - - it("should throw if eslint.config.js file is not present", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("..") - }); - await assert.rejects(() => eslint.lintText("var foo = 'bar';"), /Could not find config file/u); - }); - - it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: true - }); - await eslint.lintText("var foo = 'bar';"); - }); - - it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/configurations/quotes-error.js" - }); - await eslint.lintText("var foo = 'bar';"); - }); - - it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(""), - overrideConfigFile: "does-not-exist.js" - }); - await assert.rejects(() => eslint.lintText("var foo = 'bar';"), { code: "ENOENT" }); - }); - - it("should throw if non-string value is given to 'code' parameter", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText(100), /'code' must be a string/u); - }); - - it("should throw if non-object value is given to 'options' parameter", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", "foo.js"), /'options' must be an object, null, or undefined/u); - }); - - it("should throw if 'options' argument contains unknown key", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option\(s\): filename/u); - }); - - it("should throw if non-string value is given to 'options.filePath' option", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { filePath: "" }), /'options.filePath' must be a non-empty string or undefined/u); - }); - - it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { warnIgnored: "" }), /'options.warnIgnored' must be a boolean or undefined/u); - }); - - it("should work with config file that exports a promise", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("promise-config") - }); - const results = await eslint.lintText('var foo = "bar";'); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - }); - - describe("Alternate config files", () => { - - it("should find eslint.config.mjs when present", async () => { - - const cwd = getFixturePath("mjs-config"); - - eslint = new FlatESLint({ - cwd - }); - - const results = await eslint.lintText("foo"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - - }); - - it("should find eslint.config.cjs when present", async () => { - - const cwd = getFixturePath("cjs-config"); - - eslint = new FlatESLint({ - cwd - }); - - const results = await eslint.lintText("foo"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - - }); - - it("should favor eslint.config.js when eslint.config.mjs and eslint.config.cjs are present", async () => { - - const cwd = getFixturePath("js-mjs-cjs-config"); - - eslint = new FlatESLint({ - cwd - }); - - const results = await eslint.lintText("foo"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should favor eslint.config.mjs when eslint.config.cjs is present", async () => { - - const cwd = getFixturePath("mjs-cjs-config"); - - eslint = new FlatESLint({ - cwd - }); - - const results = await eslint.lintText("foo"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - }); - }); - }); - - describe("lintFiles()", () => { - - /** @type {InstanceType} */ - let eslint; - - it("should use correct parser when custom parser is specified", async () => { - const filePath = path.resolve(__dirname, "../../fixtures/configurations/parser/custom.js"); - - eslint = new FlatESLint({ - cwd: originalDir, - ignore: false, - overrideConfigFile: true, - overrideConfig: { - languageOptions: { - parser: require(filePath) - } - } - }); - - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].message, "Parsing error: Boom!"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report zero messages when given a config file and a valid file", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/simple-valid-project/eslint.config.js" - }); - const results = await eslint.lintFiles(["tests/fixtures/simple-valid-project/**/foo*.js"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should handle multiple patterns with overlapping files", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/simple-valid-project/eslint.config.js" - }); - const results = await eslint.lintFiles([ - "tests/fixtures/simple-valid-project/**/foo*.js", - "tests/fixtures/simple-valid-project/foo.?s", - "tests/fixtures/simple-valid-project/{foo,src/foobar}.js" - ]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report zero messages when given a config file and a valid file and espree as parser", async () => { - eslint = new FlatESLint({ - overrideConfig: { - languageOptions: { - parser: require("espree"), - parserOptions: { - ecmaVersion: 2021 - } - } - }, - overrideConfigFile: true - }); - const results = await eslint.lintFiles(["lib/cli.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { - eslint = new FlatESLint({ - overrideConfig: { - languageOptions: { - parser: require("esprima") - } - }, - overrideConfigFile: true, - ignore: false - }); - const results = await eslint.lintFiles(["tests/fixtures/passing.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should throw if eslint.config.js file is not present", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("..") - }); - await assert.rejects(() => eslint.lintFiles("fixtures/undef*.js"), /Could not find config file/u); - }); - - it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: true - }); - await eslint.lintFiles("fixtures/undef*.js"); - }); - - it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/configurations/quotes-error.js" - }); - await eslint.lintFiles("fixtures/undef*.js"); - }); - - it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - overrideConfigFile: "does-not-exist.js" - }); - await assert.rejects(() => eslint.lintFiles("undef*.js"), { code: "ENOENT" }); - }); - - it("should throw an error when given a config file and a valid file and invalid parser", async () => { - eslint = new FlatESLint({ - overrideConfig: { - languageOptions: { - parser: "test11" - } - }, - overrideConfigFile: true - }); - - await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); - }); - - describe("Invalid inputs", () => { - - [ - ["a string with a single space", " "], - ["an array with one empty string", [""]], - ["an array with two empty strings", ["", ""]], - ["undefined", void 0] - ].forEach(([name, value]) => { - - it(`should throw an error when passed ${name}`, async () => { - eslint = new FlatESLint({ - overrideConfigFile: true - }); - - await assert.rejects(async () => await eslint.lintFiles(value), /'patterns' must be a non-empty string or an array of non-empty strings/u); - }); - }); - - }); - - describe("Normalized inputs", () => { - - [ - ["an empty string", ""], - ["an empty array", []] - - ].forEach(([name, value]) => { - - it(`should normalize to '.' when ${name} is passed`, async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("files"), - overrideConfig: { files: ["**/*.js"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - }); - const results = await eslint.lintFiles(value); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].filePath, getFixturePath("files/.bar.js")); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].filePath, getFixturePath("files/foo.js")); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it(`should return an empty array when ${name} is passed with passOnNoPatterns: true`, async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("files"), - overrideConfig: { files: ["**/*.js"] }, - overrideConfigFile: getFixturePath("eslint.config.js"), - passOnNoPatterns: true - }); - const results = await eslint.lintFiles(value); - - assert.strictEqual(results.length, 0); - }); - }); - - }); - - it("should report zero messages when given a directory with a .js2 file", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("eslint.config.js"), - overrideConfig: { - files: ["**/*.js2"] - } - }); - const results = await eslint.lintFiles([getFixturePath("files/foo.js2")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report zero messages when given a directory with a .js and a .js2 file", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - }); - const results = await eslint.lintFiles(["fixtures/files/"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/16413 - it("should find files and report zero messages when given a parent directory with a .js", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("example-app/subdir") - }); - const results = await eslint.lintFiles(["../*.js"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/16038 - it("should allow files patterns with '..' inside", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("dots-in-files") - }); - const results = await eslint.lintFiles(["."]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("dots-in-files/a..b.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - - // https://github.com/eslint/eslint/issues/16299 - it("should only find files in the subdir1 directory when given a directory name", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("example-app2") - }); - const results = await eslint.lintFiles(["subdir1"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("example-app2/subdir1/a.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/14742 - it("should run", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("{curly-path}", "server") - }); - const results = await eslint.lintFiles(["src/**/*.{js,json}"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-console"); - assert.strictEqual( - results[0].filePath, - getFixturePath("{curly-path}/server/src/two.js") - ); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should work with config file that exports a promise", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("promise-config") - }); - const results = await eslint.lintFiles(["a*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("promise-config", "a.js")); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - }); - - // https://github.com/eslint/eslint/issues/16265 - describe("Dot files in searches", () => { - - it("should find dot files in current directory when a . pattern is used", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("dot-files") - }); - const results = await eslint.lintFiles(["."]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - it("should find dot files in current directory when a *.js pattern is used", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("dot-files") - }); - const results = await eslint.lintFiles(["*.js"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - it("should find dot files in current directory when a .a.js pattern is used", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("dot-files") - }); - const results = await eslint.lintFiles([".a.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - - // https://github.com/eslint/eslint/issues/16275 - describe("Glob patterns without matches", () => { - - it("should throw an error for a missing pattern when combined with a found pattern", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("example-app2") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1", "doesnotexist/*.js"]); - }, /No files matching 'doesnotexist\/\*\.js' were found/u); - }); - - it("should throw an error for an ignored directory pattern when combined with a found pattern", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - overrideConfig: { - ignores: ["subdir2"] - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); - }, /All files matched by 'subdir2\/\*\.js' are ignored/u); - }); - - it("should throw an error for an ignored file pattern when combined with a found pattern", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - overrideConfig: { - ignores: ["subdir2/*.js"] - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); - }, /All files matched by 'subdir2\/\*\.js' are ignored/u); - }); - - it("should always throw an error for the first unmatched file pattern", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - overrideConfig: { - ignores: ["subdir1/*.js", "subdir2/*.js"] - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["doesnotexist1/*.js", "doesnotexist2/*.js"]); - }, /No files matching 'doesnotexist1\/\*\.js' were found/u); - - await assert.rejects(async () => { - await eslint.lintFiles(["doesnotexist1/*.js", "subdir1/*.js"]); - }, /No files matching 'doesnotexist1\/\*\.js' were found/u); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1/*.js", "doesnotexist1/*.js"]); - }, /All files matched by 'subdir1\/\*\.js' are ignored/u); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); - }, /All files matched by 'subdir1\/\*\.js' are ignored/u); - }); - - it("should not throw an error for an ignored file pattern when errorOnUnmatchedPattern is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - overrideConfig: { - ignores: ["subdir2/*.js"] - }, - errorOnUnmatchedPattern: false - }); - - const results = await eslint.lintFiles(["subdir2/*.js"]); - - assert.strictEqual(results.length, 0); - }); - - it("should not throw an error for a non-existing file pattern when errorOnUnmatchedPattern is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - errorOnUnmatchedPattern: false - }); - - const results = await eslint.lintFiles(["doesexist/*.js"]); - - assert.strictEqual(results.length, 0); - }); - }); - - // https://github.com/eslint/eslint/issues/16260 - describe("Globbing based on configs", () => { - it("should report zero messages when given a directory with a .js and config file specifying a subdirectory", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("shallow-glob") - }); - const results = await eslint.lintFiles(["target-dir"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should glob for .jsx file in a subdirectory of the passed-in directory and not glob for any other patterns", async () => { - eslint = new FlatESLint({ - ignore: false, - overrideConfigFile: true, - overrideConfig: { - files: ["subdir/**/*.jsx", "target-dir/*.js"], - languageOptions: { - parserOptions: { - jsx: true - } - } - }, - cwd: getFixturePath("shallow-glob") - }); - const results = await eslint.lintFiles(["subdir/subsubdir"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("shallow-glob/subdir/subsubdir/broken.js")); - assert(results[0].messages[0].fatal, "Fatal error expected."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].filePath, getFixturePath("shallow-glob/subdir/subsubdir/plain.jsx")); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - }); - - it("should glob for all files in subdir when passed-in on the command line with a partial matching glob", async () => { - eslint = new FlatESLint({ - ignore: false, - overrideConfigFile: true, - overrideConfig: { - files: ["s*/subsubdir/*.jsx", "target-dir/*.js"], - languageOptions: { - parserOptions: { - jsx: true - } - } - }, - cwd: getFixturePath("shallow-glob") - }); - const results = await eslint.lintFiles(["subdir"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 1); - assert(results[0].messages[0].fatal, "Fatal error expected."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 1); - assert(results[0].messages[0].fatal, "Fatal error expected."); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - }); - - it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: path.join(fixtureDir, ".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - - }); - const results = await eslint.lintFiles(["fixtures/files/*"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - it("should resolve globs when 'globInputPaths' option is true", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - - }); - const results = await eslint.lintFiles(["fixtures/files/*"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - // only works on a Windows machine - if (os.platform() === "win32") { - - it("should resolve globs with Windows slashes when 'globInputPaths' option is true", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - - }); - const results = await eslint.lintFiles(["fixtures\\files\\*"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - } - - - it("should not resolve globs when 'globInputPaths' option is false", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: true, - globInputPaths: false - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["fixtures/files/*"]); - }, /No files matching 'fixtures\/files\/\*' were found \(glob was disabled\)\./u); - }); - - describe("Ignoring Files", () => { - - it("should report on a file in the node_modules folder passed explicitly, even if ignored by default", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const results = await eslint.lintFiles(["node_modules/foo.js"]); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report on a file in a node_modules subfolder passed explicitly, even if ignored by default", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const results = await eslint.lintFiles(["nested_node_modules/subdir/node_modules/text.js"]); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report on an ignored file with \"node_modules\" in its name", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - ignorePatterns: ["*.js"] - }); - const results = await eslint.lintFiles(["node_modules_cleaner.js"]); - const expectedMsg = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should suppress the warning when a file in the node_modules folder passed explicitly and warnIgnored is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - warnIgnored: false - }); - const results = await eslint.lintFiles(["node_modules/foo.js"]); - - assert.strictEqual(results.length, 0); - }); - - it("should report on globs with explicit inclusion of dotfiles", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } - }); - const results = await eslint.lintFiles(["hidden/.hiddenfolder/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - it("should ignore node_modules files when using ignore file", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - overrideConfigFile: true - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules' are ignored\./u); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should ignore node_modules files even with ignore: false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - ignore: false - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules' are ignored\./u); - }); - - it("should throw an error when all given files are ignored", async () => { - eslint = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config-with-ignores.js") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - it("should throw an error when all given files are ignored even with a `./` prefix", async () => { - eslint = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config-with-ignores.js") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - // https://github.com/eslint/eslint/issues/3788 - it("should ignore one-level down node_modules by default", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: [2, "double"] - } - }, - cwd: getFixturePath("cli-engine", "nested_node_modules") - }); - const results = await eslint.lintFiles(["."]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - // https://github.com/eslint/eslint/issues/3812 - it("should ignore all files and throw an error when **/fixtures/** is in `ignores` in the config file", async () => { - eslint = new FlatESLint({ - overrideConfigFile: getFixturePath("cli-engine/eslint.config-with-ignores2.js"), - overrideConfig: { - rules: { - quotes: [2, "double"] - } - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - it("should throw an error when all given files are ignored via ignorePatterns", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - ignorePatterns: ["tests/fixtures/single-quoted.js"] - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["tests/fixtures/*-quoted.js"]); - }, /All files matched by 'tests\/fixtures\/\*-quoted\.js' are ignored\./u); - }); - - it("should not throw an error when ignorePatterns is an empty array", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - ignorePatterns: [] - }); - - await assert.doesNotReject(async () => { - await eslint.lintFiles(["*.js"]); - }); - }); - - - it("should return a warning when an explicitly given file is ignored", async () => { - eslint = new FlatESLint({ - overrideConfigFile: "eslint.config-with-ignores.js", - cwd: getFixturePath() - }); - const filePath = getFixturePath("passing.js"); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should suppress the warning when an explicitly given file is ignored and warnIgnored is false", async () => { - eslint = new FlatESLint({ - overrideConfigFile: "eslint.config-with-ignores.js", - cwd: getFixturePath(), - warnIgnored: false - }); - const filePath = getFixturePath("passing.js"); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 0); - }); - - it("should return a warning about matching ignore patterns when an explicitly given dotfile is ignored", async () => { - eslint = new FlatESLint({ - overrideConfigFile: "eslint.config-with-ignores.js", - cwd: getFixturePath() - }); - const filePath = getFixturePath("dot-files/.a.js"); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return two messages when given a file in excluded files list while ignore is off", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - ignore: false, - overrideConfigFile: getFixturePath("eslint.config-with-ignores.js"), - overrideConfig: { - rules: { - "no-undef": 2 - } - } - }); - const filePath = fs.realpathSync(getFixturePath("undef.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[1].severity, 2); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/16300 - it("should process ignore patterns relative to basePath not cwd", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-relative/subdir") - }); - const results = await eslint.lintFiles(["**/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/subdir/a.js")); - }); - - // https://github.com/eslint/eslint/issues/16354 - it("should skip subdirectory files when ignore pattern matches deep subdirectory", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-directory") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir/**"]); - }, /All files matched by 'subdir\/\*\*' are ignored\./u); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir/subsubdir/**"]); - }, /All files matched by 'subdir\/subsubdir\/\*\*' are ignored\./u); - - const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); - assert.strictEqual(results[0].warningCount, 1); - assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); - - }); - - // https://github.com/eslint/eslint/issues/16414 - it("should skip subdirectory files when ignore pattern matches subdirectory", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-subdirectory") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir/**/*.js"]); - }, /All files matched by 'subdir\/\*\*\/\*\.js' are ignored\./u); - - const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-subdirectory/subdir/subsubdir/a.js")); - assert.strictEqual(results[0].warningCount, 1); - assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); - - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-subdirectory/subdir") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subsubdir/**/*.js"]); - }, /All files matched by 'subsubdir\/\*\*\/\*\.js' are ignored\./u); - - - }); - - // https://github.com/eslint/eslint/issues/16340 - it("should lint files even when cwd directory name matches ignores pattern", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-self") - }); - - const results = await eslint.lintFiles(["*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-self/eslint.config.js")); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - - }); - - // https://github.com/eslint/eslint/issues/16416 - it("should allow reignoring of previously ignored files", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-relative"), - overrideConfigFile: true, - overrideConfig: { - ignores: [ - "*.js", - "!a*.js", - "a.js" - ] - } - }); - const results = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/a.js")); - }); - - // https://github.com/eslint/eslint/issues/16415 - it("should allow directories to be unignored", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-directory"), - overrideConfigFile: true, - overrideConfig: { - ignores: [ - "subdir/*", - "!subdir/subsubdir" - ] - } - }); - const results = await eslint.lintFiles(["subdir/**/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); - }); - - - }); - - - it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { - eslint = new FlatESLint({ - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - ignore: false, - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true - }); - const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - it("should return one error message when given a config with rules with options and severity level set to error", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: ["error", "double"] - } - }, - ignore: false - }); - const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return 5 results when given a config and a directory of 5 valid files", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 1, - strict: 0 - } - } - }); - - const formattersDir = getFixturePath("formatters"); - const results = await eslint.lintFiles([formattersDir]); - - assert.strictEqual(results.length, 5); - assert.strictEqual(path.relative(formattersDir, results[0].filePath), "async.js"); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(path.relative(formattersDir, results[1].filePath), "broken.js"); - assert.strictEqual(results[1].errorCount, 0); - assert.strictEqual(results[1].warningCount, 0); - assert.strictEqual(results[1].fatalErrorCount, 0); - assert.strictEqual(results[1].fixableErrorCount, 0); - assert.strictEqual(results[1].fixableWarningCount, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(path.relative(formattersDir, results[2].filePath), "cwd.js"); - assert.strictEqual(results[2].errorCount, 0); - assert.strictEqual(results[2].warningCount, 0); - assert.strictEqual(results[2].fatalErrorCount, 0); - assert.strictEqual(results[2].fixableErrorCount, 0); - assert.strictEqual(results[2].fixableWarningCount, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - assert.strictEqual(path.relative(formattersDir, results[3].filePath), "simple.js"); - assert.strictEqual(results[3].errorCount, 0); - assert.strictEqual(results[3].warningCount, 0); - assert.strictEqual(results[3].fatalErrorCount, 0); - assert.strictEqual(results[3].fixableErrorCount, 0); - assert.strictEqual(results[3].fixableWarningCount, 0); - assert.strictEqual(results[3].messages.length, 0); - assert.strictEqual(results[3].suppressedMessages.length, 0); - assert.strictEqual(path.relative(formattersDir, results[4].filePath), path.join("test", "simple.js")); - assert.strictEqual(results[4].errorCount, 0); - assert.strictEqual(results[4].warningCount, 0); - assert.strictEqual(results[4].fatalErrorCount, 0); - assert.strictEqual(results[4].fixableErrorCount, 0); - assert.strictEqual(results[4].fixableWarningCount, 0); - assert.strictEqual(results[4].messages.length, 0); - assert.strictEqual(results[4].suppressedMessages.length, 0); - }); - - it("should return zero messages when given a config with browser globals", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "env-browser.js") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0, "Should have no messages."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return zero messages when given an option to add browser globals", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - languageOptions: { - globals: { - window: false - } - }, - rules: { - "no-alert": 0, - "no-undef": 2 - } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return zero messages when given a config with sourceType set to commonjs and Node.js globals", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "env-node.js") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-node.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0, "Should have no messages."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not return results from previous call when calling more than once", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("eslint.config.js"), - ignore: false, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - const failFilePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); - const passFilePath = fs.realpathSync(getFixturePath("passing.js")); - - let results = await eslint.lintFiles([failFilePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, failFilePath); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].messages[0].severity, 2); - - results = await eslint.lintFiles([passFilePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, passFilePath); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return zero messages when executing a file with a shebang", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(), - overrideConfigFile: getFixturePath("eslint.config.js") - }); - const results = await eslint.lintFiles([getFixturePath("shebang.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0, "Should have lint messages."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return zero messages when executing without a config file", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - ignore: false, - overrideConfigFile: true - }); - const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - // working - describe("Deprecated Rules", () => { - - it("should warn when deprecated rules are configured", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: true, - overrideConfig: { - rules: { - "indent-legacy": 1, - "callback-return": 1 - } - } - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual( - results[0].usedDeprecatedRules, - [ - { ruleId: "indent-legacy", replacedBy: ["indent"] }, - { ruleId: "callback-return", replacedBy: [] } - ] - ); - }); - - it("should not warn when deprecated rules are not configured", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: true, - overrideConfig: { - rules: { eqeqeq: 1, "callback-return": 0 } - } - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual(results[0].usedDeprecatedRules, []); - }); - - it("should warn when deprecated rules are found in a config", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/eslint.config.js" - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual( - results[0].usedDeprecatedRules, - [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] - ); - }); - }); - - // working - describe("Fix Mode", () => { - - it("correctly autofixes semicolon-conflicting-fixes", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true - }); - const inputPath = getFixturePath("autofix/semicolon-conflicting-fixes.js"); - const outputPath = getFixturePath("autofix/semicolon-conflicting-fixes.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("correctly autofixes return-conflicting-fixes", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true - }); - const inputPath = getFixturePath("autofix/return-conflicting-fixes.js"); - const outputPath = getFixturePath("autofix/return-conflicting-fixes.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should return fixed text on multiple files when in fix mode", async () => { - - /** - * Converts CRLF to LF in output. - * This is a workaround for git's autocrlf option on Windows. - * @param {Object} result A result object to convert. - * @returns {void} - */ - function convertCRLF(result) { - if (result && result.output) { - result.output = result.output.replace(/\r\n/gu, "\n"); - } - } - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"], - eqeqeq: 2, - "no-undef": 2, - "space-infix-ops": 2 - } - } - }); - const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - results.forEach(convertCRLF); - assert.deepStrictEqual(results, [ - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "true ? \"yes\" : \"no\";\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), - messages: [ - { - column: 9, - line: 2, - endColumn: 11, - endLine: 2, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), - messages: [ - { - column: 18, - line: 1, - endColumn: 21, - endLine: 1, - messageId: "undef", - message: "'foo' is not defined.", - nodeType: "Identifier", - ruleId: "no-undef", - severity: 2 - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\" + foo;\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - } - ]); - }); - - // Cannot be run properly until cache is implemented - it("should run autofix even if files are cached without autofix results", async () => { - const baseOptions = { - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"], - eqeqeq: 2, - "no-undef": 2, - "space-infix-ops": 2 - } - } - }; - - eslint = new FlatESLint(Object.assign({}, baseOptions, { cache: true, fix: false })); - - // Do initial lint run and populate the cache file - await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - eslint = new FlatESLint(Object.assign({}, baseOptions, { cache: true, fix: true })); - const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - assert(results.some(result => result.output)); - }); - }); - - describe("plugins", () => { - it("should return two messages when executing with config file that specifies a plugin", async () => { - eslint = eslintWithPlugins({ - cwd: path.resolve(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix.js") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test/test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2, "Expected two messages."); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should return two messages when executing with cli option that specifies a plugin", async () => { - eslint = eslintWithPlugins({ - cwd: path.resolve(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { "example/example-rule": 1 } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { - eslint = new FlatESLint({ - cwd: path.resolve(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { "test/example-rule": 1 } - }, - plugins: { - "eslint-plugin-test": { rules: { "example-rule": require("../../fixtures/rules/custom-rule") } } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - }); - - describe("cache", () => { - - /** - * helper method to delete a file without caring about exceptions - * @param {string} filePath The file path - * @returns {void} - */ - function doDelete(filePath) { - try { - fs.unlinkSync(filePath); - } catch { - - /* - * we don't care if the file didn't exist, since our - * intention was to remove the file - */ - } - } - - let cacheFilePath; - - beforeEach(() => { - cacheFilePath = null; - }); - - afterEach(() => { - sinon.restore(); - if (cacheFilePath) { - doDelete(cacheFilePath); - } - }); - - describe("when cacheLocation is a directory or looks like a directory", () => { - - const cwd = getFixturePath(); - - /** - * helper method to delete the directory used in testing - * @returns {void} - */ - function deleteCacheDir() { - try { - fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); - } catch { - - /* - * we don't care if the file didn't exist, since our - * intention was to remove the file - */ - } - } - beforeEach(() => { - deleteCacheDir(); - }); - - afterEach(() => { - deleteCacheDir(); - }); - - it("should create the directory and the cache file inside it when cacheLocation ends with a slash", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir/", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); - - it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - - fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir/", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); - - it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - - fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); - }); - - it("should create the cache file inside cwd when no cacheLocation provided", async () => { - const cwd = path.resolve(getFixturePath("cli-engine")); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cache: true, - cwd, - overrideConfig: { - rules: { - "no-console": 0 - } - }, - ignore: false - }); - const file = getFixturePath("cli-engine", "console.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created at provided cwd"); - }); - - it("should invalidate the cache if the overrideConfig changed between executions", async () => { - const cwd = getFixturePath("cache/src"); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - - let spy = sinon.spy(fs.promises, "readFile"); - - let file = path.join(cwd, "test-file.js"); - - file = fs.realpathSync(file); - const results = await eslint.lintFiles([file]); - - for (const { errorCount, warningCount } of results) { - assert.strictEqual(errorCount + warningCount, 0, "the file should have passed linting without errors or warnings"); - } - - assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - // destroy the spy - sinon.restore(); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - ignore: false - }); - - // create a new spy - spy = sinon.spy(fs.promises, "readFile"); - - const [newResult] = await eslint.lintFiles([file]); - - assert(spy.calledWith(file), "ESLint should have read the file again because it's considered changed because the config changed"); - assert.strictEqual(newResult.errorCount, 1, "since configuration changed the cache should have not been used and one error should have been reported"); - assert.strictEqual(newResult.messages[0].ruleId, "no-console"); - assert(shell.test("-f", cacheFilePath), "The cache for ESLint should still exist"); - }); - - it("should remember the files from a previous run and do not operate on them if not changed", async () => { - const cwd = getFixturePath("cache/src"); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - - let spy = sinon.spy(fs.promises, "readFile"); - - let file = getFixturePath("cache/src", "test-file.js"); - - file = fs.realpathSync(file); - - const result = await eslint.lintFiles([file]); - - assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - // destroy the spy - sinon.restore(); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - - // create a new spy - spy = sinon.spy(fs.promises, "readFile"); - - const cachedResult = await eslint.lintFiles([file]); - - assert.deepStrictEqual(result, cachedResult, "the result should have been the same"); - - // assert the file was not processed because the cache was used - assert(!spy.calledWith(file), "the file should not have been reloaded"); - }); - - it("when `cacheLocation` is specified, should create the cache file with `cache:true` and then delete it with `cache:false`", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - const eslintOptions = { - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - cwd: path.join(fixtureDir, "..") - }; - - eslint = new FlatESLint(eslintOptions); - - let file = getFixturePath("cache/src", "test-file.js"); - - file = fs.realpathSync(file); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - eslintOptions.cache = false; - eslint = new FlatESLint(eslintOptions); - - await eslint.lintFiles([file]); - - assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted since last run did not use the cache"); - }); - - it("should not throw an error if the cache file to be deleted does not exist on a read-only file system", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - // Simulate a read-only file system. - sinon.stub(fsp, "unlink").rejects( - Object.assign(new Error("read-only file system"), { code: "EROFS" }) - ); - - const eslintOptions = { - overrideConfigFile: true, - - // specifying cache false the cache will be deleted - cache: false, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - cwd: path.join(fixtureDir, "..") - }; - - eslint = new FlatESLint(eslintOptions); - - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(fsp.unlink.calledWithExactly(cacheFilePath), "Expected attempt to delete the cache was not made."); - }); - - it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const result = await eslint.lintFiles([badFile, goodFile]); - const [badFileResult, goodFileResult] = result; - - assert.notStrictEqual(badFileResult.errorCount + badFileResult.warningCount, 0, "the bad file should have some lint errors or warnings"); - assert.strictEqual(goodFileResult.errorCount + badFileResult.warningCount, 0, "the good file should have passed linting without errors or warnings"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.createFromFile(cacheFilePath); - const { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(goodFile), "object", "the entry for the good file should have been in the cache"); - assert.strictEqual(typeof cache.getKey(badFile), "object", "the entry for the bad file should have been in the cache"); - const cachedResult = await eslint.lintFiles([badFile, goodFile]); - - assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); - }); - - it("should not contain in the cache a file that was deleted", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const toBeDeletedFile = fs.realpathSync(getFixturePath("cache/src", "file-to-delete.js")); - - await eslint.lintFiles([badFile, goodFile, toBeDeletedFile]); - const fileCache = fCache.createFromFile(cacheFilePath); - let { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(toBeDeletedFile), "object", "the entry for the file to be deleted should have been in the cache"); - - // delete the file from the file system - fs.unlinkSync(toBeDeletedFile); - - /* - * file-entry-cache@2.0.0 will remove from the cache deleted files - * even when they were not part of the array of files to be analyzed - */ - await eslint.lintFiles([badFile, goodFile]); - - cache = JSON.parse(fs.readFileSync(cacheFilePath)); - - assert.strictEqual(typeof cache[0][toBeDeletedFile], "undefined", "the entry for the file to be deleted should not have been in the cache"); - - // make sure that the previos assertion checks the right place - assert.notStrictEqual(typeof cache[0][badFile], "undefined", "the entry for the bad file should have been in the cache"); - assert.notStrictEqual(typeof cache[0][goodFile], "undefined", "the entry for the good file should have been in the cache"); - }); - - it("should contain files that were not visited in the cache provided they still exist", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const testFile2 = fs.realpathSync(getFixturePath("cache/src", "test-file2.js")); - - await eslint.lintFiles([badFile, goodFile, testFile2]); - - let fileCache = fCache.createFromFile(cacheFilePath); - let { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); - - /* - * we pass a different set of files (minus test-file2) - * previous version of file-entry-cache would remove the non visited - * entries. 2.0.0 version will keep them unless they don't exist - */ - await eslint.lintFiles([badFile, goodFile]); - - fileCache = fCache.createFromFile(cacheFilePath); - cache = fileCache.cache; - - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); - }); - - it("should not delete cache when executing on text", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintText("var foo = 'bar';"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should not delete cache when executing on text with a provided filename", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintText("var bar = foo;", { filePath: "fixtures/passing.js" }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should not delete cache when executing on files with --cache flag", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, ""); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const file = getFixturePath("cli-engine", "console.js"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should delete cache when executing on files without --cache flag", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const file = getFixturePath("cli-engine", "console.js"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintFiles([file]); - - assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted"); - }); - - it("should use the specified cache file", async () => { - cacheFilePath = path.resolve(".cache/custom-cache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - - // specify a custom cache file - cacheLocation: cacheFilePath, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - - cwd: path.join(fixtureDir, "..") - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const result = await eslint.lintFiles([badFile, goodFile]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.createFromFile(cacheFilePath); - const { cache } = fileCache; - - assert(typeof cache.getKey(goodFile) === "object", "the entry for the good file should have been in the cache"); - assert(typeof cache.getKey(badFile) === "object", "the entry for the bad file should have been in the cache"); - - const cachedResult = await eslint.lintFiles([badFile, goodFile]); - - assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); - }); - - // https://github.com/eslint/eslint/issues/13507 - it("should not store `usedDeprecatedRules` in the cache file", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - const deprecatedRuleId = "space-in-parens"; - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - [deprecatedRuleId]: 2 - } - } - }); - - const filePath = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - /* - * Run linting on the same file 3 times to cover multiple cases: - * Run 1: Lint result wasn't already cached. - * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. - * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible - * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. - */ - for (let i = 0; i < 3; i++) { - const [result] = await eslint.lintFiles([filePath]); - - assert( - result.usedDeprecatedRules && result.usedDeprecatedRules.some(rule => rule.ruleId === deprecatedRuleId), - "the deprecated rule should have been in result.usedDeprecatedRules" - ); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.create(cacheFilePath); - const descriptor = fileCache.getFileDescriptor(filePath); - - assert(typeof descriptor === "object", "an entry for the file should have been in the cache file"); - assert(typeof descriptor.meta.results === "object", "lint result for the file should have been in its cache entry in the cache file"); - assert(typeof descriptor.meta.results.usedDeprecatedRules === "undefined", "lint result in the cache file contains `usedDeprecatedRules`"); - } - - }); - - // https://github.com/eslint/eslint/issues/13507 - it("should store `source` as `null` in the cache file if the lint result has `source` property", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-unused-vars": 2 - } - } - }); - - const filePath = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - - /* - * Run linting on the same file 3 times to cover multiple cases: - * Run 1: Lint result wasn't already cached. - * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. - * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible - * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. - */ - for (let i = 0; i < 3; i++) { - const [result] = await eslint.lintFiles([filePath]); - - assert(typeof result.source === "string", "the result should have contained the `source` property"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.create(cacheFilePath); - const descriptor = fileCache.getFileDescriptor(filePath); - - assert(typeof descriptor === "object", "an entry for the file should have been in the cache file"); - assert(typeof descriptor.meta.results === "object", "lint result for the file should have been in its cache entry in the cache file"); - - // if the lint result contains `source`, it should be stored as `null` in the cache file - assert.strictEqual(descriptor.meta.results.source, null, "lint result in the cache file contains non-null `source`"); - } - - }); - - describe("cacheStrategy", () => { - it("should detect changes using a file's modification time when set to 'metadata'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "metadata", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheFilePath); - const entries = fileCache.normalizeEntries([badFile, goodFile]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should result in a changed entry - shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheFilePath); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); - assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} should have been changed`); - }); - - it("should not detect changes using a file's modification time when set to 'content'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "content", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheFilePath, true); - let entries = fileCache.normalizeEntries([badFile, goodFile]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should NOT result in a changed entry - shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheFilePath, true); - entries = fileCache.normalizeEntries([badFile, goodFile]); - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have remained unchanged`); - }); - }); - - it("should detect changes using a file's contents when set to 'content'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "content", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const goodFileCopy = path.resolve(`${path.dirname(goodFile)}`, "test-file-copy.js"); - - shell.cp(goodFile, goodFileCopy); - - await eslint.lintFiles([badFile, goodFileCopy]); - let fileCache = fCache.createFromFile(cacheFilePath, true); - const entries = fileCache.normalizeEntries([badFile, goodFileCopy]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should result in a changed entry - shell.sed("-i", "abc", "xzy", goodFileCopy); - fileCache = fCache.createFromFile(cacheFilePath, true); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); - assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} should have been changed`); - }); - }); - }); - - describe("processors", () => { - - it("should return two messages when executing with config file that specifies preloaded processor", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - plugins: { - test: { - processors: { - txt: { - preprocess(text) { - return [text]; - }, - postprocess(messages) { - return messages[0]; - } - } - } - } - }, - processor: "test/txt", - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - { - files: ["**/*.txt", "**/*.txt/*.txt"] - } - ], - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should run processors when calling lintFiles with config file that specifies preloaded processor", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - plugins: { - test: { - processors: { - txt: { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; - } - } - } - } - }, - processor: "test/txt", - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - { - files: ["**/*.txt", "**/*.txt/*.txt"] - } - ], - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - plugins: { - test: { - processors: { - txt: { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; - } - } - } - } - }, - processor: "test/txt", - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - { - files: ["**/*.txt", "**/*.txt/*.txt"] - } - ], - ignore: false - }); - const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => { - let count = 0; - - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - plugins: { - test: { - processors: { - txt: { - preprocess(text) { - count++; - return [ - { - - // it will be run twice, and text will be as-is at the second time, then it will not run third time - text: text.replace("a()", "b()"), - filename: ".txt" - } - ]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; - } - } - } - } - }, - processor: "test/txt" - }, - { - files: ["**/*.txt/*.txt"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - { - files: ["**/*.txt"] - } - ], - ignore: false - }); - const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); - - assert.strictEqual(count, 2); - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - describe("autofixing with processors", () => { - const HTML_PROCESSOR = Object.freeze({ - preprocess(text) { - return [text.replace(/^", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].output, ""); - }); - - it("should not run in autofix mode when using a processor that does not support autofixing", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - files: ["**/*.html"], - plugins: { - test: { processors: { html: HTML_PROCESSOR } } - }, - processor: "test/html", - rules: { - semi: 2 - } - }, - ignore: false, - fix: true - }); - const results = await eslint.lintText("", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); - }); - - it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - files: ["**/*.html"], - plugins: { - test: { processors: { html: Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } } - }, - processor: "test/html", - rules: { - semi: 2 - } - }, - { - files: ["**/*.txt"] - } - ], - ignore: false - }); - const results = await eslint.lintText("", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); - }); - }); - }); - - describe("Patterns which match no file should throw errors.", () => { - beforeEach(() => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - overrideConfigFile: true - }); - }); - - it("one file", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["non-exist.js"]); - }, /No files matching 'non-exist\.js' were found\./u); - }); - - it("should throw if the directory exists and is empty", async () => { - ensureDirectoryExists(getFixturePath("cli-engine/empty")); - await assert.rejects(async () => { - await eslint.lintFiles(["empty"]); - }, /No files matching 'empty' were found\./u); - }); - - it("one glob pattern", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["non-exist/**/*.js"]); - }, /No files matching 'non-exist\/\*\*\/\*\.js' were found\./u); - }); - - it("two files", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["aaa.js", "bbb.js"]); - }, /No files matching 'aaa\.js' were found\./u); - }); - - it("a mix of an existing file and a non-existing file", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["console.js", "non-exist.js"]); - }, /No files matching 'non-exist\.js' were found\./u); - }); - - // https://github.com/eslint/eslint/issues/16275 - it("a mix of an existing glob pattern and a non-existing glob pattern", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["*.js", "non-exist/*.js"]); - }, /No files matching 'non-exist\/\*\.js' were found\./u); - }); - }); - - describe("multiple processors", () => { - const root = path.join(os.tmpdir(), "eslint/eslint/multiple-processors"); - const commonFiles = { - "node_modules/pattern-processor/index.js": fs.readFileSync( - require.resolve("../../fixtures/processors/pattern-processor"), - "utf8" - ), - "node_modules/eslint-plugin-markdown/index.js": ` - const { defineProcessor } = require("pattern-processor"); - const processor = defineProcessor(${/```(\w+)\n([\s\S]+?)\n```/gu}); - exports.processors = { - "markdown": { ...processor, supportsAutofix: true }, - "non-fixable": processor - }; - `, - "node_modules/eslint-plugin-html/index.js": ` - const { defineProcessor } = require("pattern-processor"); - const processor = defineProcessor(${/ - - \`\`\` - ` - }; - - // unique directory for each test to avoid quirky disk-cleanup errors - let id; - - beforeEach(() => (id = Date.now().toString())); - - /* - * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. - * Use `fs.rm(path, { recursive: true })` instead. - * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. - */ - if (typeof fsp.rm === "function") { - afterEach(async () => fsp.rm(root, { recursive: true, force: true })); - } else { - afterEach(async () => fsp.rmdir(root, { recursive: true, force: true })); - } - - it("should lint only JavaScript blocks.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - } - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1, "Should have one result."); - assert.strictEqual(results[0].messages.length, 1, "Should have one message."); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2, "Message should be on line 2."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should lint HTML blocks as well with multiple processors if represented in config.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/html" - } - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1, "Should have one result."); - assert.strictEqual(results[0].messages.length, 2, "Should have two messages."); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block - assert.strictEqual(results[0].messages[0].line, 2, "First error should be on line 2"); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block - assert.strictEqual(results[0].messages[1].line, 7, "Second error should be on line 7."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should fix HTML blocks as well with multiple processors if represented in config.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/html" - } - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] }, fix: true }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].output, unIndent` - \`\`\`js - console.log("hello");${/* ← fixed */""} - \`\`\` - \`\`\`html -
Hello
- - - \`\`\` - `); - }); - - it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/html" - }, - { - files: ["**/*.html/*.js"], - rules: { - semi: "off", - "no-console": "error" - } - } - - ];` - - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-console"); - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - }, - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/legacy", // this processor returns strings rather than '{ text, filename }' - rules: { - semi: "off", - "no-console": "error" - } - }, - { - files: ["**/*.html/*.js"], - rules: { - semi: "error", - "no-console": "off" - } - } - - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 3); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-console"); - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].messages[2].ruleId, "no-console"); - assert.strictEqual(results[0].messages[2].line, 10); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should throw an error if invalid processor was specified.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.md"], - processor: "markdown/unknown" - } - - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - await assert.rejects(async () => { - await eslint.lintFiles(["test.md"]); - }, /Key "processor": Could not find "unknown" in plugin "markdown"/u); - }); - - }); - - describe("glob pattern '[ab].js'", () => { - const root = getFixturePath("cli-engine/unmatched-glob"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should match '[ab].js' if existed.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - "a.js": "", - "b.js": "", - "ab.js": "", - "[ab].js": "", - "eslint.config.js": "module.exports = [];" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new FlatESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["[ab].js"]); - const filenames = results.map(r => path.basename(r.filePath)); - - assert.deepStrictEqual(filenames, ["[ab].js"]); - }); - - it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "a.js": "", - "b.js": "", - "ab.js": "", - "eslint.config.js": "module.exports = [];" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["[ab].js"]); - const filenames = results.map(r => path.basename(r.filePath)); - - assert.deepStrictEqual(filenames, ["a.js", "b.js"]); - }); - }); - - describe("with 'noInlineConfig' setting", () => { - const root = getFixturePath("cli-engine/noInlineConfig"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should warn directive comments if 'noInlineConfig' was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* globals foo */", - "eslint.config.js": "module.exports = [{ linterOptions: { noInlineConfig: true } }];" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/* globals foo */' has no effect because you have 'noInlineConfig' setting in your config."); - }); - - }); - - describe("with 'reportUnusedDisableDirectives' setting", () => { - const root = getFixturePath("cli-engine/reportUnusedDisableDirectives"); - - let cleanup; - let i = 0; - - beforeEach(() => { - cleanup = () => { }; - i++; - }); - - afterEach(() => cleanup()); - - it("should error unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = error'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'error' } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should error unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 2'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 2 } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = warn'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'warn' } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 1'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 1 } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = true'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: true } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = false'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: false } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = off'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'off' } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 0'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 0 } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - describe("the runtime option overrides config files.", () => { - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = [{ linterOptions: { reportUnusedDisableDirectives: true } }]" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new FlatESLint({ - cwd: teardown.getPath(), - overrideConfig: { - linterOptions: { reportUnusedDisableDirectives: "off" } - } - }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = [{ linterOptions: { reportUnusedDisableDirectives: true } }]" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new FlatESLint({ - cwd: teardown.getPath(), - overrideConfig: { - linterOptions: { reportUnusedDisableDirectives: "error" } - } - }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - }); - - it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintFiles(777), /'patterns' must be a non-empty string or an array of non-empty strings/u); - await assert.rejects(() => eslint.lintFiles([null]), /'patterns' must be a non-empty string or an array of non-empty strings/u); - }); - - describe("Alternate config files", () => { - - it("should find eslint.config.mjs when present", async () => { - - const cwd = getFixturePath("mjs-config"); - - eslint = new FlatESLint({ - cwd - }); - - const results = await eslint.lintFiles("foo.js"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - - }); - - it("should find eslint.config.cjs when present", async () => { - - const cwd = getFixturePath("cjs-config"); - - eslint = new FlatESLint({ - cwd - }); - - const results = await eslint.lintFiles("foo.js"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - - }); - - it("should favor eslint.config.js when eslint.config.mjs and eslint.config.cjs are present", async () => { - - const cwd = getFixturePath("js-mjs-cjs-config"); - - eslint = new FlatESLint({ - cwd - }); - - const results = await eslint.lintFiles("foo.js"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should favor eslint.config.mjs when eslint.config.cjs is present", async () => { - - const cwd = getFixturePath("mjs-cjs-config"); - - eslint = new FlatESLint({ - cwd - }); - - const results = await eslint.lintFiles("foo.js"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - }); - }); - - - }); - - describe("Fix Types", () => { - - let eslint; - - it("should throw an error when an invalid fix type is specified", () => { - assert.throws(() => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - fixTypes: ["layou"] - }); - }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); - }); - - it("should not fix any rules when fixTypes is used without fix", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: false, - fixTypes: ["layout"] - }); - const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - const results = await eslint.lintFiles([inputPath]); - - assert.strictEqual(results[0].output, void 0); - }); - - it("should not fix non-style rules when fixTypes has only 'layout'", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - fixTypes: ["layout"] - }); - const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - const outputPath = getFixturePath("fix-types/fix-only-semi.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - fixTypes: ["suggestion"] - }); - const inputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.js"); - const outputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - fixTypes: ["suggestion", "layout"] - }); - const inputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.js"); - const outputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - }); - - describe("isPathIgnored", () => { - it("should check if the given path is ignored", async () => { - const engine = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config-with-ignores2.js"), - cwd: getFixturePath() - }); - - assert(await engine.isPathIgnored("undef.js")); - assert(!await engine.isPathIgnored("passing.js")); - }); - - it("should return false if ignoring is disabled", async () => { - const engine = new FlatESLint({ - ignore: false, - overrideConfigFile: getFixturePath("eslint.config-with-ignores2.js"), - cwd: getFixturePath() - }); - - assert(!await engine.isPathIgnored("undef.js")); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should return true for default ignores even if ignoring is disabled", async () => { - const engine = new FlatESLint({ - ignore: false, - cwd: getFixturePath("cli-engine") - }); - - assert(await engine.isPathIgnored("node_modules/foo.js")); - }); - - describe("about the default ignore patterns", () => { - it("should always apply default ignore patterns if ignore option is true", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); - - it("should still apply default ignore patterns if ignore option is is false", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignore: false, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignorePattern constructor option", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - cwd, - overrideConfigFile: true, - ignorePatterns: ["!node_modules/", "node_modules/*", "!node_modules/package/"] - }); - - const result = await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js")); - - assert(!result, "File should not be ignored"); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignores in overrideConfig", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - cwd, - overrideConfigFile: true, - overrideConfig: { - ignores: ["!node_modules/", "node_modules/*", "!node_modules/package/"] - } - }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); - }); - - it("should ignore .git directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".git/bar"))); - }); - - it("should still ignore .git directory when ignore option disabled", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignore: false, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".git/bar"))); - }); - - it("should not ignore absolute paths containing '..'", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ cwd }); - - assert(!await engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); - }); - - it("should ignore /node_modules/ relative to cwd without any configured ignore patterns", async () => { - const cwd = getFixturePath("ignored-paths", "no-ignore-file"); - const engine = new FlatESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); - }); - - it("should not inadvertently ignore all files in parent directories", async () => { - const engine = new FlatESLint({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - }); - - describe("with ignorePatterns option", () => { - it("should accept a string for options.ignorePatterns", async () => { - const cwd = getFixturePath("ignored-paths", "ignore-pattern"); - const engine = new FlatESLint({ - ignorePatterns: ["ignore-me.txt"], - cwd - }); - - assert(await engine.isPathIgnored("ignore-me.txt")); - }); - - it("should accept an array for options.ignorePattern", async () => { - const engine = new FlatESLint({ - ignorePatterns: ["a.js", "b.js"], - overrideConfigFile: true - }); - - assert(await engine.isPathIgnored("a.js"), "a.js should be ignored"); - assert(await engine.isPathIgnored("b.js"), "b.js should be ignored"); - assert(!await engine.isPathIgnored("c.js"), "c.js should not be ignored"); - }); - - it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - ignorePatterns: ["not-a-file"], - cwd - }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "not-a-file"))); - }); - - it("should return true for file matching an ignore pattern exactly", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - ignorePatterns: ["undef.js"], - cwd, - overrideConfigFile: true - }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should return false for file in subfolder of cwd matching an ignore pattern with a base filename", async () => { - const cwd = getFixturePath("ignored-paths"); - const filePath = getFixturePath("ignored-paths", "subdir", "undef.js"); - const engine = new FlatESLint({ - ignorePatterns: ["undef.js"], - overrideConfigFile: true, - cwd - }); - - assert(!await engine.isPathIgnored(filePath)); - }); - - it("should return true for file matching a child of an ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["ignore-pattern"], cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); - }); - - it("should return true for file matching a grandchild of a directory when the pattern is directory/**", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["ignore-pattern/**"], cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.js"))); - }); - - it("should return false for file not matching any ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["failing.js"], cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "unignored.js"))); - }); - - it("two globstar '**' ignore pattern should ignore files in nested directories", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - overrideConfigFile: true, - ignorePatterns: ["**/*.js"], - cwd - }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js")), "foo.js should be ignored"); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.js")), "foo/bar.js should be ignored"); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.js")), "foo/bar/baz.js"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.cjs")), "foo.cjs should not be ignored"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.cjs")), "foo/bar.cjs should not be ignored"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.cjs")), "foo/bar/baz.cjs should not be ignored"); - }); - }); - - describe("with config ignores ignorePatterns option", () => { - it("should return false for ignored file when unignored with ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config-with-ignores2.js"), - ignorePatterns: ["!undef.js"], - cwd - }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - }); - - it("should throw if non-string value is given to 'filePath' parameter", async () => { - const eslint = new FlatESLint(); - - await assert.rejects(() => eslint.isPathIgnored(null), /'filePath' must be a non-empty string/u); - }); - }); - - describe("loadFormatter()", () => { - it("should return a formatter object when a bundled formatter is requested", async () => { - const engine = new FlatESLint(); - const formatter = await engine.loadFormatter("json"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when no argument is passed", async () => { - const engine = new FlatESLint(); - const formatter = await engine.loadFormatter(); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a custom formatter is requested", async () => { - const engine = new FlatESLint(); - const formatter = await engine.loadFormatter(getFixturePath("formatters", "simple.js")); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a custom formatter is requested, also if the path has backslashes", async () => { - const engine = new FlatESLint({ - cwd: path.join(fixtureDir, "..") - }); - const formatter = await engine.loadFormatter(".\\fixtures\\formatters\\simple.js"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter prefixed with eslint-formatter is requested", async () => { - const engine = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("bar"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", async () => { - const engine = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("eslint-formatter-bar"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested within a scoped npm package", async () => { - const engine = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("@somenamespace/foo"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", async () => { - const engine = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("@somenamespace/eslint-formatter-foo"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should throw if a custom formatter doesn't exist", async () => { - const engine = new FlatESLint(); - const formatterPath = getFixturePath("formatters", "doesntexist.js"); - const fullFormatterPath = path.resolve(formatterPath); - - await assert.rejects(async () => { - await engine.loadFormatter(formatterPath); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`), "u")); - }); - - it("should throw if a built-in formatter doesn't exist", async () => { - const engine = new FlatESLint(); - const fullFormatterPath = path.resolve(__dirname, "../../../lib/cli-engine/formatters/special"); - - await assert.rejects(async () => { - await engine.loadFormatter("special"); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}.js\nError: Cannot find module '${fullFormatterPath}.js'`), "u")); - }); - - it("should throw if the required formatter exists but has an error", async () => { - const engine = new FlatESLint(); - const formatterPath = getFixturePath("formatters", "broken.js"); - - await assert.rejects(async () => { - await engine.loadFormatter(formatterPath); - - // for some reason, the error here contains multiple "there was a problem loading formatter" lines, so omitting - }, new RegExp(escapeStringRegExp("Error: Cannot find module 'this-module-does-not-exist'"), "u")); - }); - - it("should throw if a non-string formatter name is passed", async () => { - const engine = new FlatESLint(); - - await assert.rejects(async () => { - await engine.loadFormatter(5); - }, /'name' must be a string/u); - }); - }); - - describe("getErrorResults()", () => { - - it("should report 5 error messages when looking for errors only", async () => { - process.chdir(originalDir); - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: "error", - "no-var": "error", - "eol-last": "error", - "no-unused-vars": "error" - } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const errorResults = FlatESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 4, "messages.length is wrong"); - assert.strictEqual(errorResults[0].errorCount, 4, "errorCount is wrong"); - assert.strictEqual(errorResults[0].fixableErrorCount, 3, "fixableErrorCount is wrong"); - assert.strictEqual(errorResults[0].fixableWarningCount, 0, "fixableWarningCount is wrong"); - assert.strictEqual(errorResults[0].messages[0].ruleId, "no-var"); - assert.strictEqual(errorResults[0].messages[0].severity, 2); - assert.strictEqual(errorResults[0].messages[1].ruleId, "no-unused-vars"); - assert.strictEqual(errorResults[0].messages[1].severity, 2); - assert.strictEqual(errorResults[0].messages[2].ruleId, "quotes"); - assert.strictEqual(errorResults[0].messages[2].severity, 2); - assert.strictEqual(errorResults[0].messages[3].ruleId, "eol-last"); - assert.strictEqual(errorResults[0].messages[3].severity, 2); - }); - - it("should not mutate passed report parameter", async () => { - process.chdir(originalDir); - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { quotes: [1, "double"] } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const reportResultsLength = results[0].messages.length; - - FlatESLint.getErrorResults(results); - - assert.strictEqual(results[0].messages.length, reportResultsLength); - }); - - it("should report a warningCount of 0 when looking for errors only", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - strict: ["error", "global"], - quotes: "error", - "no-var": "error", - "eol-last": "error", - "no-unused-vars": "error" - } - } - }); - const lintResults = await engine.lintText("var foo = 'bar';"); - const errorResults = FlatESLint.getErrorResults(lintResults); - - assert.strictEqual(errorResults[0].warningCount, 0); - assert.strictEqual(errorResults[0].fixableWarningCount, 0); - }); - - it("should return 0 error or warning messages even when the file has warnings", async () => { - const engine = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config-with-ignores.js"), - cwd: path.join(fixtureDir, "..") - }); - const options = { - filePath: "fixtures/passing.js", - warnIgnored: true - }; - const results = await engine.lintText("var bar = foo;", options); - const errorReport = FlatESLint.getErrorResults(results); - - assert.strictEqual(errorReport.length, 0); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - }); - - it("should return source code of file in the `source` property", async () => { - process.chdir(originalDir); - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { quotes: [2, "double"] } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const errorResults = FlatESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 1); - assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); - }); - - it("should contain `output` property after fixes", async () => { - process.chdir(originalDir); - const engine = new FlatESLint({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - semi: 2, - "no-console": 2 - } - } - }); - const results = await engine.lintText("console.log('foo')"); - const errorResults = FlatESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 1); - assert.strictEqual(errorResults[0].output, "console.log('foo');"); - }); - }); - - describe("findConfigFile()", () => { - - it("should return undefined when overrideConfigFile is true", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true - }); - - assert.strictEqual(await engine.findConfigFile(), void 0); - }); - - it("should return undefined when a config file isn't found", async () => { - const engine = new FlatESLint({ - cwd: path.resolve(__dirname, "../../../../") - }); - - assert.strictEqual(await engine.findConfigFile(), void 0); - }); - - it("should return custom config file path when overrideConfigFile is a nonempty string", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "my-config.js" - }); - const configFilePath = path.resolve(__dirname, "../../../my-config.js"); - - assert.strictEqual(await engine.findConfigFile(), configFilePath); - }); - - it("should return root level eslint.config.js when overrideConfigFile is null", async () => { - const engine = new FlatESLint({ - overrideConfigFile: null - }); - const configFilePath = path.resolve(__dirname, "../../../eslint.config.js"); - - assert.strictEqual(await engine.findConfigFile(), configFilePath); - }); - - it("should return root level eslint.config.js when overrideConfigFile is not specified", async () => { - const engine = new FlatESLint(); - const configFilePath = path.resolve(__dirname, "../../../eslint.config.js"); - - assert.strictEqual(await engine.findConfigFile(), configFilePath); - }); - - }); - - describe("getRulesMetaForResults()", () => { - - it("should throw an error when this instance did not lint any files", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true - }); - - assert.throws(() => { - engine.getRulesMetaForResults([ - { - filePath: "path/to/file.js", - messages: [ - { - ruleId: "curly", - severity: 2, - message: "Expected { after 'if' condition.", - line: 2, - column: 1, - nodeType: "IfStatement" - }, - { - ruleId: "no-process-exit", - severity: 2, - message: "Don't use process.exit(); throw an error instead.", - line: 3, - column: 1, - nodeType: "CallExpression" - } - ], - suppressedMessages: [], - errorCount: 2, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: - "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n" - } - ]); - }, { - constructor: TypeError, - message: "Results object was not created from this ESLint instance." - }); - }); - - it("should throw an error when results were created from a different instance", async () => { - const engine1 = new FlatESLint({ - overrideConfigFile: true, - cwd: path.join(fixtureDir, "foo"), - overrideConfig: { - rules: { - semi: 2 - } - } - }); - const engine2 = new FlatESLint({ - overrideConfigFile: true, - cwd: path.join(fixtureDir, "bar"), - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results1 = await engine1.lintText("1", { filePath: "file.js" }); - const results2 = await engine2.lintText("2", { filePath: "file.js" }); - - engine1.getRulesMetaForResults(results1); // should not throw an error - assert.throws(() => { - engine1.getRulesMetaForResults(results2); - }, { - constructor: TypeError, - message: "Results object was not created from this ESLint instance." - }); - }); - - it("should treat a result without `filePath` as if the file was located in `cwd`", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - cwd: path.join(fixtureDir, "foo", "bar"), - ignorePatterns: ["*/**"], // ignore all subdirectories of `cwd` - overrideConfig: { - rules: { - eqeqeq: "warn" - } - } - }); - - const results = await engine.lintText("a==b"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta.eqeqeq, coreRules.get("eqeqeq").meta); - }); - - it("should not throw an error if a result without `filePath` contains an ignored file warning", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - cwd: path.join(fixtureDir, "foo", "bar"), - ignorePatterns: ["**"] - }); - - const results = await engine.lintText("", { warnIgnored: true }); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - }); - - it("should not throw an error if results contain linted files and one ignored file", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - cwd: getFixturePath(), - ignorePatterns: ["passing*"], - overrideConfig: { - rules: { - "no-undef": 2, - semi: 1 - } - } - }); - - const results = await engine.lintFiles(["missing-semicolon.js", "passing.js", "undef.js"]); - - assert( - results.some(({ messages }) => messages.some(({ message, ruleId }) => !ruleId && message.startsWith("File ignored"))), - "At least one file should be ignored but none is." - ); - - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta["no-undef"], coreRules.get("no-undef").meta); - assert.deepStrictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return empty object when there are no linting errors", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true - }); - - const rulesMeta = engine.getRulesMetaForResults([]); - - assert.deepStrictEqual(rulesMeta, {}); - }); - - it("should return one rule meta when there is a linting error", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results = await engine.lintText("a", { filePath: "foo.js" }); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 1); - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return one rule meta when there is a suppressed linting error", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results = await engine.lintText("a // eslint-disable-line semi"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 1); - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return multiple rule meta when there are multiple linting errors", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"] - } - } - }); - - const results = await engine.lintText("'a'"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); - }); - - it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { - const customPlugin = { - rules: { - "no-var": require("../../../lib/rules/no-var") - } - }; - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - plugins: { - "custom-plugin": customPlugin - }, - rules: { - "custom-plugin/no-var": 2, - semi: 2, - quotes: [2, "double"] - } - } - }); - - const results = await engine.lintText("var foo = 0; var bar = '1'"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); - assert.strictEqual( - rulesMeta["custom-plugin/no-var"], - customPlugin.rules["no-var"].meta - ); - }); - - it("should ignore messages not related to a rule", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - ignorePatterns: ["ignored.js"], - overrideConfig: { - rules: { - "no-var": "warn" - }, - linterOptions: { - reportUnusedDisableDirectives: "warn" - } - } - }); - - { - const results = await engine.lintText("syntax error"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - { - const results = await engine.lintText("// eslint-disable-line no-var"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - { - const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - }); - - it("should return a non-empty value if some of the messages are related to a rule", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { rules: { "no-var": "warn" }, linterOptions: { reportUnusedDisableDirectives: "warn" } } - }); - - const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); - }); - - it("should return empty object if all messages are related to unknown rules", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true - }); - - const results = await engine.lintText("// eslint-disable-line foo, bar/baz, bar/baz/qux"); - - assert.strictEqual(results[0].messages.length, 3); - assert.strictEqual(results[0].messages[0].ruleId, "foo"); - assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); - assert.strictEqual(results[0].messages[2].ruleId, "bar/baz/qux"); - - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 0); - }); - - it("should return object with meta of known rules if some messages are related to unknown rules", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { rules: { "no-var": "warn" } } - }); - - const results = await engine.lintText("// eslint-disable-line foo, bar/baz, bar/baz/qux\nvar x;"); - - assert.strictEqual(results[0].messages.length, 4); - assert.strictEqual(results[0].messages[0].ruleId, "foo"); - assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); - assert.strictEqual(results[0].messages[2].ruleId, "bar/baz/qux"); - assert.strictEqual(results[0].messages[3].ruleId, "no-var"); - - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); - }); - }); - - describe("outputFixes()", () => { - afterEach(() => { - sinon.verifyAndRestore(); - }); - - it("should call fs.writeFile() for each result with output", async () => { - const fakeFS = { - writeFile: sinon.spy(() => Promise.resolve()) - }; - const spy = fakeFS.writeFile; - const { FlatESLint: localESLint } = proxyquire("../../../lib/eslint/flat-eslint", { - fs: { - promises: fakeFS - } - }); - - const results = [ - { - filePath: path.resolve("foo.js"), - output: "bar" - }, - { - filePath: path.resolve("bar.js"), - output: "baz" - } - ]; - - await localESLint.outputFixes(results); - - assert.strictEqual(spy.callCount, 2); - assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar"), "First call was incorrect."); - assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz"), "Second call was incorrect."); - }); - - it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { - const fakeFS = { - writeFile: sinon.spy(() => Promise.resolve()) - }; - const spy = fakeFS.writeFile; - const { FlatESLint: localESLint } = proxyquire("../../../lib/eslint/flat-eslint", { - fs: { - promises: fakeFS - } - }); - const results = [ - { - filePath: path.resolve("foo.js"), - output: "bar" - }, - { - filePath: path.resolve("abc.js") - }, - { - filePath: path.resolve("bar.js"), - output: "baz" - } - ]; - - await localESLint.outputFixes(results); - - assert.strictEqual(spy.callCount, 2, "Call count was wrong"); - assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar"), "First call was incorrect."); - assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz"), "Second call was incorrect."); - }); - - it("should throw if non object array is given to 'results' parameter", async () => { - await assert.rejects(() => FlatESLint.outputFixes(null), /'results' must be an array/u); - await assert.rejects(() => FlatESLint.outputFixes([null]), /'results' must include only objects/u); - }); - }); - - describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { - it("should report a violation for disabling rules", async () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - ignore: true, - overrideConfigFile: true, - allowInlineConfig: false, - overrideConfig: { - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - } - }; - const eslintCLI = new FlatESLint(config); - const results = await eslintCLI.lintText(code); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not report a violation by default", async () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - ignore: true, - overrideConfigFile: true, - allowInlineConfig: true, - overrideConfig: { - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - } - }; - const eslintCLI = new FlatESLint(config); - const results = await eslintCLI.lintText(code); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 1); - assert.strictEqual(results[0].suppressedMessages[0].ruleId, "no-alert"); - }); - }); - - describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { - it("should report problems for unused eslint-disable directives", async () => { - const eslint = new FlatESLint({ overrideConfigFile: true, overrideConfig: { linterOptions: { reportUnusedDisableDirectives: "error" } } }); - - assert.deepStrictEqual( - await eslint.lintText("/* eslint-disable */"), - [ - { - filePath: "", - messages: [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - source: "/* eslint-disable */", - usedDeprecatedRules: [] - } - ] - ); - }); - }); - - describe("when retrieving version number", () => { - it("should return current version number", () => { - const eslintCLI = require("../../../lib/eslint/flat-eslint").FlatESLint; - const version = eslintCLI.version; - - assert.strictEqual(typeof version, "string"); - assert(parseInt(version[0], 10) >= 3); - }); - }); - - describe("mutability", () => { - - describe("rules", () => { - it("Loading rules in one instance doesn't mutate to another instance", async () => { - const filePath = getFixturePath("single-quoted.js"); - const engine1 = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - plugins: { - example: { - rules: { - "example-rule"() { - return {}; - } - } - } - }, - rules: { "example/example-rule": 1 } - } - }); - const engine2 = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true - }); - const fileConfig1 = await engine1.calculateConfigForFile(filePath); - const fileConfig2 = await engine2.calculateConfigForFile(filePath); - - // plugin - assert.deepStrictEqual(fileConfig1.rules["example/example-rule"], [1], "example is present for engine 1"); - assert.strictEqual(fileConfig2.rules, void 0, "example is not present for engine 2"); - }); - }); - }); - - describe("configs with 'ignores' and without 'files'", () => { - - // https://github.com/eslint/eslint/issues/17103 - describe("config with ignores: ['error.js']", () => { - const cwd = getFixturePath("config-with-ignores-without-files"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd, - files: { - "eslint.config.js": `module.exports = [ - { - rules: { - "no-unused-vars": "error", - }, - }, - { - ignores: ["error.js"], - rules: { - "no-unused-vars": "warn", - }, - }, - ];`, - "error.js": "let unusedVar;", - "warn.js": "let unusedVar;" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should apply to all files except for 'error.js'", async () => { - const engine = new FlatESLint({ - cwd - }); - - const results = await engine.lintFiles("{error,warn}.js"); - - assert.strictEqual(results.length, 2); - - const [errorResult, warnResult] = results; - - assert.strictEqual(errorResult.filePath, path.join(getPath(), "error.js")); - assert.strictEqual(errorResult.messages.length, 1); - assert.strictEqual(errorResult.messages[0].ruleId, "no-unused-vars"); - assert.strictEqual(errorResult.messages[0].severity, 2); - - assert.strictEqual(warnResult.filePath, path.join(getPath(), "warn.js")); - assert.strictEqual(warnResult.messages.length, 1); - assert.strictEqual(warnResult.messages[0].ruleId, "no-unused-vars"); - assert.strictEqual(warnResult.messages[0].severity, 1); - }); - }); - - describe("config with ignores: ['**/*.json']", () => { - const cwd = getFixturePath("config-with-ignores-without-files"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd, - files: { - "eslint.config.js": `module.exports = [ - { - rules: { - "no-undef": "error", - }, - }, - { - ignores: ["**/*.json"], - rules: { - "no-unused-vars": "error", - }, - }, - ];`, - "foo.js": "", - "foo.json": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should not add json files as lint targets", async () => { - const engine = new FlatESLint({ - cwd - }); - - const results = await engine.lintFiles("foo*"); - - // should not lint `foo.json` - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, path.join(getPath(), "foo.js")); - }); - }); - - }); - - describe("with ignores config", () => { - const root = getFixturePath("cli-engine/ignore-patterns"); - - describe("ignores can add an ignore pattern ('foo.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "eslint.config.js": `module.exports = { - ignores: ["**/foo.js"] - };`, - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); - }); - - it("'lintFiles()' should not verify 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js"), - path.join(root, "eslint.config.js"), - path.join(root, "subdir/bar.js") - ]); - }); - }); - - describe("ignores can add ignore patterns ('**/foo.js', '/bar.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + Date.now(), - files: { - "eslint.config.js": `module.exports = { - ignores: ["**/foo.js", "bar.js"] - };`, - "foo.js": "", - "bar.js": "", - "baz.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "", - "subdir/baz.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); - }); - - it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "baz.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "subdir/bar.js"), - path.join(getPath(), "subdir/baz.js") - ]); - }); - }); - - - describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/'].", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}-unignores`, - files: { - "eslint.config.js": `module.exports = { - ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/"] - };`, - "node_modules/foo/index.js": "", - "node_modules/foo/.dot.js": "", - "node_modules/bar/index.js": "", - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); - }); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); - }); - - it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo.js"), - path.join(getPath(), "node_modules/foo/.dot.js"), - path.join(getPath(), "node_modules/foo/index.js") - ]); - }); - }); - - describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/**'].", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}-unignores`, - files: { - "eslint.config.js": `module.exports = { - ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/**"] - };`, - "node_modules/foo/index.js": "", - "node_modules/foo/.dot.js": "", - "node_modules/bar/index.js": "", - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); - }); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); - }); - - it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const result = (await engine.lintFiles("**/*.js")); - - const filePaths = result - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo.js"), - path.join(getPath(), "node_modules/foo/.dot.js"), - path.join(getPath(), "node_modules/foo/index.js") - ]); - }); - }); - - describe("ignore pattern can re-ignore files that are unignored by a previous pattern.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}-reignore`, - files: { - "eslint.config.js": `module.exports = ${JSON.stringify({ - ignores: ["!.*", ".foo*"] - })}`, - ".foo.js": "", - ".bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".bar.js"), false); - }); - - it("'lintFiles()' should not lint re-ignored '.foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), ".bar.js"), - path.join(getPath(), "eslint.config.js") - ]); - }); - }); - - describe("ignore pattern can unignore files that are ignored by a previous pattern.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}-dignore`, - files: { - "eslint.config.js": `module.exports = ${JSON.stringify({ - ignores: ["**/*.js", "!foo.js"] - })}`, - "foo.js": "", - "bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), true); - }); - - it("'lintFiles()' should verify unignored 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "foo.js") - ]); - }); - }); - - describe("ignores in a config file should not be used if ignore: false.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "eslint.config.js": `module.exports = { - ignores: ["*.js"] - }`, - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath(), ignore: false }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - }); - - it("'lintFiles()' should verify 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath(), ignore: false }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "eslint.config.js"), - path.join(root, "foo.js") - ]); - }); - }); - - }); - - describe("config.files adds lint targets", () => { - const root = getFixturePath("cli-engine/additional-lint-targets"); - - - describe("if { files: 'foo/*.txt', ignores: '**/ignore.txt' } is present,", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + 1, - files: { - "eslint.config.js": `module.exports = [{ - files: ["foo/*.txt"], - ignores: ["**/ignore.txt"] - }];`, - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "foo/ignore.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "bar/ignore.txt": "", - "test.js": "", - "test.txt": "", - "ignore.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "bar/test.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "foo/test.txt"), - path.join(getPath(), "test.js") - ]); - }); - - it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "bar/test.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "test.js") - ]); - }); - }); - - describe("if { files: 'foo/*.txt', ignores: '**/ignore.txt' } is present and subdirectory is passed,", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + 2, - files: { - "eslint.config.js": `module.exports = [{ - files: ["foo/*.txt"], - ignores: ["**/ignore.txt"] - }];`, - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "foo/ignore.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "bar/ignore.txt": "", - "test.js": "", - "test.txt": "", - "ignore.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("foo")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "foo/test.txt") - ]); - }); - - it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("foo/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "foo/test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*.txt' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + 3, - files: { - "eslint.config.js": `module.exports = [ - { - files: ["foo/**/*.txt"] - } - ]`, - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "bar/test.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo/nested/test.txt"), - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "foo/test.txt"), - path.join(getPath(), "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + 4, - files: { - "eslint.config.js": `module.exports = [ - { - files: ["foo/**/*"] - } - ]`, - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "bar/test.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "test.js") - ]); - }); - }); - - }); - - describe("'ignores', 'files' of the configuration that the '--config' option provided should be resolved from CWD.", () => { - const root = getFixturePath("cli-engine/config-and-overrides-files"); - - describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}a1`, - files: { - "node_modules/myconf/eslint.config.js": `module.exports = [ - { - files: ["foo/*.js"], - rules: { - eqeqeq: "error" - } - } - ];`, - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with 'foo/test.js' should use the files entry.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "node_modules/myconf/eslint.config.js", - cwd: getPath(), - ignore: false - }); - const results = await engine.lintFiles("foo/test.js"); - - // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - suppressedMessages: [], - errorCount: 1, - filePath: path.join(getPath(), "foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - column: 3, - endColumn: 5, - endLine: 1, - line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - source: "a == b", - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - - it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the files entry.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "node_modules/myconf/eslint.config.js", - cwd: getPath(), - ignore: false - }); - const results = await engine.lintFiles("node_modules/myconf/foo/test.js"); - - // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - suppressedMessages: [], - errorCount: 0, - filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - ruleId: null, - fatal: false, - message: "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.", - severity: 1, - nodeType: null - } - ], - usedDeprecatedRules: [], - warningCount: 1, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { files: '*', ignores: 'foo/*.txt', ... } is present by '--config bar/myconf/eslint.config.js',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}a2`, - files: { - "bar/myconf/eslint.config.js": `module.exports = [ - { - files: ["**/*"], - ignores: ["foo/*.js"], - rules: { - eqeqeq: "error" - } - } - ]`, - "bar/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with 'foo/test.js' should have no errors because no rules are enabled.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "bar/myconf/eslint.config.js", - cwd: getPath(), - ignore: false - }); - const results = await engine.lintFiles("foo/test.js"); - - // Expected to be no errors because the file matches to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - suppressedMessages: [], - errorCount: 0, - filePath: path.join(getPath(), "foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [], - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - - it("'lintFiles()' with 'bar/myconf/foo/test.js' should have an error because eqeqeq is enabled.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "bar/myconf/eslint.config.js", - cwd: getPath(), - ignore: false - }); - const results = await engine.lintFiles("bar/myconf/foo/test.js"); - - // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - suppressedMessages: [], - errorCount: 1, - filePath: path.join(getPath(), "bar/myconf/foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - column: 3, - endColumn: 5, - endLine: 1, - line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - source: "a == b", - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { ignores: 'foo/*.js', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}a3`, - files: { - "node_modules/myconf/eslint.config.js": `module.exports = [{ - ignores: ["!node_modules", "node_modules/*", "!node_modules/myconf", "foo/*.js"], - }, { - rules: { - eqeqeq: "error" - } - }]`, - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with '**/*.js' should lint 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "node_modules/myconf/eslint.config.js", - cwd: getPath() - }); - const files = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(files, [ - path.join(getPath(), "node_modules/myconf/eslint.config.js"), - path.join(getPath(), "node_modules/myconf/foo/test.js") - ]); - }); - }); - }); - - describe("baseConfig", () => { - it("can be an object", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: { - rules: { - semi: 2 - } - } - }); - - const [{ messages }] = await eslint.lintText("foo"); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - }); - - it("can be an array", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: [ - { - rules: { - "no-var": 2 - } - }, - { - rules: { - semi: 2 - } - } - ] - }); - - const [{ messages }] = await eslint.lintText("var foo"); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-var"); - assert.strictEqual(messages[1].ruleId, "semi"); - }); - - it("should be inserted after default configs", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: { - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } - } - }); - - const [{ messages }] = await eslint.lintText("let x"); - - /* - * if baseConfig was inserted before default configs, - * `ecmaVersion: "latest"` from default configs would overwrite - * `ecmaVersion: 5` from baseConfig, so this wouldn't be a parsing error. - */ - - assert.strictEqual(messages.length, 1); - assert(messages[0].fatal, "Fatal error expected."); - }); - - it("should be inserted before configs from the config file", async () => { - const eslint = new FlatESLint({ - cwd: getFixturePath(), - baseConfig: { - rules: { - strict: ["error", "global"] - }, - languageOptions: { - sourceType: "script" - } - } - }); - - const [{ messages }] = await eslint.lintText("foo"); - - /* - * if baseConfig was inserted after configs from the config file, - * `strict: 0` from eslint.config.js wouldn't overwrite `strict: ["error", "global"]` - * from baseConfig, so there would be an error message from the `strict` rule. - */ - - assert.strictEqual(messages.length, 0); - }); - - it("should be inserted before overrideConfig", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: { - rules: { - semi: 2 - } - }, - overrideConfig: { - rules: { - semi: 1 - } - } - }); - - const [{ messages }] = await eslint.lintText("foo"); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].severity, 1); - }); - - it("should be inserted before configs from the config file and overrideConfig", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config-with-rules.js"), - baseConfig: { - rules: { - quotes: ["error", "double"], - semi: "error" - } - }, - overrideConfig: { - rules: { - quotes: "warn" - } - } - }); - - const [{ messages }] = await eslint.lintText('const foo = "bar"'); - - /* - * baseConfig: { quotes: ["error", "double"], semi: "error" } - * eslint.config-with-rules.js: { quotes: ["error", "single"] } - * overrideConfig: { quotes: "warn" } - * - * Merged config: { quotes: ["warn", "single"], semi: "error" } - */ - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "quotes"); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[1].ruleId, "semi"); - assert.strictEqual(messages[1].severity, 2); - }); - - it("when it has 'files' they should be interpreted as relative to the config file", async () => { - - /* - * `fixtures/plugins` directory does not have a config file. - * It's parent directory `fixtures` does have a config file, so - * the base path will be `fixtures`, cwd will be `fixtures/plugins` - */ - const eslint = new FlatESLint({ - cwd: getFixturePath("plugins"), - baseConfig: { - files: ["plugins/a.js"], - rules: { - semi: 2 - } - } - }); - - const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js") }); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - }); - - it("when it has 'ignores' they should be interpreted as relative to the config file", async () => { - - /* - * `fixtures/plugins` directory does not have a config file. - * It's parent directory `fixtures` does have a config file, so - * the base path will be `fixtures`, cwd will be `fixtures/plugins` - */ - const eslint = new FlatESLint({ - cwd: getFixturePath("plugins"), - baseConfig: { - ignores: ["plugins/a.js"] - } - }); - - const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js"), warnIgnored: true }); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.match(messages[0].message, /ignored/u); - }); - }); - - describe("config file", () => { - - it("new instance of FlatESLint should use the latest version of the config file (ESM)", async () => { - const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); - const configFileContent = "export default [{ rules: { semi: ['error', 'always'] } }];"; - const teardown = createCustomTeardown({ - cwd, - files: { - "package.json": '{ "type": "module" }', - "eslint.config.js": configFileContent, - "a.js": "foo\nbar;" - } - }); - - await teardown.prepare(); - - let eslint = new FlatESLint({ cwd }); - let [{ messages }] = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].messageId, "missingSemi"); - assert.strictEqual(messages[0].line, 1); - - await sleep(100); - await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); - - eslint = new FlatESLint({ cwd }); - [{ messages }] = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].messageId, "extraSemi"); - assert.strictEqual(messages[0].line, 2); - }); - - it("new instance of FlatESLint should use the latest version of the config file (CJS)", async () => { - const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); - const configFileContent = "module.exports = [{ rules: { semi: ['error', 'always'] } }];"; - const teardown = createCustomTeardown({ - cwd, - files: { - "eslint.config.js": configFileContent, - "a.js": "foo\nbar;" - } - }); - - await teardown.prepare(); - - let eslint = new FlatESLint({ cwd }); - let [{ messages }] = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].messageId, "missingSemi"); - assert.strictEqual(messages[0].line, 1); - - await sleep(100); - await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); - - eslint = new FlatESLint({ cwd }); - [{ messages }] = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].messageId, "extraSemi"); - assert.strictEqual(messages[0].line, 2); - }); - }); - - // only works on a Windows machine - if (os.platform() === "win32") { - - // https://github.com/eslint/eslint/issues/17042 - describe("with cwd that is using forward slash on Windows", () => { - const cwd = getFixturePath("example-app3"); - const cwdForwardSlash = cwd.replace(/\\/gu, "/"); - - it("should correctly handle ignore patterns", async () => { - const engine = new FlatESLint({ cwd: cwdForwardSlash }); - const results = await engine.lintFiles(["./src"]); - - // src/dist/2.js should be ignored - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, path.join(cwd, "src\\1.js")); - }); - - it("should pass cwd with backslashes to rules", async () => { - const engine = new FlatESLint({ - cwd: cwdForwardSlash, - overrideConfigFile: true, - overrideConfig: { - plugins: { - test: require(path.join(cwd, "node_modules", "eslint-plugin-test")) - }, - rules: { - "test/report-cwd": "error" - } - } - }); - const results = await engine.lintText(""); - - assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); - assert.strictEqual(results[0].messages[0].message, cwd); - }); - - it("should pass cwd with backslashes to formatters", async () => { - const engine = new FlatESLint({ - cwd: cwdForwardSlash - }); - const results = await engine.lintText(""); - const formatter = await engine.loadFormatter("cwd"); - - assert.strictEqual(formatter.format(results), cwd); - }); - }); - } - - describe("config with circular references", () => { - it("in 'settings'", async () => { - let resolvedSettings = null; - - const circular = {}; - - circular.self = circular; - - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: { - settings: { - sharedData: circular - }, - rules: { - "test-plugin/test-rule": 1 - } - }, - plugins: { - "test-plugin": { - rules: { - "test-rule": { - create(context) { - resolvedSettings = context.settings; - return { }; - } - } - } - } - } - }); - - await eslint.lintText("debugger;"); - - assert.deepStrictEqual(resolvedSettings.sharedData, circular); - }); - - it("in 'parserOptions'", async () => { - let resolvedParserOptions = null; - - const circular = {}; - - circular.self = circular; - - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: { - languageOptions: { - parser: { - parse(text, parserOptions) { - resolvedParserOptions = parserOptions; - } - }, - parserOptions: { - testOption: circular - } - } - } - }); - - await eslint.lintText("debugger;"); - - assert.deepStrictEqual(resolvedParserOptions.testOption, circular); - }); - }); - -}); - -describe("shouldUseFlatConfig", () => { - - /** - * Check that `shouldUseFlatConfig` returns the expected value from a CWD - * with a flat config and one without a flat config. - * @param {boolean} expectedValueWithConfig the expected return value of - * `shouldUseFlatConfig` when in a directory with a flat config present - * @param {boolean} expectedValueWithoutConfig the expected return value of - * `shouldUseFlatConfig` when in a directory without any flat config present - * @returns {void} - */ - function testShouldUseFlatConfig(expectedValueWithConfig, expectedValueWithoutConfig) { - describe("when there is a flat config file present", () => { - const originalDir = process.cwd(); - - beforeEach(() => { - process.chdir(__dirname); - }); - - afterEach(() => { - process.chdir(originalDir); - }); - - it(`is \`${expectedValueWithConfig}\``, async () => { - assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithConfig); - }); - }); - - describe("when there is no flat config file present", () => { - const originalDir = process.cwd(); - - beforeEach(() => { - process.chdir(os.tmpdir()); - }); - - afterEach(() => { - process.chdir(originalDir); - }); - - it(`is \`${expectedValueWithoutConfig}\``, async () => { - assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithoutConfig); - }); - }); - } - - describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'true'`", () => { - beforeEach(() => { - process.env.ESLINT_USE_FLAT_CONFIG = true; - }); - - afterEach(() => { - delete process.env.ESLINT_USE_FLAT_CONFIG; - }); - - testShouldUseFlatConfig(true, true); - }); - - describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'false'`", () => { - beforeEach(() => { - process.env.ESLINT_USE_FLAT_CONFIG = false; - }); - - afterEach(() => { - delete process.env.ESLINT_USE_FLAT_CONFIG; - }); - - testShouldUseFlatConfig(false, false); - }); - - describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is unset", () => { - testShouldUseFlatConfig(true, true); - }); -}); diff --git a/tests/lib/eslint/legacy-eslint.js b/tests/lib/eslint/legacy-eslint.js new file mode 100644 index 00000000000..cdf1bba2a89 --- /dev/null +++ b/tests/lib/eslint/legacy-eslint.js @@ -0,0 +1,7638 @@ +/** + * @fileoverview Tests for the ESLint class. + * @author Kai Cataldo + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("assert"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +const escapeStringRegExp = require("escape-string-regexp"); +const fCache = require("file-entry-cache"); +const sinon = require("sinon"); +const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); +const shell = require("shelljs"); +const { + Legacy: { + CascadingConfigArrayFactory + } +} = require("@eslint/eslintrc"); +const hash = require("../../../lib/cli-engine/hash"); +const { unIndent, createCustomTeardown } = require("../../_utils"); +const coreRules = require("../../../lib/rules"); +const childProcess = require("child_process"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("LegacyESLint", () => { + const examplePluginName = "eslint-plugin-example"; + const examplePluginNameWithNamespace = "@eslint/eslint-plugin-example"; + const examplePlugin = { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule"), + "make-syntax-error": require("../../fixtures/rules/make-syntax-error-rule") + } + }; + const examplePreprocessorName = "eslint-plugin-processor"; + const originalDir = process.cwd(); + const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); + + /** @type {import("../../../lib/eslint/legacy-eslint").LegacyESLint} */ + let LegacyESLint; + + /** + * Returns the path inside of the fixture directory. + * @param {...string} args file path segments. + * @returns {string} The path inside the fixture directory. + * @private + */ + function getFixturePath(...args) { + const filepath = path.join(fixtureDir, ...args); + + try { + return fs.realpathSync(filepath); + } catch { + return filepath; + } + } + + /** + * Create the ESLint object by mocking some of the plugins + * @param {Object} options options for ESLint + * @returns {ESLint} engine object + * @private + */ + function eslintWithPlugins(options) { + return new LegacyESLint({ + ...options, + plugins: { + [examplePluginName]: examplePlugin, + [examplePluginNameWithNamespace]: examplePlugin, + [examplePreprocessorName]: require("../../fixtures/processors/custom-processor") + } + }); + } + + /** + * Call the last argument. + * @param {any[]} args Arguments + * @returns {void} + */ + function callLastArgument(...args) { + process.nextTick(args[args.length - 1], null); + } + + // copy into clean area so as not to get "infected" by this project's .eslintrc files + before(function() { + + /* + * GitHub Actions Windows and macOS runners occasionally exhibit + * extremely slow filesystem operations, during which copying fixtures + * exceeds the default test timeout, so raise it just for this hook. + * Mocha uses `this` to set timeouts on an individual hook level. + */ + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API + shell.mkdir("-p", fixtureDir); + shell.cp("-r", "./tests/fixtures/.", fixtureDir); + }); + + beforeEach(() => { + ({ LegacyESLint } = require("../../../lib/eslint/legacy-eslint")); + }); + + after(() => { + shell.rm("-r", fixtureDir); + }); + + describe("ESLint constructor function", () => { + it("the default value of 'options.cwd' should be the current working directory.", async () => { + process.chdir(__dirname); + try { + const engine = new LegacyESLint({ useEslintrc: false }); + const results = await engine.lintFiles("eslint.js"); + + assert.strictEqual(path.dirname(results[0].filePath), __dirname); + } finally { + process.chdir(originalDir); + } + }); + + it("should normalize 'options.cwd'.", async () => { + const cwd = getFixturePath("example-app3"); + const engine = new LegacyESLint({ + cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { + "test/report-cwd": "error" + } + } + }); + const results = await engine.lintText(""); + + assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); + assert.strictEqual(results[0].messages[0].message, cwd); + + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + + it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { + assert.throws(() => { + // eslint-disable-next-line no-new -- Check for throwing + new LegacyESLint({ ignorePath: fixtureDir }); + }, new RegExp(escapeStringRegExp(`Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`), "u")); + }); + + // https://github.com/eslint/eslint/issues/2380 + it("should not modify baseConfig when format is specified", () => { + const customBaseConfig = { root: true }; + + new LegacyESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new -- Check for argument side effects + + assert.deepStrictEqual(customBaseConfig, { root: true }); + }); + + it("should throw readable messages if removed options are present", () => { + assert.throws( + () => new LegacyESLint({ + cacheFile: "", + configFile: "", + envs: [], + globals: [], + ignorePattern: [], + parser: "", + parserOptions: {}, + rules: {}, + plugins: [] + }), + new RegExp(escapeStringRegExp([ + "Invalid Options:", + "- Unknown options: cacheFile, configFile, envs, globals, ignorePattern, parser, parserOptions, rules", + "- 'cacheFile' has been removed. Please use the 'cacheLocation' option instead.", + "- 'configFile' has been removed. Please use the 'overrideConfigFile' option instead.", + "- 'envs' has been removed. Please use the 'overrideConfig.env' option instead.", + "- 'globals' has been removed. Please use the 'overrideConfig.globals' option instead.", + "- 'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.", + "- 'parser' has been removed. Please use the 'overrideConfig.parser' option instead.", + "- 'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.", + "- 'rules' has been removed. Please use the 'overrideConfig.rules' option instead.", + "- 'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead." + ].join("\n")), "u") + ); + }); + + it("should throw readable messages if wrong type values are given to options", () => { + assert.throws( + () => new LegacyESLint({ + allowInlineConfig: "", + baseConfig: "", + cache: "", + cacheLocation: "", + cwd: "foo", + errorOnUnmatchedPattern: "", + extensions: "", + fix: "", + fixTypes: ["xyz"], + globInputPaths: "", + ignore: "", + ignorePath: "", + overrideConfig: "", + overrideConfigFile: "", + plugins: "", + reportUnusedDisableDirectives: "", + resolvePluginsRelativeTo: "", + rulePaths: "", + useEslintrc: "" + }), + new RegExp(escapeStringRegExp([ + "Invalid Options:", + "- 'allowInlineConfig' must be a boolean.", + "- 'baseConfig' must be an object or null.", + "- 'cache' must be a boolean.", + "- 'cacheLocation' must be a non-empty string.", + "- 'cwd' must be an absolute path.", + "- 'errorOnUnmatchedPattern' must be a boolean.", + "- 'extensions' must be an array of non-empty strings or null.", + "- 'fix' must be a boolean or a function.", + "- 'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".", + "- 'globInputPaths' must be a boolean.", + "- 'ignore' must be a boolean.", + "- 'ignorePath' must be a non-empty string or null.", + "- 'overrideConfig' must be an object or null.", + "- 'overrideConfigFile' must be a non-empty string or null.", + "- 'plugins' must be an object or null.", + "- 'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.", + "- 'resolvePluginsRelativeTo' must be a non-empty string or null.", + "- 'rulePaths' must be an array of non-empty strings.", + "- 'useEslintrc' must be a boolean." + ].join("\n")), "u") + ); + }); + + it("should throw readable messages if 'plugins' option contains empty key", () => { + assert.throws( + () => new LegacyESLint({ + plugins: { + "eslint-plugin-foo": {}, + "eslint-plugin-bar": {}, + "": {} + } + }), + new RegExp(escapeStringRegExp([ + "Invalid Options:", + "- 'plugins' must not include an empty string." + ].join("\n")), "u") + ); + }); + }); + + describe("lintText()", () => { + let eslint; + + describe("when using local cwd .eslintrc", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/multiple-rules-config"), + files: { + ".eslintrc.json": { + root: true, + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should report the total and per file errors", async () => { + eslint = new LegacyESLint({ cwd: getPath() }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 5); + assert.strictEqual(results[0].messages[0].ruleId, "strict"); + assert.strictEqual(results[0].messages[1].ruleId, "no-var"); + assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); + assert.strictEqual(results[0].messages[3].ruleId, "quotes"); + assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 3); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); + }); + + it("should report the total and per file warnings", async () => { + eslint = new LegacyESLint({ + cwd: getPath(), + overrideConfig: { + rules: { + quotes: 1, + "no-var": 1, + "eol-last": 1, + strict: 1, + "no-unused-vars": 1 + } + } + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 5); + assert.strictEqual(results[0].messages[0].ruleId, "strict"); + assert.strictEqual(results[0].messages[1].ruleId, "no-var"); + assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); + assert.strictEqual(results[0].messages[3].ruleId, "quotes"); + assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 3); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); + }); + }); + + it("should report one message when using specific config file", async () => { + eslint = new LegacyESLint({ + overrideConfigFile: "fixtures/configurations/quotes-error.json", + useEslintrc: false, + cwd: getFixturePath("..") + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 1); + assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); + }); + + it("should report the filename when passed in", async () => { + eslint = new LegacyESLint({ + ignore: false, + cwd: getFixturePath() + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText("var foo = 'bar';", options); + + assert.strictEqual(results[0].filePath, getFixturePath("test.js")); + }); + + it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath("..") + }); + const options = { filePath: "fixtures/passing.js", warnIgnored: true }; + const results = await eslint.lintText("var bar = foo;", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + }); + + it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath("..") + }); + const options = { + filePath: "fixtures/passing.js", + warnIgnored: false + }; + + // intentional parsing error + const results = await eslint.lintText("va r bar = foo;", options); + + // should not report anything because the file is ignored + assert.strictEqual(results.length, 0); + }); + + it("should suppress excluded file warnings by default", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath("..") + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText("var bar = foo;", options); + + // should not report anything because there are no errors + assert.strictEqual(results.length, 0); + }); + + it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { + eslint = new LegacyESLint({ + ignorePath: "fixtures/.eslintignore", + cwd: getFixturePath(".."), + ignore: false, + useEslintrc: false, + overrideConfig: { + rules: { + "no-undef": 2 + } + } + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText("var bar = foo;", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].output, void 0); + }); + + it("should return a message and fixed text when in fix mode", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + semi: 2 + } + }, + ignore: false, + cwd: getFixturePath() + }); + const options = { filePath: "passing.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("passing.js"), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var bar = foo;", + usedDeprecatedRules: [{ + ruleId: "semi", + replacedBy: [] + }] + } + ]); + }); + + it("should use eslint:recommended rules when eslint:recommended configuration is specified", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + extends: ["eslint:recommended"] + }, + ignore: false, + cwd: getFixturePath() + }); + const options = { filePath: "file.js" }; + const results = await eslint.lintText("foo ()", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + it("should use eslint:all rules when eslint:all configuration is specified", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + extends: ["eslint:all"] + }, + ignore: false, + cwd: getFixturePath() + }); + const options = { filePath: "file.js" }; + const results = await eslint.lintText("if (true) { foo() }", options); + + assert.strictEqual(results.length, 1); + + const { messages } = results[0]; + + // Some rules that should report errors in the given code. Not all, as we don't want to update this test when we add new rules. + const expectedRules = ["no-undef", "no-constant-condition"]; + + expectedRules.forEach(ruleId => { + const messageFromRule = messages.find(message => message.ruleId === ruleId); + + assert.ok( + typeof messageFromRule === "object" && messageFromRule !== null, // LintMessage object + `Expected a message from rule '${ruleId}'` + ); + assert.strictEqual(messageFromRule.severity, 2); + }); + + }); + + it("correctly autofixes semicolon-conflicting-fixes", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true + }); + const inputPath = getFixturePath("autofix/semicolon-conflicting-fixes.js"); + const outputPath = getFixturePath("autofix/semicolon-conflicting-fixes.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("correctly autofixes return-conflicting-fixes", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true + }); + const inputPath = getFixturePath("autofix/return-conflicting-fixes.js"); + const outputPath = getFixturePath("autofix/return-conflicting-fixes.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + describe("Fix Types", () => { + it("should throw an error when an invalid fix type is specified", () => { + assert.throws(() => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layou"] + }); + }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); + }); + + it("should not fix any rules when fixTypes is used without fix", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: false, + fixTypes: ["layout"] + }); + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const results = await eslint.lintFiles([inputPath]); + + assert.strictEqual(results[0].output, void 0); + }); + + it("should not fix non-style rules when fixTypes has only 'layout'", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"] + }); + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const outputPath = getFixturePath("fix-types/fix-only-semi.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["suggestion"] + }); + const inputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.js"); + const outputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["suggestion", "layout"] + }); + const inputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.js"); + const outputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule doesn't have a 'meta' property", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + rulePaths: [getFixturePath("rules", "fix-types-test")] + }); + const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); + const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule is loaded after initialization with lintFiles()", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + plugins: { + test: { + rules: { + "no-program": require(getFixturePath("rules", "fix-types-test", "no-program.js")) + } + } + } + }); + const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); + const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule is loaded after initialization with lintText()", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + plugins: { + test: { + rules: { + "no-program": require(getFixturePath("rules", "fix-types-test", "no-program.js")) + } + } + } + }); + const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); + const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); + const results = await eslint.lintText(fs.readFileSync(inputPath, { encoding: "utf8" }), { filePath: inputPath }); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + }); + + it("should return a message and omit fixed text when in fix mode and fixes aren't done", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + "no-undef": 2 + } + }, + ignore: false, + cwd: getFixturePath() + }); + const options = { filePath: "passing.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("passing.js"), + messages: [ + { + ruleId: "no-undef", + severity: 2, + messageId: "undef", + message: "'foo' is not defined.", + line: 1, + column: 11, + endLine: 1, + endColumn: 14, + nodeType: "Identifier" + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar = foo", + usedDeprecatedRules: [] + } + ]); + }); + + it("should not delete code if there is a syntax error after trying to autofix.", async () => { + eslint = eslintWithPlugins({ + useEslintrc: false, + fix: true, + overrideConfig: { + plugins: ["example"], + rules: { + "example/make-syntax-error": "error" + } + }, + ignore: false, + cwd: getFixturePath() + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("test.js"), + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token is", + line: 1, + column: 19, + nodeType: null + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var bar = foothis is a syntax error.", + usedDeprecatedRules: [] + } + ]); + }); + + it("should not crash even if there are any syntax error since the first time.", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + "example/make-syntax-error": "error" + } + }, + ignore: false, + cwd: getFixturePath() + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText("var bar =", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("test.js"), + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token", + line: 1, + column: 10, + nodeType: null + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar =", + usedDeprecatedRules: [] + } + ]); + }); + + it("should return source code of file in `source` property when errors are present", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { semi: 2 } + } + }); + const results = await eslint.lintText("var foo = 'bar'"); + + assert.strictEqual(results[0].source, "var foo = 'bar'"); + }); + + it("should return source code of file in `source` property when warnings are present", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { semi: 1 } + } + }); + const results = await eslint.lintText("var foo = 'bar'"); + + assert.strictEqual(results[0].source, "var foo = 'bar'"); + }); + + + it("should not return a `source` property when no errors or warnings are present", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { semi: 2 } + } + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].source, void 0); + }); + + it("should not return a `source` property when fixes are applied", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + semi: 2, + "no-unused-vars": 2 + } + } + }); + const results = await eslint.lintText("var msg = 'hi' + foo\n"); + + assert.strictEqual(results[0].source, void 0); + assert.strictEqual(results[0].output, "var msg = 'hi' + foo;\n"); + }); + + it("should return a `source` property when a parsing error has occurred", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { eqeqeq: 2 } + } + }); + const results = await eslint.lintText("var bar = foothis is a syntax error.\n return bar;"); + + assert.deepStrictEqual(results, [ + { + filePath: "", + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token is", + line: 1, + column: 19, + nodeType: null + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar = foothis is a syntax error.\n return bar;", + usedDeprecatedRules: [] + } + ]); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should respect default ignore rules, even with --no-ignore", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath(), + ignore: false + }); + const results = await eslint.lintText("var bar = foo;", { filePath: "node_modules/passing.js", warnIgnored: true }); + const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js")); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + describe('plugin shorthand notation ("@scope" for "@scope/eslint-plugin")', () => { + const Module = require("module"); + let originalFindPath = null; + + /* eslint-disable no-underscore-dangle -- Override Node API */ + before(() => { + originalFindPath = Module._findPath; + Module._findPath = function(id, ...otherArgs) { + if (id === "@scope/eslint-plugin") { + return path.resolve(__dirname, "../../fixtures/plugin-shorthand/basic/node_modules/@scope/eslint-plugin/index.js"); + } + return originalFindPath.call(this, id, ...otherArgs); + }; + }); + after(() => { + Module._findPath = originalFindPath; + }); + /* eslint-enable no-underscore-dangle -- Override Node API */ + + it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { + eslint = new LegacyESLint({ cwd: getFixturePath("plugin-shorthand/basic") }); + const [result] = await eslint.lintText("var x = 0", { filePath: "index.js" }); + + assert.strictEqual(result.filePath, getFixturePath("plugin-shorthand/basic/index.js")); + assert.strictEqual(result.messages[0].ruleId, "@scope/rule"); + assert.strictEqual(result.messages[0].message, "OK"); + }); + + it("should resolve 'extends:[\"plugin:@scope/recommended\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { + eslint = new LegacyESLint({ cwd: getFixturePath("plugin-shorthand/extends") }); + const [result] = await eslint.lintText("var x = 0", { filePath: "index.js" }); + + assert.strictEqual(result.filePath, getFixturePath("plugin-shorthand/extends/index.js")); + assert.strictEqual(result.messages[0].ruleId, "@scope/rule"); + assert.strictEqual(result.messages[0].message, "OK"); + }); + }); + + it("should warn when deprecated rules are found in a config", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml" + }); + const [result] = await eslint.lintText("foo"); + + assert.deepStrictEqual( + result.usedDeprecatedRules, + [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] + ); + }); + + it("should throw if non-string value is given to 'code' parameter", async () => { + eslint = new LegacyESLint(); + await assert.rejects(() => eslint.lintText(100), /'code' must be a string/u); + }); + + it("should throw if non-object value is given to 'options' parameter", async () => { + eslint = new LegacyESLint(); + await assert.rejects(() => eslint.lintText("var a = 0", "foo.js"), /'options' must be an object, null, or undefined/u); + }); + + it("should throw if 'options' argument contains unknown key", async () => { + eslint = new LegacyESLint(); + await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option\(s\): filename/u); + }); + + it("should throw if non-string value is given to 'options.filePath' option", async () => { + eslint = new LegacyESLint(); + await assert.rejects(() => eslint.lintText("var a = 0", { filePath: "" }), /'options.filePath' must be a non-empty string or undefined/u); + }); + + it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { + eslint = new LegacyESLint(); + await assert.rejects(() => eslint.lintText("var a = 0", { warnIgnored: "" }), /'options.warnIgnored' must be a boolean or undefined/u); + }); + }); + + describe("lintFiles()", () => { + + /** @type {InstanceType} */ + let eslint; + + it("should use correct parser when custom parser is specified", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + ignore: false + }); + const filePath = path.resolve(__dirname, "../../fixtures/configurations/parser/custom.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].message, "Parsing error: Boom!"); + }); + + it("should report zero messages when given a config file and a valid file", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + ignore: false, + overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" + }); + const results = await eslint.lintFiles(["tests/fixtures/simple-valid-project/**/foo*.js"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should handle multiple patterns with overlapping files", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + ignore: false, + overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" + }); + const results = await eslint.lintFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + "tests/fixtures/simple-valid-project/foo.?s", + "tests/fixtures/simple-valid-project/{foo,src/foobar}.js" + ]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and espree as parser", async () => { + eslint = new LegacyESLint({ + overrideConfig: { + parser: "espree", + parserOptions: { + ecmaVersion: 2021 + } + }, + useEslintrc: false + }); + const results = await eslint.lintFiles(["lib/cli.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { + eslint = new LegacyESLint({ + overrideConfig: { + parser: "esprima" + }, + useEslintrc: false, + ignore: false + }); + const results = await eslint.lintFiles(["tests/fixtures/passing.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should throw an error when given a config file and a valid file and invalid parser", async () => { + eslint = new LegacyESLint({ + overrideConfig: { + parser: "test11" + }, + useEslintrc: false + }); + + await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Cannot find module 'test11'/u); + }); + + describe("Invalid inputs", () => { + + [ + ["an empty string", ""], + ["an empty array", []], + ["a string with a single space", " "], + ["an array with one empty string", [""]], + ["an array with two empty strings", ["", ""]] + + ].forEach(([name, value]) => { + + it(`should throw an error when passed ${name}`, async () => { + eslint = new LegacyESLint({ + useEslintrc: false + }); + + await assert.rejects(async () => await eslint.lintFiles(value), /'patterns' must be a non-empty string or an array of non-empty strings/u); + }); + + if (value === "" || Array.isArray(value) && value.length === 0) { + it(`should not throw an error when passed ${name} and passOnNoPatterns: true`, async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + passOnNoPatterns: true + }); + + const results = await eslint.lintFiles(value); + + assert.strictEqual(results.length, 0); + }); + } + + }); + + + }); + + it("should report zero messages when given a directory with a .js2 file", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + extensions: [".js2"] + }); + const results = await eslint.lintFiles([getFixturePath("files/foo.js2")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should fall back to defaults when extensions is set to an empty array", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("configurations"), + overrideConfigFile: getFixturePath("configurations", "quotes-error.json"), + extensions: [] + }); + const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should report zero messages when given a directory with a .js and a .js2 file", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath("..") + }); + const results = await eslint.lintFiles(["fixtures/files/"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles(["fixtures/files/*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should resolve globs when 'globInputPaths' option is true", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath("..") + }); + const results = await eslint.lintFiles(["fixtures/files/*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should not resolve globs when 'globInputPaths' option is false", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath(".."), + globInputPaths: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["fixtures/files/*"]); + }, /No files matching 'fixtures\/files\/\*' were found \(glob was disabled\)\./u); + }); + + it("should report on all files passed explicitly, even if ignored by default", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine") + }); + const results = await eslint.lintFiles(["node_modules/foo.js"]); + const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + it("should report on globs with explicit inclusion of dotfiles, even though ignored by default", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + overrideConfig: { + rules: { + quotes: [2, "single"] + } + } + }); + const results = await eslint.lintFiles(["hidden/.hiddenfolder/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should not check default ignored files without --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["node_modules"]); + }, /All files matched by 'node_modules' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should not check node_modules files even with --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + ignore: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["node_modules"]); + }, /All files matched by 'node_modules' are ignored\./u); + }); + + it("should not check .hidden files if they are passed explicitly without --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath(".."), + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "single"] + } + } + }); + const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); + const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + // https://github.com/eslint/eslint/issues/12873 + it("should not check files within a .hidden folder if they are passed explicitly without the --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "single"] + } + } + }); + const results = await eslint.lintFiles(["hidden/.hiddenfolder/double-quotes.js"]); + const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + it("should check .hidden files if they are passed explicitly with --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath(".."), + ignore: false, + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "single"] + } + } + }); + const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + it("should check .hidden files if they are unignored with an --ignore-pattern", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + ignore: true, + useEslintrc: false, + overrideConfig: { + ignorePatterns: "!.hidden*", + rules: { + quotes: [2, "single"] + } + } + }); + const results = await eslint.lintFiles(["hidden/"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should return one error message when given a config with rules with options and severity level set to error", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("configurations"), + overrideConfigFile: getFixturePath("configurations", "quotes-error.json") + }); + const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should return 3 messages when given a config file and a directory of 3 valid files", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "semi-error.json") + }); + const fixturePath = getFixturePath("formatters"); + const results = await eslint.lintFiles([fixturePath]); + + assert.strictEqual(results.length, 5); + assert.strictEqual(path.relative(fixturePath, results[0].filePath), "async.js"); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(path.relative(fixturePath, results[1].filePath), "broken.js"); + assert.strictEqual(results[1].errorCount, 0); + assert.strictEqual(results[1].warningCount, 0); + assert.strictEqual(results[1].fixableErrorCount, 0); + assert.strictEqual(results[1].fixableWarningCount, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(path.relative(fixturePath, results[2].filePath), "cwd.js"); + assert.strictEqual(results[2].errorCount, 0); + assert.strictEqual(results[2].warningCount, 0); + assert.strictEqual(results[2].fixableErrorCount, 0); + assert.strictEqual(results[2].fixableWarningCount, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(path.relative(fixturePath, results[3].filePath), "simple.js"); + assert.strictEqual(results[3].errorCount, 0); + assert.strictEqual(results[3].warningCount, 0); + assert.strictEqual(results[3].fixableErrorCount, 0); + assert.strictEqual(results[3].fixableWarningCount, 0); + assert.strictEqual(results[3].messages.length, 0); + assert.strictEqual(path.relative(fixturePath, results[4].filePath), path.join("test", "simple.js")); + assert.strictEqual(results[4].errorCount, 0); + assert.strictEqual(results[4].warningCount, 0); + assert.strictEqual(results[4].fixableErrorCount, 0); + assert.strictEqual(results[4].fixableWarningCount, 0); + assert.strictEqual(results[4].messages.length, 0); + }); + + it("should process when file is given by not specifying extensions", async () => { + eslint = new LegacyESLint({ + ignore: false, + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles(["fixtures/files/foo.js2"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given a config with environment set to browser", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "env-browser.json") + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given an option to set environment to browser", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfig: { + env: { browser: true }, + rules: { + "no-alert": 0, + "no-undef": 2 + } + } + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given a config with environment set to Node.js", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "env-node.json") + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-node.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should not return results from previous call when calling more than once", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + ignore: false, + overrideConfig: { + rules: { + semi: 2 + } + } + }); + const failFilePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); + const passFilePath = fs.realpathSync(getFixturePath("passing.js")); + + let results = await eslint.lintFiles([failFilePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, failFilePath); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].severity, 2); + + results = await eslint.lintFiles([passFilePath]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, passFilePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should throw an error when given a directory with all eslint excluded files in the directory", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore") + }); + + await assert.rejects(async () => { + await eslint.lintFiles([getFixturePath("./cli-engine/")]); + }, new RegExp(escapeStringRegExp(`All files matched by '${getFixturePath("./cli-engine/")}' are ignored.`), "u")); + }); + + it("should throw an error when all given files are ignored", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + ignorePath: getFixturePath(".eslintignore") + }); + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/cli-engine/"]); + }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + it("should throw an error when all given files are ignored even with a `./` prefix", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + ignorePath: getFixturePath(".eslintignore") + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/3788 + it("should ignore one-level down node_modules when ignore file has 'node_modules/' in it", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath("cli-engine", "nested_node_modules", ".eslintignore"), + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "double"] + } + }, + cwd: getFixturePath("cli-engine", "nested_node_modules") + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + // https://github.com/eslint/eslint/issues/3812 + it("should ignore all files and throw an error when tests/fixtures/ is in ignore file", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath("cli-engine/.eslintignore2"), + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "double"] + } + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/15642 + it("should ignore files that are ignored by patterns with escaped brackets", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithEscapedBrackets"), + useEslintrc: false, + cwd: getFixturePath("ignored-paths") + }); + + // Only `brackets/index.js` should be linted. Other files in `brackets/` should be ignored. + const results = await eslint.lintFiles(["brackets/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, getFixturePath("ignored-paths", "brackets", "index.js")); + }); + + it("should throw an error when all given files are ignored via ignore-pattern", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + ignorePatterns: "tests/fixtures/single-quoted.js" + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/*-quoted.js"]); + }, /All files matched by 'tests\/fixtures\/\*-quoted\.js' are ignored\./u); + }); + + it("should not throw an error when ignorePatterns is an empty array", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + ignorePatterns: [] + } + }); + + await assert.doesNotReject(async () => { + await eslint.lintFiles(["*.js"]); + }); + }); + + it("should return a warning when an explicitly given file is ignored", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath() + }); + const filePath = getFixturePath("passing.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should return two messages when given a file in excluded files list while ignore is off", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + ignore: false, + overrideConfig: { + rules: { + "no-undef": 2 + } + } + }); + const filePath = fs.realpathSync(getFixturePath("undef.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[1].severity, 2); + }); + + it("should return zero messages when executing a file with a shebang", async () => { + eslint = new LegacyESLint({ + ignore: false + }); + const results = await eslint.lintFiles([getFixturePath("shebang.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should give a warning when loading a custom rule that doesn't exist", async () => { + eslint = new LegacyESLint({ + ignore: false, + rulePaths: [getFixturePath("rules", "dir1")], + overrideConfigFile: getFixturePath("rules", "missing-rule.json") + }); + const results = await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "missing-rule"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].message, "Definition for rule 'missing-rule' was not found."); + }); + + it("should throw an error when loading a bad custom rule", async () => { + eslint = new LegacyESLint({ + ignore: false, + rulePaths: [getFixturePath("rules", "wrong")], + overrideConfigFile: getFixturePath("rules", "eslint.json") + }); + + + await assert.rejects(async () => { + await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); + }, /Error while loading rule 'custom-rule'/u); + }); + + it("should throw an error when loading a function-style custom rule", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + rulePaths: [getFixturePath("rules", "function-style")], + overrideConfig: { + rules: { + "no-strings": "error" + } + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); + }, /Error while loading rule 'no-strings': Rule must be an object with a `create` method/u); + }); + + it("should return one message when a custom rule matches a file", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + rulePaths: [getFixturePath("rules/")], + overrideConfigFile: getFixturePath("rules", "eslint.json") + }); + const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + it("should load custom rule from the provided cwd", async () => { + const cwd = path.resolve(getFixturePath("rules")); + + eslint = new LegacyESLint({ + ignore: false, + cwd, + rulePaths: ["./"], + overrideConfigFile: "eslint.json" + }); + const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + it("should return messages when multiple custom rules match a file", async () => { + eslint = new LegacyESLint({ + ignore: false, + rulePaths: [ + getFixturePath("rules", "dir1"), + getFixturePath("rules", "dir2") + ], + overrideConfigFile: getFixturePath("rules", "multi-rulesdirs.json") + }); + const filePath = fs.realpathSync(getFixturePath("rules", "test-multi-rulesdirs.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-literals"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-strings"); + assert.strictEqual(results[0].messages[1].severity, 2); + }); + + it("should return zero messages when executing without useEslintrc flag", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false + }); + const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when executing without useEslintrc flag in Node.js environment", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + overrideConfig: { + env: { node: true } + } + }); + const filePath = fs.realpathSync(getFixturePath("process-exit.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages and ignore .eslintrc files when executing with no-eslintrc flag", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + overrideConfig: { + env: { node: true } + } + }); + const filePath = fs.realpathSync(getFixturePath("eslintrc", "quotes.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages and ignore package.json files when executing with no-eslintrc flag", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + overrideConfig: { + env: { node: true } + } + }); + const filePath = fs.realpathSync(getFixturePath("packagejson", "quotes.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should warn when deprecated rules are configured", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + overrideConfig: { + rules: { + "indent-legacy": 1, + "callback-return": 1 + } + } + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual( + results[0].usedDeprecatedRules, + [ + { ruleId: "indent-legacy", replacedBy: ["indent"] }, + { ruleId: "callback-return", replacedBy: [] } + ] + ); + }); + + it("should not warn when deprecated rules are not configured", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + overrideConfig: { + rules: { eqeqeq: 1, "callback-return": 0 } + } + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, []); + }); + + it("should warn when deprecated rules are found in a config", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", + useEslintrc: false + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual( + results[0].usedDeprecatedRules, + [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] + ); + }); + + describe("Fix Mode", () => { + it("should return fixed text on multiple files when in fix mode", async () => { + + /** + * Converts CRLF to LF in output. + * This is a workaround for git's autocrlf option on Windows. + * @param {Object} result A result object to convert. + * @returns {void} + */ + function convertCRLF(result) { + if (result && result.output) { + result.output = result.output.replace(/\r\n/gu, "\n"); + } + } + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2 + } + } + }); + const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); + + results.forEach(convertCRLF); + assert.deepStrictEqual(results, [ + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "true ? \"yes\" : \"no\";\n", + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] + }, + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] + }, + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), + messages: [ + { + column: 9, + line: 2, + endColumn: 11, + endLine: 2, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2 + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] + }, + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), + messages: [ + { + column: 18, + line: 1, + endColumn: 21, + endLine: 1, + messageId: "undef", + message: "'foo' is not defined.", + nodeType: "Identifier", + ruleId: "no-undef", + severity: 2 + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var msg = \"hi\" + foo;\n", + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi" + }, + { + replacedBy: [], + ruleId: "quotes" + }, + { + replacedBy: [], + ruleId: "space-infix-ops" + } + ] + } + ]); + }); + + it("should run autofix even if files are cached without autofix results", async () => { + const baseOptions = { + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2 + } + } + }; + + eslint = new LegacyESLint(Object.assign({}, baseOptions, { cache: true, fix: false })); + + // Do initial lint run and populate the cache file + await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); + + eslint = new LegacyESLint(Object.assign({}, baseOptions, { cache: true, fix: true })); + const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); + + assert(results.some(result => result.output)); + }); + }); + + // These tests have to do with https://github.com/eslint/eslint/issues/963 + + describe("configuration hierarchy", () => { + + // Default configuration - blank + it("should return zero messages when executing with no .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // No default configuration rules - conf/environments.js (/*eslint-env node*/) + it("should return zero messages when executing with no .eslintrc in the Node.js environment", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes-node.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return one message when executing with .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Project configuration - second level .eslintrc + it("should return one message when executing with local .eslintrc that overrides parent .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - third level .eslintrc + it("should return one message when executing with local .eslintrc that overrides parent and grandparent .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/subsubbroken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - first level package.json + it("should return one message when executing with package.json", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - second level package.json + it("should return zero messages when executing with local package.json that overrides parent package.json", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - third level package.json + it("should return one message when executing with local package.json that overrides parent and grandparent package.json", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Project configuration - .eslintrc overrides package.json in same directory + it("should return one message when executing with .eslintrc that overrides a package.json in the same directory", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return two messages when executing with config file that adds to local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); + assert.strictEqual(results[0].messages[1].severity, 1); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return no messages when executing with config file that overrides local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Command line configuration - --config with second level .eslintrc + it("should return two messages when executing with config file that adds to local and parent .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); + assert.strictEqual(results[0].messages[1].severity, 1); + }); + + // Command line configuration - --config with second level .eslintrc + it("should return one message when executing with config file that overrides local and parent .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("config-hierarchy/broken/override-conf.yaml") + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return no messages when executing with config file that overrides local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Command line configuration - --rule with --config and first level .eslintrc + it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("config-hierarchy/broken/override-conf.yaml"), + overrideConfig: { + rules: { + quotes: [1, "double"] + } + } + }); + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Command line configuration - --rule with --config and first level .eslintrc + it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("/config-hierarchy/broken/override-conf.yaml"), + overrideConfig: { + rules: { + quotes: [1, "double"] + } + } + }); + const results = await eslint.lintFiles([getFixturePath("config-hierarchy/broken/console-wrong-quotes.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + }); + + describe("plugins", () => { + it("should return two messages when executing with config file that specifies a plugin", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix.json"), + useEslintrc: false + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test/test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + }); + + it("should return two messages when executing with config file that specifies a plugin with namespace", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix-and-namespace.json"), + useEslintrc: false + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "@eslint/example/example-rule"); + }); + + it("should return two messages when executing with config file that specifies a plugin without prefix", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "plugins-without-prefix.json"), + useEslintrc: false + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + }); + + it("should return two messages when executing with config file that specifies a plugin without prefix and with namespace", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("configurations", "plugins-without-prefix-with-namespace.json"), + useEslintrc: false + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "@eslint/example/example-rule"); + }); + + it("should return two messages when executing with cli option that specifies a plugin", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + plugins: ["example"], + rules: { "example/example-rule": 1 } + } + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + }); + + it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { "test/example-rule": 1 } + }, + plugins: { + "eslint-plugin-test": { rules: { "example-rule": require("../../fixtures/rules/custom-rule") } } + } + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); + }); + + it("should throw an error when executing with a function-style rule from a preloaded plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { "test/example-rule": 1 } + }, + plugins: { + "eslint-plugin-test": { rules: { "example-rule": () => ({}) } } + } + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Error while loading rule 'test\/example-rule': Rule must be an object with a `create` method/u); + }); + + it("should return two messages when executing with `baseConfig` that extends preloaded plugin config", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + baseConfig: { + extends: ["plugin:test/preset"] + }, + plugins: { + test: { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule") + }, + configs: { + preset: { + rules: { + "test/example-rule": 1 + }, + plugins: ["test"] + } + } + } + } + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); + }); + + it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", async () => { + eslint = new LegacyESLint({ + resolvePluginsRelativeTo: getFixturePath("plugins"), + baseConfig: { + plugins: ["with-rules"], + rules: { "with-rules/rule1": "error" } + }, + useEslintrc: false + }); + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "with-rules/rule1"); + assert.strictEqual(results[0].messages[0].message, "Rule report from plugin"); + }); + + it("should throw an error when executing with a function-style rule from a plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["with-function-style-rules"], + rules: { "with-function-style-rules/rule1": "error" } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Error while loading rule 'with-function-style-rules\/rule1': Rule must be an object with a `create` method/u); + }); + + it("should throw an error when executing with a rule with `schema:true` from a plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-true"], + rules: { "schema-true/rule1": "error" } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Error while processing options validation schema of rule 'schema-true\/rule1': Rule's `meta.schema` must be an array or object/u); + }); + + it("should throw an error when executing with a rule with `schema:null` from a plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-null"], + rules: { "schema-null/rule1": "error" } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Error while processing options validation schema of rule 'schema-null\/rule1': Rule's `meta.schema` must be an array or object/u); + }); + + it("should throw an error when executing with a rule with invalid JSON schema type from a plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-invalid"], + rules: { "schema-invalid/rule1": "error" } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Error while processing options validation schema of rule 'schema-invalid\/rule1': minItems must be number/u); + }); + + it("should succesfully execute with a rule with `schema:false` from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-false"], + rules: { "schema-false/rule1": "error" } + }, + useEslintrc: false + }); + + const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].ruleId, "schema-false/rule1"); + assert.strictEqual(result.messages[0].message, "No options were passed"); + }); + + it("should succesfully execute with a rule with `schema:false` from a plugin when an option is passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-false"], + rules: { "schema-false/rule1": ["error", "always"] } + }, + useEslintrc: false + }); + + const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].ruleId, "schema-false/rule1"); + assert.strictEqual(result.messages[0].message, "Option 'always' was passed"); + }); + + it("should succesfully execute with a rule with `schema:[]` from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-empty-array"], + rules: { "schema-empty-array/rule1": "error" } + }, + useEslintrc: false + }); + + const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].ruleId, "schema-empty-array/rule1"); + assert.strictEqual(result.messages[0].message, "Hello"); + }); + + it("should throw when executing with a rule with `schema:[]` from a plugin when an option is passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-empty-array"], + rules: { "schema-empty-array/rule1": ["error", "always"] } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Configuration for rule "schema-empty-array\/rule1" is invalid.*should NOT have more than 0 items/us); + }); + + it("should succesfully execute with a rule with no schema from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-missing"], + rules: { "schema-missing/rule1": "error" } + }, + useEslintrc: false + }); + + const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].ruleId, "schema-missing/rule1"); + assert.strictEqual(result.messages[0].message, "Hello"); + }); + + it("should throw when executing with a rule with no schema from a plugin when an option is passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-missing"], + rules: { "schema-missing/rule1": ["error", "always"] } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Configuration for rule "schema-missing\/rule1" is invalid.*should NOT have more than 0 items/us); + }); + + it("should succesfully execute with a rule with an array schema from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-array"], + rules: { "schema-array/rule1": "error" } + }, + useEslintrc: false + }); + + const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].ruleId, "schema-array/rule1"); + assert.strictEqual(result.messages[0].message, "No options were passed"); + }); + + it("should succesfully execute with a rule with an array schema from a plugin when a correct option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-array"], + rules: { "schema-array/rule1": ["error", "always"] } + }, + useEslintrc: false + }); + + const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].ruleId, "schema-array/rule1"); + assert.strictEqual(result.messages[0].message, "Option 'always' was passed"); + }); + + it("should throw when executing with a rule with an array schema from a plugin when an incorrect option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-array"], + rules: { "schema-array/rule1": ["error", 5] } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Configuration for rule "schema-array\/rule1" is invalid.*Value 5 should be string/us); + }); + + it("should throw when executing with a rule with an array schema from a plugin when an extra option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-array"], + rules: { "schema-array/rule1": ["error", "always", "never"] } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Configuration for rule "schema-array\/rule1" is invalid.*should NOT have more than 1 items/us); + }); + + it("should succesfully execute with a rule with an object schema from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-object"], + rules: { "schema-object/rule1": "error" } + }, + useEslintrc: false + }); + + const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].ruleId, "schema-object/rule1"); + assert.strictEqual(result.messages[0].message, "No options were passed"); + }); + + it("should succesfully execute with a rule with an object schema from a plugin when a correct option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-object"], + rules: { "schema-object/rule1": ["error", "always"] } + }, + useEslintrc: false + }); + + const [result] = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].ruleId, "schema-object/rule1"); + assert.strictEqual(result.messages[0].message, "Option 'always' was passed"); + }); + + it("should throw when executing with a rule with an object schema from a plugin when an incorrect option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-object"], + rules: { "schema-object/rule1": ["error", 5] } + }, + useEslintrc: false + }); + + await assert.rejects(async () => { + await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + }, /Configuration for rule "schema-object\/rule1" is invalid.*Value 5 should be string/us); + }); + }); + + describe("cache", () => { + + /** + * helper method to delete a file without caring about exceptions + * @param {string} filePath The file path + * @returns {void} + */ + function doDelete(filePath) { + try { + fs.unlinkSync(filePath); + } catch { + + /* + * we don't care if the file didn't exist, since our + * intention was to remove the file + */ + } + } + + let cacheFilePath; + + beforeEach(() => { + cacheFilePath = null; + }); + + afterEach(() => { + sinon.restore(); + if (cacheFilePath) { + doDelete(cacheFilePath); + } + }); + + describe("when cacheLocation is a directory or looks like a directory", () => { + + const cwd = getFixturePath(); + + /** + * helper method to delete the cache files created during testing + * @returns {void} + */ + function deleteCacheDir() { + try { + fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); + } catch { + + /* + * we don't care if the file didn't exist, since our + * intention was to remove the file + */ + } + } + beforeEach(() => { + deleteCacheDir(); + }); + + afterEach(() => { + deleteCacheDir(); + }); + + it("should create the directory and the cache file inside it when cacheLocation ends with a slash", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + ignore: false + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); + }); + + it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); + + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + ignore: false + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); + }); + + it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { + assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); + + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + ignore: false + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); + }); + }); + + it("should create the cache file inside cwd when no cacheLocation provided", async () => { + const cwd = path.resolve(getFixturePath("cli-engine")); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + useEslintrc: false, + cache: true, + cwd, + overrideConfig: { + rules: { + "no-console": 0 + } + }, + extensions: ["js"], + ignore: false + }); + const file = getFixturePath("cli-engine", "console.js"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created at provided cwd"); + }); + + it("should invalidate the cache if the configuration changed between executions", async () => { + const cwd = getFixturePath("cache/src"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + ignore: false + }); + + let spy = sinon.spy(fs, "readFileSync"); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + const results = await eslint.lintFiles([file]); + + for (const { errorCount, warningCount } of results) { + assert.strictEqual(errorCount + warningCount, 0, "the file should have passed linting without errors or warnings"); + } + assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); + + // destroy the spy + sinon.restore(); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 2, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + ignore: false + }); + + // create a new spy + spy = sinon.spy(fs, "readFileSync"); + + const [newResult] = await eslint.lintFiles([file]); + + assert(spy.calledWith(file), "ESLint should have read the file again because it's considered changed because the config changed"); + assert.strictEqual(newResult.errorCount, 1, "since configuration changed the cache should have not been used and one error should have been reported"); + assert.strictEqual(newResult.messages[0].ruleId, "no-console"); + assert(shell.test("-f", cacheFilePath), "The cache for ESLint should still exist"); + }); + + it("should remember the files from a previous run and do not operate on them if not changed", async () => { + const cwd = getFixturePath("cache/src"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + ignore: false + }); + + let spy = sinon.spy(fs, "readFileSync"); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + const result = await eslint.lintFiles([file]); + + assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); + + // destroy the spy + sinon.restore(); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + ignore: false + }); + + // create a new spy + spy = sinon.spy(fs, "readFileSync"); + + const cachedResult = await eslint.lintFiles([file]); + + assert.deepStrictEqual(result, cachedResult, "the result should have been the same"); + + // assert the file was not processed because the cache was used + assert(!spy.calledWith(file), "the file should not have been reloaded"); + }); + + it("when `cacheLocation` is specified, should create the cache file with `cache:true` and then delete it with `cache:false`", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + const eslintOptions = { + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + cwd: path.join(fixtureDir, "..") + }; + + eslint = new LegacyESLint(eslintOptions); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); + + eslintOptions.cache = false; + eslint = new LegacyESLint(eslintOptions); + + await eslint.lintFiles([file]); + + assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted since last run did not use the cache"); + }); + + it("should not throw an error if the cache file to be deleted does not exist on a read-only file system", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + // Simulate a read-only file system. + sinon.stub(fs, "unlinkSync").throws( + Object.assign(new Error("read-only file system"), { code: "EROFS" }) + ); + + const eslintOptions = { + useEslintrc: false, + + // specifying cache true the cache will be created + cache: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + cwd: path.join(fixtureDir, "..") + }; + + eslint = new LegacyESLint(eslintOptions); + + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(fs.unlinkSync.calledWithExactly(cacheFilePath), "Expected attempt to delete the cache was not made."); + }); + + it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + const result = await eslint.lintFiles([badFile, goodFile]); + const [badFileResult, goodFileResult] = result; + + assert.notStrictEqual(badFileResult.errorCount + badFileResult.warningCount, 0, "the bad file should have some lint errors or warnings"); + assert.strictEqual(goodFileResult.errorCount + badFileResult.warningCount, 0, "the good file should have passed linting without errors or warnings"); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); + + const fileCache = fCache.createFromFile(cacheFilePath); + const { cache } = fileCache; + + assert.strictEqual(typeof cache.getKey(goodFile), "object", "the entry for the good file should have been in the cache"); + assert.strictEqual(typeof cache.getKey(badFile), "object", "the entry for the bad file should have been in the cache"); + const cachedResult = await eslint.lintFiles([badFile, goodFile]); + + assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); + }); + + it("should not contain in the cache a file that was deleted", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + const toBeDeletedFile = fs.realpathSync(getFixturePath("cache/src", "file-to-delete.js")); + + await eslint.lintFiles([badFile, goodFile, toBeDeletedFile]); + const fileCache = fCache.createFromFile(cacheFilePath); + let { cache } = fileCache; + + assert.strictEqual(typeof cache.getKey(toBeDeletedFile), "object", "the entry for the file to be deleted should have been in the cache"); + + // delete the file from the file system + fs.unlinkSync(toBeDeletedFile); + + /* + * file-entry-cache@2.0.0 will remove from the cache deleted files + * even when they were not part of the array of files to be analyzed + */ + await eslint.lintFiles([badFile, goodFile]); + + cache = JSON.parse(fs.readFileSync(cacheFilePath)); + + assert.strictEqual(typeof cache[0][toBeDeletedFile], "undefined", "the entry for the file to be deleted should not have been in the cache"); + + // make sure that the previos assertion checks the right place + assert.notStrictEqual(typeof cache[0][badFile], "undefined", "the entry for the bad file should have been in the cache"); + assert.notStrictEqual(typeof cache[0][goodFile], "undefined", "the entry for the good file should have been in the cache"); + }); + + it("should contain files that were not visited in the cache provided they still exist", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + const testFile2 = fs.realpathSync(getFixturePath("cache/src", "test-file2.js")); + + await eslint.lintFiles([badFile, goodFile, testFile2]); + + let fileCache = fCache.createFromFile(cacheFilePath); + let { cache } = fileCache; + + assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); + + /* + * we pass a different set of files minus test-file2 + * previous version of file-entry-cache would remove the non visited + * entries. 2.0.0 version will keep them unless they don't exist + */ + await eslint.lintFiles([badFile, goodFile]); + + fileCache = fCache.createFromFile(cacheFilePath); + cache = fileCache.cache; + + assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); + }); + + it("should not delete cache when executing on text", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); + + await eslint.lintText("var foo = 'bar';"); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); + }); + + it("should not delete cache when executing on text with a provided filename", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); + + await eslint.lintText("var bar = foo;", { filePath: "fixtures/passing.js" }); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); + }); + + it("should not delete cache when executing on files with --cache flag", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, ""); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + const file = getFixturePath("cli-engine", "console.js"); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); + + await eslint.lintFiles([file]); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); + }); + + it("should delete cache when executing on files without --cache flag", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + const file = getFixturePath("cli-engine", "console.js"); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); + + await eslint.lintFiles([file]); + + assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted"); + }); + + it("should use the specified cache file", async () => { + cacheFilePath = path.resolve(".cache/custom-cache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + useEslintrc: false, + + // specify a custom cache file + cacheLocation: cacheFilePath, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"], + cwd: path.join(fixtureDir, "..") + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + const result = await eslint.lintFiles([badFile, goodFile]); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); + + const fileCache = fCache.createFromFile(cacheFilePath); + const { cache } = fileCache; + + assert(typeof cache.getKey(goodFile) === "object", "the entry for the good file should have been in the cache"); + assert(typeof cache.getKey(badFile) === "object", "the entry for the bad file should have been in the cache"); + + const cachedResult = await eslint.lintFiles([badFile, goodFile]); + + assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); + }); + + // https://github.com/eslint/eslint/issues/13507 + it("should not store `usedDeprecatedRules` in the cache file", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + const deprecatedRuleId = "space-in-parens"; + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + [deprecatedRuleId]: 2 + } + } + }); + + const filePath = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + + /* + * Run linting on the same file 3 times to cover multiple cases: + * Run 1: Lint result wasn't already cached. + * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. + * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible + * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. + */ + for (let i = 0; i < 3; i++) { + const [result] = await eslint.lintFiles([filePath]); + + assert( + result.usedDeprecatedRules && result.usedDeprecatedRules.some(rule => rule.ruleId === deprecatedRuleId), + "the deprecated rule should have been in result.usedDeprecatedRules" + ); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); + + const fileCache = fCache.create(cacheFilePath); + const descriptor = fileCache.getFileDescriptor(filePath); + + assert(typeof descriptor === "object", "an entry for the file should have been in the cache file"); + assert(typeof descriptor.meta.results === "object", "lint result for the file should have been in its cache entry in the cache file"); + assert(typeof descriptor.meta.results.usedDeprecatedRules === "undefined", "lint result in the cache file contains `usedDeprecatedRules`"); + } + + }); + + // https://github.com/eslint/eslint/issues/13507 + it("should store `source` as `null` in the cache file if the lint result has `source` property", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-unused-vars": 2 + } + } + }); + + const filePath = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + + /* + * Run linting on the same file 3 times to cover multiple cases: + * Run 1: Lint result wasn't already cached. + * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. + * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible + * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. + */ + for (let i = 0; i < 3; i++) { + const [result] = await eslint.lintFiles([filePath]); + + assert(typeof result.source === "string", "the result should have contained the `source` property"); + + assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); + + const fileCache = fCache.create(cacheFilePath); + const descriptor = fileCache.getFileDescriptor(filePath); + + assert(typeof descriptor === "object", "an entry for the file should have been in the cache file"); + assert(typeof descriptor.meta.results === "object", "lint result for the file should have been in its cache entry in the cache file"); + + // if the lint result contains `source`, it should be stored as `null` in the cache file + assert.strictEqual(descriptor.meta.results.source, null, "lint result in the cache file contains non-null `source`"); + } + + }); + + describe("cacheStrategy", () => { + it("should detect changes using a file's modification time when set to 'metadata'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "metadata", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + + await eslint.lintFiles([badFile, goodFile]); + let fileCache = fCache.createFromFile(cacheFilePath); + const entries = fileCache.normalizeEntries([badFile, goodFile]); + + entries.forEach(entry => { + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); + }); + + // this should result in a changed entry + shell.touch(goodFile); + fileCache = fCache.createFromFile(cacheFilePath); + assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); + assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} should have been changed`); + }); + + it("should not detect changes using a file's modification time when set to 'content'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "content", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + + await eslint.lintFiles([badFile, goodFile]); + let fileCache = fCache.createFromFile(cacheFilePath, true); + let entries = fileCache.normalizeEntries([badFile, goodFile]); + + entries.forEach(entry => { + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); + }); + + // this should NOT result in a changed entry + shell.touch(goodFile); + fileCache = fCache.createFromFile(cacheFilePath, true); + entries = fileCache.normalizeEntries([badFile, goodFile]); + entries.forEach(entry => { + assert(entry.changed === false, `the entry for ${entry.key} should have remained unchanged`); + }); + }); + + it("should detect changes using a file's contents when set to 'content'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "content", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2 + } + }, + extensions: ["js"] + }); + const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); + const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); + const goodFileCopy = path.resolve(`${path.dirname(goodFile)}`, "test-file-copy.js"); + + shell.cp(goodFile, goodFileCopy); + + await eslint.lintFiles([badFile, goodFileCopy]); + let fileCache = fCache.createFromFile(cacheFilePath, true); + const entries = fileCache.normalizeEntries([badFile, goodFileCopy]); + + entries.forEach(entry => { + assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); + }); + + // this should result in a changed entry + shell.sed("-i", "abc", "xzy", goodFileCopy); + fileCache = fCache.createFromFile(cacheFilePath, true); + assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); + assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} should have been changed`); + }); + }); + }); + + describe("processors", () => { + it("should return two messages when executing with config file that specifies a processor", async () => { + eslint = eslintWithPlugins({ + overrideConfigFile: getFixturePath("configurations", "processors.json"), + useEslintrc: false, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + }); + + it("should return two messages when executing with config file that specifies preloaded processor", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2 + } + }, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + plugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text]; + }, + postprocess(messages) { + return messages[0]; + } + } + } + } + } + }); + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + }); + + it("should run processors when calling lintFiles with config file that specifies a processor", async () => { + eslint = eslintWithPlugins({ + overrideConfigFile: getFixturePath("configurations", "processors.json"), + useEslintrc: false, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, "..") + }); + const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); + + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + + it("should run processors when calling lintFiles with config file that specifies preloaded processor", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2 + } + }, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + plugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = "post-processed"; + return messages[0]; + } + } + } + } + } + }); + const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); + + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + + it("should run processors when calling lintText with config file that specifies a processor", async () => { + eslint = eslintWithPlugins({ + overrideConfigFile: getFixturePath("configurations", "processors.json"), + useEslintrc: false, + extensions: ["js", "txt"], + ignore: false + }); + const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); + + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + + it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2 + } + }, + extensions: ["js", "txt"], + ignore: false, + plugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = "post-processed"; + return messages[0]; + } + } + } + } + } + }); + const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); + + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + + it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => { + let count = 0; + + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + overrides: [{ + files: ["**/*.txt/*.txt"], + rules: { + "no-console": 2, + "no-unused-vars": 2 + } + }] + }, + extensions: ["txt"], + ignore: false, + plugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + count++; + return [ + { + + // it will be run twice, and text will be as-is at the second time, then it will not run third time + text: text.replace("a()", "b()"), + filename: ".txt" + } + ]; + }, + postprocess(messages) { + messages[0][0].ruleId = "post-processed"; + return messages[0]; + } + } + } + } + } + }); + const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); + + assert.strictEqual(count, 2); + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + + describe("autofixing with processors", () => { + const HTML_PROCESSOR = Object.freeze({ + preprocess(text) { + return [text.replace(/^", { filePath: "foo.html" }); + + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].output, ""); + }); + + it("should not run in autofix mode when using a processor that does not support autofixing", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + semi: 2 + } + }, + extensions: ["js", "txt"], + ignore: false, + fix: true, + plugins: { + "test-processor": { processors: { ".html": HTML_PROCESSOR } } + } + }); + const results = await eslint.lintText("", { filePath: "foo.html" }); + + assert.strictEqual(results[0].messages.length, 1); + assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); + }); + + it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + semi: 2 + } + }, + extensions: ["js", "txt"], + ignore: false, + plugins: { + "test-processor": { + processors: { + ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) + } + } + } + }); + const results = await eslint.lintText("", { filePath: "foo.html" }); + + assert.strictEqual(results[0].messages.length, 1); + assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); + }); + }); + }); + + describe("Patterns which match no file should throw errors.", () => { + beforeEach(() => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + useEslintrc: false + }); + }); + + it("one file", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["non-exist.js"]); + }, /No files matching 'non-exist\.js' were found\./u); + }); + + it("should throw if the directory exists and is empty", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["empty"]); + }, /No files matching 'empty' were found\./u); + }); + + it("one glob pattern", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["non-exist/**/*.js"]); + }, /No files matching 'non-exist\/\*\*\/\*\.js' were found\./u); + }); + + it("two files", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["aaa.js", "bbb.js"]); + }, /No files matching 'aaa\.js' were found\./u); + }); + + it("a mix of an existing file and a non-existing file", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["console.js", "non-exist.js"]); + }, /No files matching 'non-exist\.js' were found\./u); + }); + }); + + describe("overrides", () => { + beforeEach(() => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine/overrides-with-dot"), + ignore: false + }); + }); + + it("should recognize dotfiles", async () => { + const ret = await eslint.lintFiles([".test-target.js"]); + + assert.strictEqual(ret.length, 1); + assert.strictEqual(ret[0].messages.length, 1); + assert.strictEqual(ret[0].messages[0].ruleId, "no-unused-vars"); + }); + }); + + describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/11510"), + files: { + "no-console-error-in-overrides.json": JSON.stringify({ + overrides: [{ + files: ["*.js"], + rules: { "no-console": "error" } + }] + }), + ".eslintrc.json": JSON.stringify({ + extends: "./no-console-error-in-overrides.json", + rules: { "no-console": "off" } + }), + "a.js": "console.log();" + } + }); + + beforeEach(() => { + eslint = new LegacyESLint({ cwd: getPath() }); + return prepare(); + }); + + afterEach(cleanup); + + it("should not report 'no-console' error.", async () => { + const results = await eslint.lintFiles("a.js"); + + assert.strictEqual(results.length, 1); + assert.deepStrictEqual(results[0].messages, []); + }); + }); + + describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/11559"), + files: { + "node_modules/eslint-plugin-test/index.js": ` + exports.configs = { + recommended: { plugins: ["test"] } + }; + exports.rules = { + foo: { + meta: { schema: [{ type: "number" }] }, + create() { return {}; } + } + }; + `, + ".eslintrc.json": JSON.stringify({ + + // Import via the recommended config. + extends: "plugin:test/recommended", + + // Has invalid option. + rules: { "test/foo": ["error", "invalid-option"] } + }), + "a.js": "console.log();" + } + }); + + beforeEach(() => { + eslint = new LegacyESLint({ cwd: getPath() }); + return prepare(); + }); + + afterEach(cleanup); + + + it("should throw fatal error.", async () => { + await assert.rejects(async () => { + await eslint.lintFiles("a.js"); + }, /invalid-option/u); + }); + }); + + describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "cli-engine/11586"), + files: { + "node_modules/eslint-plugin-test/index.js": ` + exports.rules = { + "no-example": { + meta: { type: "problem", fixable: "code" }, + create(context) { + return { + Identifier(node) { + if (node.name === "example") { + context.report({ + node, + message: "fix", + fix: fixer => fixer.replaceText(node, "fixed") + }) + } + } + }; + } + } + }; + `, + ".eslintrc.json": { + plugins: ["test"], + rules: { "test/no-example": "error" } + }, + "a.js": "example;" + } + }); + + beforeEach(() => { + eslint = new LegacyESLint({ + cwd: getPath(), + fix: true, + fixTypes: ["problem"] + }); + + return prepare(); + }); + + afterEach(cleanup); + + it("should not crash.", async () => { + const results = await eslint.lintFiles("a.js"); + + assert.strictEqual(results.length, 1); + assert.deepStrictEqual(results[0].messages, []); + assert.deepStrictEqual(results[0].output, "fixed;"); + }); + }); + + describe("multiple processors", () => { + const root = path.join(os.tmpdir(), "eslint/eslint/multiple-processors"); + const commonFiles = { + "node_modules/pattern-processor/index.js": fs.readFileSync( + require.resolve("../../fixtures/processors/pattern-processor"), + "utf8" + ), + "node_modules/eslint-plugin-markdown/index.js": ` + const { defineProcessor } = require("pattern-processor"); + const processor = defineProcessor(${/```(\w+)\n([\s\S]+?)\n```/gu}); + exports.processors = { + ".md": { ...processor, supportsAutofix: true }, + "non-fixable": processor + }; + `, + "node_modules/eslint-plugin-html/index.js": ` + const { defineProcessor } = require("pattern-processor"); + const processor = defineProcessor(${/ + + \`\`\` + ` + }; + + let cleanup; + + beforeEach(() => { + cleanup = () => { }; + }); + + afterEach(() => cleanup()); + + it("should lint only JavaScript blocks if '--ext' was not given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" } + } + } + }); + + cleanup = teardown.cleanup; + await teardown.prepare(); + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + }); + + it("should fix only JavaScript blocks if '--ext' was not given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" } + } + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath(), fix: true }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].output, unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `); + }); + + it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" } + } + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual(results[0].messages[1].line, 7); + }); + + it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" } + } + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].output, unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `); + }); + + it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/non-fixable" // supportsAutofix: false + } + ] + } + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS Block in HTML Block + assert.strictEqual(results[0].messages[0].line, 7); + assert.strictEqual(results[0].messages[0].fix, void 0); + assert.strictEqual(results[0].output, unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `); + }); + + it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + + // this rules are not used because ESLint re-resolve configs if a code block had a different file extension. + rules: { + semi: "error", + "no-console": "off" + } + }, + { + files: "**/*.html/*.js", + rules: { + semi: "off", + "no-console": "error" + } + } + ] + } + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-console"); + assert.strictEqual(results[0].messages[1].line, 7); + }); + + it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/legacy", // this processor returns strings rather than `{text, filename}` + rules: { + semi: "off", + "no-console": "error" + } + }, + { + files: "**/*.html/*.js", + rules: { + semi: "error", + "no-console": "off" + } + } + ] + } + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 3); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-console"); + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].messages[2].ruleId, "no-console"); + assert.strictEqual(results[0].messages[2].line, 10); + }); + + it("should throw an error if invalid processor was specified.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + processor: "markdown/unknown" + } + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + await assert.rejects(async () => { + await eslint.lintFiles(["test.md"]); + }, /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u); + }); + + it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/.html" + }, + { + files: "*.md", + processor: "markdown/.md" + } + ] + } + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual(results[0].messages[1].line, 7); + }); + }); + + describe("MODULE_NOT_FOUND error handling", () => { + const cwd = getFixturePath("module-not-found"); + + beforeEach(() => { + eslint = new LegacyESLint({ cwd }); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", async () => { + try { + await eslint.lintText("test", { filePath: "extends-js/test.js" }); + } catch (err) { + assert.strictEqual(err.messageTemplate, "extend-config-missing"); + assert.deepStrictEqual(err.messageData, { + configName: "nonexistent-config", + importerName: getFixturePath("module-not-found", "extends-js", ".eslintrc.yml") + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence plugin config.", async () => { + try { + await eslint.lintText("test", { filePath: "extends-plugin/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `extends-plugin${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: path.join(cwd, "extends-plugin") // the directory of the config file. + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'plugins' property has a non-existence plugin.", async () => { + try { + await eslint.lintText("test", { filePath: "plugins/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `plugins${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: path.join(cwd, "plugins") // the directory of the config file. + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when a JavaScript config threw a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { filePath: "throw-in-config-itself/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a JavaScript config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { filePath: "throw-in-extends-js/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { filePath: "throw-in-extends-plugin/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'plugins' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { filePath: "throw-in-plugins/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + }); + + describe("with '--rulesdir' option", () => { + + const rootPath = getFixturePath("cli-engine/with-rulesdir"); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: rootPath, + files: { + "internal-rules/test.js": ` + module.exports = { + create(context) { + return { + ExpressionStatement(node) { + context.report({ node, message: "ok" }); + }, + }; + }, + }; + `, + ".eslintrc.json": { + root: true, + rules: { test: "error" } + }, + "test.js": "console.log('hello')" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + + it("should use the configured rules which are defined by '--rulesdir' option.", async () => { + eslint = new LegacyESLint({ + cwd: getPath(), + rulePaths: ["internal-rules"] + }); + const results = await eslint.lintFiles(["test.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].message, "ok"); + }); + }); + + describe("glob pattern '[ab].js'", () => { + const root = getFixturePath("cli-engine/unmatched-glob"); + + let cleanup; + + beforeEach(() => { + cleanup = () => { }; + }); + + afterEach(() => cleanup()); + + it("should match '[ab].js' if existed.", async () => { + + const teardown = createCustomTeardown({ + cwd: root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + "[ab].js": "", + ".eslintrc.yml": "root: true" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["[ab].js"]); + const filenames = results.map(r => path.basename(r.filePath)); + + assert.deepStrictEqual(filenames, ["[ab].js"]); + }); + + it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + ".eslintrc.yml": "root: true" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["[ab].js"]); + const filenames = results.map(r => path.basename(r.filePath)); + + assert.deepStrictEqual(filenames, ["a.js", "b.js"]); + }); + }); + + describe("with 'noInlineConfig' setting", () => { + const root = getFixturePath("cli-engine/noInlineConfig"); + + let cleanup; + + beforeEach(() => { + cleanup = () => { }; + }); + + afterEach(() => cleanup()); + + it("should warn directive comments if 'noInlineConfig' was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* globals foo */", + ".eslintrc.yml": "noInlineConfig: true" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml)."); + }); + + it("should show the config file what the 'noInlineConfig' came from.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}", + "test.js": "/* globals foo */", + ".eslintrc.yml": "extends: foo" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml » eslint-config-foo)."); + }); + }); + + describe("with 'reportUnusedDisableDirectives' setting", () => { + const root = getFixturePath("cli-engine/reportUnusedDisableDirectives"); + + let cleanup; + + beforeEach(() => { + cleanup = () => { }; + }); + + afterEach(() => cleanup()); + + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": "reportUnusedDisableDirectives: true" + } + }); + + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + }); + + describe("the runtime option overrides config files.", () => { + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": "reportUnusedDisableDirectives: true" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + reportUnusedDisableDirectives: "off" + }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + }); + + it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": "reportUnusedDisableDirectives: true" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + reportUnusedDisableDirectives: "error" + }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + }); + }); + }); + + describe("with 'overrides[*].extends' setting on deep locations", () => { + const root = getFixturePath("cli-engine/deeply-overrides-i-extends"); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ + overrides: [{ files: ["*test*"], extends: "two" }] + })}`, + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ + overrides: [{ files: ["*.js"], extends: "three" }] + })}`, + "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({ + rules: { "no-console": "error" } + })}`, + "test.js": "console.log('hello')", + ".eslintrc.yml": "extends: one" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should not throw.", async () => { + eslint = new LegacyESLint({ cwd: getPath() }); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + }); + }); + + describe("don't ignore the entry directory.", () => { + const root = getFixturePath("cli-engine/dont-ignore-entry-dir"); + + let cleanup; + + beforeEach(() => { + cleanup = () => { }; + }); + + afterEach(async () => { + await cleanup(); + + const configFilePath = path.resolve(root, "../.eslintrc.json"); + + if (shell.test("-e", configFilePath)) { + shell.rm(configFilePath); + } + }); + + it("'lintFiles(\".\")' should not load config files from outside of \".\".", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "../.eslintrc.json": "BROKEN FILE", + ".eslintrc.json": JSON.stringify({ root: true }), + "index.js": "console.log(\"hello\")" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + // Don't throw "failed to load config file" error. + await eslint.lintFiles("."); + }); + + it("'lintFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "../.eslintrc.json": { ignorePatterns: ["/dont-ignore-entry-dir"] }, + ".eslintrc.json": { root: true }, + "index.js": "console.log(\"hello\")" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + // Don't throw "file not found" error. + await eslint.lintFiles("."); + }); + + it("'lintFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { ignorePatterns: ["/subdir"] }, + "subdir/.eslintrc.json": { root: true }, + "subdir/index.js": "console.log(\"hello\")" + } + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + // Don't throw "file not found" error. + await eslint.lintFiles("subdir"); + }); + }); + + it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { + eslint = new LegacyESLint(); + await assert.rejects(() => eslint.lintFiles(777), /'patterns' must be a non-empty string or an array of non-empty strings/u); + await assert.rejects(() => eslint.lintFiles([null]), /'patterns' must be a non-empty string or an array of non-empty strings/u); + }); + }); + + describe("calculateConfigForFile", () => { + it("should return the info from Config#getConfig when called", async () => { + const options = { + overrideConfigFile: getFixturePath("configurations", "quotes-error.json") + }; + const engine = new LegacyESLint(options); + const filePath = getFixturePath("single-quoted.js"); + const actualConfig = await engine.calculateConfigForFile(filePath); + const expectedConfig = new CascadingConfigArrayFactory({ specificConfigPath: options.overrideConfigFile }) + .getConfigArrayForFile(filePath) + .extractConfig(filePath) + .toCompatibleObjectAsConfigFileContent(); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should return the config for a file that doesn't exist", async () => { + const engine = new LegacyESLint(); + const filePath = getFixturePath("does_not_exist.js"); + const existingSiblingFilePath = getFixturePath("single-quoted.js"); + const actualConfig = await engine.calculateConfigForFile(filePath); + const expectedConfig = await engine.calculateConfigForFile(existingSiblingFilePath); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should return the config for a virtual file that is a child of an existing file", async () => { + const engine = new LegacyESLint(); + const parentFileName = "single-quoted.js"; + const filePath = getFixturePath(parentFileName, "virtual.js"); // single-quoted.js/virtual.js + const parentFilePath = getFixturePath(parentFileName); + const actualConfig = await engine.calculateConfigForFile(filePath); + const expectedConfig = await engine.calculateConfigForFile(parentFilePath); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should return the config when run from within a subdir", async () => { + const options = { + cwd: getFixturePath("config-hierarchy", "root-true", "parent", "root", "subdir") + }; + const engine = new LegacyESLint(options); + const filePath = getFixturePath("config-hierarchy", "root-true", "parent", "root", ".eslintrc"); + const actualConfig = await engine.calculateConfigForFile("./.eslintrc"); + const expectedConfig = new CascadingConfigArrayFactory(options) + .getConfigArrayForFile(filePath) + .extractConfig(filePath) + .toCompatibleObjectAsConfigFileContent(); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should throw an error if a directory path was given.", async () => { + const engine = new LegacyESLint(); + + try { + await engine.calculateConfigForFile("."); + } catch (error) { + assert.strictEqual(error.messageTemplate, "print-config-with-directory-path"); + return; + } + assert.fail("should throw an error"); + }); + + it("should throw if non-string value is given to 'filePath' parameter", async () => { + const eslint = new LegacyESLint(); + + await assert.rejects(() => eslint.calculateConfigForFile(null), /'filePath' must be a non-empty string/u); + }); + + // https://github.com/eslint/eslint/issues/13793 + it("should throw with an invalid built-in rule config", async () => { + const options = { + baseConfig: { + rules: { + "no-alert": ["error", { + thisDoesNotExist: true + }] + } + } + }; + const engine = new LegacyESLint(options); + const filePath = getFixturePath("single-quoted.js"); + + await assert.rejects( + () => engine.calculateConfigForFile(filePath), + /Configuration for rule "no-alert" is invalid:/u + ); + }); + }); + + describe("isPathIgnored", () => { + it("should check if the given path is ignored", async () => { + const engine = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore2"), + cwd: getFixturePath() + }); + + assert(await engine.isPathIgnored("undef.js")); + assert(!await engine.isPathIgnored("passing.js")); + }); + + it("should return false if ignoring is disabled", async () => { + const engine = new LegacyESLint({ + ignore: false, + ignorePath: getFixturePath(".eslintignore2"), + cwd: getFixturePath() + }); + + assert(!await engine.isPathIgnored("undef.js")); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should return true for default ignores even if ignoring is disabled", async () => { + const engine = new LegacyESLint({ + ignore: false, + cwd: getFixturePath("cli-engine") + }); + + assert(await engine.isPathIgnored("node_modules/foo.js")); + }); + + describe("about the default ignore patterns", () => { + it("should always apply defaultPatterns if ignore option is true", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); + }); + + it("should still apply defaultPatterns if ignore option is is false", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ ignore: false, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); + }); + + it("should allow subfolders of defaultPatterns to be unignored by ignorePattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + cwd, + overrideConfig: { + ignorePatterns: "!/node_modules/package" + } + }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); + }); + + it("should allow subfolders of defaultPatterns to be unignored by ignorePath", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd, ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithUnignoredDefaults") }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); + }); + + it("should ignore dotfiles", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); + }); + + it("should ignore directories beginning with a dot", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); + }); + + it("should still ignore dotfiles when ignore option disabled", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ ignore: false, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); + }); + + it("should still ignore directories beginning with a dot when ignore option disabled", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ ignore: false, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); + }); + + it("should not ignore absolute paths containing '..'", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + assert(!await engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); + }); + + it("should ignore /node_modules/ relative to .eslintignore when loaded", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ ignorePath: getFixturePath("ignored-paths", ".eslintignore"), cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "existing.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo", "node_modules", "existing.js"))); + }); + + it("should ignore /node_modules/ relative to cwd without an .eslintignore", async () => { + const cwd = getFixturePath("ignored-paths", "no-ignore-file"); + const engine = new LegacyESLint({ cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); + }); + }); + + describe("with no .eslintignore file", () => { + it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", async () => { + const cwd = getFixturePath("ignored-paths", "configurations"); + const engine = new LegacyESLint({ cwd }); + + // an .eslintignore in parent directories includes `*.js`, but don't load it. + assert(!await engine.isPathIgnored("foo.js")); + assert(await engine.isPathIgnored("node_modules/foo.js")); + }); + + it("should return false for files outside of the cwd (with no ignore file provided)", async () => { + + // Default ignore patterns should not inadvertently ignore files in parent directories + const engine = new LegacyESLint({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + }); + }); + + describe("with .eslintignore file or package.json file", () => { + it("should load .eslintignore from cwd when explicitly passed", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + // `${cwd}/.eslintignore` includes `sampleignorepattern`. + assert(await engine.isPathIgnored("sampleignorepattern")); + }); + + it("should use package.json's eslintIgnore files if no specified .eslintignore file", async () => { + const cwd = getFixturePath("ignored-paths", "package-json-ignore"); + const engine = new LegacyESLint({ cwd }); + + assert(await engine.isPathIgnored("hello.js")); + assert(await engine.isPathIgnored("world.js")); + }); + + it("should use correct message template if failed to parse package.json", () => { + const cwd = getFixturePath("ignored-paths", "broken-package-json"); + + assert.throws(() => { + try { + // eslint-disable-next-line no-new -- Check for error + new LegacyESLint({ cwd }); + } catch (error) { + assert.strictEqual(error.messageTemplate, "failed-to-read-json"); + throw error; + } + }); + }); + + it("should not use package.json's eslintIgnore files if specified .eslintignore file", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + /* + * package.json includes `hello.js` and `world.js`. + * .eslintignore includes `sampleignorepattern`. + */ + assert(!await engine.isPathIgnored("hello.js")); + assert(!await engine.isPathIgnored("world.js")); + assert(await engine.isPathIgnored("sampleignorepattern")); + }); + + it("should error if package.json's eslintIgnore is not an array of file paths", () => { + const cwd = getFixturePath("ignored-paths", "bad-package-json-ignore"); + + assert.throws(() => { + // eslint-disable-next-line no-new -- Check for throwing + new LegacyESLint({ cwd }); + }, /Package\.json eslintIgnore property requires an array of paths/u); + }); + }); + + describe("with --ignore-pattern option", () => { + it("should accept a string for options.ignorePattern", async () => { + const cwd = getFixturePath("ignored-paths", "ignore-pattern"); + const engine = new LegacyESLint({ + overrideConfig: { + ignorePatterns: "ignore-me.txt" + }, + cwd + }); + + assert(await engine.isPathIgnored("ignore-me.txt")); + }); + + it("should accept an array for options.ignorePattern", async () => { + const engine = new LegacyESLint({ + overrideConfig: { + ignorePatterns: ["a", "b"] + }, + useEslintrc: false + }); + + assert(await engine.isPathIgnored("a")); + assert(await engine.isPathIgnored("b")); + assert(!await engine.isPathIgnored("c")); + }); + + it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { + ignorePatterns: "not-a-file" + }, + cwd + }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "not-a-file"))); + }); + + it("should return true for file matching an ignore pattern exactly", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ overrideConfig: { ignorePatterns: "undef.js" }, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + }); + + it("should return false for file matching an invalid ignore pattern with leading './'", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ overrideConfig: { ignorePatterns: "./undef.js" }, cwd }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + }); + + it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ overrideConfig: { ignorePatterns: "/undef.js" }, cwd }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir", "undef.js"))); + }); + + it("should return true for file matching a child of an ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ overrideConfig: { ignorePatterns: "ignore-pattern" }, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); + }); + + it("should return true for file matching a grandchild of an ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ overrideConfig: { ignorePatterns: "ignore-pattern" }, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.txt"))); + }); + + it("should return false for file not matching any ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ overrideConfig: { ignorePatterns: "failing.js" }, cwd }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "unignored.js"))); + }); + + it("two globstar '**' ignore pattern should ignore files in nested directories", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ overrideConfig: { ignorePatterns: "**/*.js" }, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.js"))); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.j2"))); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.j2"))); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.j2"))); + }); + }); + + describe("with --ignore-path option", () => { + it("initialization with ignorePath should work when cwd is a parent directory", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("custom-name/foo.js")); + }); + + it("initialization with ignorePath should work when the file is in the cwd", async () => { + const cwd = getFixturePath("ignored-paths", "custom-name"); + const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("foo.js")); + }); + + it("initialization with ignorePath should work when cwd is a subdirectory", async () => { + const cwd = getFixturePath("ignored-paths", "custom-name", "subdirectory"); + const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("../custom-name/foo.js")); + }); + + it("initialization with invalid file should throw error", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz"); + + assert.throws(() => { + // eslint-disable-next-line no-new -- Check for throwing + new LegacyESLint({ ignorePath, cwd }); + }, /Cannot read \.eslintignore file/u); + }); + + it("should return false for files outside of ignorePath's directory", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + }); + + it("should resolve relative paths from CWD", async () => { + const cwd = getFixturePath("ignored-paths", "subdir"); + const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + }); + + it("should resolve relative paths from CWD when it's in a child directory", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/foo.js"))); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/bar.js"))); + }); + + it("should resolve relative paths from CWD when it contains negated globs", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("subdir/blah.txt")); + assert(await engine.isPathIgnored("blah.txt")); + assert(await engine.isPathIgnored("subdir/bar.txt")); + assert(!await engine.isPathIgnored("bar.txt")); + assert(!await engine.isPathIgnored("subdir/baz.txt")); + assert(!await engine.isPathIgnored("baz.txt")); + }); + + it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("node_modules/blah.js")); + }); + + it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", async () => { + const cwd = getFixturePath("ignored-paths", "subdir"); + const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("node_modules/blah.js")); + }); + + it("should handle .eslintignore which contains CRLF correctly.", async () => { + const ignoreFileContent = fs.readFileSync(getFixturePath("ignored-paths", "crlf/.eslintignore"), "utf8"); + + assert(ignoreFileContent.includes("\r"), "crlf/.eslintignore should contains CR."); + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", "crlf/.eslintignore"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide1/a.js"))); + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide2/a.js"))); + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide3/a.js"))); + }); + + it("should not include comments in ignore rules", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithComments"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(!await engine.isPathIgnored("# should be ignored")); + assert(await engine.isPathIgnored("this_one_not")); + }); + + it("should ignore a non-negated pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "ignore.js"))); + }); + + it("should not ignore a negated pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "unignore.js"))); + }); + + // https://github.com/eslint/eslint/issues/15642 + it("should correctly handle patterns with escaped brackets", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithEscapedBrackets"); + const engine = new LegacyESLint({ ignorePath, cwd }); + + const subdir = "brackets"; + + assert( + !await engine.isPathIgnored(getFixturePath("ignored-paths", subdir, "index.js")), + `'${subdir}/index.js' should not be ignored` + ); + + for (const filename of ["[index.js", "index].js", "[index].js"]) { + assert( + await engine.isPathIgnored(getFixturePath("ignored-paths", subdir, filename)), + `'${subdir}/${filename}' should be ignored` + ); + } + + }); + }); + + describe("with --ignore-path option and --ignore-pattern option", () => { + it("should return false for ignored file when unignored with ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + ignorePath: getFixturePath("ignored-paths", ".eslintignore"), + overrideConfig: { + ignorePatterns: "!sampleignorepattern" + }, + cwd + }); + + assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "sampleignorepattern"))); + }); + }); + + it("should throw if non-string value is given to 'filePath' parameter", async () => { + const eslint = new LegacyESLint(); + + await assert.rejects(() => eslint.isPathIgnored(null), /'filePath' must be a non-empty string/u); + }); + }); + + describe("loadFormatter()", () => { + it("should return a formatter object when a bundled formatter is requested", async () => { + const engine = new LegacyESLint(); + const formatter = await engine.loadFormatter("json"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when no argument is passed", async () => { + const engine = new LegacyESLint(); + const formatter = await engine.loadFormatter(); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a custom formatter is requested", async () => { + const engine = new LegacyESLint(); + const formatter = await engine.loadFormatter(getFixturePath("formatters", "simple.js")); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a custom formatter is requested, also if the path has backslashes", async () => { + const engine = new LegacyESLint({ + cwd: path.join(fixtureDir, "..") + }); + const formatter = await engine.loadFormatter(".\\fixtures\\formatters\\simple.js"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter prefixed with eslint-formatter is requested", async () => { + const engine = new LegacyESLint({ + cwd: getFixturePath("cli-engine") + }); + const formatter = await engine.loadFormatter("bar"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", async () => { + const engine = new LegacyESLint({ + cwd: getFixturePath("cli-engine") + }); + const formatter = await engine.loadFormatter("eslint-formatter-bar"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested within a scoped npm package", async () => { + const engine = new LegacyESLint({ + cwd: getFixturePath("cli-engine") + }); + const formatter = await engine.loadFormatter("@somenamespace/foo"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", async () => { + const engine = new LegacyESLint({ + cwd: getFixturePath("cli-engine") + }); + const formatter = await engine.loadFormatter("@somenamespace/eslint-formatter-foo"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should throw if a custom formatter doesn't exist", async () => { + const engine = new LegacyESLint(); + const formatterPath = getFixturePath("formatters", "doesntexist.js"); + const fullFormatterPath = path.resolve(formatterPath); + + await assert.rejects(async () => { + await engine.loadFormatter(formatterPath); + }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`), "u")); + }); + + it("should throw if a built-in formatter doesn't exist", async () => { + const engine = new LegacyESLint(); + const fullFormatterPath = path.resolve(__dirname, "../../../lib/cli-engine/formatters/special"); + + await assert.rejects(async () => { + await engine.loadFormatter("special"); + }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`), "u")); + }); + + it("should throw if the required formatter exists but has an error", async () => { + const engine = new LegacyESLint(); + const formatterPath = getFixturePath("formatters", "broken.js"); + + await assert.rejects(async () => { + await engine.loadFormatter(formatterPath); + }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${formatterPath}\nError: Cannot find module 'this-module-does-not-exist'`), "u")); + }); + + it("should throw if a non-string formatter name is passed", async () => { + const engine = new LegacyESLint(); + + await assert.rejects(async () => { + await engine.loadFormatter(5); + }, /'name' must be a string/u); + }); + + it("should pass cwd to the `cwd` property of the second argument.", async () => { + const cwd = getFixturePath(); + const engine = new LegacyESLint({ cwd }); + const formatterPath = getFixturePath("formatters", "cwd.js"); + const formatter = await engine.loadFormatter(formatterPath); + + assert.strictEqual(formatter.format([]), cwd); + }); + }); + + describe("getErrorResults()", () => { + it("should report 5 error messages when looking for errors only", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + }); + const results = await engine.lintText("var foo = 'bar';"); + const errorResults = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].messages.length, 5); + assert.strictEqual(errorResults[0].errorCount, 5); + assert.strictEqual(errorResults[0].fixableErrorCount, 3); + assert.strictEqual(errorResults[0].fixableWarningCount, 0); + assert.strictEqual(errorResults[0].messages[0].ruleId, "strict"); + assert.strictEqual(errorResults[0].messages[0].severity, 2); + assert.strictEqual(errorResults[0].messages[1].ruleId, "no-var"); + assert.strictEqual(errorResults[0].messages[1].severity, 2); + assert.strictEqual(errorResults[0].messages[2].ruleId, "no-unused-vars"); + assert.strictEqual(errorResults[0].messages[2].severity, 2); + assert.strictEqual(errorResults[0].messages[3].ruleId, "quotes"); + assert.strictEqual(errorResults[0].messages[3].severity, 2); + assert.strictEqual(errorResults[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(errorResults[0].messages[4].severity, 2); + }); + + it("should not mutate passed report parameter", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [1, "double"], + "no-var": 2 + } + } + }); + const results = await engine.lintText("var foo = 'bar';"); + const reportResultsLength = results[0].messages.length; + + assert.strictEqual(results[0].messages.length, 2); + + LegacyESLint.getErrorResults(results); + + assert.strictEqual(results[0].messages.length, reportResultsLength); + }); + + it("should report a warningCount of 0 when looking for errors only", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2 + }, + env: { + node: true + } + } + }); + const results = await engine.lintText("var foo = 'bar';"); + const errorResults = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].warningCount, 0); + assert.strictEqual(errorResults[0].fixableWarningCount, 0); + }); + + it("should return 0 error or warning messages even when the file has warnings", async () => { + const engine = new LegacyESLint({ + ignorePath: path.join(fixtureDir, ".eslintignore"), + cwd: path.join(fixtureDir, "..") + }); + const options = { + filePath: "fixtures/passing.js", + warnIgnored: true + }; + const results = await engine.lintText("var bar = foo;", options); + const errorReport = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorReport.length, 0); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should return source code of file in the `source` property", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { quotes: [2, "double"] } + } + }); + const results = await engine.lintText("var foo = 'bar';"); + const errorResults = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].messages.length, 1); + assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); + }); + + it("should contain `output` property after fixes", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + semi: 2, + "no-console": 2 + } + } + }); + const results = await engine.lintText("console.log('foo')"); + const errorResults = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].messages.length, 1); + assert.strictEqual(errorResults[0].output, "console.log('foo');"); + }); + }); + + describe("getRulesMetaForResults()", () => { + it("should return empty object when there are no linting errors", async () => { + const engine = new LegacyESLint({ + useEslintrc: false + }); + + const rulesMeta = engine.getRulesMetaForResults([]); + + assert.deepStrictEqual(rulesMeta, {}); + }); + + it("should return one rule meta when there is a linting error", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2 + } + } + }); + + const results = await engine.lintText("a"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return one rule meta when there is a suppressed linting error", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2 + } + } + }); + + const results = await engine.lintText("a // eslint-disable-line semi"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return multiple rule meta when there are multiple linting errors", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"] + } + } + }); + + const results = await engine.lintText("'a'"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); + }); + + it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { + const customPlugin = { + rules: { + "no-var": require("../../../lib/rules/no-var") + } + }; + + const engine = new LegacyESLint({ + useEslintrc: false, + plugins: { + "custom-plugin": customPlugin + }, + overrideConfig: { + plugins: ["custom-plugin"], + rules: { + "custom-plugin/no-var": 2, + semi: 2, + quotes: [2, "double"] + } + } + }); + + const results = await engine.lintText("var foo = 0; var bar = '1'"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); + assert.strictEqual( + rulesMeta["custom-plugin/no-var"], + customPlugin.rules["no-var"].meta + ); + }); + + it("should ignore messages not related to a rule", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + ignorePatterns: "ignored.js", + rules: { + "no-var": "warn" + } + }, + reportUnusedDisableDirectives: "warn" + }); + + { + const results = await engine.lintText("syntax error"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("// eslint-disable-line no-var"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + }); + + it("should return a non-empty value if some of the messages are related to a rule", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { rules: { "no-var": "warn" } }, + reportUnusedDisableDirectives: "warn" + }); + + const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); + }); + }); + + describe("outputFixes()", () => { + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("should call fs.writeFile() for each result with output", async () => { + const fakeFS = { + writeFile: sinon.spy(callLastArgument) + }; + const spy = fakeFS.writeFile; + const { LegacyESLint: localESLint } = proxyquire("../../../lib/eslint/legacy-eslint", { + fs: fakeFS + }); + + const results = [ + { + filePath: path.resolve("foo.js"), + output: "bar" + }, + { + filePath: path.resolve("bar.js"), + output: "baz" + } + ]; + + await localESLint.outputFixes(results); + + assert.strictEqual(spy.callCount, 2); + assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar", sinon.match.func), "First call was incorrect."); + assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz", sinon.match.func), "Second call was incorrect."); + }); + + it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { + const fakeFS = { + writeFile: sinon.spy(callLastArgument) + }; + const spy = fakeFS.writeFile; + const { LegacyESLint: localESLint } = proxyquire("../../../lib/eslint/legacy-eslint", { + fs: fakeFS + }); + const results = [ + { + filePath: path.resolve("foo.js"), + output: "bar" + }, + { + filePath: path.resolve("abc.js") + }, + { + filePath: path.resolve("bar.js"), + output: "baz" + } + ]; + + await localESLint.outputFixes(results); + + assert.strictEqual(spy.callCount, 2); + assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar", sinon.match.func), "First call was incorrect."); + assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz", sinon.match.func), "Second call was incorrect."); + }); + + it("should throw if non object array is given to 'results' parameter", async () => { + await assert.rejects(() => LegacyESLint.outputFixes(null), /'results' must be an array/u); + await assert.rejects(() => LegacyESLint.outputFixes([null]), /'results' must include only objects/u); + }); + }); + + describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { + it("should report a violation for disabling rules", async () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert" + ].join("\n"); + const config = { + ignore: true, + useEslintrc: false, + allowInlineConfig: false, + overrideConfig: { + env: { browser: true }, + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0 + } + } + }; + const eslintCLI = new LegacyESLint(config); + const results = await eslintCLI.lintText(code); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + }); + + it("should not report a violation by default", async () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert" + ].join("\n"); + const config = { + ignore: true, + useEslintrc: false, + allowInlineConfig: true, + overrideConfig: { + env: { browser: true }, + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0 + } + } + }; + const eslintCLI = new LegacyESLint(config); + const results = await eslintCLI.lintText(code); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + }); + }); + + describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { + it("should report problems for unused eslint-disable directives", async () => { + const eslint = new LegacyESLint({ useEslintrc: false, reportUnusedDisableDirectives: "error" }); + + assert.deepStrictEqual( + await eslint.lintText("/* eslint-disable */"), + [ + { + filePath: "", + messages: [ + { + ruleId: null, + message: "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " " + }, + severity: 2, + nodeType: null + } + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + source: "/* eslint-disable */", + usedDeprecatedRules: [] + } + ] + ); + }); + }); + + describe("when retrieving version number", () => { + it("should return current version number", () => { + const eslintCLI = require("../../../lib/eslint").ESLint; + const version = eslintCLI.version; + + assert.strictEqual(typeof version, "string"); + assert(parseInt(version[0], 10) >= 3); + }); + }); + + describe("mutability", () => { + describe("plugins", () => { + it("Loading plugin in one instance doesn't mutate to another instance", async () => { + const filePath = getFixturePath("single-quoted.js"); + const engine1 = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + plugins: ["example"], + rules: { "example/example-rule": 1 } + } + }); + const engine2 = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false + }); + const fileConfig1 = await engine1.calculateConfigForFile(filePath); + const fileConfig2 = await engine2.calculateConfigForFile(filePath); + + // plugin + assert.deepStrictEqual(fileConfig1.plugins, ["example"], "Plugin is present for engine 1"); + assert.deepStrictEqual(fileConfig2.plugins, [], "Plugin is not present for engine 2"); + }); + }); + + describe("rules", () => { + it("Loading rules in one instance doesn't mutate to another instance", async () => { + const filePath = getFixturePath("single-quoted.js"); + const engine1 = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { rules: { "example/example-rule": 1 } } + }); + const engine2 = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false + }); + const fileConfig1 = await engine1.calculateConfigForFile(filePath); + const fileConfig2 = await engine2.calculateConfigForFile(filePath); + + // plugin + assert.deepStrictEqual(fileConfig1.rules["example/example-rule"], [1], "example is present for engine 1"); + assert.strictEqual(fileConfig2.rules["example/example-rule"], void 0, "example is not present for engine 2"); + }); + }); + }); + + describe("with ignorePatterns config", () => { + const root = getFixturePath("cli-engine/ignore-patterns"); + + describe("ignorePatterns can add an ignore pattern ('foo.js').", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: "foo.js" + }, + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); + }); + + it("'lintFiles()' should not verify 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js"), + path.join(root, "subdir/bar.js") + ]); + }); + }); + + describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: ["foo.js", "/bar.js"] + }, + "foo.js": "", + "bar.js": "", + "baz.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + "subdir/baz.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); + }); + + it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); + }); + + it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "baz.js"), + path.join(root, "subdir/bar.js"), + path.join(root, "subdir/baz.js") + ]); + }); + }); + + describe("ignorePatterns can unignore '/node_modules/foo'.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: "!/node_modules/foo" + }, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), true); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); + }); + + it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "foo.js"), + path.join(root, "node_modules/foo/index.js") + ]); + }); + }); + + describe("ignorePatterns can unignore '.eslintrc.js'.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "!.eslintrc.js" + })}`, + "foo.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored(".eslintrc.js"), false); + }); + + it("'lintFiles()' should verify '.eslintrc.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, ".eslintrc.js"), + path.join(root, "foo.js") + ]); + }); + }); + + describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "!.*" + })}`, + ".eslintignore": ".foo*", + ".foo.js": "", + ".bar.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored(".foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored(".bar.js"), false); + }); + + it("'lintFiles()' should not verify re-ignored '.foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, ".bar.js"), + path.join(root, ".eslintrc.js") + ]); + }); + }); + + describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "*.js" + })}`, + ".eslintignore": "!foo.js", + "foo.js": "", + "bar.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), true); + }); + + it("'lintFiles()' should verify unignored 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "foo.js") + ]); + }); + }); + + describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "foo.js" + }), + "subdir/.eslintrc.json": JSON.stringify({ + ignorePatterns: "bar.js" + }), + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + "subdir/subsubdir/foo.js": "", + "subdir/subsubdir/bar.js": "" + } + }); + + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/subsubdir/foo.js"), true); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/subsubdir/bar.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'lintFiles()' should verify 'bar.js' in the outside of 'subdir'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js") + ]); + }); + }); + + describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "foo.js" + }), + "subdir/.eslintrc.json": JSON.stringify({ + ignorePatterns: "!foo.js" + }), + "foo.js": "", + "subdir/foo.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); + }); + + it("'lintFiles()' should verify 'foo.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "subdir/foo.js") + ]); + }); + }); + + describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({}), + "subdir/.eslintrc.json": JSON.stringify({ + ignorePatterns: "*.js" + }), + ".eslintignore": "!foo.js", + "foo.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), false); + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); + }); + + it("'lintFiles()' should verify unignored 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "foo.js"), + path.join(root, "subdir/foo.js") + ]); + }); + }); + + describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "foo.js" + }), + "subdir/.eslintrc.json": JSON.stringify({ + root: true, + ignorePatterns: "bar.js" + }), + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); + }); + + it("'lintFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js"), + path.join(root, "subdir/foo.js") + ]); + }); + }); + + describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({}), + "subdir/.eslintrc.json": JSON.stringify({ + root: true, + ignorePatterns: "bar.js" + }), + ".eslintignore": "foo.js", + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); + }); + + it("'lintFiles()' should verify 'bar.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js") + ]); + }); + }); + + describe("ignorePatterns in the shareable config should be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "foo.js" + })}`, + ".eslintrc.json": JSON.stringify({ + extends: "one" + }), + "foo.js": "", + "bar.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'lintFiles()' should verify 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js") + ]); + }); + }); + + describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "/foo.js" + })}`, + ".eslintrc.json": JSON.stringify({ + extends: "one" + }), + "foo.js": "", + "subdir/foo.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); + }); + + it("'lintFiles()' should verify 'subdir/foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "subdir/foo.js") + ]); + }); + }); + + describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "*.js" + })}`, + ".eslintrc.json": JSON.stringify({ + extends: "one", + ignorePatterns: "!bar.js" + }), + "foo.js": "", + "bar.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'lintFiles()' should verify 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js") + ]); + }); + }); + + describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "*.js" + }), + "foo.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath(), ignore: false }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), false); + }); + + it("'lintFiles()' should verify 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath(), ignore: false }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "foo.js") + ]); + }); + }); + + describe("ignorePatterns in overrides section is not allowed.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + overrides: [ + { + files: "*.js", + ignorePatterns: "foo.js" + } + ] + })}`, + "foo.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should throw a configuration error.", async () => { + await assert.rejects(async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await engine.lintFiles("*.js"); + }, /Unexpected top-level property "overrides\[0\]\.ignorePatterns"/u); + }); + }); + }); + + describe("'overrides[].files' adds lint targets", () => { + const root = getFixturePath("cli-engine/additional-lint-targets"); + + + describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/*.txt", + excludedFiles: "**/ignore.txt" + } + ] + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "foo/ignore.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "bar/ignore.txt": "", + "test.js": "", + "test.txt": "", + "ignore.txt": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js") + ]); + }); + + it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "test.js") + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present,", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/**/*.txt" + } + ] + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js") + ]); + }); + }); + + describe("if { files: 'foo/**/*' } is present,", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/**/*" + } + ] + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "test.js") + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({ + overrides: [ + { + files: "foo/**/*.txt" + } + ] + })}`, + ".eslintrc.json": JSON.stringify({ + extends: "foo" + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js") + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({ + bar: { + overrides: [ + { + files: "foo/**/*.txt" + } + ] + } + })}`, + ".eslintrc.json": JSON.stringify({ + extends: "plugin:foo/bar" + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js") + ]); + }); + }); + }); + + describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => { + const root = getFixturePath("cli-engine/config-and-overrides-files"); + + describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": { + overrides: [ + { + files: "foo/*.js", + rules: { + eqeqeq: "error" + } + } + ] + }, + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with 'foo/test.js' should use the override entry.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + ignore: false, + useEslintrc: false + }); + const results = await engine.lintFiles("foo/test.js"); + + // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 1, + filePath: path.join(getPath(), "foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + column: 3, + endColumn: 5, + endLine: 1, + line: 1, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2 + } + ], + suppressedMessages: [], + source: "a == b", + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0 + } + ]); + }); + + it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: root, + ignore: false, + useEslintrc: false + }); + const results = await engine.lintFiles("node_modules/myconf/foo/test.js"); + + // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 0, + filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [], + suppressedMessages: [], + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0 + } + ]); + }); + }); + + describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "*", + excludedFiles: "foo/*.js", + rules: { + eqeqeq: "error" + } + } + ] + }), + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with 'foo/test.js' should NOT use the override entry.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: root, + ignore: false, + useEslintrc: false + }); + const results = await engine.lintFiles("foo/test.js"); + + // Expected to be no errors because the file matches to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 0, + filePath: path.join(getPath(), "foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [], + suppressedMessages: [], + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0 + } + ]); + }); + + it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: root, + ignore: false, + useEslintrc: false + }); + const results = await engine.lintFiles("node_modules/myconf/foo/test.js"); + + // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 1, + filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + column: 3, + endColumn: 5, + endLine: 1, + line: 1, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2 + } + ], + suppressedMessages: [], + source: "a == b", + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0 + } + ]); + }); + }); + + describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": JSON.stringify({ + ignorePatterns: ["!/node_modules/myconf", "foo/*.js"], + rules: { + eqeqeq: "error" + } + }), + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + useEslintrc: false + }); + const files = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(files, [ + path.join(root, "node_modules/myconf/foo/test.js") + ]); + }); + }); + }); + + describe("plugin conflicts", () => { + let uid = 0; + const root = getFixturePath("cli-engine/plugin-conflicts-"); + + /** + * Verify thrown errors. + * @param {() => Promise} f The function to run and throw. + * @param {Record} props The properties to verify. + * @returns {Promise} void + */ + async function assertThrows(f, props) { + try { + await f(); + } catch (error) { + for (const [key, value] of Object.entries(props)) { + assert.deepStrictEqual(error[key], value, key); + } + return; + } + + assert.fail("Function should throw an error, but not."); + } + + describe("between a config file and linear extendees.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ + extends: ["two"], + plugins: ["foo"] + })}`, + "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ + plugins: ["foo"] + })}`, + ".eslintrc.json": JSON.stringify({ + extends: ["one"], + plugins: ["foo"] + }), + "test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await engine.lintFiles("test.js"); + }); + }); + + describe("between a config file and same-depth extendees.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ + plugins: ["foo"] + })}`, + "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ + plugins: ["foo"] + })}`, + ".eslintrc.json": JSON.stringify({ + extends: ["one", "two"], + plugins: ["foo"] + }), + "test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await engine.lintFiles("test.js"); + }); + }); + + describe("between two config files in different directories, with single node_modules.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "subdir/test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await engine.lintFiles("subdir/test.js"); + }); + }); + + describe("between two config files in different directories, with multiple node_modules.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "subdir/test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await assertThrows( + () => engine.lintFiles("subdir/test.js"), + { + message: `Plugin "foo" was conflicted between "subdir${path.sep}.eslintrc.json" and ".eslintrc.json".`, + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"), + importerName: `subdir${path.sep}.eslintrc.json` + }, + { + filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), + importerName: ".eslintrc.json" + } + ] + } + } + ); + }); + }); + + describe("between '--config' option and a regular config file, with single node_modules.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/mine/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + overrideConfigFile: "node_modules/mine/.eslintrc.json" + }); + + await engine.lintFiles("test.js"); + }); + }); + + describe("between '--config' option and a regular config file, with multiple node_modules.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "", + "node_modules/mine/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + overrideConfigFile: "node_modules/mine/.eslintrc.json" + }); + + await assertThrows( + () => engine.lintFiles("test.js"), + { + message: "Plugin \"foo\" was conflicted between \"--config\" and \".eslintrc.json\".", + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join(getPath(), "node_modules/mine/node_modules/eslint-plugin-foo/index.js"), + importerName: "--config" + }, + { + filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), + importerName: ".eslintrc.json" + } + ] + } + } + ); + }); + }); + + describe("between '--plugin' option and a regular config file, with single node_modules.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "subdir/test.js": "" + } + }); + + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + overrideConfig: { plugins: ["foo"] } + }); + + await engine.lintFiles("subdir/test.js"); + }); + }); + + describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "subdir/test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + overrideConfig: { plugins: ["foo"] } + }); + + await assertThrows( + () => engine.lintFiles("subdir/test.js"), + { + message: `Plugin "foo" was conflicted between "CLIOptions" and "subdir${path.sep}.eslintrc.json".`, + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), + importerName: "CLIOptions" + }, + { + filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"), + importerName: `subdir${path.sep}.eslintrc.json` + } + ] + } + } + ); + }); + }); + + describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "subdir/test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + resolvePluginsRelativeTo: getPath() + }); + + await engine.lintFiles("subdir/test.js"); + }); + }); + + describe("between two config files with different target files.", () => { + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "one/node_modules/eslint-plugin-foo/index.js": "", + "one/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "one/test.js": "", + "two/node_modules/eslint-plugin-foo/index.js": "", + "two/.eslintrc.json": JSON.stringify({ + plugins: ["foo"] + }), + "two/test.js": "" + } + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const results = await engine.lintFiles("*/test.js"); + + assert.strictEqual(results.length, 2); + }); + }); + }); + + describe("loading rules", () => { + it("should not load unused core rules", done => { + let calledDone = false; + + const cwd = getFixturePath("lazy-loading-rules"); + const pattern = "foo.js"; + const usedRules = ["semi"]; + + const forkedProcess = childProcess.fork( + path.join(__dirname, "../../_utils/test-lazy-loading-rules.js"), + [cwd, pattern, String(usedRules)] + ); + + // this is an error message + forkedProcess.on("message", ({ message, stack }) => { + if (calledDone) { + return; + } + calledDone = true; + + const error = new Error(message); + + error.stack = stack; + done(error); + }); + + forkedProcess.on("exit", exitCode => { + if (calledDone) { + return; + } + calledDone = true; + + if (exitCode === 0) { + done(); + } else { + done(new Error("Forked process exited with a non-zero exit code")); + } + }); + }); + }); + + // only works on a Windows machine + if (os.platform() === "win32") { + + // https://github.com/eslint/eslint/issues/17042 + describe("with cwd that is using forward slash on Windows", () => { + const cwd = getFixturePath("example-app3"); + const cwdForwardSlash = cwd.replace(/\\/gu, "/"); + + it("should correctly handle ignore patterns", async () => { + const engine = new LegacyESLint({ cwd: cwdForwardSlash }); + const results = await engine.lintFiles(["./src"]); + + // src/dist/2.js should be ignored + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, path.join(cwd, "src\\1.js")); + }); + + it("should pass cwd with backslashes to rules", async () => { + const engine = new LegacyESLint({ + cwd: cwdForwardSlash, + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { + "test/report-cwd": "error" + } + } + }); + const results = await engine.lintText(""); + + assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); + assert.strictEqual(results[0].messages[0].message, cwd); + }); + + it("should pass cwd with backslashes to formatters", async () => { + const engine = new LegacyESLint({ + cwd: cwdForwardSlash + }); + const results = await engine.lintText(""); + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + }); + } +});