Skip to content

Commit

Permalink
New: support specifying extensions in the config (fixes #10828)
Browse files Browse the repository at this point in the history
  • Loading branch information
g-plane committed Nov 11, 2019
1 parent 0afb518 commit 164079d
Show file tree
Hide file tree
Showing 15 changed files with 154 additions and 19 deletions.
1 change: 1 addition & 0 deletions conf/config-schema.js
Expand Up @@ -8,6 +8,7 @@
const baseConfigProperties = {
env: { type: "object" },
extends: { $ref: "#/definitions/stringOrStrings" },
extensions: { type: "array", items: "string" },
globals: { type: "object" },
overrides: {
type: "array",
Expand Down
7 changes: 5 additions & 2 deletions lib/cli-engine/cli-engine.js
Expand Up @@ -789,7 +789,7 @@ class CLIEngine {
fix,
allowInlineConfig,
reportUnusedDisableDirectives,
extensionRegExp: fileEnumerator.extensionRegExp,
extensionRegExp: fileEnumerator.getExtensionRegExp(config, filePath),
linter
});

Expand Down Expand Up @@ -878,7 +878,10 @@ class CLIEngine {
fix,
allowInlineConfig,
reportUnusedDisableDirectives,
extensionRegExp: fileEnumerator.extensionRegExp,
extensionRegExp: fileEnumerator.getExtensionRegExp(
config,
resolvedFilename || path.join(cwd, "__placeholder__.js")
),
linter
}));
}
Expand Down
2 changes: 2 additions & 0 deletions lib/cli-engine/config-array-factory.js
Expand Up @@ -525,6 +525,7 @@ class ConfigArrayFactory {
{
env,
extends: extend,
extensions,
globals,
noInlineConfig,
parser: parserName,
Expand Down Expand Up @@ -568,6 +569,7 @@ class ConfigArrayFactory {
// Config data.
criteria: null,
env,
extensions,
globals,
noInlineConfig,
parser,
Expand Down
2 changes: 2 additions & 0 deletions lib/cli-engine/config-array/config-array.js
Expand Up @@ -53,6 +53,7 @@ const { ExtractedConfig } = require("./extracted-config");
* @property {string} filePath The path to the source file of this config element.
* @property {InstanceType<OverrideTester>|null} criteria The tester for the `files` and `excludedFiles` of this config element.
* @property {Record<string, boolean>|undefined} env The environment settings.
* @property {string[]|undefined} extensions The extensions to match files for directory patterns.
* @property {Record<string, GlobalConf>|undefined} globals The global variable settings.
* @property {boolean|undefined} noInlineConfig The flag that disables directive comments.
* @property {DependentParser|undefined} parser The parser loader.
Expand Down Expand Up @@ -262,6 +263,7 @@ function createConfig(instance, indices) {

// Merge others.
mergeWithoutOverwrite(config.env, element.env);
config.extensions = config.extensions.concat(element.extensions || []);
mergeWithoutOverwrite(config.globals, element.globals);
mergeWithoutOverwrite(config.parserOptions, element.parserOptions);
mergeWithoutOverwrite(config.settings, element.settings);
Expand Down
6 changes: 6 additions & 0 deletions lib/cli-engine/config-array/extracted-config.js
Expand Up @@ -41,6 +41,12 @@ class ExtractedConfig {
*/
this.env = {};

/**
* Extensions to match files for directory patterns.
* @type {string[]}
*/
this.extensions = [];

/**
* Global variables.
* @type {Record<string, GlobalConf>}
Expand Down
40 changes: 24 additions & 16 deletions lib/cli-engine/file-enumerator.js
Expand Up @@ -89,7 +89,7 @@ const IGNORED = 2;
* @typedef {Object} FileEnumeratorInternalSlots
* @property {CascadingConfigArrayFactory} configArrayFactory The factory for config arrays.
* @property {string} cwd The base directory to start lookup.
* @property {RegExp} extensionRegExp The RegExp to test if a string ends with specific file extensions.
* @property {string[]} baseExtensions The array to check if a string ends with specific file extensions.
* @property {boolean} globInputPaths Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
* @property {boolean} ignoreFlag The flag to check ignored files.
* @property {IgnoredPaths} ignoredPathsWithDotfiles The ignored paths but don't include dot files.
Expand Down Expand Up @@ -198,17 +198,7 @@ class FileEnumerator {
internalSlotsMap.set(this, {
configArrayFactory,
cwd,
extensionRegExp: new RegExp(
`.\\.(?:${extensions
.map(ext => escapeRegExp(
ext.startsWith(".")
? ext.slice(1)
: ext
))
.join("|")
})$`,
"u"
),
baseExtensions: extensions,
globInputPaths,
ignoreFlag: ignore,
ignoredPaths,
Expand All @@ -221,10 +211,27 @@ class FileEnumerator {

/**
* The `RegExp` object that tests if a file path has the allowed file extensions.
* @type {RegExp}
* @param {ConfigArray} [configArray] ESLint configuration.
* @param {string} [filePath] The absolute path to the target file.
* @returns {RegExp} The RegExp to test if a string ends with specific file extensions.
*/
get extensionRegExp() {
return internalSlotsMap.get(this).extensionRegExp;
getExtensionRegExp(configArray, filePath) {
let extensions = internalSlotsMap.get(this).baseExtensions;

if (configArray) {
const config = configArray.extractConfig(filePath);

extensions = extensions.concat(config.extensions || []);
}

// Use set to deduplicate.
const exts = new Set(
extensions
.filter(Boolean)
.map(ext => escapeRegExp(ext.startsWith(".") ? ext.slice(1) : ext))
);

return new RegExp(`.\\.${Array.from(exts).join("|")}$`, "u");
}

/**
Expand Down Expand Up @@ -386,7 +393,7 @@ class FileEnumerator {
return;
}
debug(`Enter the directory: ${directoryPath}`);
const { configArrayFactory, extensionRegExp } = internalSlotsMap.get(this);
const { configArrayFactory } = internalSlotsMap.get(this);

/** @type {ConfigArray|null} */
let config = null;
Expand All @@ -401,6 +408,7 @@ class FileEnumerator {
if (!config) {
config = configArrayFactory.getConfigArrayForFile(filePath);
}
const extensionRegExp = this.getExtensionRegExp(config, filePath);
const ignored = this._isIgnoredFile(filePath, options);
const flag = ignored ? IGNORED_SILENTLY : NONE;
const matched = options.selector
Expand Down
1 change: 1 addition & 0 deletions lib/shared/types.js
Expand Up @@ -29,6 +29,7 @@ module.exports = {};
* @typedef {Object} ConfigData
* @property {Record<string, boolean>} [env] The environment settings.
* @property {string | string[]} [extends] The path to other config files or the package name of shareable configs.
* @property {string[]} [extensions] The array of filename extensions that should be checked for code.
* @property {Record<string, GlobalConf>} [globals] The global variable settings.
* @property {boolean} [noInlineConfig] The flag that disables directive comments.
* @property {OverrideConfigData[]} [overrides] The override settings per kind of files.
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/configurations/extensions.json
@@ -0,0 +1,3 @@
{
"extensions": ["ts"]
}
1 change: 1 addition & 0 deletions tests/fixtures/extensions/extensions-js.js
@@ -0,0 +1 @@
function fn() {}
1 change: 1 addition & 0 deletions tests/fixtures/extensions/extensions-ts.ts
@@ -0,0 +1 @@
function fn() {}
26 changes: 25 additions & 1 deletion tests/lib/cli-engine/cli-engine.js
Expand Up @@ -633,7 +633,6 @@ describe("CLIEngine", () => {
assert.strictEqual(report.results[0].source, "var foo = 'bar'");
});


it("should not return a `source` property when no errors or warnings are present", () => {
engine = new CLIEngine({
useEslintrc: false,
Expand Down Expand Up @@ -753,6 +752,7 @@ describe("CLIEngine", () => {
assert.strictEqual(report.messages[0].message, "OK");
});
});

it("should warn when deprecated rules are found in a config", () => {
engine = new CLIEngine({
cwd: originalDir,
Expand Down Expand Up @@ -1157,6 +1157,30 @@ describe("CLIEngine", () => {
assert.strictEqual(report.results[0].messages.length, 0);
});

it("should process files with specifying extensions in configuration file", () => {

engine = new CLIEngine({
cwd: path.join(fixtureDir, "extensions"),
configFile: getFixturePath("configurations", "extensions.json")
});

let report = engine.executeOnFiles(["."]);

assert.lengthOf(report.results, 2);
assert.lengthOf(report.results[0].messages, 0);
assert.lengthOf(report.results[1].messages, 0);

engine = new CLIEngine({
cwd: path.join(fixtureDir, "extensions"),
configFile: getFixturePath("configurations", "es6.json")
});

report = engine.executeOnFiles(["."]);

assert.lengthOf(report.results, 1);
assert.lengthOf(report.results[0].messages, 0);
});

it("should return zero messages when given a config with environment set to browser", () => {

engine = new CLIEngine({
Expand Down
19 changes: 19 additions & 0 deletions tests/lib/cli-engine/config-array-factory.js
Expand Up @@ -29,6 +29,7 @@ function assertConfigArrayElement(actual, providedExpected) {
filePath: "",
criteria: null,
env: void 0,
extensions: void 0,
globals: void 0,
noInlineConfig: void 0,
parser: void 0,
Expand All @@ -54,6 +55,7 @@ function assertConfigArrayElement(actual, providedExpected) {
function assertConfig(actual, providedExpected) {
const expected = {
env: {},
extensions: [],
globals: {},
noInlineConfig: void 0,
parser: null,
Expand Down Expand Up @@ -461,6 +463,23 @@ describe("ConfigArrayFactory", () => {
});
});

describe("if the config data had 'extensions' property, the returned value", () => {
const extensions = ["ts"];
let configArray;

beforeEach(() => {
configArray = create({ extensions });
});

it("should have an element.", () => {
assert.strictEqual(configArray.length, 1);
});

it("should have the 'extensions' value in the element.", () => {
assertConfigArrayElement(configArray[0], { extensions });
});
});

describe("if the config data had 'globals' property, the returned value", () => {
const globals = { window: "readonly" };
let configArray;
Expand Down
7 changes: 7 additions & 0 deletions tests/lib/cli-engine/config-array/config-array.js
Expand Up @@ -424,6 +424,7 @@ describe("ConfigArray", () => {
assert.deepStrictEqual(result, {
configNameOfNoInlineConfig: "",
env: {},
extensions: [],
globals: {},
noInlineConfig: void 0,
parser: null,
Expand Down Expand Up @@ -456,6 +457,7 @@ describe("ConfigArray", () => {
assert.deepStrictEqual(result, {
configNameOfNoInlineConfig: "",
env: {},
extensions: [],
globals: {},
noInlineConfig: void 0,
parser: null,
Expand Down Expand Up @@ -550,6 +552,7 @@ describe("ConfigArray", () => {
ecmaFeatures: { jsx: true }
},
env: { browser: true },
extensions: ["ts"],
globals: { foo: false }
},
{
Expand All @@ -563,6 +566,7 @@ describe("ConfigArray", () => {
ecmaFeatures: { globalReturn: true }
},
env: { browser: false },
extensions: ["vue"],
globals: { foo: true }
}
];
Expand All @@ -582,6 +586,7 @@ describe("ConfigArray", () => {
env: {
browser: false
},
extensions: ["vue", "ts"],
globals: {
foo: true
},
Expand Down Expand Up @@ -623,6 +628,7 @@ describe("ConfigArray", () => {
ecmaFeatures: { jsx: true }
},
env: { browser: true },
extensions: ["ts"],
globals: { foo: false }
});
assert.deepStrictEqual(config[1], {
Expand All @@ -636,6 +642,7 @@ describe("ConfigArray", () => {
ecmaFeatures: { globalReturn: true }
},
env: { browser: false },
extensions: ["vue"],
globals: { foo: true }
});
});
Expand Down
4 changes: 4 additions & 0 deletions tests/lib/cli-engine/config-array/extracted-config.js
Expand Up @@ -21,6 +21,10 @@ describe("'ExtractedConfig' class", () => {
assert.deepStrictEqual(config.env, {});
});

it("should have 'extensions' property.", () => {
assert.deepStrictEqual(config.extensions, []);
});

it("should have 'globals' property.", () => {
assert.deepStrictEqual(config.globals, {});
});
Expand Down
53 changes: 53 additions & 0 deletions tests/lib/cli-engine/file-enumerator.js
Expand Up @@ -9,6 +9,7 @@ const path = require("path");
const os = require("os");
const { assert } = require("chai");
const sh = require("shelljs");
const { ConfigArrayFactory } = require("../../../lib/cli-engine/config-array-factory");
const { CascadingConfigArrayFactory } =
require("../../../lib/cli-engine/cascading-config-array-factory");
const { IgnoredPaths } = require("../../../lib/cli-engine/ignored-paths");
Expand Down Expand Up @@ -476,4 +477,56 @@ describe("FileEnumerator", () => {
});
});
});

describe("'getExtensionRegExp()' method should return correct regex", () => {
const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator");

/**
* Generate extension regex with reading from configuration.
* @param {import("../../../lib/shared/types").ConfigData} [config] ESLint configuration.
* @returns {RegExp} The RegExp to test if a string ends with specific file extensions.
*/
function readFromConfig(config = {}) {
const configArrayFactory = new ConfigArrayFactory();
const filePath = path.join(process.cwd(), "foo.js");
const configArray = configArrayFactory.create(config, { filePath });
const fileEnumerator = new FileEnumerator();

return fileEnumerator.getExtensionRegExp(configArray, filePath);
}

it("should return default regex without configuration", () => {
const fileEnumerator = new FileEnumerator();
const regex = fileEnumerator.getExtensionRegExp();

assert.match("foo.js", regex);
assert.notMatch("foo.ts", regex);
});

it("should read from configuration", () => {
const regex = readFromConfig({ extensions: ["ts"] });

assert.match("foo.js", regex);
assert.match("foo.ts", regex);
});

it("should deduplicate extensions", () => {
const regex = readFromConfig({ extensions: ["js", "js"] });

assert.strictEqual(regex.source, ".\\.js$");
});

it("should strip prefixed dot", () => {
const regex = readFromConfig({ extensions: [".ts"] });

assert.strictEqual(regex.source, ".\\.js|ts$");
});

it("should allow missing in configuration", () => {
const regex = readFromConfig();

assert.match("foo.js", regex);
assert.notMatch("foo.ts", regex);
});
});
});

0 comments on commit 164079d

Please sign in to comment.