From d7276fd7855f311d4b8bcff67385122b3e1984aa Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Thu, 5 Mar 2020 21:12:32 -0500 Subject: [PATCH] Add lintFiles() tests --- lib/eslint/eslint.js | 12 +- .../_utils.js => _utils/in-memory-fs.js} | 114 +- tests/_utils/index.js | 39 + tests/lib/_utils.js | 76 - .../cascading-config-array-factory.js | 2 +- tests/lib/cli-engine/cli-engine.js | 3 +- tests/lib/cli-engine/config-array-factory.js | 2 +- tests/lib/cli-engine/file-enumerator.js | 2 +- tests/lib/eslint/_utils.js | 6 - tests/lib/eslint/eslint.js | 3409 +++++++++++++++-- tests/lib/init/npm-utils.js | 2 +- tests/lib/rules/implicit-arrow-linebreak.js | 2 +- tests/lib/rules/indent.js | 24 +- tests/lib/rules/object-shorthand.js | 2 +- tests/lib/shared/runtime-info.js | 2 +- 15 files changed, 3321 insertions(+), 376 deletions(-) rename tests/{lib/cli-engine/_utils.js => _utils/in-memory-fs.js} (78%) create mode 100644 tests/_utils/index.js delete mode 100644 tests/lib/_utils.js delete mode 100644 tests/lib/eslint/_utils.js diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 99d4d235b6b..c913ad4a73f 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -136,10 +136,10 @@ function processOptions({ ignore = true, ignorePath = null, ignorePattern = [], - parser = "espree", + parser = null, // Question: if this is set to a default value it ends up overriding the value set in the config. That seems like unexpected behavior to me. parserOptions = {}, // TODO: Is this correct? plugins = [], - reportUnusedDisableDirectives = false, + reportUnusedDisableDirectives = null, resolvePluginsRelativeTo = cwd, rulePaths = [], rules = {}, // TODO: Is this correct? @@ -214,8 +214,8 @@ function processOptions({ throw new Error("ignorePattern must be a string or an array of strings."); } - if (typeof parser !== "string") { - throw new Error("parser must be a string."); + if (typeof parser !== "string" && parser !== null) { + throw new Error("parser must be a string or null."); } if (typeof parserOptions !== "object" && parserOptions !== null) { @@ -226,8 +226,8 @@ function processOptions({ throw new Error("plugins must be an array."); } - if (typeof reportUnusedDisableDirectives !== "boolean") { - throw new Error("reportUnusedDisableDirectives must be a boolean."); + if (typeof reportUnusedDisableDirectives !== "boolean" && reportUnusedDisableDirectives !== null) { + throw new Error("reportUnusedDisableDirectives must be a boolean or null."); } if (typeof resolvePluginsRelativeTo !== "string") { diff --git a/tests/lib/cli-engine/_utils.js b/tests/_utils/in-memory-fs.js similarity index 78% rename from tests/lib/cli-engine/_utils.js rename to tests/_utils/in-memory-fs.js index 1d1bcd275b0..fdd72a3adc2 100644 --- a/tests/lib/cli-engine/_utils.js +++ b/tests/_utils/in-memory-fs.js @@ -2,12 +2,13 @@ * @fileoverview Define classes what use the in-memory file system. * * This provides utilities to test `ConfigArrayFactory`, - * `CascadingConfigArrayFactory`, `FileEnumerator`, and `CLIEngine`. + * `CascadingConfigArrayFactory`, `FileEnumerator`, `CLIEngine`, and `ESLint`. * * - `defineConfigArrayFactoryWithInMemoryFileSystem({ cwd, files })` * - `defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd, files })` * - `defineFileEnumeratorWithInMemoryFileSystem({ cwd, files })` * - `defineCLIEngineWithInMemoryFileSystem({ cwd, files })` + * - `defineESLintWithInMemoryFileSystem({ cwd, files })` * * Those functions define correspond classes with the in-memory file system. * Those search config files, parsers, and plugins in the `files` option via the @@ -55,23 +56,25 @@ const path = require("path"); const vm = require("vm"); +const { Volume, createFsFromVolume } = require("memfs"); const Proxyquire = require("proxyquire/lib/proxyquire"); -const { defineInMemoryFs } = require("../_utils"); const CascadingConfigArrayFactoryPath = - require.resolve("../../../lib/cli-engine/cascading-config-array-factory"); + require.resolve("../../lib/cli-engine/cascading-config-array-factory"); const CLIEnginePath = - require.resolve("../../../lib/cli-engine/cli-engine"); + require.resolve("../../lib/cli-engine/cli-engine"); const ConfigArrayFactoryPath = - require.resolve("../../../lib/cli-engine/config-array-factory"); + require.resolve("../../lib/cli-engine/config-array-factory"); const FileEnumeratorPath = - require.resolve("../../../lib/cli-engine/file-enumerator"); + require.resolve("../../lib/cli-engine/file-enumerator"); const LoadRulesPath = - require.resolve("../../../lib/cli-engine/load-rules"); + require.resolve("../../lib/cli-engine/load-rules"); +const ESLintPath = + require.resolve("../../lib/eslint/eslint"); const ESLintAllPath = - require.resolve("../../../conf/eslint-all"); + require.resolve("../../conf/eslint-all"); const ESLintRecommendedPath = - require.resolve("../../../conf/eslint-recommended"); + require.resolve("../../conf/eslint-recommended"); // Ensure the needed files has been loaded and cached. require(CascadingConfigArrayFactoryPath); @@ -79,6 +82,7 @@ require(CLIEnginePath); require(ConfigArrayFactoryPath); require(FileEnumeratorPath); require(LoadRulesPath); +require(ESLintPath); require("js-yaml"); require("espree"); @@ -236,12 +240,57 @@ function fsImportFresh(fs, stubs, absolutePath) { ); } +/** + * Define in-memory file system. + * @param {Object} options The options. + * @param {() => string} [options.cwd] The current working directory. + * @param {Object} [options.files] The initial files definition in the in-memory file system. + * @returns {import("fs")} The stubbed `ConfigArrayFactory` class. + */ +function defineInMemoryFs({ + cwd = process.cwd, + files = {} +} = {}) { + + /** + * The in-memory file system for this mock. + * @type {import("fs")} + */ + const fs = createFsFromVolume(new Volume()); + + fs.mkdirSync(cwd(), { recursive: true }); + + /* + * Write all files to the in-memory file system and compile all JavaScript + * files then set to `stubs`. + */ + (function initFiles(directoryPath, definition) { + for (const [filename, content] of Object.entries(definition)) { + const filePath = path.resolve(directoryPath, filename); + const parentPath = path.dirname(filePath); + + if (typeof content === "object") { + initFiles(filePath, content); + } else if (typeof content === "string") { + if (!fs.existsSync(parentPath)) { + fs.mkdirSync(parentPath, { recursive: true }); + } + fs.writeFileSync(filePath, content); + } else { + throw new Error(`Invalid content: ${typeof content}`); + } + } + }(cwd(), files)); + + return fs; +} + /** * Define stubbed `ConfigArrayFactory` class what uses the in-memory file system. * @param {Object} options The options. * @param {() => string} [options.cwd] The current working directory. * @param {Object} [options.files] The initial files definition in the in-memory file system. - * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"] }} The stubbed `ConfigArrayFactory` class. + * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"] }} The stubbed `ConfigArrayFactory` class. */ function defineConfigArrayFactoryWithInMemoryFileSystem({ cwd = process.cwd, @@ -438,9 +487,52 @@ function defineCLIEngineWithInMemoryFileSystem({ }; } +/** + * Define stubbed `ESLint` class that uses the in-memory file system. + * @param {Object} options The options. + * @param {() => string} [options.cwd] The current working directory. + * @param {Object} [options.files] The initial files definition in the in-memory file system. + * @returns {{ fs: import("fs"), RelativeModuleResolver: import("../../lib/shared/relative-module-resolver"), ConfigArrayFactory: import("../../lib/cli-engine/config-array-factory")["ConfigArrayFactory"], CascadingConfigArrayFactory: import("../../lib/cli-engine/cascading-config-array-factory")["CascadingConfigArrayFactory"], FileEnumerator: import("../../lib/cli-engine/file-enumerator")["FileEnumerator"], ESLint: import("../../lib/eslint/eslint")["ESLint"], getCLIEngineInternalSlots: import("../../lib//eslint/eslint")["getESLintInternalSlots"] }} The stubbed `ESLint` class. + */ +function defineESLintWithInMemoryFileSystem({ + cwd = process.cwd, + files = {} +} = {}) { + const { + fs, + RelativeModuleResolver, + ConfigArrayFactory, + CascadingConfigArrayFactory, + FileEnumerator, + CLIEngine + } = defineCLIEngineWithInMemoryFileSystem({ cwd, files }); + const { ESLint, getESLintInternalSlots } = proxyquire(ESLintPath, { + "../cli-engine": { CLIEngine } + }); + + // Override the default cwd. + return { + fs, + RelativeModuleResolver, + ConfigArrayFactory, + CascadingConfigArrayFactory, + FileEnumerator, + ESLint: cwd === process.cwd + ? ESLint + : class extends ESLint { + constructor(options) { + super({ cwd: cwd(), ...options }); + } + }, + getESLintInternalSlots + }; +} + module.exports = { + defineInMemoryFs, defineConfigArrayFactoryWithInMemoryFileSystem, defineCascadingConfigArrayFactoryWithInMemoryFileSystem, defineFileEnumeratorWithInMemoryFileSystem, - defineCLIEngineWithInMemoryFileSystem + defineCLIEngineWithInMemoryFileSystem, + defineESLintWithInMemoryFileSystem }; diff --git a/tests/_utils/index.js b/tests/_utils/index.js new file mode 100644 index 00000000000..0431e3aced4 --- /dev/null +++ b/tests/_utils/index.js @@ -0,0 +1,39 @@ +"use strict"; + +const { + defineInMemoryFs, + defineConfigArrayFactoryWithInMemoryFileSystem, + defineCascadingConfigArrayFactoryWithInMemoryFileSystem, + defineFileEnumeratorWithInMemoryFileSystem, + defineCLIEngineWithInMemoryFileSystem, + defineESLintWithInMemoryFileSystem +} = require("./in-memory-fs"); + + +/** + * Prevents leading spaces in a multiline template literal from appearing in the resulting string + * @param {string[]} strings The strings in the template literal + * @param {any[]} values The interpolation values in the template literal. + * @returns {string} The template literal, with spaces removed from all lines + */ +function unIndent(strings, ...values) { + const text = strings + .map((s, i) => (i === 0 ? s : values[i - 1] + s)) + .join(""); + const lines = text.replace(/^\n/u, "").replace(/\n\s*$/u, "").split("\n"); + const lineIndents = lines.filter(line => line.trim()).map(line => line.match(/ */u)[0].length); + const minLineIndent = Math.min(...lineIndents); + + return lines.map(line => line.slice(minLineIndent)).join("\n"); +} + + +module.exports = { + unIndent, + defineInMemoryFs, + defineConfigArrayFactoryWithInMemoryFileSystem, + defineCascadingConfigArrayFactoryWithInMemoryFileSystem, + defineFileEnumeratorWithInMemoryFileSystem, + defineCLIEngineWithInMemoryFileSystem, + defineESLintWithInMemoryFileSystem +}; diff --git a/tests/lib/_utils.js b/tests/lib/_utils.js deleted file mode 100644 index 76e973cc667..00000000000 --- a/tests/lib/_utils.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @fileoverview utils for rule tests. - * @author 唯然 - */ - -"use strict"; - -const path = require("path"); -const { Volume, createFsFromVolume } = require("memfs"); - -/** - * Prevents leading spaces in a multiline template literal from appearing in the resulting string - * @param {string[]} strings The strings in the template literal - * @param {any[]} values The interpolation values in the template literal. - * @returns {string} The template literal, with spaces removed from all lines - */ -function unIndent(strings, ...values) { - const text = strings - .map((s, i) => (i === 0 ? s : values[i - 1] + s)) - .join(""); - const lines = text.replace(/^\n/u, "").replace(/\n\s*$/u, "").split("\n"); - const lineIndents = lines.filter(line => line.trim()).map(line => line.match(/ */u)[0].length); - const minLineIndent = Math.min(...lineIndents); - - return lines.map(line => line.slice(minLineIndent)).join("\n"); -} - -/** - * Define in-memory file system. - * @param {Object} options The options. - * @param {() => string} [options.cwd] The current working directory. - * @param {Object} [options.files] The initial files definition in the in-memory file system. - * @returns {import("fs")} The stubbed `ConfigArrayFactory` class. - */ -function defineInMemoryFs({ - cwd = process.cwd, - files = {} -} = {}) { - - /** - * The in-memory file system for this mock. - * @type {import("fs")} - */ - const fs = createFsFromVolume(new Volume()); - - fs.mkdirSync(cwd(), { recursive: true }); - - /* - * Write all files to the in-memory file system and compile all JavaScript - * files then set to `stubs`. - */ - (function initFiles(directoryPath, definition) { - for (const [filename, content] of Object.entries(definition)) { - const filePath = path.resolve(directoryPath, filename); - const parentPath = path.dirname(filePath); - - if (typeof content === "object") { - initFiles(filePath, content); - } else if (typeof content === "string") { - if (!fs.existsSync(parentPath)) { - fs.mkdirSync(parentPath, { recursive: true }); - } - fs.writeFileSync(filePath, content); - } else { - throw new Error(`Invalid content: ${typeof content}`); - } - } - }(cwd(), files)); - - return fs; -} - -module.exports = { - defineInMemoryFs, - unIndent -}; diff --git a/tests/lib/cli-engine/cascading-config-array-factory.js b/tests/lib/cli-engine/cascading-config-array-factory.js index 0bc49b461bf..f539c8b8298 100644 --- a/tests/lib/cli-engine/cascading-config-array-factory.js +++ b/tests/lib/cli-engine/cascading-config-array-factory.js @@ -12,7 +12,7 @@ const sh = require("shelljs"); const sinon = require("sinon"); const { ConfigArrayFactory } = require("../../../lib/cli-engine/config-array-factory"); const { ExtractedConfig } = require("../../../lib/cli-engine/config-array/extracted-config"); -const { defineCascadingConfigArrayFactoryWithInMemoryFileSystem } = require("./_utils"); +const { defineCascadingConfigArrayFactoryWithInMemoryFileSystem } = require("../../_utils"); /** @typedef {InstanceType["CascadingConfigArrayFactory"]>} CascadingConfigArrayFactory */ /** @typedef {ReturnType} ConfigArray */ diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index 898401f1a6e..fc4965bc176 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -18,8 +18,7 @@ const assert = require("chai").assert, os = require("os"), hash = require("../../../lib/cli-engine/hash"), { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory"), - { unIndent } = require("../_utils"), - { defineCLIEngineWithInMemoryFileSystem } = require("./_utils"); + { unIndent, defineCLIEngineWithInMemoryFileSystem } = require("../../_utils"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); const fCache = require("file-entry-cache"); diff --git a/tests/lib/cli-engine/config-array-factory.js b/tests/lib/cli-engine/config-array-factory.js index 2e1d230a574..20dd656478a 100644 --- a/tests/lib/cli-engine/config-array-factory.js +++ b/tests/lib/cli-engine/config-array-factory.js @@ -10,7 +10,7 @@ const { assert } = require("chai"); const { spy } = require("sinon"); const { ConfigArray } = require("../../../lib/cli-engine/config-array"); const { OverrideTester } = require("../../../lib/cli-engine/config-array"); -const { defineConfigArrayFactoryWithInMemoryFileSystem } = require("./_utils"); +const { defineConfigArrayFactoryWithInMemoryFileSystem } = require("../../_utils"); const tempDir = path.join(os.tmpdir(), "eslint/config-array-factory"); diff --git a/tests/lib/cli-engine/file-enumerator.js b/tests/lib/cli-engine/file-enumerator.js index c62578c9162..43728862d95 100644 --- a/tests/lib/cli-engine/file-enumerator.js +++ b/tests/lib/cli-engine/file-enumerator.js @@ -11,7 +11,7 @@ const { assert } = require("chai"); const sh = require("shelljs"); const { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory"); -const { defineFileEnumeratorWithInMemoryFileSystem } = require("./_utils"); +const { defineFileEnumeratorWithInMemoryFileSystem } = require("../../_utils"); describe("FileEnumerator", () => { describe("'iterateFiles(patterns)' method should iterate files and configs.", () => { diff --git a/tests/lib/eslint/_utils.js b/tests/lib/eslint/_utils.js deleted file mode 100644 index 5a6c341c152..00000000000 --- a/tests/lib/eslint/_utils.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @fileoverview Utilities for testing the ESLint class. - * @author Kai Cataldo - */ - -"use strict"; diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 77f10f013f8..89177a1dd04 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -13,7 +13,11 @@ const path = require("path"); const os = require("os"); const fs = require("fs"); const assert = require("chai").assert; +const sinon = require("sinon"); const shell = require("shelljs"); +const hash = require("../../../lib/cli-engine/hash"); +const fCache = require("file-entry-cache"); +const { unIndent, defineESLintWithInMemoryFileSystem } = require("../../_utils"); //------------------------------------------------------------------------------ // Tests @@ -35,6 +39,9 @@ describe("ESLint", () => { /** @type {import("../../../lib/eslint")["ESLint"]} */ let ESLint; + /** @type {import("../../../lib/eslint")["ESLint"]} */ + let eslint; + /** @type {import("../../../lib/eslint/eslint")["getESLintPrivateMembers"]} */ let getESLintPrivateMembers; @@ -82,11 +89,11 @@ describe("ESLint", () => { }); describe("ESLint constructor function", () => { - it("the default value of 'options.cwd' should be the current working directory.", () => { + it("the default value of 'options.cwd' should be the current working directory.", async () => { process.chdir(__dirname); try { - const eslint = new ESLint(); + eslint = new ESLint(); const { options } = getESLintPrivateMembers(eslint); assert.strictEqual(options.cwd, __dirname); @@ -95,7 +102,7 @@ describe("ESLint", () => { } }); - it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { + it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", async () => { assert.throws(() => { // eslint-disable-next-line no-new new ESLint({ ignorePath: fixtureDir, ignore: true }); @@ -104,8 +111,8 @@ describe("ESLint", () => { }); describe("lintText()", () => { - it("should report the total and per file errors when using local cwd .eslintrc", async() => { - const eslint = new ESLint(); + it("should report the total and per file errors when using local cwd .eslintrc", async () => { + eslint = new ESLint(); const results = await eslint.lintText("var foo = 'bar';"); assert.strictEqual(results.length, 1); @@ -120,8 +127,8 @@ describe("ESLint", () => { assert.strictEqual(results[0].usedDeprecatedRules.length, 0); }); - it("should report the total and per file warnings when using local cwd .eslintrc", async() => { - const eslint = new ESLint({ + it("should report the total and per file warnings when using local cwd .eslintrc", async () => { + eslint = new ESLint({ rules: { quotes: 1, "no-var": 1, @@ -144,8 +151,8 @@ describe("ESLint", () => { assert.strictEqual(results[0].usedDeprecatedRules.length, 0); }); - it("should report one message when using specific config file", async() => { - const eslint = new ESLint({ + it("should report one message when using specific config file", async () => { + eslint = new ESLint({ configFile: "fixtures/configurations/quotes-error.json", useEslintrc: false, cwd: getFixturePath("..") @@ -162,8 +169,8 @@ describe("ESLint", () => { assert.strictEqual(results[0].usedDeprecatedRules.length, 0); }); - it("should report the filename when passed in", async() => { - const eslint = new ESLint({ + it("should report the filename when passed in", async () => { + eslint = new ESLint({ ignore: false, cwd: getFixturePath() }); @@ -173,8 +180,8 @@ describe("ESLint", () => { assert.strictEqual(results[0].filePath, getFixturePath("test.js")); }); - it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async() => { - const eslint = new ESLint({ + it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { + eslint = new ESLint({ ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath("..") }); @@ -192,8 +199,8 @@ describe("ESLint", () => { assert.strictEqual(results[0].usedDeprecatedRules.length, 0); }); - it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async() => { - const eslint = new ESLint({ + it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { + eslint = new ESLint({ ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath("..") }); @@ -209,8 +216,8 @@ describe("ESLint", () => { assert.strictEqual(results.length, 0); }); - it("should suppress excluded file warnings by default", async() => { - const eslint = new ESLint({ + it("should suppress excluded file warnings by default", async () => { + eslint = new ESLint({ ignorePath: getFixturePath(".eslintignore"), cwd: getFixturePath("..") }); @@ -221,8 +228,8 @@ describe("ESLint", () => { assert.strictEqual(results.length, 0); }); - it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async() => { - const eslint = new ESLint({ + it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { + eslint = new ESLint({ ignorePath: "fixtures/.eslintignore", cwd: getFixturePath(".."), ignore: false, @@ -241,8 +248,8 @@ describe("ESLint", () => { assert.isUndefined(results[0].messages[0].output); }); - it("should return a message and fixed text when in fix mode", async() => { - const eslint = new ESLint({ + it("should return a message and fixed text when in fix mode", async () => { + eslint = new ESLint({ useEslintrc: false, fix: true, rules: { @@ -268,8 +275,170 @@ describe("ESLint", () => { ]); }); - it("should return a message and omit fixed text when in fix mode and fixes aren't done", async() => { - const eslint = new ESLint({ + it("correctly autofixes semicolon-conflicting-fixes", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true + }); + const inputPath = getFixturePath("autofix/semicolon-conflicting-fixes.js"); + const outputPath = getFixturePath("autofix/semicolon-conflicting-fixes.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("correctly autofixes return-conflicting-fixes", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true + }); + const inputPath = getFixturePath("autofix/return-conflicting-fixes.js"); + const outputPath = getFixturePath("autofix/return-conflicting-fixes.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + describe("Fix Types", () => { + it("should throw an error when an invalid fix type is specified", async () => { + assert.throws(() => { + eslint = new ESLint({ + 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", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: false, + fixTypes: ["layout"] + }); + + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const results = await eslint.lintFiles([inputPath]); + + assert.isUndefined(results[0].output); + }); + + it("should not fix non-style rules when fixTypes has only 'layout'", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"] + }); + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const outputPath = getFixturePath("fix-types/fix-only-semi.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["suggestion"] + }); + const inputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.js"); + const outputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["suggestion", "layout"] + }); + const inputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.js"); + const outputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule doesn't have a 'meta' property", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + rulePaths: [getFixturePath("rules", "fix-types-test")] + }); + + const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); + const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule is loaded after initialization with executeOnFiles()", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"] + }); + const privateMembers = getESLintPrivateMembers(eslint); + + privateMembers.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 results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule is loaded after initialization with executeOnText()", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"] + }); + const privateMembers = getESLintPrivateMembers(eslint); + + privateMembers.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 results = await eslint.lintText(fs.readFileSync(inputPath, { encoding: "utf8" }), inputPath); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + }); + + it("should return a message and omit fixed text when in fix mode and fixes aren't done", async () => { + eslint = new ESLint({ useEslintrc: false, fix: true, rules: { @@ -307,8 +476,8 @@ describe("ESLint", () => { ]); }); - it("should not delete code if there is a syntax error after trying to autofix.", async() => { - const eslint = new ESLint({ + it("should not delete code if there is a syntax error after trying to autofix.", async () => { + eslint = new ESLint({ useEslintrc: false, fix: true, plugins: createMockedPluginsOption(), @@ -344,8 +513,8 @@ describe("ESLint", () => { ]); }); - it("should not crash even if there are any syntax error since the first time.", async() => { - const eslint = new ESLint({ + it("should not crash even if there are any syntax error since the first time.", async () => { + eslint = new ESLint({ useEslintrc: false, fix: true, rules: { @@ -380,8 +549,8 @@ describe("ESLint", () => { ]); }); - it("should return source code of file in `source` property when errors are present", async() => { - const eslint = new ESLint({ + it("should return source code of file in `source` property when errors are present", async () => { + eslint = new ESLint({ useEslintrc: false, rules: { semi: 2 } }); @@ -390,8 +559,8 @@ describe("ESLint", () => { assert.strictEqual(results[0].source, "var foo = 'bar'"); }); - it("should return source code of file in `source` property when warnings are present", async() => { - const eslint = new ESLint({ + it("should return source code of file in `source` property when warnings are present", async () => { + eslint = new ESLint({ useEslintrc: false, rules: { semi: 1 } }); @@ -401,8 +570,8 @@ describe("ESLint", () => { }); - it("should not return a `source` property when no errors or warnings are present", async() => { - const eslint = new ESLint({ + it("should not return a `source` property when no errors or warnings are present", async () => { + eslint = new ESLint({ useEslintrc: false, rules: { semi: 2 } }); @@ -412,8 +581,8 @@ describe("ESLint", () => { assert.isUndefined(results[0].source); }); - it("should not return a `source` property when fixes are applied", async() => { - const eslint = new ESLint({ + it("should not return a `source` property when fixes are applied", async () => { + eslint = new ESLint({ useEslintrc: false, fix: true, rules: { @@ -427,8 +596,8 @@ describe("ESLint", () => { assert.strictEqual(results[0].output, "var msg = 'hi' + foo;\n"); }); - it("should return a `source` property when a parsing error has occurred", async() => { - const eslint = new ESLint({ + it("should return a `source` property when a parsing error has occurred", async () => { + eslint = new ESLint({ useEslintrc: false, rules: { semi: 2 } }); @@ -458,8 +627,8 @@ describe("ESLint", () => { }); // https://github.com/eslint/eslint/issues/5547 - it("should respect default ignore rules, even with --no-ignore", async() => { - const eslint = new ESLint({ + it("should respect default ignore rules, even with --no-ignore", async () => { + eslint = new ESLint({ cwd: getFixturePath(), ignore: false }); @@ -491,8 +660,8 @@ describe("ESLint", () => { }); /* eslint-enable no-underscore-dangle */ - it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", async() => { - const eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/basic") }); + it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { + eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/basic") }); const [result] = await eslint.lintText("var x = 0", { filePath: "index.js" }); assert.strictEqual(result.filePath, getFixturePath("plugin-shorthand/basic/index.js")); @@ -500,8 +669,8 @@ describe("ESLint", () => { assert.strictEqual(result.messages[0].message, "OK"); }); - it("should resolve 'extends:[\"plugin:@scope/recommended\"]' to 'node_modules/@scope/eslint-plugin'.", async() => { - const eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/extends") }); + it("should resolve 'extends:[\"plugin:@scope/recommended\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { + eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/extends") }); const [result] = await eslint.lintText("var x = 0", { filePath: "index.js" }); assert.strictEqual(result.filePath, getFixturePath("plugin-shorthand/extends/index.js")); @@ -510,8 +679,8 @@ describe("ESLint", () => { }); }); - it("should warn when deprecated rules are found in a config", async() => { - const eslint = new ESLint({ + it("should warn when deprecated rules are found in a config", async () => { + eslint = new ESLint({ cwd: originalDir, useEslintrc: false, configFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml" @@ -523,218 +692,2946 @@ describe("ESLint", () => { [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] ); }); + }); + + describe("lintFiles()", () => { + it("should use correct parser when custom parser is specified", async () => { + eslint = new ESLint({ + cwd: originalDir, + ignore: false + }); + + const filePath = path.resolve(__dirname, "../../fixtures/configurations/parser/custom.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].message, "Parsing error: Boom!"); + + }); + + it("should report zero messages when given a config file and a valid file", async () => { + eslint = new ESLint({ + cwd: originalDir, + configFile: ".eslintrc.js" + }); + + const results = await eslint.lintFiles(["lib/**/cli*.js"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should handle multiple patterns with overlapping files", async () => { + eslint = new ESLint({ + cwd: originalDir, + configFile: ".eslintrc.js" + }); + + const results = await eslint.lintFiles(["lib/**/cli*.js", "lib/cli.?s", "lib/{cli,cli-engine/cli-engine}.js"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and espree as parser", async () => { + eslint = new ESLint({ + parser: "espree", + envs: ["es6"], + useEslintrc: false + }); + + const results = await eslint.lintFiles(["lib/cli.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { + + eslint = new ESLint({ + parser: "esprima", + useEslintrc: false + }); + + const results = await eslint.lintFiles(["lib/cli.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should throw an error when given a config file and a valid file and invalid parser", async () => { + + eslint = new ESLint({ + parser: "test11", + useEslintrc: false + }); + + assert.throws(() => eslint.lintFiles(["lib/cli.js"]), "Cannot find module 'test11'"); + }); + + it("should report zero messages when given a directory with a .js2 file", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + extensions: [".js2"] + }); + + const results = await eslint.lintFiles([getFixturePath("files/foo.js2")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should fall back to defaults when extensions is set to an empty array", async () => { + + eslint = new ESLint({ + cwd: getFixturePath("configurations"), + configFile: getFixturePath("configurations", "quotes-error.json"), + extensions: [] + }); + const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should report zero messages when given a directory with a .js and a .js2 file", async () => { + + eslint = new ESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath("..") + }); + + const results = await eslint.lintFiles(["fixtures/files/"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { + + eslint = new ESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles(["fixtures/files/*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should resolve globs when 'globInputPaths' option is true", async () => { + eslint = new ESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath("..") + }); + + const results = await eslint.lintFiles(["fixtures/files/*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should not resolve globs when 'globInputPaths' option is false", async () => { + eslint = new ESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath(".."), + globInputPaths: false + }); + + assert.throws(() => { + eslint.lintFiles(["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", async () => { + + eslint = new ESLint({ + cwd: getFixturePath("cli-engine") + }); + + const results = await eslint.lintFiles(["node_modules/foo.js"]); + const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + it("should report on globs with explicit inclusion of dotfiles, even though ignored by default", async () => { + + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + rules: { + quotes: [2, "single"] + } + }); + + const results = await eslint.lintFiles(["hidden/.hiddenfolder/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should not check default ignored files without --no-ignore flag", async () => { + + eslint = new ESLint({ + cwd: getFixturePath("cli-engine") + }); + + assert.throws(() => { + eslint.lintFiles(["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", async () => { + + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + ignore: false + }); + + assert.throws(() => { + eslint.lintFiles(["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", async () => { + + eslint = new ESLint({ + cwd: getFixturePath(".."), + useEslintrc: false, + rules: { + quotes: [2, "single"] + } + }); + + const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); + const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + it("should check .hidden files if they are passed explicitly with --no-ignore flag", async () => { + + eslint = new ESLint({ + cwd: getFixturePath(".."), + ignore: false, + useEslintrc: false, + rules: { + quotes: [2, "single"] + } + }); + + const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + it("should check .hidden files if they are unignored with an --ignore-pattern", async () => { + + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + ignore: true, + useEslintrc: false, + ignorePattern: "!.hidden*", + rules: { + quotes: [2, "single"] + } + }); + + const results = await eslint.lintFiles(["hidden/"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { + + eslint = new ESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should return one error message when given a config with rules with options and severity level set to error", async () => { + + eslint = new ESLint({ + cwd: getFixturePath("configurations"), + configFile: getFixturePath("configurations", "quotes-error.json") + }); + const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should return 3 messages when given a config file and a directory of 3 valid files", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "semi-error.json") + }); + + const results = await eslint.lintFiles([getFixturePath("formatters")]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[1].errorCount, 0); + assert.strictEqual(results[1].warningCount, 0); + assert.strictEqual(results[1].fixableErrorCount, 0); + assert.strictEqual(results[1].fixableWarningCount, 0); + assert.strictEqual(results[2].errorCount, 0); + assert.strictEqual(results[2].warningCount, 0); + assert.strictEqual(results[2].fixableErrorCount, 0); + assert.strictEqual(results[2].fixableWarningCount, 0); + }); - /* - * describe("Fix Types", () => { - * it("should throw an error when an invalid fix type is specified", () => { - * assert.throws(() => { - * const eslint = new ESLint({ - * 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", () => { - * const eslint = new ESLint({ - * 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'", () => { - * const eslint = new ESLint({ - * cwd: path.join(fixtureDir, ".."), - * useEslintrc: false, - * fix: true, - * fixTypes: ["layout"] - * }); - * const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - * const outputPath = getFixturePath("fix-types/fix-only-semi.expected.js"); - * const 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'", () => { - * const eslint = new ESLint({ - * cwd: path.join(fixtureDir, ".."), - * useEslintrc: false, - * fix: true, - * fixTypes: ["suggestion"] - * }); - * const inputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.js"); - * const outputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.expected.js"); - * const 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'", () => { - * const eslint = new ESLint({ - * cwd: path.join(fixtureDir, ".."), - * useEslintrc: false, - * fix: true, - * fixTypes: ["suggestion", "layout"] - * }); - * const inputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.js"); - * const outputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js"); - * const 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", () => { - * const eslint = new ESLint({ - * cwd: path.join(fixtureDir, ".."), - * useEslintrc: false, - * fix: true, - * fixTypes: ["layout"], - * rulePaths: [getFixturePath("rules", "fix-types-test")] - * }); - */ - - /* - * const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); - * const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); - * const 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()", () => { - * const eslint = new ESLint({ - * cwd: path.join(fixtureDir, ".."), - * useEslintrc: false, - * fix: true, - * fixTypes: ["layout"] - * }); - * const internalSlots = getESLintInternalSlots(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()", () => { - * const eslint = new ESLint({ - * cwd: path.join(fixtureDir, ".."), - * useEslintrc: false, - * fix: true, - * fixTypes: ["layout"] - * }); - * const internalSlots = getESLintInternalSlots(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 results = await eslint.lintText(fs.readFileSync(inputPath, { encoding: "utf8" }), inputPath); - * const expectedOutput = fs.readFileSync(outputPath, "utf8"); - */ - - /* - * assert.strictEqual(report.results[0].output, expectedOutput); - * }); - */ - - // }); - - /* - * it("correctly autofixes semicolon-conflicting-fixes", () => { - * const eslint = new ESLint({ - * cwd: path.join(fixtureDir, ".."), - * useEslintrc: false, - * fix: true - * }); - * const inputPath = getFixturePath("autofix/semicolon-conflicting-fixes.js"); - * const outputPath = getFixturePath("autofix/semicolon-conflicting-fixes.expected.js"); - * const report = engine.executeOnFiles([inputPath]); - * const expectedOutput = fs.readFileSync(outputPath, "utf8"); - */ - - /* - * assert.strictEqual(report.results[0].output, expectedOutput); - * }); - */ - - /* - * it("correctly autofixes return-conflicting-fixes", () => { - * const eslint = new ESLint({ - * cwd: path.join(fixtureDir, ".."), - * useEslintrc: false, - * fix: true - * }); - * const inputPath = getFixturePath("autofix/return-conflicting-fixes.js"); - * const outputPath = getFixturePath("autofix/return-conflicting-fixes.expected.js"); - * const report = engine.executeOnFiles([inputPath]); - * const expectedOutput = fs.readFileSync(outputPath, "utf8"); - */ - - /* - * assert.strictEqual(report.results[0].output, expectedOutput); - * }); - */ + it("should return the total number of errors when given multiple files", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "single-quotes-error.json") + }); + + const results = await eslint.lintFiles([getFixturePath("formatters")]); + + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[1].errorCount, 3); + assert.strictEqual(results[1].warningCount, 0); + assert.strictEqual(results[1].fixableErrorCount, 3); + assert.strictEqual(results[1].fixableWarningCount, 0); + assert.strictEqual(results[2].errorCount, 3); + assert.strictEqual(results[2].warningCount, 0); + assert.strictEqual(results[2].fixableErrorCount, 3); + assert.strictEqual(results[2].fixableWarningCount, 0); + }); + + it("should process when file is given by not specifying extensions", async () => { + + eslint = new ESLint({ + ignore: false, + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles(["fixtures/files/foo.js2"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given a config with environment set to browser", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "env-browser.json") + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given an option to set environment to browser", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + envs: ["browser"], + rules: { + "no-alert": 0, + "no-undef": 2 + } + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given a config with environment set to Node.js", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "env-node.json") + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-node.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should not return results from previous call when calling more than once", async () => { + eslint = new ESLint({ + 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 results = await eslint.lintFiles([failFilePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, failFilePath); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].severity, 2); + + results = await eslint.lintFiles([passFilePath]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, passFilePath); + assert.strictEqual(results[0].messages.length, 0); + + }); + + it("should throw an error when given a directory with all eslint excluded files in the directory", async () => { + eslint = new ESLint({ + ignorePath: getFixturePath(".eslintignore") + }); + + assert.throws(() => { + eslint.lintFiles([getFixturePath("./cli-engine/")]); + }, `All files matched by '${getFixturePath("./cli-engine/")}' are ignored.`); + }); + + it("should throw an error when all given files are ignored", async () => { + eslint = new ESLint({ + ignorePath: getFixturePath(".eslintignore") + }); + + assert.throws(() => { + eslint.lintFiles(["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", async () => { + eslint = new ESLint({ + ignorePath: getFixturePath(".eslintignore") + }); + + assert.throws(() => { + eslint.lintFiles(["./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", async () => { + eslint = new ESLint({ + ignorePath: getFixturePath("cli-engine", "nested_node_modules", ".eslintignore"), + useEslintrc: false, + rules: { + quotes: [2, "double"] + }, + cwd: getFixturePath("cli-engine", "nested_node_modules") + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + // https://github.com/eslint/eslint/issues/3812 + it("should ignore all files and throw an error when tests/fixtures/ is in ignore file", async () => { + eslint = new ESLint({ + ignorePath: getFixturePath("cli-engine/.eslintignore2"), + useEslintrc: false, + rules: { + quotes: [2, "double"] + } + }); + + assert.throws(() => { + eslint.lintFiles(["./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", async () => { + eslint = new ESLint({ + ignorePattern: "tests/fixtures/single-quoted.js" + }); + + assert.throws(() => { + eslint.lintFiles(["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", async () => { + eslint = new ESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath() + }); + + const filePath = getFixturePath("passing.js"); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should return two messages when given a file in excluded files list while ignore is off", async () => { + + eslint = new ESLint({ + ignorePath: getFixturePath(".eslintignore"), + ignore: false, + rules: { + "no-undef": 2 + } + }); + + const filePath = fs.realpathSync(getFixturePath("undef.js")); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[1].severity, 2); + }); + + it("should return zero messages when executing a file with a shebang", async () => { + + eslint = new ESLint({ + ignore: false + }); + + const results = await eslint.lintFiles([getFixturePath("shebang.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should give a warning when loading a custom rule that doesn't exist", async () => { + + eslint = new ESLint({ + ignore: false, + rulesPaths: [getFixturePath("rules", "dir1")], + configFile: getFixturePath("rules", "missing-rule.json") + }); + const results = await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "missing-rule"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].message, "Definition for rule 'missing-rule' was not found."); + + + }); + + it("should throw an error when loading a bad custom rule", async () => { + + eslint = new ESLint({ + ignore: false, + rulePaths: [getFixturePath("rules", "wrong")], + configFile: getFixturePath("rules", "eslint.json") + }); + + + assert.throws(() => { + eslint.lintFiles([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", async () => { + + eslint = new ESLint({ + ignore: false, + useEslintrc: false, + rulePaths: [getFixturePath("rules/")], + configFile: getFixturePath("rules", "eslint.json") + }); + + const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + it("should load custom rule from the provided cwd", async () => { + const cwd = path.resolve(getFixturePath("rules")); + + eslint = new ESLint({ + ignore: false, + cwd, + rulePaths: ["./"], + configFile: "eslint.json" + }); + + const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + it("should return messages when multiple custom rules match a file", async () => { + + eslint = new ESLint({ + 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 results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-literals"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-strings"); + assert.strictEqual(results[0].messages[1].severity, 2); + }); + + it("should return zero messages when executing without useEslintrc flag", async () => { + + eslint = new ESLint({ + ignore: false, + useEslintrc: false + }); + + const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when executing without useEslintrc flag in Node.js environment", async () => { + + eslint = new ESLint({ + ignore: false, + useEslintrc: false, + envs: ["node"] + }); + + const filePath = fs.realpathSync(getFixturePath("process-exit.js")); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when executing with base-config flag set to false", async () => { + + eslint = new ESLint({ + ignore: false, + baseConfig: false, + useEslintrc: false + }); + + const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages and ignore .eslintrc files when executing with no-eslintrc flag", async () => { + + eslint = new ESLint({ + ignore: false, + useEslintrc: false, + envs: ["node"] + }); + + const filePath = fs.realpathSync(getFixturePath("eslintrc", "quotes.js")); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages and ignore package.json files when executing with no-eslintrc flag", async () => { + + eslint = new ESLint({ + ignore: false, + useEslintrc: false, + envs: ["node"] + }); + + const filePath = fs.realpathSync(getFixturePath("packagejson", "quotes.js")); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should warn when deprecated rules are configured", async () => { + eslint = new ESLint({ + cwd: originalDir, + configFile: ".eslintrc.js", + rules: { + "indent-legacy": 1, + "require-jsdoc": 1, + "valid-jsdoc": 1 + } + }); + + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.sameDeepMembers( + results[0].usedDeprecatedRules, + [ + { ruleId: "indent-legacy", replacedBy: ["indent"] }, + { ruleId: "require-jsdoc", replacedBy: [] }, + { ruleId: "valid-jsdoc", replacedBy: [] } + ] + ); + }); + + it("should not warn when deprecated rules are not configured", async () => { + eslint = new ESLint({ + cwd: originalDir, + configFile: ".eslintrc.js", + rules: { indent: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } + }); + + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, []); + }); + + it("should warn when deprecated rules are found in a config", async () => { + eslint = new ESLint({ + cwd: originalDir, + configFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", + useEslintrc: false + }); + + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual( + results[0].usedDeprecatedRules, + [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] + ); + }); + + describe("Fix Mode", () => { + + it("should return fixed text on multiple files when in fix mode", async () => { + + /** + * Converts CRLF to LF in output. + * This is a workaround for git's autocrlf option on Windows. + * @param {Object} result A result object to convert. + * @returns {void} + */ + function convertCRLF(result) { + if (result && result.output) { + result.output = result.output.replace(/\r\n/gu, "\n"); + } + } + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2 + } + }); + + const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); + + results.forEach(convertCRLF); + assert.deepStrictEqual(results, [ + { + filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), + messages: [], + 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" + } + ]); + }); + + it("should run autofix even if files are cached without autofix results", async () => { + const baseOptions = { + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2 + } + }; + + eslint = new ESLint(Object.assign({}, baseOptions, { cache: true, fix: false })); + + // Do initial lint run and populate the cache file + eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); + + eslint = new ESLint(Object.assign({}, baseOptions, { cache: true, fix: true })); + + const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); + + assert.ok(results.some(result => result.output)); + }); + + }); + + // These tests have to do with https://github.com/eslint/eslint/issues/963 + + describe("configuration hierarchy", () => { + + // Default configuration - blank + it("should return zero messages when executing with no .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // No default configuration rules - conf/environments.js (/*eslint-env node*/) + it("should return zero messages when executing with no .eslintrc in the Node.js environment", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + reset: true, + useEslintrc: false + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes-node.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return one message when executing with .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Project configuration - second level .eslintrc + it("should return one message when executing with local .eslintrc that overrides parent .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - third level .eslintrc + it("should return one message when executing with local .eslintrc that overrides parent and grandparent .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/subsubbroken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - first level package.json + it("should return one message when executing with package.json", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - second level package.json + it("should return zero messages when executing with local package.json that overrides parent package.json", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - third level package.json + it("should return one message when executing with local package.json that overrides parent and grandparent package.json", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Project configuration - .eslintrc overrides package.json in same directory + it("should return one message when executing with .eslintrc that overrides a package.json in the same directory", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return two messages when executing with config file that adds to local .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); + assert.strictEqual(results[0].messages[1].severity, 1); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return no messages when executing with config file that overrides local .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Command line configuration - --config with second level .eslintrc + it("should return two messages when executing with config file that adds to local and parent .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); + assert.strictEqual(results[0].messages[1].severity, 1); + }); + + // Command line configuration - --config with second level .eslintrc + it("should return one message when executing with config file that overrides local and parent .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("config-hierarchy/broken/override-conf.yaml") + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return no messages when executing with config file that overrides local .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Command line configuration - --rule with --config and first level .eslintrc + it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("config-hierarchy/broken/override-conf.yaml"), + rules: { + quotes: [1, "double"] + } + }); + + const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Command line configuration - --rule with --config and first level .eslintrc + it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("/config-hierarchy/broken/override-conf.yaml"), + rules: { + quotes: [1, "double"] + } + }); + + const results = await eslint.lintFiles([getFixturePath("config-hierarchy/broken/console-wrong-quotes.js")]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + }); + + describe("plugins", () => { + it("should return two messages when executing with config file that specifies a plugin", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "plugins-with-prefix.json"), + useEslintrc: false, + plugins: createMockedPluginsOption() + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test/test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + }); + + it("should return two messages when executing with config file that specifies a plugin with namespace", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "plugins-with-prefix-and-namespace.json"), + useEslintrc: false, + plugins: createMockedPluginsOption() + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "@eslint/example/example-rule"); + }); + + it("should return two messages when executing with config file that specifies a plugin without prefix", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "plugins-without-prefix.json"), + useEslintrc: false, + plugins: createMockedPluginsOption() + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + }); + + it("should return two messages when executing with config file that specifies a plugin without prefix and with namespace", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "plugins-without-prefix-with-namespace.json"), + useEslintrc: false, + plugins: createMockedPluginsOption() + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "@eslint/example/example-rule"); + }); + + it("should return two messages when executing with cli option that specifies a plugin", async () => { + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + plugins: ["example", ...createMockedPluginsOption()], + rules: { "example/example-rule": 1 } + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); + }); + + it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", async () => { + eslint = new ESLint({ + resolvePluginsRelativeTo: getFixturePath("plugins"), + baseConfig: { + plugins: ["with-rules"], + rules: { "with-rules/rule1": "error" } + }, + useEslintrc: false + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "with-rules/rule1"); + assert.strictEqual(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", async () => { + assert.isFalse(shell.test("-d", path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist"); + + eslint = new ESLint({ + 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"); + + eslint.lintFiles([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", async () => { + assert.isFalse(shell.test("-d", path.resolve("./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory")), "the cache for eslint does not exist"); + + eslint = new ESLint({ + 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"); + + eslint.lintFiles([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", async () => { + const cwd = path.resolve(getFixturePath("cli-engine")); + + eslint = new ESLint({ + useEslintrc: false, + cache: true, + cwd, + rules: { + "no-console": 0 + }, + extensions: ["js"], + ignore: false + }); + + const file = getFixturePath("cli-engine", "console.js"); + + eslint.lintFiles([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", async () => { + assert.isFalse(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint does not exist"); + + eslint = new ESLint({ + 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 = await eslint.lintFiles([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(); + + eslint = new ESLint({ + 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 = await eslint.lintFiles([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", async () => { + + assert.isFalse(shell.test("-f", path.resolve(".eslintcache")), "the cache for eslint does not exist"); + + eslint = new ESLint({ + 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 = await eslint.lintFiles([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(); + + eslint = new ESLint({ + 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 = await eslint.lintFiles([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", async () => { + + const cacheFile = getFixturePath(".eslintcache"); + const eslintOptions = { + 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"); + + eslint = new ESLint(eslintOptions); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + eslint.lintFiles([file]); + + assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint was created"); + + eslintOptions.cache = false; + eslint = new ESLint(eslintOptions); + + eslint.lintFiles([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", async () => { + + const cacheFile = getFixturePath(".eslintcache"); + + assert.isFalse(shell.test("-f", cacheFile), "the cache for eslint does not exist"); + + eslint = new ESLint({ + 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 = await eslint.lintFiles([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 = await eslint.lintFiles([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", async () => { + + const cacheFile = getFixturePath(".eslintcache"); + + doDelete(cacheFile); + + eslint = new ESLint({ + 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")); + + eslint.lintFiles([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 + */ + eslint.lintFiles([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", async () => { + + const cacheFile = getFixturePath(".eslintcache"); + + doDelete(cacheFile); + + eslint = new ESLint({ + 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")); + + eslint.lintFiles([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 + */ + eslint.lintFiles([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", async () => { + const cacheFile = getFixturePath(".eslintcache"); + + eslint = new ESLint({ + 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"); + + eslint.lintText("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", async () => { + const cacheFile = getFixturePath(".eslintcache"); + + eslint = new ESLint({ + 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"); + + eslint.lintText("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", async () => { + const cacheFile = getFixturePath(".eslintcache"); + + eslint = new ESLint({ + 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"); + + eslint.lintFiles([file]); + + assert.isTrue(shell.test("-f", cacheFile), "the cache for eslint still exists"); + }); + + it("should delete cache when executing on files without --cache flag", async () => { + const cacheFile = getFixturePath(".eslintcache"); + + eslint = new ESLint({ + 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"); + + eslint.lintFiles([file]); + + assert.isFalse(shell.test("-f", cacheFile), "the cache for eslint has been deleted"); + }); + + describe("cacheFile", () => { + it("should use the specified cache file", async () => { + const customCacheFile = path.resolve(".cache/custom-cache"); + + assert.isFalse(shell.test("-f", customCacheFile), "the cache for eslint does not exist"); + + eslint = new ESLint({ + 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 = await eslint.lintFiles([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 = await eslint.lintFiles([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", async () => { + eslint = new ESLint({ + configFile: getFixturePath("configurations", "processors.json"), + useEslintrc: false, + extensions: ["js", "txt"], + plugins: createMockedPluginsOption(), + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + }); + + xit("should return two messages when executing with config file that specifies preloaded processor", async () => { + eslint = new ESLint({ + 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 results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + }); + it("should run processors when calling executeOnFiles with config file that specifies a processor", async () => { + eslint = new ESLint({ + configFile: getFixturePath("configurations", "processors.json"), + useEslintrc: false, + extensions: ["js", "txt"], + plugins: createMockedPluginsOption(), + cwd: path.join(fixtureDir, "..") + }); + + const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); + + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + xit("should run processors when calling executeOnFiles with config file that specifies preloaded processor", async () => { + eslint = new ESLint({ + 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 results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); + + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + + it("should run processors when calling executeOnText with config file that specifies a processor", async () => { + eslint = new ESLint({ + configFile: getFixturePath("configurations", "processors.json"), + useEslintrc: false, + extensions: ["js", "txt"], + plugins: createMockedPluginsOption(), + ignore: false + }); + + const results = await eslint.lintText("function a() {console.log(\"Test\");}", "tests/fixtures/processors/test/test-processor.txt"); + + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + + xit("should run processors when calling executeOnText with config file that specifies preloaded processor", async () => { + eslint = new ESLint({ + 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 results = await eslint.lintText("function a() {console.log(\"Test\");}", "tests/fixtures/processors/test/test-processor.txt"); + + assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); + assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); + }); + + xdescribe("autofixing with processors", async () => { + + /* + * const HTML_PROCESSOR = Object.freeze({ + * preprocess(text) { + * return [text.replace(/^", "foo.html"); + + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].output, ""); + }); + + xit("should not run in autofix mode when using a processor that does not support autofixing", async () => { + eslint = new ESLint({ + useEslintrc: false, + plugins: ["test-processor"], + rules: { + semi: 2 + }, + extensions: ["js", "txt"], + ignore: false, + fix: true + }); + + // engine.addPlugin("test-processor", { processors: { ".html": HTML_PROCESSOR } }); + + const results = await eslint.lintText("", "foo.html"); + + assert.strictEqual(results[0].messages.length, 1); + assert.isFalse(Object.prototype.hasOwnProperty.call(results[0], "output")); + }); + + xit("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { + eslint = new ESLint({ + 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 results = await eslint.lintText("", "foo.html"); + + assert.strictEqual(results[0].messages.length, 1); + assert.isFalse(Object.prototype.hasOwnProperty.call(results[0], "output")); + }); + }); + }); + + describe("Patterns which match no file should throw errors.", () => { + beforeEach(() => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine"), + useEslintrc: false + }); + }); + + it("one file", async () => { + assert.throws(() => { + eslint.lintFiles(["non-exist.js"]); + }, "No files matching 'non-exist.js' were found."); + }); + + it("should throw if the directory exists and is empty", async () => { + assert.throws(() => { + eslint.lintFiles(["empty"]); + }, "No files matching 'empty' were found."); + }); + + it("one glob pattern", async () => { + assert.throws(() => { + eslint.lintFiles(["non-exist/**/*.js"]); + }, "No files matching 'non-exist/**/*.js' were found."); + }); + + it("two files", async () => { + assert.throws(() => { + eslint.lintFiles(["aaa.js", "bbb.js"]); + }, "No files matching 'aaa.js' were found."); + }); + + it("a mix of an existing file and a non-existing file", async () => { + assert.throws(() => { + eslint.lintFiles(["console.js", "non-exist.js"]); + }, "No files matching 'non-exist.js' were found."); + }); + }); + + describe("overrides", () => { + it("should recognize dotfiles", async () => { + eslint = new ESLint({ + cwd: getFixturePath("cli-engine/overrides-with-dot"), + ignore: false + }); + const results = await eslint.lintFiles([".test-target.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(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", async () => { + beforeEach(() => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + 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();" + } + })); + eslint = new ESLint(); + }); + + it("should not report 'no-console' error.", async () => { + const results = await eslint.lintFiles("a.js"); + + assert.strictEqual(results.length, 1); + assert.deepStrictEqual(results[0].messages, []); + }); + }); + + describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", async () => { + beforeEach(() => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + 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();" + } + })); + eslint = new ESLint(); + }); + + it("should throw fatal error.", async () => { + assert.throws(() => { + eslint.lintFiles("a.js"); + }, /invalid-option/u); + }); + }); + + describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", async () => { + beforeEach(() => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + 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;" + } + })); + eslint = new ESLint({ fix: true, fixTypes: ["problem"] }); + }); + + it("should not crash.", async () => { + const results = await eslint.lintFiles("a.js"); + + assert.strictEqual(results.length, 1); + assert.deepStrictEqual(results[0].messages, []); + assert.deepStrictEqual(results[0].output, "fixed;"); + }); + }); + + describe("multiple processors", () => { + const root = path.join(os.tmpdir(), "eslint/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.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + ...commonFiles, + ".eslintrc.json": JSON.stringify({ + plugins: ["markdown", "html"], + rules: { semi: "error" } + }) + } + })); + eslint = new ESLint({ cwd: root }); + + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + }); + + it("should fix only JavaScript blocks if '--ext' was not given.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + ...commonFiles, + ".eslintrc.json": JSON.stringify({ + plugins: ["markdown", "html"], + rules: { semi: "error" } + }) + } + })); + eslint = new ESLint({ cwd: root, fix: true }); + + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].output, unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `); + }); + + it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + ...commonFiles, + ".eslintrc.json": JSON.stringify({ + plugins: ["markdown", "html"], + rules: { semi: "error" } + }) + } + })); + eslint = new ESLint({ cwd: root, extensions: ["js", "html"] }); + + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual(results[0].messages[1].line, 7); + }); + + it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + ...commonFiles, + ".eslintrc.json": JSON.stringify({ + plugins: ["markdown", "html"], + rules: { semi: "error" } + }) + } + })); + eslint = new ESLint({ cwd: root, extensions: ["js", "html"], fix: true }); + + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].output, unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `); + }); + + it("should use overriden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + ...commonFiles, + ".eslintrc.json": JSON.stringify({ + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/non-fixable" // supportsAutofix: false + } + ] + }) + } + })); + eslint = new ESLint({ cwd: root, extensions: ["js", "html"], fix: true }); + + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS Block in HTML Block + assert.strictEqual(results[0].messages[0].line, 7); + assert.strictEqual(results[0].messages[0].fix, void 0); + assert.strictEqual(results[0].output, unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `); + }); + + it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + 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" + } + } + ] + }) + } + })); + eslint = new ESLint({ cwd: root, extensions: ["js", "html"] }); + + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-console"); + assert.strictEqual(results[0].messages[1].line, 7); + }); + + it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + 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" + } + } + ] + }) + } + })); + eslint = new ESLint({ cwd: root, extensions: ["js", "html"] }); + + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 3); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-console"); + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].messages[2].ruleId, "no-console"); + assert.strictEqual(results[0].messages[2].line, 10); + }); + + it("should throw an error if invalid processor was specified.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + ...commonFiles, + ".eslintrc.json": JSON.stringify({ + plugins: ["markdown", "html"], + processor: "markdown/unknown" + }) + } + })); + eslint = new ESLint({ cwd: root }); + + try { + await eslint.lintFiles(["test.md"]); + } catch (e) { + assert.strictEqual( + /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u.test(e.message) + , true + ); + return; + } + + assert.fail("Expected to throw an error"); + }); + + it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + 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" + } + ] + }) + } + })); + eslint = new ESLint({ cwd: root }); + + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual(results[0].messages[1].line, 7); + }); + }); + + describe("MODULE_NOT_FOUND error handling", () => { + const cwd = getFixturePath("module-not-found"); + + beforeEach(() => { + eslint = new ESLint({ cwd }); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", async () => { + try { + await eslint.lintText("test", { filePath: "extends-js/test.js" }); + } catch (err) { + assert.strictEqual(err.messageTemplate, "extend-config-missing"); + assert.deepStrictEqual(err.messageData, { + configName: "nonexistent-config", + importerName: getFixturePath("module-not-found", "extends-js", ".eslintrc.yml") + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence plugin config.", async () => { + try { + await eslint.lintText("test", { filePath: "extends-plugin/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `extends-plugin${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: cwd + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'plugins' property has a non-existence plugin.", async () => { + try { + await eslint.lintText("test", { filePath: "plugins/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `plugins${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: cwd + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when a JavaScript config threw a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { filePath: "throw-in-config-itself/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a JavaScript config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { filePath: "throw-in-extends-js/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { filePath: "throw-in-extends-plugin/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'plugins' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { filePath: "throw-in-plugins/test.js" }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + }); + + describe("with '--rulesdir' option", async () => { + it("should use the configured rules which are defined by '--rulesdir' option.", async () => { + const rootPath = getFixturePath("cli-engine/with-rulesdir"); + + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + 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')" + } + })); + eslint = new ESLint({ + rulePaths: ["internal-rules"] + }); + const results = await eslint.lintFiles(["test.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].message, "ok"); + }); + }); + + describe("glob pattern '[ab].js'", () => { + const root = getFixturePath("cli-engine/unmatched-glob"); + + it("should match '[ab].js' if existed.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + "[ab].js": "", + ".eslintrc.yml": "root: true" + } + })); + eslint = new ESLint(); + + const results = await eslint.lintFiles(["[ab].js"]); + const filenames = results.map(r => path.basename(r.filePath)); + + assert.deepStrictEqual(filenames, ["[ab].js"]); + }); + + it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + ".eslintrc.yml": "root: true" + } + })); + eslint = new ESLint(); + + const results = await eslint.lintFiles(["[ab].js"]); + const filenames = results.map(r => path.basename(r.filePath)); + + assert.deepStrictEqual(filenames, ["a.js", "b.js"]); + }); + }); + + describe("with 'noInlineConfig' setting", async () => { + const root = getFixturePath("cli-engine/noInlineConfig"); + + it("should warn directive comments if 'noInlineConfig' was given.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "test.js": "/* globals foo */", + ".eslintrc.yml": "noInlineConfig: true" + } + })); + eslint = new ESLint(); + + const [{ messages }] = await eslint.lintFiles(["test.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml)."); + }); + + it("should show the config file what the 'noInlineConfig' came from.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}", + "test.js": "/* globals foo */", + ".eslintrc.yml": "extends: foo" + } + })); + eslint = new ESLint(); + + const [{ messages }] = await eslint.lintFiles(["test.js"]); + + 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", async () => { + const root = getFixturePath("cli-engine/reportUnusedDisableDirectives"); + + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": "reportUnusedDisableDirectives: true" + } + })); + eslint = new ESLint(); + + const [{ messages }] = await eslint.lintFiles(["test.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); + }); + + describe("the runtime option overrides config files.", () => { + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": "reportUnusedDisableDirectives: true" + } + })); + eslint = new ESLint({ reportUnusedDisableDirectives: false }); + + const [{ messages }] = await eslint.lintFiles(["test.js"]); + + assert.strictEqual(messages.length, 0); + }); + + it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": "reportUnusedDisableDirectives: true" + } + })); + eslint = new ESLint({ reportUnusedDisableDirectives: true }); + + const [{ messages }] = await eslint.lintFiles(["test.js"]); + + 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", async () => { + const root = getFixturePath("cli-engine/deeply-overrides-i-extends"); + + it("should not throw.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + 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" + } + })); + eslint = new ESLint(); + + const [{ messages }] = await eslint.lintFiles(["test.js"]); + + 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 \".\".", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "../.eslintrc.json": "BROKEN FILE", + ".eslintrc.json": JSON.stringify({ root: true }), + "index.js": "console.log(\"hello\")" + } + })); + eslint = new ESLint(); + + // Don't throw "failed to load config file" error. + eslint.lintFiles("."); + }); + + it("'executeOnFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + "../.eslintrc.json": JSON.stringify({ ignorePatterns: ["/dont-ignore-entry-dir"] }), + ".eslintrc.json": JSON.stringify({ root: true }), + "index.js": "console.log(\"hello\")" + } + })); + eslint = new ESLint(); + + // Don't throw "file not found" error. + eslint.lintFiles("."); + }); + + it("'executeOnFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => { + ({ ESLint } = defineESLintWithInMemoryFileSystem({ + cwd: () => root, + files: { + ".eslintrc.json": JSON.stringify({ ignorePatterns: ["/subdir"] }), + "subdir/.eslintrc.json": JSON.stringify({ root: true }), + "subdir/index.js": "console.log(\"hello\")" + } + })); + eslint = new ESLint(); + + // Don't throw "file not found" error. + eslint.lintFiles("subdir"); + }); + }); }); }); diff --git a/tests/lib/init/npm-utils.js b/tests/lib/init/npm-utils.js index d0326bd1330..8465796a367 100644 --- a/tests/lib/init/npm-utils.js +++ b/tests/lib/init/npm-utils.js @@ -14,7 +14,7 @@ const sinon = require("sinon"), npmUtils = require("../../../lib/init/npm-utils"), log = require("../../../lib/shared/logging"), - { defineInMemoryFs } = require("../_utils"); + { defineInMemoryFs } = require("../../_utils"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); diff --git a/tests/lib/rules/implicit-arrow-linebreak.js b/tests/lib/rules/implicit-arrow-linebreak.js index 089424e1c47..c65d4750a7d 100644 --- a/tests/lib/rules/implicit-arrow-linebreak.js +++ b/tests/lib/rules/implicit-arrow-linebreak.js @@ -10,7 +10,7 @@ const rule = require("../../../lib/rules/implicit-arrow-linebreak"); const { RuleTester } = require("../../../lib/rule-tester"); -const { unIndent } = require("../_utils"); +const { unIndent } = require("../../_utils"); const EXPECTED_LINEBREAK = { messageId: "expected" }; const UNEXPECTED_LINEBREAK = { messageId: "unexpected" }; diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js index 7496da218d1..56589be5169 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -21,7 +21,7 @@ const path = require("path"); const fixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/indent/indent-invalid-fixture-1.js"), "utf8"); const fixedFixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/indent/indent-valid-fixture-1.js"), "utf8"); const parser = require("../../fixtures/fixture-parser"); -const { unIndent } = require("../_utils"); +const { unIndent } = require("../../_utils"); /** @@ -5590,9 +5590,9 @@ ruleTester.run("indent", rule, { \${a} \${b} template literal - \`(() => { + \`(() => { foo(); - + tagTwo\`multiline template literal @@ -5609,12 +5609,12 @@ ruleTester.run("indent", rule, { tagOne\`multiline template literal - \${a} \${b}\`({ + \${a} \${b}\`({ foo: 1, bar: tagTwo\`multiline template literal\`(() => { - + baz(); }) }); @@ -5651,7 +5651,7 @@ ruleTester.run("indent", rule, { code: unIndent` foo .bar - .baz\` template + .baz\` template literal \`(() => { baz(); }) @@ -11207,9 +11207,9 @@ ruleTester.run("indent", rule, { tagOne\`multiline \${a} \${b} template literal - \`(() => { + \`(() => { foo(); - + tagTwo\`multiline template literal @@ -11223,9 +11223,9 @@ ruleTester.run("indent", rule, { tagOne\`multiline \${a} \${b} template literal - \`(() => { + \`(() => { foo(); - + tagTwo\`multiline template literal @@ -11250,7 +11250,7 @@ ruleTester.run("indent", rule, { bar: tagTwo\`multiline template literal\`(() => { - + baz(); }) }); @@ -11263,7 +11263,7 @@ ruleTester.run("indent", rule, { bar: tagTwo\`multiline template literal\`(() => { - + baz(); }) }); diff --git a/tests/lib/rules/object-shorthand.js b/tests/lib/rules/object-shorthand.js index b9b81329f91..01a72f4e6f0 100644 --- a/tests/lib/rules/object-shorthand.js +++ b/tests/lib/rules/object-shorthand.js @@ -11,7 +11,7 @@ const rule = require("../../../lib/rules/object-shorthand"), { RuleTester } = require("../../../lib/rule-tester"); -const { unIndent } = require("../_utils"); +const { unIndent } = require("../../_utils"); //------------------------------------------------------------------------------ // Tests diff --git a/tests/lib/shared/runtime-info.js b/tests/lib/shared/runtime-info.js index 79c50a414ac..76ca7c7ca28 100644 --- a/tests/lib/shared/runtime-info.js +++ b/tests/lib/shared/runtime-info.js @@ -12,7 +12,7 @@ const assert = require("chai").assert; const sinon = require("sinon"); const spawn = require("cross-spawn"); -const { unIndent } = require("../_utils"); +const { unIndent } = require("../../_utils"); const RuntimeInfo = require("../../../lib/shared/runtime-info"); const log = require("../../../lib/shared/logging"); const packageJson = require("../../../package.json");