/** * @fileoverview Tests for CLIEngine. * @author Nicholas C. Zakas */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const assert = require("chai").assert, path = require("path"), sinon = require("sinon"), leche = require("leche"), shell = require("shelljs"), fs = require("fs"), os = require("os"), hash = require("../../../lib/cli-engine/hash"), { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory"), { unIndent } = require("../_utils"), { defineCLIEngineWithInMemoryFileSystem } = require("./_utils"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); const fCache = require("file-entry-cache"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("CLIEngine", () => { const examplePluginName = "eslint-plugin-example", examplePluginNameWithNamespace = "@eslint/eslint-plugin-example", examplePlugin = { rules: { "example-rule": require("../../fixtures/rules/custom-rule"), "make-syntax-error": require("../../fixtures/rules/make-syntax-error-rule") } }, examplePreprocessorName = "eslint-plugin-processor", originalDir = process.cwd(), fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); /** @type {import("../../../lib/cli-engine")["CLIEngine"]} */ let CLIEngine; /** @type {import("../../../lib/cli-engine/cli-engine")["getCLIEngineInternalSlots"]} */ let getCLIEngineInternalSlots; /** * 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 (e) { return filepath; } } /** * Create the CLIEngine object by mocking some of the plugins * @param {Object} options options for CLIEngine * @returns {CLIEngine} engine object * @private */ function cliEngineWithPlugins(options) { const engine = new CLIEngine(options); // load the mocked plugins engine.addPlugin(examplePluginName, examplePlugin); engine.addPlugin(examplePluginNameWithNamespace, examplePlugin); engine.addPlugin(examplePreprocessorName, require("../../fixtures/processors/custom-processor")); return engine; } // copy into clean area so as not to get "infected" by this project's .eslintrc files before(() => { shell.mkdir("-p", fixtureDir); shell.cp("-r", "./tests/fixtures/.", fixtureDir); }); beforeEach(() => { ({ CLIEngine, getCLIEngineInternalSlots } = require("../../../lib/cli-engine/cli-engine")); }); after(() => { shell.rm("-r", fixtureDir); }); describe("new CLIEngine(options)", () => { it("the default value of 'options.cwd' should be the current working directory.", () => { process.chdir(__dirname); try { const engine = new CLIEngine(); const internalSlots = getCLIEngineInternalSlots(engine); assert.strictEqual(internalSlots.options.cwd, __dirname); } finally { process.chdir(originalDir); } }); 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 new CLIEngine({ ignorePath: fixtureDir }); }, `Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`); }); // https://github.com/eslint/eslint/issues/2380 it("should not modify baseConfig when format is specified", () => { const customBaseConfig = { root: true }; new CLIEngine({ baseConfig: customBaseConfig, format: "foo" }); // eslint-disable-line no-new assert.deepStrictEqual(customBaseConfig, { root: true }); }); }); describe("executeOnText()", () => { let engine; it("should report the total and per file errors when using local cwd .eslintrc", () => { engine = new CLIEngine(); const report = engine.executeOnText("var foo = 'bar';"); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.errorCount, 5); assert.strictEqual(report.warningCount, 0); assert.strictEqual(report.fixableErrorCount, 3); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].messages.length, 5); assert.strictEqual(report.results[0].messages[0].ruleId, "strict"); assert.strictEqual(report.results[0].messages[1].ruleId, "no-var"); assert.strictEqual(report.results[0].messages[2].ruleId, "no-unused-vars"); assert.strictEqual(report.results[0].messages[3].ruleId, "quotes"); assert.strictEqual(report.results[0].messages[4].ruleId, "eol-last"); assert.strictEqual(report.results[0].fixableErrorCount, 3); assert.strictEqual(report.results[0].fixableWarningCount, 0); }); it("should report the total and per file warnings when using local cwd .eslintrc", () => { engine = new CLIEngine({ rules: { quotes: 1, "no-var": 1, "eol-last": 1, strict: 1, "no-unused-vars": 1 } }); const report = engine.executeOnText("var foo = 'bar';"); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.errorCount, 0); assert.strictEqual(report.warningCount, 5); assert.strictEqual(report.fixableErrorCount, 0); assert.strictEqual(report.fixableWarningCount, 3); assert.strictEqual(report.results[0].messages.length, 5); assert.strictEqual(report.results[0].messages[0].ruleId, "strict"); assert.strictEqual(report.results[0].messages[1].ruleId, "no-var"); assert.strictEqual(report.results[0].messages[2].ruleId, "no-unused-vars"); assert.strictEqual(report.results[0].messages[3].ruleId, "quotes"); assert.strictEqual(report.results[0].messages[4].ruleId, "eol-last"); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.results[0].fixableWarningCount, 3); }); it("should report one message when using specific config file", () => { engine = new CLIEngine({ configFile: "fixtures/configurations/quotes-error.json", useEslintrc: false, cwd: getFixturePath("..") }); const report = engine.executeOnText("var foo = 'bar';"); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.errorCount, 1); assert.strictEqual(report.warningCount, 0); assert.strictEqual(report.fixableErrorCount, 1); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.isUndefined(report.results[0].messages[0].output); assert.strictEqual(report.results[0].errorCount, 1); assert.strictEqual(report.results[0].fixableErrorCount, 1); assert.strictEqual(report.results[0].warningCount, 0); }); it("should report the filename when passed in", () => { engine = new CLIEngine({ ignore: false, cwd: getFixturePath() }); const report = engine.executeOnText("var foo = 'bar';", "test.js"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath("..") }); const report = engine.executeOnText("var bar = foo;", "fixtures/passing.js", true); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.errorCount, 0); assert.strictEqual(report.warningCount, 1); assert.strictEqual(report.fixableErrorCount, 0); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].filePath, getFixturePath("passing.js")); assert.strictEqual(report.results[0].messages[0].severity, 1); assert.strictEqual(report.results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); assert.isUndefined(report.results[0].messages[0].output); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 1); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.results[0].fixableWarningCount, 0); }); it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", () => { engine = new CLIEngine({ ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath("..") }); // intentional parsing error const report = engine.executeOnText("va r bar = foo;", "fixtures/passing.js", false); // should not report anything because the file is ignored assert.strictEqual(report.results.length, 0); }); it("should suppress excluded file warnings by default", () => { engine = new CLIEngine({ ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath("..") }); const report = engine.executeOnText("var bar = foo;", "fixtures/passing.js"); // should not report anything because there are no errors assert.strictEqual(report.results.length, 0); }); it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", () => { engine = new CLIEngine({ ignorePath: "fixtures/.eslintignore", cwd: getFixturePath(".."), ignore: false, useEslintrc: false, rules: { "no-undef": 2 } }); const report = engine.executeOnText("var bar = foo;", "fixtures/passing.js"); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, getFixturePath("passing.js")); assert.strictEqual(report.results[0].messages[0].ruleId, "no-undef"); assert.strictEqual(report.results[0].messages[0].severity, 2); assert.isUndefined(report.results[0].messages[0].output); }); it("should return a message and fixed text when in fix mode", () => { engine = new CLIEngine({ useEslintrc: false, fix: true, rules: { semi: 2 }, ignore: false, cwd: getFixturePath() }); const report = engine.executeOnText("var bar = foo", "passing.js"); assert.deepStrictEqual(report, { results: [ { filePath: getFixturePath("passing.js"), messages: [], errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foo;" } ], errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] }); }); it("correctly autofixes semicolon-conflicting-fixes", () => { engine = new CLIEngine({ 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 report = engine.executeOnFiles([inputPath]); const expectedOutput = fs.readFileSync(outputPath, "utf8"); assert.strictEqual(report.results[0].output, expectedOutput); }); it("correctly autofixes return-conflicting-fixes", () => { engine = new CLIEngine({ 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 report = engine.executeOnFiles([inputPath]); const expectedOutput = fs.readFileSync(outputPath, "utf8"); assert.strictEqual(report.results[0].output, expectedOutput); }); describe("Fix Types", () => { it("should throw an error when an invalid fix type is specified", () => { assert.throws(() => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, fix: true, fixTypes: ["layou"] }); }, /invalid fix type/iu); }); it("should not fix any rules when fixTypes is used without fix", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, fix: false, fixTypes: ["layout"] }); const inputPath = getFixturePath("fix-types/fix-only-semi.js"); const report = engine.executeOnFiles([inputPath]); assert.isUndefined(report.results[0].output); }); it("should not fix non-style rules when fixTypes has only 'layout'", () => { engine = new CLIEngine({ 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 report = engine.executeOnFiles([inputPath]); const expectedOutput = fs.readFileSync(outputPath, "utf8"); assert.strictEqual(report.results[0].output, expectedOutput); }); it("should not fix style or problem rules when fixTypes has only 'suggestion'", () => { engine = new CLIEngine({ 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 report = engine.executeOnFiles([inputPath]); const expectedOutput = fs.readFileSync(outputPath, "utf8"); assert.strictEqual(report.results[0].output, expectedOutput); }); it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", () => { engine = new CLIEngine({ 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 report = engine.executeOnFiles([inputPath]); const expectedOutput = fs.readFileSync(outputPath, "utf8"); assert.strictEqual(report.results[0].output, expectedOutput); }); it("should not throw an error when a rule doesn't have a 'meta' property", () => { engine = new CLIEngine({ 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 report = engine.executeOnFiles([inputPath]); const expectedOutput = fs.readFileSync(outputPath, "utf8"); assert.strictEqual(report.results[0].output, expectedOutput); }); it("should not throw an error when a rule is loaded after initialization with executeOnFiles()", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, fix: true, fixTypes: ["layout"] }); const internalSlots = getCLIEngineInternalSlots(engine); internalSlots.linter.defineRule( "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 report = engine.executeOnFiles([inputPath]); const expectedOutput = fs.readFileSync(outputPath, "utf8"); assert.strictEqual(report.results[0].output, expectedOutput); }); it("should not throw an error when a rule is loaded after initialization with executeOnText()", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, fix: true, fixTypes: ["layout"] }); const internalSlots = getCLIEngineInternalSlots(engine); internalSlots.linter.defineRule( "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 report = engine.executeOnText(fs.readFileSync(inputPath, { encoding: "utf8" }), inputPath); const expectedOutput = fs.readFileSync(outputPath, "utf8"); assert.strictEqual(report.results[0].output, expectedOutput); }); }); it("should return a message and omit fixed text when in fix mode and fixes aren't done", () => { engine = new CLIEngine({ useEslintrc: false, fix: true, rules: { "no-undef": 2 }, ignore: false, cwd: getFixturePath() }); const report = engine.executeOnText("var bar = foo", "passing.js"); assert.deepStrictEqual(report, { 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" } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar = foo" } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] }); }); it("should not delete code if there is a syntax error after trying to autofix.", () => { engine = cliEngineWithPlugins({ useEslintrc: false, fix: true, plugins: ["example"], rules: { "example/make-syntax-error": "error" }, ignore: false, cwd: getFixturePath() }); const report = engine.executeOnText("var bar = foo", "test.js"); assert.deepStrictEqual(report, { results: [ { filePath: getFixturePath("test.js"), messages: [ { ruleId: null, fatal: true, severity: 2, message: "Parsing error: Unexpected token is", line: 1, column: 19 } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var bar = foothis is a syntax error." } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] }); }); it("should not crash even if there are any syntax error since the first time.", () => { engine = new CLIEngine({ useEslintrc: false, fix: true, rules: { "example/make-syntax-error": "error" }, ignore: false, cwd: getFixturePath() }); const report = engine.executeOnText("var bar =", "test.js"); assert.deepStrictEqual(report, { results: [ { filePath: getFixturePath("test.js"), messages: [ { ruleId: null, fatal: true, severity: 2, message: "Parsing error: Unexpected token", line: 1, column: 10 } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar =" } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] }); }); it("should return source code of file in `source` property when errors are present", () => { engine = new CLIEngine({ useEslintrc: false, rules: { semi: 2 } }); const report = engine.executeOnText("var foo = 'bar'"); assert.strictEqual(report.results[0].source, "var foo = 'bar'"); }); it("should return source code of file in `source` property when warnings are present", () => { engine = new CLIEngine({ useEslintrc: false, rules: { semi: 1 } }); const report = engine.executeOnText("var foo = 'bar'"); assert.strictEqual(report.results[0].source, "var foo = 'bar'"); }); it("should not return a `source` property when no errors or warnings are present", () => { engine = new CLIEngine({ useEslintrc: false, rules: { semi: 2 } }); const report = engine.executeOnText("var foo = 'bar';"); assert.lengthOf(report.results[0].messages, 0); assert.isUndefined(report.results[0].source); }); it("should not return a `source` property when fixes are applied", () => { engine = new CLIEngine({ useEslintrc: false, fix: true, rules: { semi: 2, "no-unused-vars": 2 } }); const report = engine.executeOnText("var msg = 'hi' + foo\n"); assert.isUndefined(report.results[0].source); assert.strictEqual(report.results[0].output, "var msg = 'hi' + foo;\n"); }); it("should return a `source` property when a parsing error has occurred", () => { engine = new CLIEngine({ useEslintrc: false, rules: { semi: 2 } }); const report = engine.executeOnText("var bar = foothis is a syntax error.\n return bar;"); assert.deepStrictEqual(report, { results: [ { filePath: "", messages: [ { ruleId: null, fatal: true, severity: 2, message: "Parsing error: Unexpected token is", line: 1, column: 19 } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, source: "var bar = foothis is a syntax error.\n return bar;" } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] }); }); // https://github.com/eslint/eslint/issues/5547 it("should respect default ignore rules, even with --no-ignore", () => { engine = new CLIEngine({ cwd: getFixturePath(), ignore: false }); const report = engine.executeOnText("var bar = foo;", "node_modules/passing.js", true); const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, getFixturePath("node_modules/passing.js")); assert.strictEqual(report.results[0].messages[0].message, expectedMsg); }); // @scope for @scope/eslint-plugin describe("(plugin shorthand)", () => { const Module = require("module"); let originalFindPath = null; /* eslint-disable no-underscore-dangle */ 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 */ it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", () => { engine = new CLIEngine({ cwd: getFixturePath("plugin-shorthand/basic") }); const report = engine.executeOnText("var x = 0", "index.js").results[0]; assert.strictEqual(report.filePath, getFixturePath("plugin-shorthand/basic/index.js")); assert.strictEqual(report.messages[0].ruleId, "@scope/rule"); assert.strictEqual(report.messages[0].message, "OK"); }); it("should resolve 'extends:[\"plugin:@scope/recommended\"]' to 'node_modules/@scope/eslint-plugin'.", () => { engine = new CLIEngine({ cwd: getFixturePath("plugin-shorthand/extends") }); const report = engine.executeOnText("var x = 0", "index.js").results[0]; assert.strictEqual(report.filePath, getFixturePath("plugin-shorthand/extends/index.js")); assert.strictEqual(report.messages[0].ruleId, "@scope/rule"); assert.strictEqual(report.messages[0].message, "OK"); }); }); it("should warn when deprecated rules are found in a config", () => { engine = new CLIEngine({ cwd: originalDir, useEslintrc: false, configFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml" }); const report = engine.executeOnText("foo"); assert.deepStrictEqual( report.usedDeprecatedRules, [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] ); }); }); describe("executeOnFiles()", () => { /** @type {InstanceType} */ let engine; it("should use correct parser when custom parser is specified", () => { engine = new CLIEngine({ cwd: originalDir, ignore: false }); const filePath = path.resolve(__dirname, "../../fixtures/configurations/parser/custom.js"); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].message, "Parsing error: Boom!"); }); it("should report zero messages when given a config file and a valid file", () => { engine = new CLIEngine({ cwd: originalDir, configFile: ".eslintrc.js" }); const report = engine.executeOnFiles(["lib/**/cli*.js"]); assert.strictEqual(report.results.length, 2); assert.strictEqual(report.results[0].messages.length, 0); assert.strictEqual(report.results[1].messages.length, 0); }); it("should handle multiple patterns with overlapping files", () => { engine = new CLIEngine({ cwd: originalDir, configFile: ".eslintrc.js" }); const report = engine.executeOnFiles(["lib/**/cli*.js", "lib/cli.?s", "lib/{cli,cli-engine/cli-engine}.js"]); assert.strictEqual(report.results.length, 2); assert.strictEqual(report.results[0].messages.length, 0); assert.strictEqual(report.results[1].messages.length, 0); }); it("should report zero messages when given a config file and a valid file and espree as parser", () => { engine = new CLIEngine({ parser: "espree", envs: ["es6"], useEslintrc: false }); const report = engine.executeOnFiles(["lib/cli.js"]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); it("should report zero messages when given a config file and a valid file and esprima as parser", () => { engine = new CLIEngine({ parser: "esprima", useEslintrc: false }); const report = engine.executeOnFiles(["lib/cli.js"]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); it("should throw an error when given a config file and a valid file and invalid parser", () => { engine = new CLIEngine({ parser: "test11", useEslintrc: false }); assert.throws(() => engine.executeOnFiles(["lib/cli.js"]), "Cannot find module 'test11'"); }); it("should report zero messages when given a directory with a .js2 file", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), extensions: [".js2"] }); const report = engine.executeOnFiles([getFixturePath("files/foo.js2")]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); it("should report zero messages when given a directory with a .js and a .js2 file", () => { engine = new CLIEngine({ extensions: [".js", ".js2"], ignore: false, cwd: getFixturePath("..") }); const report = engine.executeOnFiles(["fixtures/files/"]); assert.strictEqual(report.results.length, 2); assert.strictEqual(report.results[0].messages.length, 0); assert.strictEqual(report.results[1].messages.length, 0); }); it("should report zero messages when given a '**' pattern with a .js and a .js2 file", () => { engine = new CLIEngine({ extensions: [".js", ".js2"], ignore: false, cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles(["fixtures/files/*"]); assert.strictEqual(report.results.length, 2); assert.strictEqual(report.results[0].messages.length, 0); assert.strictEqual(report.results[1].messages.length, 0); }); it("should resolve globs when 'globInputPaths' option is true", () => { engine = new CLIEngine({ extensions: [".js", ".js2"], ignore: false, cwd: getFixturePath("..") }); const report = engine.executeOnFiles(["fixtures/files/*"]); assert.strictEqual(report.results.length, 2); assert.strictEqual(report.results[0].messages.length, 0); assert.strictEqual(report.results[1].messages.length, 0); }); it("should not resolve globs when 'globInputPaths' option is false", () => { engine = new CLIEngine({ extensions: [".js", ".js2"], ignore: false, cwd: getFixturePath(".."), globInputPaths: false }); assert.throws(() => { engine.executeOnFiles(["fixtures/files/*"]); }, "No files matching 'fixtures/files/*' were found (glob was disabled)."); }); it("should report on all files passed explicitly, even if ignored by default", () => { engine = new CLIEngine({ cwd: getFixturePath("cli-engine") }); const report = engine.executeOnFiles(["node_modules/foo.js"]); const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 1); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.results[0].fixableWarningCount, 0); assert.strictEqual(report.results[0].messages[0].message, expectedMsg); }); it("should report on globs with explicit inclusion of dotfiles, even though ignored by default", () => { engine = new CLIEngine({ cwd: getFixturePath("cli-engine"), rules: { quotes: [2, "single"] } }); const report = engine.executeOnFiles(["hidden/.hiddenfolder/*.js"]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].errorCount, 1); assert.strictEqual(report.results[0].warningCount, 0); assert.strictEqual(report.results[0].fixableErrorCount, 1); assert.strictEqual(report.results[0].fixableWarningCount, 0); }); it("should not check default ignored files without --no-ignore flag", () => { engine = new CLIEngine({ cwd: getFixturePath("cli-engine") }); assert.throws(() => { engine.executeOnFiles(["node_modules"]); }, "All files matched by 'node_modules' are ignored."); }); // https://github.com/eslint/eslint/issues/5547 it("should not check node_modules files even with --no-ignore flag", () => { engine = new CLIEngine({ cwd: getFixturePath("cli-engine"), ignore: false }); assert.throws(() => { engine.executeOnFiles(["node_modules"]); }, "All files matched by 'node_modules' are ignored."); }); it("should not check .hidden files if they are passed explicitly without --no-ignore flag", () => { engine = new CLIEngine({ cwd: getFixturePath(".."), useEslintrc: false, rules: { quotes: [2, "single"] } }); const report = engine.executeOnFiles(["fixtures/files/.bar.js"]); const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 1); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.results[0].fixableWarningCount, 0); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: getFixturePath("cli-engine"), useEslintrc: false, rules: { quotes: [2, "single"] } }); const report = engine.executeOnFiles(["hidden/.hiddenfolder/double-quotes.js"]); const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 1); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.results[0].fixableWarningCount, 0); assert.strictEqual(report.results[0].messages[0].message, expectedMsg); }); it("should check .hidden files if they are passed explicitly with --no-ignore flag", () => { engine = new CLIEngine({ cwd: getFixturePath(".."), ignore: false, useEslintrc: false, rules: { quotes: [2, "single"] } }); const report = engine.executeOnFiles(["fixtures/files/.bar.js"]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].warningCount, 0); assert.strictEqual(report.results[0].errorCount, 1); assert.strictEqual(report.results[0].fixableErrorCount, 1); assert.strictEqual(report.results[0].fixableWarningCount, 0); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); }); it("should check .hidden files if they are unignored with an --ignore-pattern", () => { engine = new CLIEngine({ cwd: getFixturePath("cli-engine"), ignore: true, useEslintrc: false, ignorePattern: "!.hidden*", rules: { quotes: [2, "single"] } }); const report = engine.executeOnFiles(["hidden/"]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].warningCount, 0); assert.strictEqual(report.results[0].errorCount, 1); assert.strictEqual(report.results[0].fixableErrorCount, 1); assert.strictEqual(report.results[0].fixableWarningCount, 0); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); }); it("should report zero messages when given a pattern with a .js and a .js2 file", () => { engine = new CLIEngine({ extensions: [".js", ".js2"], ignore: false, cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles(["fixtures/files/*.?s*"]); assert.strictEqual(report.results.length, 2); assert.strictEqual(report.results[0].messages.length, 0); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: getFixturePath("configurations"), configFile: getFixturePath("configurations", "quotes-error.json") }); const report = engine.executeOnFiles([getFixturePath("single-quoted.js")]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.errorCount, 1); assert.strictEqual(report.warningCount, 0); assert.strictEqual(report.fixableErrorCount, 1); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.results[0].messages[0].severity, 2); assert.strictEqual(report.results[0].errorCount, 1); assert.strictEqual(report.results[0].warningCount, 0); assert.strictEqual(report.results[0].fixableErrorCount, 1); assert.strictEqual(report.results[0].fixableWarningCount, 0); }); it("should return 3 messages when given a config file and a directory of 3 valid files", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("configurations", "semi-error.json") }); const report = engine.executeOnFiles([getFixturePath("formatters")]); assert.strictEqual(report.results.length, 3); assert.strictEqual(report.errorCount, 0); assert.strictEqual(report.warningCount, 0); assert.strictEqual(report.fixableErrorCount, 0); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].messages.length, 0); assert.strictEqual(report.results[1].messages.length, 0); assert.strictEqual(report.results[2].messages.length, 0); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 0); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.results[0].fixableWarningCount, 0); assert.strictEqual(report.results[1].errorCount, 0); assert.strictEqual(report.results[1].warningCount, 0); assert.strictEqual(report.results[1].fixableErrorCount, 0); assert.strictEqual(report.results[1].fixableWarningCount, 0); assert.strictEqual(report.results[2].errorCount, 0); assert.strictEqual(report.results[2].warningCount, 0); assert.strictEqual(report.results[2].fixableErrorCount, 0); assert.strictEqual(report.results[2].fixableWarningCount, 0); }); it("should return the total number of errors when given multiple files", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("configurations", "single-quotes-error.json") }); const report = engine.executeOnFiles([getFixturePath("formatters")]); assert.strictEqual(report.errorCount, 6); assert.strictEqual(report.warningCount, 0); assert.strictEqual(report.fixableErrorCount, 6); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 0); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.results[0].fixableWarningCount, 0); assert.strictEqual(report.results[1].errorCount, 3); assert.strictEqual(report.results[1].warningCount, 0); assert.strictEqual(report.results[1].fixableErrorCount, 3); assert.strictEqual(report.results[1].fixableWarningCount, 0); assert.strictEqual(report.results[2].errorCount, 3); assert.strictEqual(report.results[2].warningCount, 0); assert.strictEqual(report.results[2].fixableErrorCount, 3); assert.strictEqual(report.results[2].fixableWarningCount, 0); }); it("should process when file is given by not specifying extensions", () => { engine = new CLIEngine({ ignore: false, cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles(["fixtures/files/foo.js2"]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); it("should return zero messages when given a config with environment set to browser", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("configurations", "env-browser.json") }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); it("should return zero messages when given an option to set environment to browser", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), envs: ["browser"], rules: { "no-alert": 0, "no-undef": 2 } }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); it("should return zero messages when given a config with environment set to Node.js", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("configurations", "env-node.json") }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("globals-node.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); it("should not return results from previous call when calling more than once", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), ignore: false, rules: { semi: 2 } }); const failFilePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); const passFilePath = fs.realpathSync(getFixturePath("passing.js")); let report = engine.executeOnFiles([failFilePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, failFilePath); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "semi"); assert.strictEqual(report.results[0].messages[0].severity, 2); report = engine.executeOnFiles([passFilePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, passFilePath); assert.strictEqual(report.results[0].messages.length, 0); }); it("should throw an error when given a directory with all eslint excluded files in the directory", () => { engine = new CLIEngine({ ignorePath: getFixturePath(".eslintignore") }); assert.throws(() => { engine.executeOnFiles([getFixturePath("./cli-engine/")]); }, `All files matched by '${getFixturePath("./cli-engine/")}' are ignored.`); }); it("should throw an error when all given files are ignored", () => { assert.throws(() => { engine.executeOnFiles(["tests/fixtures/cli-engine/"]); }, "All files matched by 'tests/fixtures/cli-engine/' are ignored."); }); it("should throw an error when all given files are ignored even with a `./` prefix", () => { engine = new CLIEngine({ ignorePath: getFixturePath(".eslintignore") }); assert.throws(() => { engine.executeOnFiles(["./tests/fixtures/cli-engine/"]); }, "All files matched by './tests/fixtures/cli-engine/' are ignored."); }); // https://github.com/eslint/eslint/issues/3788 it("should ignore one-level down node_modules when ignore file has 'node_modules/' in it", () => { engine = new CLIEngine({ ignorePath: getFixturePath("cli-engine", "nested_node_modules", ".eslintignore"), useEslintrc: false, rules: { quotes: [2, "double"] }, cwd: getFixturePath("cli-engine", "nested_node_modules") }); const report = engine.executeOnFiles(["."]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 0); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.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", () => { engine = new CLIEngine({ ignorePath: getFixturePath("cli-engine/.eslintignore2"), useEslintrc: false, rules: { quotes: [2, "double"] } }); assert.throws(() => { engine.executeOnFiles(["./tests/fixtures/cli-engine/"]); }, "All files matched by './tests/fixtures/cli-engine/' are ignored."); }); it("should throw an error when all given files are ignored via ignore-pattern", () => { engine = new CLIEngine({ ignorePattern: "tests/fixtures/single-quoted.js" }); assert.throws(() => { engine.executeOnFiles(["tests/fixtures/*-quoted.js"]); }, "All files matched by 'tests/fixtures/*-quoted.js' are ignored."); }); it("should return a warning when an explicitly given file is ignored", () => { engine = new CLIEngine({ ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath() }); const filePath = getFixturePath("passing.js"); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.errorCount, 0); assert.strictEqual(report.warningCount, 1); assert.strictEqual(report.fixableErrorCount, 0); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages[0].severity, 1); assert.strictEqual(report.results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 1); assert.strictEqual(report.results[0].fixableErrorCount, 0); assert.strictEqual(report.results[0].fixableWarningCount, 0); }); it("should return two messages when given a file in excluded files list while ignore is off", () => { engine = new CLIEngine({ ignorePath: getFixturePath(".eslintignore"), ignore: false, rules: { "no-undef": 2 } }); const filePath = fs.realpathSync(getFixturePath("undef.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages[0].ruleId, "no-undef"); assert.strictEqual(report.results[0].messages[0].severity, 2); assert.strictEqual(report.results[0].messages[1].ruleId, "no-undef"); assert.strictEqual(report.results[0].messages[1].severity, 2); }); it("should return zero messages when executing a file with a shebang", () => { engine = new CLIEngine({ ignore: false }); const report = engine.executeOnFiles([getFixturePath("shebang.js")]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); it("should give a warning when loading a custom rule that doesn't exist", () => { engine = new CLIEngine({ ignore: false, rulesPaths: [getFixturePath("rules", "dir1")], configFile: getFixturePath("rules", "missing-rule.json") }); const report = engine.executeOnFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "missing-rule"); assert.strictEqual(report.results[0].messages[0].severity, 2); assert.strictEqual(report.results[0].messages[0].message, "Definition for rule 'missing-rule' was not found."); }); it("should throw an error when loading a bad custom rule", () => { engine = new CLIEngine({ ignore: false, rulePaths: [getFixturePath("rules", "wrong")], configFile: getFixturePath("rules", "eslint.json") }); assert.throws(() => { engine.executeOnFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); }, /Error while loading rule 'custom-rule'/u); }); it("should return one message when a custom rule matches a file", () => { engine = new CLIEngine({ ignore: false, useEslintrc: false, rulePaths: [getFixturePath("rules/")], configFile: getFixturePath("rules", "eslint.json") }); const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "custom-rule"); assert.strictEqual(report.results[0].messages[0].severity, 1); }); it("should load custom rule from the provided cwd", () => { const cwd = path.resolve(getFixturePath("rules")); engine = new CLIEngine({ ignore: false, cwd, rulePaths: ["./"], configFile: "eslint.json" }); const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "custom-rule"); assert.strictEqual(report.results[0].messages[0].severity, 1); }); it("should return messages when multiple custom rules match a file", () => { engine = new CLIEngine({ ignore: false, rulePaths: [ getFixturePath("rules", "dir1"), getFixturePath("rules", "dir2") ], configFile: getFixturePath("rules", "multi-rulesdirs.json") }); const filePath = fs.realpathSync(getFixturePath("rules", "test-multi-rulesdirs.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "no-literals"); assert.strictEqual(report.results[0].messages[0].severity, 2); assert.strictEqual(report.results[0].messages[1].ruleId, "no-strings"); assert.strictEqual(report.results[0].messages[1].severity, 2); }); it("should return zero messages when executing without useEslintrc flag", () => { engine = new CLIEngine({ ignore: false, useEslintrc: false }); const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages.length, 0); }); it("should return zero messages when executing without useEslintrc flag in Node.js environment", () => { engine = new CLIEngine({ ignore: false, useEslintrc: false, envs: ["node"] }); const filePath = fs.realpathSync(getFixturePath("process-exit.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages.length, 0); }); it("should return zero messages when executing with base-config flag set to false", () => { engine = new CLIEngine({ ignore: false, baseConfig: false, useEslintrc: false }); const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages.length, 0); }); it("should return zero messages and ignore .eslintrc files when executing with no-eslintrc flag", () => { engine = new CLIEngine({ ignore: false, useEslintrc: false, envs: ["node"] }); const filePath = fs.realpathSync(getFixturePath("eslintrc", "quotes.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages.length, 0); }); it("should return zero messages and ignore package.json files when executing with no-eslintrc flag", () => { engine = new CLIEngine({ ignore: false, useEslintrc: false, envs: ["node"] }); const filePath = fs.realpathSync(getFixturePath("packagejson", "quotes.js")); const report = engine.executeOnFiles([filePath]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].filePath, filePath); assert.strictEqual(report.results[0].messages.length, 0); }); it("should warn when deprecated rules are configured", () => { engine = new CLIEngine({ cwd: originalDir, configFile: ".eslintrc.js", rules: { "indent-legacy": 1, "require-jsdoc": 1, "valid-jsdoc": 1 } }); const report = engine.executeOnFiles(["lib/cli*.js"]); assert.sameDeepMembers( report.usedDeprecatedRules, [ { ruleId: "indent-legacy", replacedBy: ["indent"] }, { ruleId: "require-jsdoc", replacedBy: [] }, { ruleId: "valid-jsdoc", replacedBy: [] } ] ); }); it("should not warn when deprecated rules are not configured", () => { engine = new CLIEngine({ cwd: originalDir, configFile: ".eslintrc.js", rules: { indent: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } }); const report = engine.executeOnFiles(["lib/cli*.js"]); assert.deepStrictEqual(report.usedDeprecatedRules, []); }); it("should warn when deprecated rules are found in a config", () => { engine = new CLIEngine({ cwd: originalDir, configFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", useEslintrc: false }); const report = engine.executeOnFiles(["lib/cli*.js"]); assert.deepStrictEqual( report.usedDeprecatedRules, [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] ); }); describe("Fix Mode", () => { it("should return fixed text on multiple files when in fix mode", () => { /** * 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"); } } engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, fix: true, rules: { semi: 2, quotes: [2, "double"], eqeqeq: 2, "no-undef": 2, "space-infix-ops": 2 } }); const report = engine.executeOnFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); report.results.forEach(convertCRLF); assert.deepStrictEqual(report.results, [ { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), messages: [], errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "true ? \"yes\" : \"no\";\n" }, { filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), messages: [], errorCount: 0, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0 }, { 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 } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n" }, { 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 } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, output: "var msg = \"hi\" + foo;\n" } ]); assert.strictEqual(report.errorCount, 2); assert.strictEqual(report.warningCount, 0); assert.strictEqual(report.fixableErrorCount, 0); assert.strictEqual(report.fixableWarningCount, 0); }); it("should run autofix even if files are cached without autofix results", () => { const baseOptions = { cwd: path.join(fixtureDir, ".."), useEslintrc: false, rules: { semi: 2, quotes: [2, "double"], eqeqeq: 2, "no-undef": 2, "space-infix-ops": 2 } }; engine = new CLIEngine(Object.assign({}, baseOptions, { cache: true, fix: false })); // Do initial lint run and populate the cache file engine.executeOnFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); engine = new CLIEngine(Object.assign({}, baseOptions, { cache: true, fix: true })); const report = engine.executeOnFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); assert.ok(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), reset: true, useEslintrc: false }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes-node.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); // Project configuration - first level .eslintrc it("should return zero messages when executing with .eslintrc in the Node.js environment", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); // Project configuration - first level .eslintrc it("should return zero messages when executing with .eslintrc in the Node.js environment", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 0); }); // Project configuration - first level .eslintrc it("should return one message when executing with .eslintrc", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "no-console"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/subsubbroken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.results[0].messages[0].severity, 1); }); // Project configuration - first level package.json it("should return one message when executing with package.json", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.results[0].messages[0].severity, 2); assert.strictEqual(report.results[0].messages[1].ruleId, "semi"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "no-console"); assert.strictEqual(report.results[0].messages[0].severity, 1); assert.strictEqual(report.results[0].messages[1].ruleId, "semi"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("config-hierarchy/broken/override-conf.yaml") }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "no-console"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("config-hierarchy/broken/override-conf.yaml"), rules: { quotes: [1, "double"] } }); const report = engine.executeOnFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.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", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("/config-hierarchy/broken/override-conf.yaml"), rules: { quotes: [1, "double"] } }); const report = engine.executeOnFiles([getFixturePath("config-hierarchy/broken/console-wrong-quotes.js")]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); assert.strictEqual(report.results[0].messages[0].severity, 1); }); }); describe("plugins", () => { it("should return two messages when executing with config file that specifies a plugin", () => { engine = cliEngineWithPlugins({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("configurations", "plugins-with-prefix.json"), useEslintrc: false }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("rules", "test/test-custom-rule.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "example/example-rule"); }); it("should return two messages when executing with config file that specifies a plugin with namespace", () => { engine = cliEngineWithPlugins({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("configurations", "plugins-with-prefix-and-namespace.json"), useEslintrc: false }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.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", () => { engine = cliEngineWithPlugins({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("configurations", "plugins-without-prefix.json"), useEslintrc: false }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.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", () => { engine = cliEngineWithPlugins({ cwd: path.join(fixtureDir, ".."), configFile: getFixturePath("configurations", "plugins-without-prefix-with-namespace.json"), useEslintrc: false }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "@eslint/example/example-rule"); }); it("should return two messages when executing with cli option that specifies a plugin", () => { engine = cliEngineWithPlugins({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, plugins: ["example"], rules: { "example/example-rule": 1 } }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "example/example-rule"); }); it("should return two messages when executing with cli option that specifies preloaded plugin", () => { engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, plugins: ["test"], rules: { "test/example-rule": 1 } }); engine.addPlugin("eslint-plugin-test", { rules: { "example-rule": require("../../fixtures/rules/custom-rule") } }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); assert.strictEqual(report.results[0].messages[0].ruleId, "test/example-rule"); }); it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", () => { engine = new CLIEngine({ resolvePluginsRelativeTo: getFixturePath("plugins"), baseConfig: { plugins: ["with-rules"], rules: { "with-rules/rule1": "error" } }, useEslintrc: false }); const report = engine.executeOnText("foo"); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].ruleId, "with-rules/rule1"); assert.strictEqual(report.results[0].messages[0].message, "Rule report from plugin"); }); }); 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 (ex) { /* * we don't care if the file didn't exist, since our * intention was to remove the file */ } } /** * helper method to delete the cache files created during testing * @returns {void} */ function deleteCache() { doDelete(path.resolve(".eslintcache")); doDelete(path.resolve(".cache/custom-cache")); } beforeEach(() => { deleteCache(); }); afterEach(() => { sinon.restore(); deleteCache(); }); describe("when the cacheFile is a directory or looks like a directory", () => { /** * helper method to delete the cache files created during testing * @returns {void} */ function deleteCacheDir() { try { fs.unlinkSync("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory"); } catch (ex) { /* * 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 cache file inside the provided directory", () => { assert.isFalse(shell.test("-d", path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist"); engine = new CLIEngine({ useEslintrc: false, // specifying cache true the cache will be created cache: true, cacheFile: "./tmp/.cacheFileDir/", rules: { "no-console": 0, "no-unused-vars": 2 }, extensions: ["js"], ignore: false }); const file = getFixturePath("cache/src", "test-file.js"); engine.executeOnFiles([file]); assert.isTrue(shell.test("-f", path.resolve(`./tmp/.cacheFileDir/.cache_${hash(process.cwd())}`)), "the cache for eslint was created"); sinon.restore(); }); }); it("should create the cache file inside the provided directory using the cacheLocation option", () => { assert.isFalse(shell.test("-d", path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist"); engine = new CLIEngine({ useEslintrc: false, // specifying cache true the cache will be created cache: true, cacheLocation: "./tmp/.cacheFileDir/", rules: { "no-console": 0, "no-unused-vars": 2 }, extensions: ["js"], ignore: false }); const file = getFixturePath("cache/src", "test-file.js"); engine.executeOnFiles([file]); assert.isTrue(shell.test("-f", path.resolve(`./tmp/.cacheFileDir/.cache_${hash(process.cwd())}`)), "the cache for eslint was created"); sinon.restore(); }); it("should create the cache file inside cwd when no cacheLocation provided", () => { const cwd = path.resolve(getFixturePath("cli-engine")); engine = new CLIEngine({ useEslintrc: false, cache: true, cwd, rules: { "no-console": 0 }, extensions: ["js"], ignore: false }); const file = getFixturePath("cli-engine", "console.js"); engine.executeOnFiles([file]); assert.isTrue(shell.test("-f", path.resolve(cwd, ".eslintcache")), "the cache for eslint was created at provided cwd"); }); it("should invalidate the cache if the configuration changed between executions", () => { assert.isFalse(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint does not exist"); engine = new CLIEngine({ useEslintrc: false, // specifying cache true the cache will be created cache: true, 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 = engine.executeOnFiles([file]); assert.strictEqual(result.errorCount + result.warningCount, 0, "the file passed without errors or warnings"); assert.strictEqual(spy.getCall(0).args[0], file, "the module read the file because is considered changed"); assert.isTrue(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint was created"); // destroy the spy sinon.restore(); engine = new CLIEngine({ useEslintrc: false, // specifying cache true the cache will be created cache: true, rules: { "no-console": 2, "no-unused-vars": 2 }, extensions: ["js"], ignore: false }); // create a new spy spy = sinon.spy(fs, "readFileSync"); const cachedResult = engine.executeOnFiles([file]); assert.strictEqual(spy.getCall(0).args[0], file, "the module read the file because is considered changed because the config changed"); assert.strictEqual(cachedResult.errorCount, 1, "since configuration changed the cache was not used an one error was reported"); assert.isTrue(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint was created"); }); it("should remember the files from a previous run and do not operate on them if not changed", () => { assert.isFalse(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint does not exist"); engine = new CLIEngine({ useEslintrc: false, // specifying cache true the cache will be created cache: true, 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 = engine.executeOnFiles([file]); assert.strictEqual(spy.getCall(0).args[0], file, "the module read the file because is considered changed"); assert.isTrue(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint was created"); // destroy the spy sinon.restore(); engine = new CLIEngine({ useEslintrc: false, // specifying cache true the cache will be created cache: true, rules: { "no-console": 0, "no-unused-vars": 2 }, extensions: ["js"], ignore: false }); // create a new spy spy = sinon.spy(fs, "readFileSync"); const cachedResult = engine.executeOnFiles([file]); assert.deepStrictEqual(result, cachedResult, "the result is the same regardless of using cache or not"); // assert the file was not processed because the cache was used assert.isFalse(spy.calledWith(file), "the file was not loaded because it used the cache"); }); it("should remember the files from a previous run and do not operate on then if not changed", () => { const cacheFile = getFixturePath(".eslintcache"); const cliEngineOptions = { useEslintrc: false, // specifying cache true the cache will be created cache: true, cacheFile, rules: { "no-console": 0, "no-unused-vars": 2 }, extensions: ["js"], cwd: path.join(fixtureDir, "..") }; assert.isFalse(shell.test("-f", cacheFile), "the cache for eslint does not exist"); engine = new CLIEngine(cliEngineOptions); let file = getFixturePath("cache/src", "test-file.js"); file = fs.realpathSync(file); engine.executeOnFiles([file]); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint was created"); cliEngineOptions.cache = false; engine = new CLIEngine(cliEngineOptions); engine.executeOnFiles([file]); assert.isFalse(shell.test("-f", cacheFile), "the cache for eslint was deleted since last run did not used the cache"); }); it("should store in the cache a file that failed the test", () => { const cacheFile = getFixturePath(".eslintcache"); assert.isFalse(shell.test("-f", cacheFile), "the cache for eslint does not exist"); engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, // specifying cache true the cache will be created cache: true, cacheFile, 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 = engine.executeOnFiles([badFile, goodFile]); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint was created"); const fileCache = fCache.createFromFile(cacheFile); const { cache } = fileCache; assert.isTrue(typeof cache.getKey(goodFile) === "object", "the entry for the good file is in the cache"); assert.isTrue(typeof cache.getKey(badFile) === "object", "the entry for the bad file is in the cache"); const cachedResult = engine.executeOnFiles([badFile, goodFile]); assert.deepStrictEqual(result, cachedResult, "result is the same with or without cache"); }); it("should not contain in the cache a file that was deleted", () => { const cacheFile = getFixturePath(".eslintcache"); doDelete(cacheFile); engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, // specifying cache true the cache will be created cache: true, cacheFile, 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")); engine.executeOnFiles([badFile, goodFile, toBeDeletedFile]); const fileCache = fCache.createFromFile(cacheFile); let { cache } = fileCache; assert.isTrue(typeof cache.getKey(toBeDeletedFile) === "object", "the entry for the file to be deleted is 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 */ engine.executeOnFiles([badFile, goodFile]); cache = JSON.parse(fs.readFileSync(cacheFile)); assert.isTrue(typeof cache[toBeDeletedFile] === "undefined", "the entry for the file to be deleted is not in the cache"); }); it("should contain files that were not visited in the cache provided they still exist", () => { const cacheFile = getFixturePath(".eslintcache"); doDelete(cacheFile); engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, // specifying cache true the cache will be created cache: true, cacheFile, 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")); engine.executeOnFiles([badFile, goodFile, testFile2]); let fileCache = fCache.createFromFile(cacheFile); let { cache } = fileCache; assert.isTrue(typeof cache.getKey(testFile2) === "object", "the entry for the test-file2 is 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 */ engine.executeOnFiles([badFile, goodFile]); fileCache = fCache.createFromFile(cacheFile); cache = fileCache.cache; assert.isTrue(typeof cache.getKey(testFile2) === "object", "the entry for the test-file2 is in the cache"); }); it("should not delete cache when executing on text", () => { const cacheFile = getFixturePath(".eslintcache"); engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, cacheFile, rules: { "no-console": 0, "no-unused-vars": 2 }, extensions: ["js"] }); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint exists"); engine.executeOnText("var foo = 'bar';"); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint still exists"); }); it("should not delete cache when executing on text with a provided filename", () => { const cacheFile = getFixturePath(".eslintcache"); engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, cacheFile, rules: { "no-console": 0, "no-unused-vars": 2 }, extensions: ["js"] }); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint exists"); engine.executeOnText("var bar = foo;", "fixtures/passing.js"); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint still exists"); }); it("should not delete cache when executing on files with --cache flag", () => { const cacheFile = getFixturePath(".eslintcache"); engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, cache: true, cacheFile, rules: { "no-console": 0, "no-unused-vars": 2 }, extensions: ["js"] }); const file = getFixturePath("cli-engine", "console.js"); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint exists"); engine.executeOnFiles([file]); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint still exists"); }); it("should delete cache when executing on files without --cache flag", () => { const cacheFile = getFixturePath(".eslintcache"); engine = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, cacheFile, rules: { "no-console": 0, "no-unused-vars": 2 }, extensions: ["js"] }); const file = getFixturePath("cli-engine", "console.js"); assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint exists"); engine.executeOnFiles([file]); assert.isFalse(shell.test("-f", cacheFile), "the cache for eslint has been deleted"); }); describe("cacheFile", () => { it("should use the specified cache file", () => { const customCacheFile = path.resolve(".cache/custom-cache"); assert.isFalse(shell.test("-f", customCacheFile), "the cache for eslint does not exist"); engine = new CLIEngine({ useEslintrc: false, // specify a custom cache file cacheFile: customCacheFile, // specifying cache true the cache will be created cache: true, 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 = engine.executeOnFiles([badFile, goodFile]); assert.isTrue(shell.test("-f", customCacheFile), "the cache for eslint was created"); const fileCache = fCache.createFromFile(customCacheFile); const { cache } = fileCache; assert.isTrue(typeof cache.getKey(goodFile) === "object", "the entry for the good file is in the cache"); assert.isTrue(typeof cache.getKey(badFile) === "object", "the entry for the bad file is in the cache"); const cachedResult = engine.executeOnFiles([badFile, goodFile]); assert.deepStrictEqual(result, cachedResult, "result is the same with or without cache"); }); }); }); describe("processors", () => { it("should return two messages when executing with config file that specifies a processor", () => { engine = cliEngineWithPlugins({ configFile: getFixturePath("configurations", "processors.json"), useEslintrc: false, extensions: ["js", "txt"], cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); }); it("should return two messages when executing with config file that specifies preloaded processor", () => { engine = new CLIEngine({ useEslintrc: false, plugins: ["test-processor"], rules: { "no-console": 2, "no-unused-vars": 2 }, extensions: ["js", "txt"], cwd: path.join(fixtureDir, "..") }); engine.addPlugin("test-processor", { processors: { ".txt": { preprocess(text) { return [text]; }, postprocess(messages) { return messages[0]; } } } }); const report = engine.executeOnFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 2); }); it("should run processors when calling executeOnFiles with config file that specifies a processor", () => { engine = cliEngineWithPlugins({ configFile: getFixturePath("configurations", "processors.json"), useEslintrc: false, extensions: ["js", "txt"], cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnFiles([getFixturePath("processors", "test", "test-processor.txt")]); assert.strictEqual(report.results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(report.results[0].messages[0].ruleId, "post-processed"); }); it("should run processors when calling executeOnFiles with config file that specifies preloaded processor", () => { engine = new CLIEngine({ useEslintrc: false, plugins: ["test-processor"], rules: { "no-console": 2, "no-unused-vars": 2 }, extensions: ["js", "txt"], cwd: path.join(fixtureDir, "..") }); engine.addPlugin("test-processor", { processors: { ".txt": { preprocess(text) { return [text.replace("a()", "b()")]; }, postprocess(messages) { messages[0][0].ruleId = "post-processed"; return messages[0]; } } } }); const report = engine.executeOnFiles([getFixturePath("processors", "test", "test-processor.txt")]); assert.strictEqual(report.results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(report.results[0].messages[0].ruleId, "post-processed"); }); it("should run processors when calling executeOnText with config file that specifies a processor", () => { engine = cliEngineWithPlugins({ configFile: getFixturePath("configurations", "processors.json"), useEslintrc: false, extensions: ["js", "txt"], ignore: false }); const report = engine.executeOnText("function a() {console.log(\"Test\");}", "tests/fixtures/processors/test/test-processor.txt"); assert.strictEqual(report.results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(report.results[0].messages[0].ruleId, "post-processed"); }); it("should run processors when calling executeOnText with config file that specifies preloaded processor", () => { engine = new CLIEngine({ useEslintrc: false, plugins: ["test-processor"], rules: { "no-console": 2, "no-unused-vars": 2 }, extensions: ["js", "txt"], ignore: false }); engine.addPlugin("test-processor", { processors: { ".txt": { preprocess(text) { return [text.replace("a()", "b()")]; }, postprocess(messages) { messages[0][0].ruleId = "post-processed"; return messages[0]; } } } }); const report = engine.executeOnText("function a() {console.log(\"Test\");}", "tests/fixtures/processors/test/test-processor.txt"); assert.strictEqual(report.results[0].messages[0].message, "'b' is defined but never used."); assert.strictEqual(report.results[0].messages[0].ruleId, "post-processed"); }); describe("autofixing with processors", () => { const HTML_PROCESSOR = Object.freeze({ preprocess(text) { return [text.replace(/^", "foo.html"); assert.strictEqual(report.results[0].messages.length, 0); assert.strictEqual(report.results[0].output, ""); }); it("should not run in autofix mode when using a processor that does not support autofixing", () => { engine = new CLIEngine({ useEslintrc: false, plugins: ["test-processor"], rules: { semi: 2 }, extensions: ["js", "txt"], ignore: false, fix: true }); engine.addPlugin("test-processor", { processors: { ".html": HTML_PROCESSOR } }); const report = engine.executeOnText("", "foo.html"); assert.strictEqual(report.results[0].messages.length, 1); assert.isFalse(Object.prototype.hasOwnProperty.call(report.results[0], "output")); }); it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", () => { engine = new CLIEngine({ useEslintrc: false, plugins: ["test-processor"], rules: { semi: 2 }, extensions: ["js", "txt"], ignore: false }); engine.addPlugin("test-processor", { processors: { ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } }); const report = engine.executeOnText("", "foo.html"); assert.strictEqual(report.results[0].messages.length, 1); assert.isFalse(Object.prototype.hasOwnProperty.call(report.results[0], "output")); }); }); }); describe("Patterns which match no file should throw errors.", () => { beforeEach(() => { engine = new CLIEngine({ cwd: getFixturePath("cli-engine"), useEslintrc: false }); }); it("one file", () => { assert.throws(() => { engine.executeOnFiles(["non-exist.js"]); }, "No files matching 'non-exist.js' were found."); }); it("should throw if the directory exists and is empty", () => { assert.throws(() => { engine.executeOnFiles(["empty"]); }, "No files matching 'empty' were found."); }); it("one glob pattern", () => { assert.throws(() => { engine.executeOnFiles(["non-exist/**/*.js"]); }, "No files matching 'non-exist/**/*.js' were found."); }); it("two files", () => { assert.throws(() => { engine.executeOnFiles(["aaa.js", "bbb.js"]); }, "No files matching 'aaa.js' were found."); }); it("a mix of an existing file and a non-existing file", () => { assert.throws(() => { engine.executeOnFiles(["console.js", "non-exist.js"]); }, "No files matching 'non-exist.js' were found."); }); }); describe("overrides", () => { beforeEach(() => { engine = new CLIEngine({ cwd: getFixturePath("cli-engine/overrides-with-dot"), ignore: false }); }); it("should recognize dotfiles", () => { const ret = engine.executeOnFiles([".test-target.js"]); assert.strictEqual(ret.results.length, 1); assert.strictEqual(ret.results[0].messages.length, 1); assert.strictEqual(ret.results[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", () => { beforeEach(() => { ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({ cwd: () => path.join(os.tmpdir(), "cli-engine/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();" } })); engine = new CLIEngine(); }); it("should not report 'no-console' error.", () => { const { results } = engine.executeOnFiles("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", () => { beforeEach(() => { ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({ cwd: () => path.join(os.tmpdir(), "cli-engine/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();" } })); engine = new CLIEngine(); }); it("should throw fatal error.", () => { assert.throws(() => { engine.executeOnFiles("a.js"); }, /invalid-option/u); }); }); describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => { beforeEach(() => { ({ CLIEngine } = defineCLIEngineWithInMemoryFileSystem({ 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": JSON.stringify({ plugins: ["test"], rules: { "test/no-example": "error" } }), "a.js": "example;" } })); engine = new CLIEngine({ fix: true, fixTypes: ["problem"] }); }); it("should not crash.", () => { const { results } = engine.executeOnFiles("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/cli-engine/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(${/ \`\`\` ` }; it("should lint only JavaScript blocks if '--ext' was not given.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ plugins: ["markdown", "html"], rules: { semi: "error" } }) } }).CLIEngine; engine = new CLIEngine({ cwd: root }); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ plugins: ["markdown", "html"], rules: { semi: "error" } }) } }).CLIEngine; engine = new CLIEngine({ cwd: root, fix: true }); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ plugins: ["markdown", "html"], rules: { semi: "error" } }) } }).CLIEngine; engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] }); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ plugins: ["markdown", "html"], rules: { semi: "error" } }) } }).CLIEngine; engine = new CLIEngine({ cwd: root, extensions: ["js", "html"], fix: true }); const { results } = engine.executeOnFiles(["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 overriden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ plugins: ["markdown", "html"], rules: { semi: "error" }, overrides: [ { files: "*.html", processor: "html/non-fixable" // supportsAutofix: false } ] }) } }).CLIEngine; engine = new CLIEngine({ cwd: root, extensions: ["js", "html"], fix: true }); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ 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" } } ] }) } }).CLIEngine; engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] }); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ 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" } } ] }) } }).CLIEngine; engine = new CLIEngine({ cwd: root, extensions: ["js", "html"] }); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ plugins: ["markdown", "html"], processor: "markdown/unknown" }) } }).CLIEngine; engine = new CLIEngine({ cwd: root }); assert.throws(() => { engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ...commonFiles, ".eslintrc.json": JSON.stringify({ plugins: ["markdown", "html"], rules: { semi: "error" }, overrides: [ { files: "*.html", processor: "html/.html" }, { files: "*.md", processor: "markdown/.md" } ] }) } }).CLIEngine; engine = new CLIEngine({ cwd: root }); const { results } = engine.executeOnFiles(["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(() => { engine = new CLIEngine({ cwd }); }); it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", () => { try { engine.executeOnText("test", "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.", () => { try { engine.executeOnText("test", "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.", () => { try { engine.executeOnText("test", "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.", () => { try { engine.executeOnText("test", "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.", () => { try { engine.executeOnText("test", "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.", () => { try { engine.executeOnText("test", "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.", () => { try { engine.executeOnText("test", "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", () => { it("should use the configured rules which are defined by '--rulesdir' option.", () => { const rootPath = getFixturePath("cli-engine/with-rulesdir"); const StubbedCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => rootPath, files: { "internal-rules/test.js": ` module.exports = context => ({ ExpressionStatement(node) { context.report({ node, message: "ok" }) } }) `, ".eslintrc.json": JSON.stringify({ root: true, rules: { test: "error" } }), "test.js": "console.log('hello')" } }).CLIEngine; engine = new StubbedCLIEngine({ rulePaths: ["internal-rules"] }); const report = engine.executeOnFiles(["test.js"]); assert.strictEqual(report.results.length, 1); assert.strictEqual(report.results[0].messages.length, 1); assert.strictEqual(report.results[0].messages[0].message, "ok"); }); }); describe("glob pattern '[ab].js'", () => { const root = getFixturePath("cli-engine/unmatched-glob"); it("should match '[ab].js' if existed.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "a.js": "", "b.js": "", "ab.js": "", "[ab].js": "", ".eslintrc.yml": "root: true" } }).CLIEngine; engine = new CLIEngine(); const { results } = engine.executeOnFiles(["[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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "a.js": "", "b.js": "", "ab.js": "", ".eslintrc.yml": "root: true" } }).CLIEngine; engine = new CLIEngine(); const { results } = engine.executeOnFiles(["[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"); it("should warn directive comments if 'noInlineConfig' was given.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "test.js": "/* globals foo */", ".eslintrc.yml": "noInlineConfig: true" } }).CLIEngine; engine = new CLIEngine(); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}", "test.js": "/* globals foo */", ".eslintrc.yml": "extends: foo" } }).CLIEngine; engine = new CLIEngine(); const { results } = engine.executeOnFiles(["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"); it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "test.js": "/* eslint-disable eqeqeq */", ".eslintrc.yml": "reportUnusedDisableDirectives: true" } }).CLIEngine; engine = new CLIEngine(); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "test.js": "/* eslint-disable eqeqeq */", ".eslintrc.yml": "reportUnusedDisableDirectives: true" } }).CLIEngine; engine = new CLIEngine({ reportUnusedDisableDirectives: "off" }); const { results } = engine.executeOnFiles(["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.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "test.js": "/* eslint-disable eqeqeq */", ".eslintrc.yml": "reportUnusedDisableDirectives: true" } }).CLIEngine; engine = new CLIEngine({ reportUnusedDisableDirectives: "error" }); const { results } = engine.executeOnFiles(["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"); it("should not throw.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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" } }).CLIEngine; engine = new CLIEngine(); const { results } = engine.executeOnFiles(["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"); it("'executeOnFiles(\".\")' should not load config files from outside of \".\".", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "../.eslintrc.json": "BROKEN FILE", ".eslintrc.json": JSON.stringify({ root: true }), "index.js": "console.log(\"hello\")" } }).CLIEngine; engine = new CLIEngine(); // Don't throw "failed to load config file" error. engine.executeOnFiles("."); }); it("'executeOnFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "../.eslintrc.json": JSON.stringify({ ignorePatterns: ["/dont-ignore-entry-dir"] }), ".eslintrc.json": JSON.stringify({ root: true }), "index.js": "console.log(\"hello\")" } }).CLIEngine; engine = new CLIEngine(); // Don't throw "file not found" error. engine.executeOnFiles("."); }); it("'executeOnFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", () => { CLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.json": JSON.stringify({ ignorePatterns: ["/subdir"] }), "subdir/.eslintrc.json": JSON.stringify({ root: true }), "subdir/index.js": "console.log(\"hello\")" } }).CLIEngine; engine = new CLIEngine(); // Don't throw "file not found" error. engine.executeOnFiles("subdir"); }); }); }); describe("getConfigForFile", () => { it("should return the info from Config#getConfig when called", () => { const options = { configFile: getFixturePath("configurations", "quotes-error.json") }; const engine = new CLIEngine(options); const filePath = getFixturePath("single-quoted.js"); const actualConfig = engine.getConfigForFile(filePath); const expectedConfig = new CascadingConfigArrayFactory({ specificConfigPath: options.configFile }) .getConfigArrayForFile(filePath) .extractConfig(filePath) .toCompatibleObjectAsConfigFileContent(); assert.deepStrictEqual(actualConfig, expectedConfig); }); it("should return the config when run from within a subdir", () => { const options = { cwd: getFixturePath("config-hierarchy", "root-true", "parent", "root", "subdir") }; const engine = new CLIEngine(options); const filePath = getFixturePath("config-hierarchy", "root-true", "parent", "root", ".eslintrc"); const actualConfig = engine.getConfigForFile("./.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.", () => { const engine = new CLIEngine(); try { engine.getConfigForFile("."); } catch (error) { assert.strictEqual(error.messageTemplate, "print-config-with-directory-path"); return; } assert.fail("should throw an error"); }); }); describe("isPathIgnored", () => { it("should check if the given path is ignored", () => { const engine = new CLIEngine({ ignorePath: getFixturePath(".eslintignore2"), cwd: getFixturePath() }); assert.isTrue(engine.isPathIgnored("undef.js")); assert.isFalse(engine.isPathIgnored("passing.js")); }); it("should return false if ignoring is disabled", () => { const engine = new CLIEngine({ ignore: false, ignorePath: getFixturePath(".eslintignore2"), cwd: getFixturePath() }); assert.isFalse(engine.isPathIgnored("undef.js")); }); // https://github.com/eslint/eslint/issues/5547 it("should return true for default ignores even if ignoring is disabled", () => { const engine = new CLIEngine({ ignore: false, cwd: getFixturePath("cli-engine") }); assert.isTrue(engine.isPathIgnored("node_modules/foo.js")); }); describe("about the default ignore patterns", () => { it("should always apply defaultPatterns if ignore option is true", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); }); it("should still apply defaultPatterns if ignore option is is false", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignore: false, cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); }); it("should allow subfolders of defaultPatterns to be unignored by ignorePattern", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ cwd, ignorePattern: "!/node_modules/package" }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); }); it("should allow subfolders of defaultPatterns to be unignored by ignorePath", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ cwd, ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithUnignoredDefaults") }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); }); it("should ignore dotfiles", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); }); it("should ignore directories beginning with a dot", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); }); it("should still ignore dotfiles when ignore option disabled", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignore: false, cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); }); it("should still ignore directories beginning with a dot when ignore option disabled", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignore: false, cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); }); it("should not ignore absolute paths containing '..'", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ cwd }); assert(!engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); }); it("should ignore /node_modules/ relative to .eslintignore when loaded", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePath: getFixturePath("ignored-paths", ".eslintignore"), cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "existing.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo", "node_modules", "existing.js"))); }); it("should ignore /node_modules/ relative to cwd without an .eslintignore", () => { const cwd = getFixturePath("ignored-paths", "no-ignore-file"); const engine = new CLIEngine({ cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); assert(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", () => { const cwd = getFixturePath("ignored-paths", "configurations"); const engine = new CLIEngine({ cwd }); // a .eslintignore in parent directories includes `*.js`, but don't load it. assert(!engine.isPathIgnored("foo.js")); assert(engine.isPathIgnored("node_modules/foo.js")); }); it("should return false for files outside of the cwd (with no ignore file provided)", () => { // Default ignore patterns should not inadvertently ignore files in parent directories const engine = new CLIEngine({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); }); describe("with .eslintignore file or package.json file", () => { it("should load .eslintignore from cwd when explicitly passed", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ cwd }); // `${cwd}/.eslintignore` includes `sampleignorepattern`. assert(engine.isPathIgnored("sampleignorepattern")); }); it("should use package.json's eslintIgnore files if no specified .eslintignore file", () => { const cwd = getFixturePath("ignored-paths", "package-json-ignore"); const engine = new CLIEngine({ cwd }); assert(engine.isPathIgnored("hello.js")); assert(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.throw(() => { try { // eslint-disable-next-line no-new new CLIEngine({ 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", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ cwd }); /* * package.json includes `hello.js` and `world.js`. * .eslintignore includes `sampleignorepattern`. */ assert(!engine.isPathIgnored("hello.js")); assert(!engine.isPathIgnored("world.js")); assert(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 new CLIEngine({ cwd }); }, "Package.json eslintIgnore property requires an array of paths"); }); }); describe("with --ignore-pattern option", () => { it("should accept a string for options.ignorePattern", () => { const cwd = getFixturePath("ignored-paths", "ignore-pattern"); const engine = new CLIEngine({ ignorePattern: "ignore-me.txt", cwd }); assert(engine.isPathIgnored("ignore-me.txt")); }); it("should accept an array for options.ignorePattern", () => { const engine = new CLIEngine({ ignorePattern: ["a", "b"], useEslintrc: false }); assert(engine.isPathIgnored("a")); assert(engine.isPathIgnored("b")); assert(!engine.isPathIgnored("c")); }); it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePattern: "not-a-file", cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "not-a-file"))); }); it("should return true for file matching an ignore pattern exactly", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePattern: "undef.js", cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); it("should return false for file matching an invalid ignore pattern with leading './'", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePattern: "./undef.js", cwd }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePattern: "/undef.js", cwd }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "subdir", "undef.js"))); }); it("should return true for file matching a child of an ignore pattern", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePattern: "ignore-pattern", cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); }); it("should return true for file matching a grandchild of an ignore pattern", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePattern: "ignore-pattern", cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.txt"))); }); it("should return false for file not matching any ignore pattern", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePattern: "failing.js", cwd }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "unignored.js"))); }); it("two globstar '**' ignore pattern should ignore files in nested directories", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePattern: "**/*.js", cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.js"))); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "foo.j2"))); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.j2"))); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.j2"))); }); }); describe("with --ignore-path option", () => { it("should load empty array with ignorePath set to false", () => { const cwd = getFixturePath("ignored-paths", "no-ignore-file"); const engine = new CLIEngine({ ignorePath: false, cwd }); // a .eslintignore in parent directories includes `*.js`, but don't load it. assert(!engine.isPathIgnored("foo.js")); assert(engine.isPathIgnored("node_modules/foo.js")); }); it("initialization with ignorePath should work when cwd is a parent directory", () => { const cwd = getFixturePath("ignored-paths"); const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); const engine = new CLIEngine({ ignorePath, cwd }); assert(engine.isPathIgnored("custom-name/foo.js")); }); it("initialization with ignorePath should work when the file is in the cwd", () => { const cwd = getFixturePath("ignored-paths", "custom-name"); const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); const engine = new CLIEngine({ ignorePath, cwd }); assert(engine.isPathIgnored("foo.js")); }); it("initialization with ignorePath should work when cwd is a subdirectory", () => { const cwd = getFixturePath("ignored-paths", "custom-name", "subdirectory"); const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); const engine = new CLIEngine({ ignorePath, cwd }); assert(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 new CLIEngine({ ignorePath, cwd }); }, "Cannot read .eslintignore file"); }); it("should return false for files outside of ignorePath's directory", () => { const cwd = getFixturePath("ignored-paths"); const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); const engine = new CLIEngine({ ignorePath, cwd }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); it("should resolve relative paths from CWD", () => { const cwd = getFixturePath("ignored-paths", "subdir"); const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); const engine = new CLIEngine({ ignorePath, cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); }); it("should resolve relative paths from CWD when it's in a child directory", () => { const cwd = getFixturePath("ignored-paths"); const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); const engine = new CLIEngine({ ignorePath, cwd }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/foo.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/bar.js"))); }); it("should resolve relative paths from CWD when it contains negated globs", () => { const cwd = getFixturePath("ignored-paths"); const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); const engine = new CLIEngine({ ignorePath, cwd }); assert(engine.isPathIgnored("subdir/blah.txt")); assert(engine.isPathIgnored("blah.txt")); assert(engine.isPathIgnored("subdir/bar.txt")); assert(!engine.isPathIgnored("bar.txt")); assert(!engine.isPathIgnored("subdir/baz.txt")); assert(!engine.isPathIgnored("baz.txt")); }); it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", () => { const cwd = getFixturePath("ignored-paths"); const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); const engine = new CLIEngine({ ignorePath, cwd }); assert(engine.isPathIgnored("node_modules/blah.js")); }); it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", () => { const cwd = getFixturePath("ignored-paths", "subdir"); const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); const engine = new CLIEngine({ ignorePath, cwd }); assert(engine.isPathIgnored("node_modules/blah.js")); }); it("should handle .eslintignore which contains CRLF correctly.", () => { 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 CLIEngine({ ignorePath, cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide1/a.js"))); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide2/a.js"))); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide3/a.js"))); }); it("should not include comments in ignore rules", () => { const cwd = getFixturePath("ignored-paths"); const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithComments"); const engine = new CLIEngine({ ignorePath, cwd }); assert(!engine.isPathIgnored("# should be ignored")); assert(engine.isPathIgnored("this_one_not")); }); it("should ignore a non-negated pattern", () => { const cwd = getFixturePath("ignored-paths"); const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); const engine = new CLIEngine({ ignorePath, cwd }); assert(engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "ignore.js"))); }); it("should not ignore a negated pattern", () => { const cwd = getFixturePath("ignored-paths"); const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); const engine = new CLIEngine({ ignorePath, cwd }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "unignore.js"))); }); }); describe("with --ignore-path option and --ignore-pattern option", () => { it("should return false for ignored file when unignored with ignore pattern", () => { const cwd = getFixturePath("ignored-paths"); const engine = new CLIEngine({ ignorePath: getFixturePath("ignored-paths", ".eslintignore"), ignorePattern: "!sampleignorepattern", cwd }); assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "sampleignorepattern"))); }); }); }); describe("getFormatter()", () => { it("should return a function when a bundled formatter is requested", () => { const engine = new CLIEngine(), formatter = engine.getFormatter("compact"); assert.isFunction(formatter); }); it("should return a function when no argument is passed", () => { const engine = new CLIEngine(), formatter = engine.getFormatter(); assert.isFunction(formatter); }); it("should return a function when a custom formatter is requested", () => { const engine = new CLIEngine(), formatter = engine.getFormatter(getFixturePath("formatters", "simple.js")); assert.isFunction(formatter); }); it("should return a function when a custom formatter is requested, also if the path has backslashes", () => { const engine = new CLIEngine({ cwd: path.join(fixtureDir, "..") }), formatter = engine.getFormatter(".\\fixtures\\formatters\\simple.js"); assert.isFunction(formatter); }); it("should return a function when a formatter prefixed with eslint-formatter is requested", () => { const engine = new CLIEngine({ cwd: getFixturePath("cli-engine") }), formatter = engine.getFormatter("bar"); assert.isFunction(formatter); }); it("should return a function when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", () => { const engine = new CLIEngine({ cwd: getFixturePath("cli-engine") }), formatter = engine.getFormatter("eslint-formatter-bar"); assert.isFunction(formatter); }); it("should return a function when a formatter is requested within a scoped npm package", () => { const engine = new CLIEngine({ cwd: getFixturePath("cli-engine") }), formatter = engine.getFormatter("@somenamespace/foo"); assert.isFunction(formatter); }); it("should return a function when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", () => { const engine = new CLIEngine({ cwd: getFixturePath("cli-engine") }), formatter = engine.getFormatter("@somenamespace/eslint-formatter-foo"); assert.isFunction(formatter); }); it("should return null when a customer formatter doesn't exist", () => { const engine = new CLIEngine(), formatterPath = getFixturePath("formatters", "doesntexist.js"), fullFormatterPath = path.resolve(formatterPath); assert.throws(() => { engine.getFormatter(formatterPath); }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`); }); it("should return null when a built-in formatter doesn't exist", () => { const engine = new CLIEngine(); const fullFormatterPath = path.resolve(__dirname, "../../../lib/cli-engine/formatters/special"); assert.throws(() => { engine.getFormatter("special"); }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`); }); it("should throw if the required formatter exists but has an error", () => { const engine = new CLIEngine(), formatterPath = getFixturePath("formatters", "broken.js"); assert.throws(() => { engine.getFormatter(formatterPath); }, `There was a problem loading formatter: ${formatterPath}\nError: Cannot find module 'this-module-does-not-exist'`); }); it("should return null when a non-string formatter name is passed", () => { const engine = new CLIEngine(), formatter = engine.getFormatter(5); assert.isNull(formatter); }); it("should return a function when called as a static function on CLIEngine", () => { const formatter = CLIEngine.getFormatter(); assert.isFunction(formatter); }); it("should return a function when called as a static function on CLIEngine and a custom formatter is requested", () => { const formatter = CLIEngine.getFormatter(getFixturePath("formatters", "simple.js")); assert.isFunction(formatter); }); }); describe("getErrorResults()", () => { it("should report 5 error messages when looking for errors only", () => { process.chdir(originalDir); const engine = new CLIEngine(); const report = engine.executeOnText("var foo = 'bar';"); const errorResults = CLIEngine.getErrorResults(report.results); assert.lengthOf(errorResults[0].messages, 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.results parameter", () => { process.chdir(originalDir); const engine = new CLIEngine({ rules: { quotes: [1, "double"] } }); const report = engine.executeOnText("var foo = 'bar';"); const reportResultsLength = report.results[0].messages.length; CLIEngine.getErrorResults(report.results); assert.lengthOf(report.results[0].messages, reportResultsLength); }); it("should report a warningCount of 0 when looking for errors only", () => { process.chdir(originalDir); const engine = new CLIEngine(); const report = engine.executeOnText("var foo = 'bar';"); const errorResults = CLIEngine.getErrorResults(report.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", () => { const engine = new CLIEngine({ ignorePath: path.join(fixtureDir, ".eslintignore"), cwd: path.join(fixtureDir, "..") }); const report = engine.executeOnText("var bar = foo;", "fixtures/passing.js", true); const errorReport = CLIEngine.getErrorResults(report.results); assert.lengthOf(errorReport, 0); assert.lengthOf(report.results, 1); assert.strictEqual(report.errorCount, 0); assert.strictEqual(report.warningCount, 1); assert.strictEqual(report.fixableErrorCount, 0); assert.strictEqual(report.fixableWarningCount, 0); assert.strictEqual(report.results[0].errorCount, 0); assert.strictEqual(report.results[0].warningCount, 1); assert.strictEqual(report.fixableErrorCount, 0); assert.strictEqual(report.fixableWarningCount, 0); }); it("should return source code of file in the `source` property", () => { process.chdir(originalDir); const engine = new CLIEngine({ useEslintrc: false, rules: { quotes: [2, "double"] } }); const report = engine.executeOnText("var foo = 'bar';"); const errorResults = CLIEngine.getErrorResults(report.results); assert.lengthOf(errorResults[0].messages, 1); assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); }); it("should contain `output` property after fixes", () => { process.chdir(originalDir); const engine = new CLIEngine({ useEslintrc: false, fix: true, rules: { semi: 2, "no-console": 2 } }); const report = engine.executeOnText("console.log('foo')"); const errorResults = CLIEngine.getErrorResults(report.results); assert.lengthOf(errorResults[0].messages, 1); assert.strictEqual(errorResults[0].output, "console.log('foo');"); }); }); describe("outputFixes()", () => { afterEach(() => { sinon.verifyAndRestore(); }); it("should call fs.writeFileSync() for each result with output", () => { const fakeFS = leche.fake(fs), localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { fs: fakeFS }).CLIEngine, report = { results: [ { filePath: "foo.js", output: "bar" }, { filePath: "bar.js", output: "baz" } ] }; fakeFS.writeFileSync = function() {}; const spy = sinon.spy(fakeFS, "writeFileSync"); localCLIEngine.outputFixes(report); assert.strictEqual(spy.callCount, 2); assert.isTrue(spy.firstCall.calledWithExactly("foo.js", "bar"), "First call was incorrect."); assert.isTrue(spy.secondCall.calledWithExactly("bar.js", "baz"), "Second call was incorrect."); }); it("should call fs.writeFileSync() for each result with output and not at all for a result without output", () => { const fakeFS = leche.fake(fs), localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { fs: fakeFS }).CLIEngine, report = { results: [ { filePath: "foo.js", output: "bar" }, { filePath: "abc.js" }, { filePath: "bar.js", output: "baz" } ] }; fakeFS.writeFileSync = function() {}; const spy = sinon.spy(fakeFS, "writeFileSync"); localCLIEngine.outputFixes(report); assert.strictEqual(spy.callCount, 2); assert.isTrue(spy.firstCall.calledWithExactly("foo.js", "bar"), "First call was incorrect."); assert.isTrue(spy.secondCall.calledWithExactly("bar.js", "baz"), "Second call was incorrect."); }); }); describe("getRules()", () => { it("should expose the list of rules", () => { const engine = new CLIEngine(); assert(engine.getRules().has("no-eval"), "no-eval is present"); }); it("should expose the list of plugin rules", () => { const engine = new CLIEngine({ plugins: ["node"] }); assert(engine.getRules().has("node/no-deprecated-api"), "node/no-deprecated-api is present"); }); it("should expose the rules of the plugin that is added by 'addPlugin'.", () => { const engine = new CLIEngine({ plugins: ["foo"] }); engine.addPlugin("foo", require("eslint-plugin-node")); assert(engine.getRules().has("foo/no-deprecated-api"), "foo/no-deprecated-api is present"); }); }); describe("resolveFileGlobPatterns", () => { leche.withData([ [".", ["**/*.{js}"]], ["./", ["**/*.{js}"]], ["../", ["../**/*.{js}"]], ["", []] ], (input, expected) => { it(`should correctly resolve ${input} to ${expected}`, () => { const engine = new CLIEngine(); const result = engine.resolveFileGlobPatterns([input]); assert.deepStrictEqual(result, expected); }); }); it("should convert a directory name with no provided extensions into a glob pattern", () => { const patterns = ["one-js-file"]; const opts = { cwd: getFixturePath("glob-util") }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); }); it("should not convert path with globInputPaths option false", () => { const patterns = ["one-js-file"]; const opts = { cwd: getFixturePath("glob-util"), globInputPaths: false }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["one-js-file"]); }); it("should convert an absolute directory name with no provided extensions into a posix glob pattern", () => { const patterns = [getFixturePath("glob-util", "one-js-file")]; const opts = { cwd: getFixturePath("glob-util") }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); const expected = [`${getFixturePath("glob-util", "one-js-file").replace(/\\/gu, "/")}/**/*.{js}`]; assert.deepStrictEqual(result, expected); }); it("should convert a directory name with a single provided extension into a glob pattern", () => { const patterns = ["one-js-file"]; const opts = { cwd: getFixturePath("glob-util"), extensions: [".jsx"] }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["one-js-file/**/*.{jsx}"]); }); it("should convert a directory name with multiple provided extensions into a glob pattern", () => { const patterns = ["one-js-file"]; const opts = { cwd: getFixturePath("glob-util"), extensions: [".jsx", ".js"] }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["one-js-file/**/*.{jsx,js}"]); }); it("should convert multiple directory names into glob patterns", () => { const patterns = ["one-js-file", "two-js-files"]; const opts = { cwd: getFixturePath("glob-util") }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["one-js-file/**/*.{js}", "two-js-files/**/*.{js}"]); }); it("should remove leading './' from glob patterns", () => { const patterns = ["./one-js-file"]; const opts = { cwd: getFixturePath("glob-util") }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); }); it("should convert a directory name with a trailing '/' into a glob pattern", () => { const patterns = ["one-js-file/"]; const opts = { cwd: getFixturePath("glob-util") }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); }); it("should return filenames as they are", () => { const patterns = ["some-file.js"]; const opts = { cwd: getFixturePath("glob-util") }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["some-file.js"]); }); it("should convert backslashes into forward slashes", () => { const patterns = ["one-js-file\\example.js"]; const opts = { cwd: getFixturePath() }; const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); assert.deepStrictEqual(result, ["one-js-file/example.js"]); }); }); describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { it("should report a violation for disabling rules", () => { const code = [ "alert('test'); // eslint-disable-line no-alert" ].join("\n"); const config = { envs: ["browser"], ignore: true, allowInlineConfig: false, rules: { "eol-last": 0, "no-alert": 1, "no-trailing-spaces": 0, strict: 0, quotes: 0 } }; const eslintCLI = new CLIEngine(config); const report = eslintCLI.executeOnText(code); const messages = report.results[0].messages; assert.strictEqual(messages.length, 1); assert.strictEqual(messages[0].ruleId, "no-alert"); }); it("should not report a violation by default", () => { const code = [ "alert('test'); // eslint-disable-line no-alert" ].join("\n"); const config = { envs: ["browser"], ignore: true, // allowInlineConfig: true is the default rules: { "eol-last": 0, "no-alert": 1, "no-trailing-spaces": 0, strict: 0, quotes: 0 } }; const eslintCLI = new CLIEngine(config); const report = eslintCLI.executeOnText(code); const messages = report.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", () => { const cliEngine = new CLIEngine({ useEslintrc: false, reportUnusedDisableDirectives: true }); assert.deepStrictEqual( cliEngine.executeOnText("/* eslint-disable */"), { results: [ { filePath: "", messages: [ { ruleId: null, message: "Unused eslint-disable directive (no problems were reported).", line: 1, column: 1, severity: 2, nodeType: null } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, source: "/* eslint-disable */" } ], errorCount: 1, warningCount: 0, fixableErrorCount: 0, fixableWarningCount: 0, usedDeprecatedRules: [] } ); }); }); describe("when retreiving version number", () => { it("should return current version number", () => { const eslintCLI = require("../../../lib/cli-engine").CLIEngine; const version = eslintCLI.version; assert.isString(version); assert.isTrue(parseInt(version[0], 10) >= 3); }); }); describe("mutability", () => { describe("plugins", () => { it("Loading plugin in one instance doesn't mutate to another instance", () => { const filePath = getFixturePath("single-quoted.js"); const engine1 = cliEngineWithPlugins({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, plugins: ["example"], rules: { "example/example-rule": 1 } }); const engine2 = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false }); const fileConfig1 = engine1.getConfigForFile(filePath); const fileConfig2 = engine2.getConfigForFile(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", () => { const filePath = getFixturePath("single-quoted.js"); const engine1 = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false, rules: { "example/example-rule": 1 } }); const engine2 = new CLIEngine({ cwd: path.join(fixtureDir, ".."), useEslintrc: false }); const fileConfig1 = engine1.getConfigForFile(filePath); const fileConfig2 = engine2.getConfigForFile(filePath); // plugin assert.deepStrictEqual(fileConfig1.rules["example/example-rule"], [1], "example is present for engine 1"); assert.isUndefined(fileConfig2.rules["example/example-rule"], "example is not present for engine 2"); }); }); }); describe("with ignorePatterns config", () => { const root = getFixturePath("cli-engine/ignore-patterns"); /** @type {typeof CLIEngine} */ let InMemoryCLIEngine; describe("ignorePatterns can add an ignore pattern ('foo.js').", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.json": JSON.stringify({ ignorePatterns: "foo.js" }), "foo.js": "", "bar.js": "", "subdir/foo.js": "", "subdir/bar.js": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); }); it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("bar.js"), false); assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), false); }); it("'executeOnFiles()' should not verify 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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').", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.json": JSON.stringify({ ignorePatterns: ["foo.js", "/bar.js"] }), "foo.js": "", "bar.js": "", "baz.js": "", "subdir/foo.js": "", "subdir/bar.js": "", "subdir/baz.js": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); }); it("'isPathIgnored()' should return 'true' for '/bar.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("bar.js"), true); assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), false); }); it("'executeOnFiles()' should not verify 'foo.js' and '/bar.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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'.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.json": JSON.stringify({ ignorePatterns: "!/node_modules/foo" }), "node_modules/foo/index.js": "", "node_modules/foo/.dot.js": "", "node_modules/bar/index.js": "", "foo.js": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("node_modules/foo/index.js"), false); }); it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("node_modules/foo/.dot.js"), true); }); it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("node_modules/bar/index.js"), true); }); it("'executeOnFiles()' should verify 'node_modules/foo/index.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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'.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.js": `module.exports = ${JSON.stringify({ ignorePatterns: "!.eslintrc.js" })}`, "foo.js": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored(".eslintrc.js"), false); }); it("'executeOnFiles()' should verify '.eslintrc.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.js": `module.exports = ${JSON.stringify({ ignorePatterns: "!.*" })}`, ".eslintignore": ".foo*", ".foo.js": "", ".bar.js": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored(".foo.js"), true); }); it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored(".bar.js"), false); }); it("'executeOnFiles()' should not verify re-ignored '.foo.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.js": `module.exports = ${JSON.stringify({ ignorePatterns: "*.js" })}`, ".eslintignore": "!foo.js", "foo.js": "", "bar.js": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), false); }); it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("bar.js"), true); }); it("'executeOnFiles()' should verify unignored 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); assert.strictEqual(engine.isPathIgnored("subdir/subsubdir/foo.js"), true); }); it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); assert.strictEqual(engine.isPathIgnored("subdir/subsubdir/bar.js"), true); }); it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("bar.js"), false); }); it("'executeOnFiles()' should verify 'bar.js' in the outside of 'subdir'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.json": JSON.stringify({ ignorePatterns: "foo.js" }), "subdir/.eslintrc.json": JSON.stringify({ ignorePatterns: "!foo.js" }), "foo.js": "", "subdir/foo.js": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); }); it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false); }); it("'executeOnFiles()' should verify 'foo.js' in the child directory.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), false); assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false); }); it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); }); it("'executeOnFiles()' should verify unignored 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); }); it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("bar.js"), false); }); it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false); }); it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); }); it("'executeOnFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); }); it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("bar.js"), false); }); it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); }); it("'executeOnFiles()' should verify 'bar.js' in the root directory.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ path.join(root, "bar.js") ]); }); }); describe("ignorePatterns in the shareable config should be used.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); }); it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("bar.js"), false); }); it("'executeOnFiles()' should verify 'bar.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); }); it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false); }); it("'executeOnFiles()' should verify 'subdir/foo.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("foo.js"), true); }); it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { const engine = new InMemoryCLIEngine(); assert.strictEqual(engine.isPathIgnored("bar.js"), false); }); it("'executeOnFiles()' should verify 'bar.js'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.json": JSON.stringify({ ignorePatterns: "*.js" }), "foo.js": "" } }).CLIEngine; }); it("'isPathIgnored()' should return 'false' for 'foo.js'.", () => { const engine = new InMemoryCLIEngine({ ignore: false }); assert.strictEqual(engine.isPathIgnored("foo.js"), false); }); it("'executeOnFiles()' should verify 'foo.js'.", () => { const engine = new InMemoryCLIEngine({ ignore: false }); const filePaths = engine.executeOnFiles("**/*.js") .results .map(r => r.filePath) .sort(); assert.deepStrictEqual(filePaths, [ path.join(root, "foo.js") ]); }); }); describe("ignorePatterns in overrides section is not allowed.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { ".eslintrc.js": `module.exports = ${JSON.stringify({ overrides: [ { files: "*.js", ignorePatterns: "foo.js" } ] })}`, "foo.js": "" } }).CLIEngine; }); it("should throw a configuration error.", () => { assert.throws(() => { const engine = new InMemoryCLIEngine(); engine.executeOnFiles("*.js"); }, "Unexpected top-level property \"overrides[0].ignorePatterns\""); }); }); }); describe("'overrides[].files' adds lint targets", () => { const root = getFixturePath("cli-engine/additional-lint-targets"); let InMemoryCLIEngine; describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'executeOnFiles()' with a directory path should contain 'foo/test.txt'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles(".") .results .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("'executeOnFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles("**/*.js") .results .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,", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles(".") .results .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,", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'executeOnFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles(".") .results .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,", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles(".") .results .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,", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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": "" } }).CLIEngine; }); it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { const engine = new InMemoryCLIEngine(); const filePaths = engine.executeOnFiles(".") .results .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"); /** @type {CLIEngine} */ let InMemoryCLIEngine; describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "node_modules/myconf/.eslintrc.json": JSON.stringify({ overrides: [ { files: "foo/*.js", rules: { eqeqeq: "error" } } ] }), "node_modules/myconf/foo/test.js": "a == b", "foo/test.js": "a == b" } }).CLIEngine; }); it("'executeOnFiles()' with 'foo/test.js' should use the override entry.", () => { const engine = new InMemoryCLIEngine({ configFile: "node_modules/myconf/.eslintrc.json", cwd: root, ignore: false, useEslintrc: false }); const { results } = engine.executeOnFiles("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(root, "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", warningCount: 0 } ]); }); it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", () => { const engine = new InMemoryCLIEngine({ configFile: "node_modules/myconf/.eslintrc.json", cwd: root, ignore: false, useEslintrc: false }); const { results } = engine.executeOnFiles("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(root, "node_modules/myconf/foo/test.js"), fixableErrorCount: 0, fixableWarningCount: 0, messages: [], warningCount: 0 } ]); }); }); describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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" } }).CLIEngine; }); it("'executeOnFiles()' with 'foo/test.js' should NOT use the override entry.", () => { const engine = new InMemoryCLIEngine({ configFile: "node_modules/myconf/.eslintrc.json", cwd: root, ignore: false, useEslintrc: false }); const { results } = engine.executeOnFiles("foo/test.js"); // Expected to be no errors because the file matches to `$CWD/foo/*.js`. assert.deepStrictEqual(results, [ { errorCount: 0, filePath: path.join(root, "foo/test.js"), fixableErrorCount: 0, fixableWarningCount: 0, messages: [], warningCount: 0 } ]); }); it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", () => { const engine = new InMemoryCLIEngine({ configFile: "node_modules/myconf/.eslintrc.json", cwd: root, ignore: false, useEslintrc: false }); const { results } = engine.executeOnFiles("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(root, "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 } ], source: "a == b", warningCount: 0 } ]); }); }); describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ 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" } }).CLIEngine; }); it("'executeOnFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", () => { const engine = new InMemoryCLIEngine({ configFile: "node_modules/myconf/.eslintrc.json", cwd: root, useEslintrc: false }); const files = engine.executeOnFiles("**/*.js") .results .map(r => r.filePath) .sort(); assert.deepStrictEqual(files, [ path.join(root, "node_modules/myconf/foo/test.js") ]); }); }); }); describe("plugin conflicts", () => { let uid = 0; let root = ""; beforeEach(() => { root = getFixturePath(`cli-engine/plugin-conflicts-${++uid}`); }); /** @type {typeof CLIEngine} */ let InMemoryCLIEngine; /** * Verify thrown errors. * @param {() => void} f The function to run and throw. * @param {Record} props The properties to verify. * @returns {void} */ function assertThrows(f, props) { try { 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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, 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": "" } }).CLIEngine; }); it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => { const engine = new InMemoryCLIEngine({ cwd: root }); engine.executeOnFiles("test.js"); }); }); describe("between a config file and same-depth extendees.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, 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": "" } }).CLIEngine; }); it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => { const engine = new InMemoryCLIEngine({ cwd: root }); engine.executeOnFiles("test.js"); }); }); describe("between two config files in different directories, with single node_modules.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "node_modules/eslint-plugin-foo/index.js": "", ".eslintrc.json": JSON.stringify({ plugins: ["foo"] }), "subdir/.eslintrc.json": JSON.stringify({ plugins: ["foo"] }), "subdir/test.js": "" } }).CLIEngine; }); it("'executeOnFiles()' 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.)", () => { const engine = new InMemoryCLIEngine({ cwd: root }); engine.executeOnFiles("subdir/test.js"); }); }); describe("between two config files in different directories, with multiple node_modules.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, 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": "" } }).CLIEngine; }); it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => { const engine = new InMemoryCLIEngine({ cwd: root }); assertThrows( () => engine.executeOnFiles("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(root, "subdir/node_modules/eslint-plugin-foo/index.js"), importerName: `subdir${path.sep}.eslintrc.json` }, { filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"), importerName: ".eslintrc.json" } ] } } ); }); }); describe("between '--config' option and a regular config file, with single node_modules.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, 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": "" } }).CLIEngine; }); it("'executeOnFiles()' 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.)", () => { const engine = new InMemoryCLIEngine({ cwd: root, configFile: "node_modules/mine/.eslintrc.json" }); engine.executeOnFiles("test.js"); }); }); describe("between '--config' option and a regular config file, with multiple node_modules.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, 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": "" } }).CLIEngine; }); it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => { const engine = new InMemoryCLIEngine({ cwd: root, configFile: "node_modules/mine/.eslintrc.json" }); assertThrows( () => engine.executeOnFiles("test.js"), { message: "Plugin \"foo\" was conflicted between \"--config\" and \".eslintrc.json\".", messageTemplate: "plugin-conflict", messageData: { pluginId: "foo", plugins: [ { filePath: path.join(root, "node_modules/mine/node_modules/eslint-plugin-foo/index.js"), importerName: "--config" }, { filePath: path.join(root, "node_modules/eslint-plugin-foo/index.js"), importerName: ".eslintrc.json" } ] } } ); }); }); describe("between '--plugin' option and a regular config file, with single node_modules.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, files: { "node_modules/eslint-plugin-foo/index.js": "", "subdir/.eslintrc.json": JSON.stringify({ plugins: ["foo"] }), "subdir/test.js": "" } }).CLIEngine; }); it("'executeOnFiles()' 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.)", () => { const engine = new InMemoryCLIEngine({ cwd: root, plugins: ["foo"] }); engine.executeOnFiles("subdir/test.js"); }); }); describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, 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": "" } }).CLIEngine; }); it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", () => { const engine = new InMemoryCLIEngine({ cwd: root, plugins: ["foo"] }); assertThrows( () => engine.executeOnFiles("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(root, "node_modules/eslint-plugin-foo/index.js"), importerName: "CLIOptions" }, { filePath: path.join(root, "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.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, 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": "" } }).CLIEngine; }); it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", () => { const engine = new InMemoryCLIEngine({ cwd: root, resolvePluginsRelativeTo: root }); engine.executeOnFiles("subdir/test.js"); }); }); describe("between two config files with different target files.", () => { beforeEach(() => { InMemoryCLIEngine = defineCLIEngineWithInMemoryFileSystem({ cwd: () => root, 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": "" } }).CLIEngine; }); it("'executeOnFiles()' 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.)", () => { const engine = new InMemoryCLIEngine({ cwd: root }); const { results } = engine.executeOnFiles("*/test.js"); assert.strictEqual(results.length, 2); }); }); }); });