mirror of
https://git.proxmox.com/git/pve-eslint
synced 2025-10-19 12:35:33 +00:00
1602 lines
70 KiB
JavaScript
1602 lines
70 KiB
JavaScript
/**
|
|
* @fileoverview Tests for CascadingConfigArrayFactory class.
|
|
* @author Toru Nagashima <https://github.com/mysticatea>
|
|
*/
|
|
"use strict";
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const os = require("os");
|
|
const { assert } = require("chai");
|
|
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");
|
|
|
|
/** @typedef {InstanceType<ReturnType<defineCascadingConfigArrayFactoryWithInMemoryFileSystem>["CascadingConfigArrayFactory"]>} CascadingConfigArrayFactory */
|
|
/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} ConfigArray */
|
|
|
|
const cwdIgnorePatterns = new ConfigArrayFactory()
|
|
.loadDefaultESLintIgnore()[0]
|
|
.ignorePattern
|
|
.patterns;
|
|
|
|
describe("CascadingConfigArrayFactory", () => {
|
|
describe("'getConfigArrayForFile(filePath)' method should retrieve the proper configuration.", () => {
|
|
describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => {
|
|
const root = path.join(os.tmpdir(), "eslint/cli-engine/cascading-config-array-factory");
|
|
const files = {
|
|
/* eslint-disable quote-props */
|
|
"lib": {
|
|
"nested": {
|
|
"one.js": "",
|
|
"two.js": "",
|
|
"parser.js": "",
|
|
".eslintrc.yml": "parser: './parser'"
|
|
},
|
|
"one.js": "",
|
|
"two.js": ""
|
|
},
|
|
"test": {
|
|
"one.js": "",
|
|
"two.js": "",
|
|
".eslintrc.yml": "env: { mocha: true }"
|
|
},
|
|
".eslintignore": "/lib/nested/parser.js",
|
|
".eslintrc.json": JSON.stringify({
|
|
rules: {
|
|
"no-undef": "error",
|
|
"no-unused-vars": "error"
|
|
}
|
|
})
|
|
/* eslint-enable quote-props */
|
|
};
|
|
const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd: () => root, files });
|
|
|
|
/** @type {CascadingConfigArrayFactory} */
|
|
let factory;
|
|
|
|
beforeEach(() => {
|
|
factory = new CascadingConfigArrayFactory();
|
|
});
|
|
|
|
it("should retrieve the config '.eslintrc.json' if 'lib/one.js' was given.", () => {
|
|
const config = factory.getConfigArrayForFile("lib/one.js");
|
|
|
|
assert.strictEqual(config.length, 3);
|
|
assert.strictEqual(config[0].name, "DefaultIgnorePattern");
|
|
assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
|
|
assert.strictEqual(config[2].filePath, path.join(root, ".eslintignore"));
|
|
});
|
|
|
|
it("should retrieve the merged config of '.eslintrc.json' and 'lib/nested/.eslintrc.yml' if 'lib/nested/one.js' was given.", () => {
|
|
const config = factory.getConfigArrayForFile("lib/nested/one.js");
|
|
|
|
assert.strictEqual(config.length, 4);
|
|
assert.strictEqual(config[0].name, "DefaultIgnorePattern");
|
|
assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
|
|
assert.strictEqual(config[2].filePath, path.join(root, "lib/nested/.eslintrc.yml"));
|
|
assert.strictEqual(config[3].filePath, path.join(root, ".eslintignore"));
|
|
});
|
|
|
|
it("should retrieve the config '.eslintrc.json' if 'lib/non-exist.js' was given.", () => {
|
|
const config = factory.getConfigArrayForFile("lib/non-exist.js");
|
|
|
|
assert.strictEqual(config.length, 3);
|
|
assert.strictEqual(config[0].name, "DefaultIgnorePattern");
|
|
assert.strictEqual(config[1].filePath, path.join(root, ".eslintrc.json"));
|
|
assert.strictEqual(config[2].filePath, path.join(root, ".eslintignore"));
|
|
});
|
|
});
|
|
|
|
describe("deprecation warnings", () => {
|
|
let uid = 0;
|
|
let uniqueHomeDirName = "";
|
|
let homeDir = "";
|
|
let cwd = "";
|
|
|
|
/** @type {{code:string, message:string}[]} */
|
|
let warnings = [];
|
|
|
|
/** @type {CascadingConfigArrayFactory} */
|
|
let factory = null;
|
|
|
|
/** @type {ConfigArray} */
|
|
let config = null;
|
|
|
|
/**
|
|
* Store a reported warning object if that code starts with `ESLINT_`.
|
|
* @param {{code:string, message:string}} w The warning object to store.
|
|
* @returns {void}
|
|
*/
|
|
function onWarning(w) {
|
|
if (w.code.startsWith("ESLINT_")) {
|
|
warnings.push({ code: w.code, message: w.message });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delay to wait for 'warning' events.
|
|
* @returns {Promise<void>} The promise that will be fulfilled after wait a timer.
|
|
*/
|
|
function delay() {
|
|
return new Promise(resolve => setTimeout(resolve, 0));
|
|
}
|
|
|
|
beforeEach(() => {
|
|
uniqueHomeDirName = `home_${++uid}`;
|
|
homeDir = path.join(__dirname, `../../../${uniqueHomeDirName}`);
|
|
warnings = [];
|
|
sinon.stub(os, "homedir").returns(homeDir);
|
|
process.on("warning", onWarning);
|
|
});
|
|
afterEach(() => {
|
|
os.homedir.restore();
|
|
process.removeListener("warning", onWarning);
|
|
});
|
|
|
|
describe("when '~/.eslintrc.json' exists and CWD is `~/`", () => {
|
|
beforeEach(() => {
|
|
cwd = homeDir;
|
|
const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
|
|
cwd: () => cwd,
|
|
files: {
|
|
|
|
// ~/.eslintrc.json
|
|
".eslintrc.json": JSON.stringify({ rules: { eqeqeq: "error" } }),
|
|
|
|
// other files
|
|
"exist-with-root/test.js": "",
|
|
"exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
|
|
"exist/test.js": "",
|
|
"exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
|
|
"not-exist/test.js": ""
|
|
}
|
|
});
|
|
|
|
factory = new CascadingConfigArrayFactory({ cwd });
|
|
});
|
|
|
|
// no warning.
|
|
describe("when it lints 'subdir/exist-with-root/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("exist-with-root/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
|
|
it("should not load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ yoda: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
|
|
// no warning.
|
|
describe("when it lints 'subdir/exist/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("exist/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
|
|
it("should load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ eqeqeq: ["error"], yoda: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
|
|
// no warning
|
|
describe("when it lints 'subdir/not-exist/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("not-exist/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
|
|
it("should load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ eqeqeq: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when '~/.eslintrc.json' exists and CWD is `~/subdir`", () => {
|
|
beforeEach(() => {
|
|
cwd = path.join(homeDir, "subdir");
|
|
const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
|
|
cwd: () => cwd,
|
|
files: {
|
|
|
|
// ~/.eslintrc.json
|
|
"../.eslintrc.json": JSON.stringify({ rules: { eqeqeq: "error" } }),
|
|
|
|
// other files
|
|
"exist-with-root/test.js": "",
|
|
"exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
|
|
"exist/test.js": "",
|
|
"exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
|
|
"not-exist/test.js": ""
|
|
}
|
|
});
|
|
|
|
factory = new CascadingConfigArrayFactory({ cwd });
|
|
});
|
|
|
|
// Project's config file has `root:true`, then no warning.
|
|
describe("when it lints 'subdir/exist-with-root/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("exist-with-root/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
|
|
it("should not load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ yoda: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
|
|
// Project's config file doesn't have `root:true` and home is ancestor, then ESLINT_PERSONAL_CONFIG_SUPPRESS.
|
|
describe("when it lints 'subdir/exist/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("exist/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should raise an ESLINT_PERSONAL_CONFIG_SUPPRESS warning.", () => {
|
|
assert.deepStrictEqual(warnings, [
|
|
{
|
|
code: "ESLINT_PERSONAL_CONFIG_SUPPRESS",
|
|
message: `'~/.eslintrc.*' config files have been deprecated. Please remove it or add 'root:true' to the config files in your projects in order to avoid loading '~/.eslintrc.*' accidentally. (found in "${uniqueHomeDirName}${path.sep}.eslintrc.json")`
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("should not load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ yoda: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
|
|
/*
|
|
* Project's config file doesn't exist and home is ancestor, then no warning.
|
|
* In this case, ESLint will continue to use `~/.eslintrc.json` even if personal config file feature is removed.
|
|
*/
|
|
describe("when it lints 'subdir/not-exist/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("not-exist/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
|
|
it("should load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ eqeqeq: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when '~/.eslintrc.json' exists and CWD is `~/../another`", () => {
|
|
beforeEach(() => {
|
|
cwd = path.join(homeDir, "../another");
|
|
const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
|
|
cwd: () => cwd,
|
|
files: {
|
|
|
|
// ~/.eslintrc.json
|
|
[`../${uniqueHomeDirName}/.eslintrc.json`]: JSON.stringify({ rules: { eqeqeq: "error" } }),
|
|
|
|
// other files
|
|
"exist-with-root/test.js": "",
|
|
"exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
|
|
"exist/test.js": "",
|
|
"exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
|
|
"not-exist/test.js": ""
|
|
}
|
|
});
|
|
|
|
factory = new CascadingConfigArrayFactory({ cwd });
|
|
});
|
|
|
|
// Project's config file has `root:true`, then no warning.
|
|
describe("when it lints 'exist-with-root/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("exist-with-root/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
|
|
it("should not load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ yoda: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
|
|
// Project's config file doesn't have `root:true` but home is not ancestor, then no warning.
|
|
describe("when it lints 'exist/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("exist/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
|
|
it("should not load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ yoda: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
|
|
// Project's config file doesn't exist and home is not ancestor, then ESLINT_PERSONAL_CONFIG_LOAD.
|
|
describe("when it lints 'not-exist/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("not-exist/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should raise an ESLINT_PERSONAL_CONFIG_LOAD warning.", () => {
|
|
assert.deepStrictEqual(warnings, [
|
|
{
|
|
code: "ESLINT_PERSONAL_CONFIG_LOAD",
|
|
message: `'~/.eslintrc.*' config files have been deprecated. Please use a config file per project or the '--config' option. (found in "${uniqueHomeDirName}${path.sep}.eslintrc.json")`
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("should load '~/.eslintrc.json'.", () => {
|
|
assert.deepStrictEqual(
|
|
config.extractConfig("a.js").rules,
|
|
{ eqeqeq: ["error"] }
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when '~/.eslintrc.json' doesn't exist and CWD is `~/subdir`", () => {
|
|
beforeEach(() => {
|
|
cwd = path.join(homeDir, "subdir");
|
|
const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
|
|
cwd: () => cwd,
|
|
files: {
|
|
"exist-with-root/test.js": "",
|
|
"exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
|
|
"exist/test.js": "",
|
|
"exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
|
|
"not-exist/test.js": ""
|
|
}
|
|
});
|
|
|
|
factory = new CascadingConfigArrayFactory({ cwd });
|
|
});
|
|
|
|
describe("when it lints 'subdir/exist/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("exist/test.js");
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when '~/.eslintrc.json' doesn't exist and CWD is `~/../another`", () => {
|
|
beforeEach(() => {
|
|
cwd = path.join(homeDir, "../another");
|
|
const { CascadingConfigArrayFactory } = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
|
|
cwd: () => cwd,
|
|
files: {
|
|
"exist-with-root/test.js": "",
|
|
"exist-with-root/.eslintrc.json": JSON.stringify({ root: true, rules: { yoda: "error" } }),
|
|
"exist/test.js": "",
|
|
"exist/.eslintrc.json": JSON.stringify({ rules: { yoda: "error" } }),
|
|
"not-exist/test.js": ""
|
|
}
|
|
});
|
|
|
|
factory = new CascadingConfigArrayFactory({ cwd });
|
|
});
|
|
|
|
describe("when it lints 'not-exist/test.js'", () => {
|
|
beforeEach(async () => {
|
|
config = factory.getConfigArrayForFile("not-exist/test.js", { ignoreNotFoundError: true });
|
|
await delay();
|
|
});
|
|
|
|
it("should not raise any warnings.", () => {
|
|
assert.deepStrictEqual(warnings, []);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
// This group moved from 'tests/lib/config.js' when refactoring to keep the cumulated test cases.
|
|
describe("with 'tests/fixtures/config-hierarchy' files", () => {
|
|
const { CascadingConfigArrayFactory } = require("../../../lib/cli-engine/cascading-config-array-factory");
|
|
let fixtureDir;
|
|
|
|
const DIRECTORY_CONFIG_HIERARCHY = require("../../fixtures/config-hierarchy/file-structure.json");
|
|
|
|
/**
|
|
* Returns the path inside of the fixture directory.
|
|
* @param {...string} args file path segments.
|
|
* @returns {string} The path inside the fixture directory.
|
|
* @private
|
|
*/
|
|
function getFixturePath(...args) {
|
|
return path.join(fixtureDir, "config-hierarchy", ...args);
|
|
}
|
|
|
|
/**
|
|
* Mocks the current user's home path
|
|
* @param {string} fakeUserHomePath fake user's home path
|
|
* @returns {void}
|
|
* @private
|
|
*/
|
|
function mockOsHomedir(fakeUserHomePath) {
|
|
sinon.stub(os, "homedir")
|
|
.returns(fakeUserHomePath);
|
|
}
|
|
|
|
/**
|
|
* Assert that given two objects have the same properties with the
|
|
* same value for each.
|
|
*
|
|
* The `expected` object is merged with the default values of config
|
|
* data before comparing, so you can specify only the properties you
|
|
* focus on.
|
|
* @param {Object} actual The config object to check.
|
|
* @param {Object} expected What the config object should look like.
|
|
* @returns {void}
|
|
* @private
|
|
*/
|
|
function assertConfigsEqual(actual, expected) {
|
|
const defaults = new ExtractedConfig().toCompatibleObjectAsConfigFileContent();
|
|
|
|
assert.deepStrictEqual(actual, { ...defaults, ...expected });
|
|
}
|
|
|
|
/**
|
|
* Wait for the next tick.
|
|
* @returns {Promise<void>} -
|
|
*/
|
|
function nextTick() {
|
|
return new Promise(resolve => process.nextTick(resolve));
|
|
}
|
|
|
|
/**
|
|
* Get the config data for a file.
|
|
* @param {CascadingConfigArrayFactory} factory The factory to get config.
|
|
* @param {string} filePath The path to a source code.
|
|
* @returns {Object} The gotten config.
|
|
*/
|
|
function getConfig(factory, filePath = "a.js") {
|
|
const { cwd } = factory;
|
|
const absolutePath = path.resolve(cwd, filePath);
|
|
|
|
return factory
|
|
.getConfigArrayForFile(absolutePath)
|
|
.extractConfig(absolutePath)
|
|
.toCompatibleObjectAsConfigFileContent();
|
|
}
|
|
|
|
// copy into clean area so as not to get "infected" by this project's .eslintrc files
|
|
before(() => {
|
|
fixtureDir = `${os.tmpdir()}/eslint/fixtures`;
|
|
sh.mkdir("-p", fixtureDir);
|
|
sh.cp("-r", "./tests/fixtures/config-hierarchy", fixtureDir);
|
|
sh.cp("-r", "./tests/fixtures/rules", fixtureDir);
|
|
});
|
|
|
|
afterEach(() => {
|
|
sinon.verifyAndRestore();
|
|
});
|
|
|
|
after(() => {
|
|
sh.rm("-r", fixtureDir);
|
|
});
|
|
|
|
it("should create config object when using baseConfig with extends", () => {
|
|
const customBaseConfig = {
|
|
extends: path.resolve(__dirname, "../../fixtures/config-extends/array/.eslintrc")
|
|
};
|
|
const factory = new CascadingConfigArrayFactory({ baseConfig: customBaseConfig, useEslintrc: false });
|
|
const config = getConfig(factory);
|
|
|
|
assert.deepStrictEqual(config.env, {
|
|
browser: false,
|
|
es6: true,
|
|
node: true
|
|
});
|
|
assert.deepStrictEqual(config.rules, {
|
|
"no-empty": [1],
|
|
"comma-dangle": [2],
|
|
"no-console": [2]
|
|
});
|
|
});
|
|
|
|
it("should return the project config when called in current working directory", () => {
|
|
const factory = new CascadingConfigArrayFactory();
|
|
const actual = getConfig(factory);
|
|
|
|
assert.strictEqual(actual.rules.strict[1], "global");
|
|
});
|
|
|
|
it("should not retain configs from previous directories when called multiple times", () => {
|
|
const firstpath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/subdir/.eslintrc");
|
|
const secondpath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/.eslintrc");
|
|
const factory = new CascadingConfigArrayFactory();
|
|
let config;
|
|
|
|
config = getConfig(factory, firstpath);
|
|
assert.deepStrictEqual(config.rules["no-new"], [0]);
|
|
config = getConfig(factory, secondpath);
|
|
assert.deepStrictEqual(config.rules["no-new"], [1]);
|
|
});
|
|
|
|
it("should throw error when a configuration file doesn't exist", () => {
|
|
const configPath = path.resolve(__dirname, "../../fixtures/configurations/.eslintrc");
|
|
const factory = new CascadingConfigArrayFactory();
|
|
|
|
sinon.stub(fs, "readFileSync").throws(new Error());
|
|
|
|
assert.throws(() => {
|
|
getConfig(factory, configPath);
|
|
}, "Cannot read config file");
|
|
|
|
});
|
|
|
|
it("should throw error when a configuration file is not require-able", () => {
|
|
const configPath = ".eslintrc";
|
|
const factory = new CascadingConfigArrayFactory();
|
|
|
|
sinon.stub(fs, "readFileSync").throws(new Error());
|
|
|
|
assert.throws(() => {
|
|
getConfig(factory, configPath);
|
|
}, "Cannot read config file");
|
|
|
|
});
|
|
|
|
it("should cache config when the same directory is passed twice", () => {
|
|
const configPath = path.resolve(__dirname, "../../fixtures/configurations/single-quotes/.eslintrc");
|
|
const configArrayFactory = new ConfigArrayFactory();
|
|
const factory = new CascadingConfigArrayFactory({ configArrayFactory });
|
|
|
|
sinon.spy(configArrayFactory, "loadInDirectory");
|
|
|
|
// If cached this should be called only once
|
|
getConfig(factory, configPath);
|
|
const callcount = configArrayFactory.loadInDirectory.callcount;
|
|
|
|
getConfig(factory, configPath);
|
|
|
|
assert.strictEqual(configArrayFactory.loadInDirectory.callcount, callcount);
|
|
});
|
|
|
|
// make sure JS-style comments don't throw an error
|
|
it("should load the config file when there are JS-style comments in the text", () => {
|
|
const specificConfigPath = path.resolve(__dirname, "../../fixtures/configurations/comments.json");
|
|
const factory = new CascadingConfigArrayFactory({ specificConfigPath, useEslintrc: false });
|
|
const config = getConfig(factory);
|
|
const { semi, strict } = config.rules;
|
|
|
|
assert.deepStrictEqual(semi, [1]);
|
|
assert.deepStrictEqual(strict, [0]);
|
|
});
|
|
|
|
// make sure YAML files work correctly
|
|
it("should load the config file when a YAML file is used", () => {
|
|
const specificConfigPath = path.resolve(__dirname, "../../fixtures/configurations/env-browser.yaml");
|
|
const factory = new CascadingConfigArrayFactory({ specificConfigPath, useEslintrc: false });
|
|
const config = getConfig(factory);
|
|
const { "no-alert": noAlert, "no-undef": noUndef } = config.rules;
|
|
|
|
assert.deepStrictEqual(noAlert, [0]);
|
|
assert.deepStrictEqual(noUndef, [2]);
|
|
});
|
|
|
|
it("should contain the correct value for parser when a custom parser is specified", () => {
|
|
const configPath = path.resolve(__dirname, "../../fixtures/configurations/parser/.eslintrc.json");
|
|
const factory = new CascadingConfigArrayFactory();
|
|
const config = getConfig(factory, configPath);
|
|
|
|
assert.strictEqual(config.parser, path.resolve(path.dirname(configPath), "./custom.js"));
|
|
});
|
|
|
|
/*
|
|
* Configuration hierarchy ---------------------------------------------
|
|
* https://github.com/eslint/eslint/issues/3915
|
|
*/
|
|
it("should correctly merge environment settings", () => {
|
|
const factory = new CascadingConfigArrayFactory({ useEslintrc: true });
|
|
const file = getFixturePath("envs", "sub", "foo.js");
|
|
const expected = {
|
|
rules: {},
|
|
env: {
|
|
browser: true,
|
|
node: false
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Default configuration - blank
|
|
it("should return a blank config when using no .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({ useEslintrc: false });
|
|
const file = getFixturePath("broken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
rules: {},
|
|
globals: {},
|
|
env: {},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should return a blank config when baseConfig is set to false and no .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({ baseConfig: false, useEslintrc: false });
|
|
const file = getFixturePath("broken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
rules: {},
|
|
globals: {},
|
|
env: {},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// No default configuration
|
|
it("should return an empty config when not using .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({ useEslintrc: false });
|
|
const file = getFixturePath("broken", "console-wrong-quotes.js");
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, { ignorePatterns: cwdIgnorePatterns });
|
|
});
|
|
|
|
it("should return a modified config when baseConfig is set to an object and no .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({
|
|
baseConfig: {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
},
|
|
useEslintrc: false
|
|
});
|
|
const file = getFixturePath("broken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should return a modified config without plugin rules enabled when baseConfig is set to an object with plugin and no .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({
|
|
baseConfig: {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
},
|
|
plugins: ["example-with-rules-config"]
|
|
},
|
|
cwd: getFixturePath("plugins"),
|
|
useEslintrc: false
|
|
});
|
|
const file = getFixturePath("broken", "plugins", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
plugins: ["example-with-rules-config"],
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Project configuration - second level .eslintrc
|
|
it("should merge configs when local .eslintrc overrides parent .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory();
|
|
const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
"no-console": [1],
|
|
quotes: [2, "single"]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Project configuration - third level .eslintrc
|
|
it("should merge configs when local .eslintrc overrides parent and grandparent .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory();
|
|
const file = getFixturePath("broken", "subbroken", "subsubbroken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
"no-console": [0],
|
|
quotes: [1, "double"]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Project configuration - root set in second level .eslintrc
|
|
it("should not return or traverse configurations in parents of config with root:true", () => {
|
|
const factory = new CascadingConfigArrayFactory();
|
|
const file = getFixturePath("root-true", "parent", "root", "wrong-semi.js");
|
|
const expected = {
|
|
rules: {
|
|
semi: [2, "never"]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Project configuration - root set in second level .eslintrc
|
|
it("should return project config when called with a relative path from a subdir", () => {
|
|
const factory = new CascadingConfigArrayFactory({ cwd: getFixturePath("root-true", "parent", "root", "subdir") });
|
|
const dir = ".";
|
|
const expected = {
|
|
rules: {
|
|
semi: [2, "never"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, dir);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Command line configuration - --config with first level .eslintrc
|
|
it("should merge command line config when config file adds to local .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({
|
|
specificConfigPath: getFixturePath("broken", "add-conf.yaml")
|
|
});
|
|
const file = getFixturePath("broken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
quotes: [2, "double"],
|
|
semi: [1, "never"]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Command line configuration - --config with first level .eslintrc
|
|
it("should merge command line config when config file overrides local .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({
|
|
specificConfigPath: getFixturePath("broken", "override-conf.yaml")
|
|
});
|
|
const file = getFixturePath("broken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
quotes: [0, "double"]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Command line configuration - --config with second level .eslintrc
|
|
it("should merge command line config when config file adds to local and parent .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({
|
|
specificConfigPath: getFixturePath("broken", "add-conf.yaml")
|
|
});
|
|
const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
quotes: [2, "single"],
|
|
"no-console": [1],
|
|
semi: [1, "never"]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Command line configuration - --config with second level .eslintrc
|
|
it("should merge command line config when config file overrides local and parent .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({
|
|
specificConfigPath: getFixturePath("broken", "override-conf.yaml")
|
|
});
|
|
const file = getFixturePath("broken", "subbroken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
quotes: [0, "single"],
|
|
"no-console": [1]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Command line configuration - --rule with --config and first level .eslintrc
|
|
it("should merge command line config and rule when rule and config file overrides local .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({
|
|
cliConfig: {
|
|
rules: {
|
|
quotes: [1, "double"]
|
|
}
|
|
},
|
|
specificConfigPath: getFixturePath("broken", "override-conf.yaml")
|
|
});
|
|
const file = getFixturePath("broken", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
rules: {
|
|
quotes: [1, "double"]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
// Command line configuration - --plugin
|
|
it("should merge command line plugin with local .eslintrc", () => {
|
|
const factory = new CascadingConfigArrayFactory({
|
|
cliConfig: {
|
|
plugins: ["another-plugin"]
|
|
},
|
|
cwd: getFixturePath("plugins"),
|
|
resolvePluginsRelativeTo: getFixturePath("plugins")
|
|
});
|
|
const file = getFixturePath("broken", "plugins", "console-wrong-quotes.js");
|
|
const expected = {
|
|
env: {
|
|
node: true
|
|
},
|
|
plugins: [
|
|
"example",
|
|
"another-plugin"
|
|
],
|
|
rules: {
|
|
quotes: [2, "double"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
|
|
it("should merge multiple different config file formats", () => {
|
|
const factory = new CascadingConfigArrayFactory();
|
|
const file = getFixturePath("fileexts/subdir/subsubdir/foo.js");
|
|
const expected = {
|
|
env: {
|
|
browser: true
|
|
},
|
|
rules: {
|
|
semi: [2, "always"],
|
|
eqeqeq: [2]
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, file);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
|
|
it("should load user config globals", () => {
|
|
const configPath = path.resolve(__dirname, "../../fixtures/globals/conf.yaml");
|
|
const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath, useEslintrc: false });
|
|
const expected = {
|
|
globals: {
|
|
foo: true
|
|
},
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, configPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should not load disabled environments", () => {
|
|
const configPath = path.resolve(__dirname, "../../fixtures/environments/disable.yaml");
|
|
const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath, useEslintrc: false });
|
|
const config = getConfig(factory, configPath);
|
|
|
|
assert.isUndefined(config.globals.window);
|
|
});
|
|
|
|
it("should gracefully handle empty files", () => {
|
|
const configPath = path.resolve(__dirname, "../../fixtures/configurations/env-node.json");
|
|
const factory = new CascadingConfigArrayFactory({ specificConfigPath: configPath });
|
|
|
|
getConfig(factory, path.resolve(__dirname, "../../fixtures/configurations/empty/empty.json"));
|
|
});
|
|
|
|
// Meaningful stack-traces
|
|
it("should include references to where an `extends` configuration was loaded from", () => {
|
|
const configPath = path.resolve(__dirname, "../../fixtures/config-extends/error.json");
|
|
|
|
assert.throws(() => {
|
|
const factory = new CascadingConfigArrayFactory({ useEslintrc: false, specificConfigPath: configPath });
|
|
|
|
getConfig(factory, configPath);
|
|
}, /Referenced from:.*?error\.json/u);
|
|
});
|
|
|
|
// Keep order with the last array element taking highest precedence
|
|
it("should make the last element in an array take the highest precedence", () => {
|
|
const configPath = path.resolve(__dirname, "../../fixtures/config-extends/array/.eslintrc");
|
|
const factory = new CascadingConfigArrayFactory({ useEslintrc: false, specificConfigPath: configPath });
|
|
const expected = {
|
|
rules: { "no-empty": [1], "comma-dangle": [2], "no-console": [2] },
|
|
env: { browser: false, node: true, es6: true },
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, configPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
describe("with env in a child configuration file", () => {
|
|
it("should not overwrite parserOptions of the parent with env of the child", () => {
|
|
const factory = new CascadingConfigArrayFactory();
|
|
const targetPath = getFixturePath("overwrite-ecmaFeatures", "child", "foo.js");
|
|
const expected = {
|
|
rules: {},
|
|
env: { commonjs: true },
|
|
parserOptions: { ecmaFeatures: { globalReturn: false } },
|
|
ignorePatterns: cwdIgnorePatterns
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
});
|
|
|
|
describe("personal config file within home directory", () => {
|
|
const {
|
|
CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
|
|
} = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
|
|
files: {
|
|
"eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Returns the path inside of the fixture directory.
|
|
* @param {...string} args file path segments.
|
|
* @returns {string} The path inside the fixture directory.
|
|
* @private
|
|
*/
|
|
function getFakeFixturePath(...args) {
|
|
return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
|
|
}
|
|
|
|
it("should load the personal config if no local config was found", () => {
|
|
const projectPath = getFakeFixturePath("personal-config", "project-without-config");
|
|
const homePath = getFakeFixturePath("personal-config", "home-folder");
|
|
const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
|
|
|
|
mockOsHomedir(homePath);
|
|
|
|
const actual = getConfig(factory, filePath);
|
|
const expected = {
|
|
rules: {
|
|
"home-folder-rule": [2]
|
|
}
|
|
};
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should ignore the personal config if a local config was found", () => {
|
|
const projectPath = getFakeFixturePath("personal-config", "home-folder", "project");
|
|
const homePath = getFakeFixturePath("personal-config", "home-folder");
|
|
const filePath = getFakeFixturePath("personal-config", "home-folder", "project", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
|
|
|
|
mockOsHomedir(homePath);
|
|
|
|
const actual = getConfig(factory, filePath);
|
|
const expected = {
|
|
rules: {
|
|
"project-level-rule": [2]
|
|
}
|
|
};
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should ignore the personal config if config is passed through cli", () => {
|
|
const configPath = getFakeFixturePath("quotes-error.json");
|
|
const projectPath = getFakeFixturePath("personal-config", "project-without-config");
|
|
const homePath = getFakeFixturePath("personal-config", "home-folder");
|
|
const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({
|
|
cwd: projectPath,
|
|
specificConfigPath: configPath
|
|
});
|
|
|
|
mockOsHomedir(homePath);
|
|
|
|
const actual = getConfig(factory, filePath);
|
|
const expected = {
|
|
rules: {
|
|
quotes: [2, "double"]
|
|
}
|
|
};
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should still load the project config if the current working directory is the same as the home folder", () => {
|
|
const projectPath = getFakeFixturePath("personal-config", "project-with-config");
|
|
const filePath = getFakeFixturePath("personal-config", "project-with-config", "subfolder", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
|
|
|
|
mockOsHomedir(projectPath);
|
|
|
|
const actual = getConfig(factory, filePath);
|
|
const expected = {
|
|
rules: {
|
|
"project-level-rule": [2],
|
|
"subfolder-level-rule": [2]
|
|
}
|
|
};
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
});
|
|
|
|
describe("when no local or personal config is found", () => {
|
|
const {
|
|
CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
|
|
} = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
|
|
files: {
|
|
"eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Returns the path inside of the fixture directory.
|
|
* @param {...string} args file path segments.
|
|
* @returns {string} The path inside the fixture directory.
|
|
* @private
|
|
*/
|
|
function getFakeFixturePath(...args) {
|
|
return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...args);
|
|
}
|
|
|
|
it("should throw an error if no local config and no personal config was found", () => {
|
|
const projectPath = getFakeFixturePath("personal-config", "project-without-config");
|
|
const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
|
|
const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
|
|
|
|
mockOsHomedir(homePath);
|
|
|
|
assert.throws(() => {
|
|
getConfig(factory, filePath);
|
|
}, "No ESLint configuration found");
|
|
});
|
|
|
|
it("should throw an error if no local config was found and ~/package.json contains no eslintConfig section", () => {
|
|
const projectPath = getFakeFixturePath("personal-config", "project-without-config");
|
|
const homePath = getFakeFixturePath("personal-config", "home-folder-with-packagejson");
|
|
const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath });
|
|
|
|
mockOsHomedir(homePath);
|
|
|
|
assert.throws(() => {
|
|
getConfig(factory, filePath);
|
|
}, "No ESLint configuration found");
|
|
});
|
|
|
|
it("should not throw an error if no local config and no personal config was found but useEslintrc is false", () => {
|
|
const projectPath = getFakeFixturePath("personal-config", "project-without-config");
|
|
const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
|
|
const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({ cwd: projectPath, useEslintrc: false });
|
|
|
|
mockOsHomedir(homePath);
|
|
|
|
getConfig(factory, filePath);
|
|
});
|
|
|
|
it("should not throw an error if no local config and no personal config was found but rules are specified", () => {
|
|
const projectPath = getFakeFixturePath("personal-config", "project-without-config");
|
|
const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
|
|
const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({
|
|
cliConfig: {
|
|
rules: { quotes: [2, "single"] }
|
|
},
|
|
cwd: projectPath
|
|
});
|
|
|
|
mockOsHomedir(homePath);
|
|
|
|
getConfig(factory, filePath);
|
|
});
|
|
|
|
it("should not throw an error if no local config and no personal config was found but baseConfig is specified", () => {
|
|
const projectPath = getFakeFixturePath("personal-config", "project-without-config");
|
|
const homePath = getFakeFixturePath("personal-config", "folder-does-not-exist");
|
|
const filePath = getFakeFixturePath("personal-config", "project-without-config", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({ baseConfig: {}, cwd: projectPath });
|
|
|
|
mockOsHomedir(homePath);
|
|
|
|
getConfig(factory, filePath);
|
|
});
|
|
});
|
|
|
|
describe("with overrides", () => {
|
|
const {
|
|
CascadingConfigArrayFactory: StubbedCascadingConfigArrayFactory
|
|
} = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({
|
|
files: {
|
|
"eslint/fixtures/config-hierarchy": DIRECTORY_CONFIG_HIERARCHY
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Returns the path inside of the fixture directory.
|
|
* @param {...string} pathSegments One or more path segments, in order of depth, shallowest first
|
|
* @returns {string} The path inside the fixture directory.
|
|
* @private
|
|
*/
|
|
function getFakeFixturePath(...pathSegments) {
|
|
return path.join(process.cwd(), "eslint", "fixtures", "config-hierarchy", ...pathSegments);
|
|
}
|
|
|
|
it("should merge override config when the pattern matches the file name", () => {
|
|
const factory = new StubbedCascadingConfigArrayFactory({});
|
|
const targetPath = getFakeFixturePath("overrides", "foo.js");
|
|
const expected = {
|
|
rules: {
|
|
quotes: [2, "single"],
|
|
"no-else-return": [0],
|
|
"no-unused-vars": [1],
|
|
semi: [1, "never"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should merge override config when the pattern matches the file path relative to the config file", () => {
|
|
const factory = new StubbedCascadingConfigArrayFactory({});
|
|
const targetPath = getFakeFixturePath("overrides", "child", "child-one.js");
|
|
const expected = {
|
|
rules: {
|
|
curly: ["error", "multi", "consistent"],
|
|
"no-else-return": [0],
|
|
"no-unused-vars": [1],
|
|
quotes: [2, "double"],
|
|
semi: [1, "never"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should not merge override config when the pattern matches the absolute file path", () => {
|
|
const resolvedPath = path.resolve(__dirname, "../../fixtures/config-hierarchy/overrides/bar.js");
|
|
|
|
assert.throws(() => new StubbedCascadingConfigArrayFactory({
|
|
baseConfig: {
|
|
overrides: [{
|
|
files: resolvedPath,
|
|
rules: {
|
|
quotes: [1, "double"]
|
|
}
|
|
}]
|
|
},
|
|
useEslintrc: false
|
|
}), /Invalid override pattern/u);
|
|
});
|
|
|
|
it("should not merge override config when the pattern traverses up the directory tree", () => {
|
|
const parentPath = "overrides/../**/*.js";
|
|
|
|
assert.throws(() => new StubbedCascadingConfigArrayFactory({
|
|
baseConfig: {
|
|
overrides: [{
|
|
files: parentPath,
|
|
rules: {
|
|
quotes: [1, "single"]
|
|
}
|
|
}]
|
|
},
|
|
useEslintrc: false
|
|
}), /Invalid override pattern/u);
|
|
});
|
|
|
|
it("should merge all local configs (override and non-override) before non-local configs", () => {
|
|
const factory = new StubbedCascadingConfigArrayFactory({});
|
|
const targetPath = getFakeFixturePath("overrides", "two", "child-two.js");
|
|
const expected = {
|
|
rules: {
|
|
"no-console": [0],
|
|
"no-else-return": [0],
|
|
"no-unused-vars": [2],
|
|
quotes: [2, "double"],
|
|
semi: [2, "never"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should apply overrides in parent .eslintrc over non-override rules in child .eslintrc", () => {
|
|
const targetPath = getFakeFixturePath("overrides", "three", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({
|
|
cwd: getFakeFixturePath("overrides"),
|
|
baseConfig: {
|
|
overrides: [
|
|
{
|
|
files: "three/**/*.js",
|
|
rules: {
|
|
"semi-style": [2, "last"]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
useEslintrc: false
|
|
});
|
|
const expected = {
|
|
rules: {
|
|
"semi-style": [2, "last"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should apply overrides if all glob patterns match", () => {
|
|
const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({
|
|
cwd: getFakeFixturePath("overrides"),
|
|
baseConfig: {
|
|
overrides: [{
|
|
files: ["one/**/*", "*.js"],
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
}]
|
|
},
|
|
useEslintrc: false
|
|
});
|
|
const expected = {
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should apply overrides even if some glob patterns do not match", () => {
|
|
const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({
|
|
cwd: getFakeFixturePath("overrides"),
|
|
baseConfig: {
|
|
overrides: [{
|
|
files: ["one/**/*", "*two.js"],
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
}]
|
|
},
|
|
useEslintrc: false
|
|
});
|
|
const expected = {
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should not apply overrides if any excluded glob patterns match", () => {
|
|
const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({
|
|
cwd: getFakeFixturePath("overrides"),
|
|
baseConfig: {
|
|
overrides: [{
|
|
files: "one/**/*",
|
|
excludedFiles: ["two/**/*", "*one.js"],
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
}]
|
|
},
|
|
useEslintrc: false
|
|
});
|
|
const expected = {
|
|
rules: {}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should apply overrides if all excluded glob patterns fail to match", () => {
|
|
const targetPath = getFakeFixturePath("overrides", "one", "child-one.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({
|
|
cwd: getFakeFixturePath("overrides"),
|
|
baseConfig: {
|
|
overrides: [{
|
|
files: "one/**/*",
|
|
excludedFiles: ["two/**/*", "*two.js"],
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
}]
|
|
},
|
|
useEslintrc: false
|
|
});
|
|
const expected = {
|
|
rules: {
|
|
quotes: [2, "single"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
|
|
it("should cascade", () => {
|
|
const targetPath = getFakeFixturePath("overrides", "foo.js");
|
|
const factory = new StubbedCascadingConfigArrayFactory({
|
|
cwd: getFakeFixturePath("overrides"),
|
|
baseConfig: {
|
|
overrides: [
|
|
{
|
|
files: "foo.js",
|
|
rules: {
|
|
semi: [2, "never"],
|
|
quotes: [2, "single"]
|
|
}
|
|
},
|
|
{
|
|
files: "foo.js",
|
|
rules: {
|
|
semi: [2, "never"],
|
|
quotes: [2, "double"]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
useEslintrc: false
|
|
});
|
|
const expected = {
|
|
rules: {
|
|
semi: [2, "never"],
|
|
quotes: [2, "double"]
|
|
}
|
|
};
|
|
const actual = getConfig(factory, targetPath);
|
|
|
|
assertConfigsEqual(actual, expected);
|
|
});
|
|
});
|
|
|
|
describe("deprecation warnings", () => {
|
|
const cwd = path.resolve(__dirname, "../../fixtures/config-file/");
|
|
let warning = null;
|
|
|
|
/**
|
|
* Store a reported warning object if that code starts with `ESLINT_`.
|
|
* @param {{code:string, message:string}} w The warning object to store.
|
|
* @returns {void}
|
|
*/
|
|
function onWarning(w) {
|
|
if (w.code.startsWith("ESLINT_")) {
|
|
warning = w;
|
|
}
|
|
}
|
|
|
|
/** @type {CascadingConfigArrayFactory} */
|
|
let factory;
|
|
|
|
beforeEach(() => {
|
|
factory = new CascadingConfigArrayFactory({ cwd });
|
|
warning = null;
|
|
process.on("warning", onWarning);
|
|
});
|
|
afterEach(() => {
|
|
process.removeListener("warning", onWarning);
|
|
});
|
|
|
|
it("should emit a deprecation warning if 'ecmaFeatures' is given.", async () => {
|
|
getConfig(factory, "ecma-features/test.js");
|
|
|
|
// Wait for "warning" event.
|
|
await nextTick();
|
|
|
|
assert.notStrictEqual(warning, null);
|
|
assert.strictEqual(
|
|
warning.message,
|
|
`The 'ecmaFeatures' config file property is deprecated and has no effect. (found in "ecma-features${path.sep}.eslintrc.yml")`
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("'clearCache()' method should clear cache.", () => {
|
|
describe("with a '.eslintrc.js' file", () => {
|
|
const root = path.join(os.tmpdir(), "eslint/cli-engine/cascading-config-array-factory");
|
|
const files = {
|
|
".eslintrc.js": ""
|
|
};
|
|
const {
|
|
CascadingConfigArrayFactory
|
|
} = defineCascadingConfigArrayFactoryWithInMemoryFileSystem({ cwd: () => root, files });
|
|
|
|
/** @type {Map<string, Object>} */
|
|
let additionalPluginPool;
|
|
|
|
/** @type {CascadingConfigArrayFactory} */
|
|
let factory;
|
|
|
|
beforeEach(() => {
|
|
additionalPluginPool = new Map();
|
|
factory = new CascadingConfigArrayFactory({
|
|
additionalPluginPool,
|
|
cliConfig: { plugins: ["test"] }
|
|
});
|
|
});
|
|
|
|
it("should use cached instance.", () => {
|
|
const one = factory.getConfigArrayForFile("a.js");
|
|
const two = factory.getConfigArrayForFile("a.js");
|
|
|
|
assert.strictEqual(one, two);
|
|
});
|
|
|
|
it("should not use cached instance if 'clearCache()' method is called after first config is retrieved", () => {
|
|
const one = factory.getConfigArrayForFile("a.js");
|
|
|
|
factory.clearCache();
|
|
const two = factory.getConfigArrayForFile("a.js");
|
|
|
|
assert.notStrictEqual(one, two);
|
|
});
|
|
|
|
it("should have a loading error in CLI config.", () => {
|
|
const config = factory.getConfigArrayForFile("a.js");
|
|
|
|
assert.strictEqual(config[2].plugins.test.definition, null);
|
|
});
|
|
|
|
it("should not have a loading error in CLI config after adding 'test' plugin to the additional plugin pool then calling 'clearCache()'.", () => {
|
|
factory.getConfigArrayForFile("a.js");
|
|
|
|
additionalPluginPool.set("test", { configs: { name: "test" } });
|
|
factory.clearCache();
|
|
|
|
// Check.
|
|
const config = factory.getConfigArrayForFile("a.js");
|
|
|
|
assert.deepStrictEqual(
|
|
config[2].plugins.test.definition,
|
|
{
|
|
configs: { name: "test" },
|
|
environments: {},
|
|
processors: {},
|
|
rules: {}
|
|
}
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|