Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Breaking: improve plugin resolving (refs eslint/rfcs#47) #12922

Merged
merged 15 commits into from Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/user-guide/configuring.md
Expand Up @@ -968,6 +968,8 @@ project-root

The config in `app/.eslintrc.json` defines the glob pattern `**/*Spec.js`. This pattern is relative to the base directory of `app/.eslintrc.json`. So, this pattern would match `app/lib/fooSpec.js` and `app/components/barSpec.js` but **NOT** `server/serverSpec.js`. If you defined the same pattern in the `.eslintrc.json` file within in the `project-root` folder, it would match all three of the `*Spec` files.

If `--config` option provides a config, the glob patterns in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the glob patterns in the config are relative to `.` rather than `./configs`.

### Example configuration

In your `.eslintrc.json`:
Expand Down Expand Up @@ -1032,6 +1034,10 @@ You can tell ESLint to ignore specific files and directories by `ignorePatterns`
* You cannot write `ignorePatterns` property under `overrides` property.
* `.eslintignore` can override `ignorePatterns` property of config files.

If a glob pattern starts with `/`, the pattern is relative to the base directory of the config file. For example, `/foo.js` in `lib/.eslintrc.json` matches to `lib/foo.js` but not `lib/subdir/foo.js`.

If `--config` option provides a config, the ignore patterns that start with `/` in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the ignore patterns in the config are relative to `.` rather than `./configs`.

### `.eslintignore`

You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following will omit all JavaScript files:
Expand All @@ -1045,7 +1051,7 @@ When ESLint is run, it looks in the current working directory to find an `.eslin
Globs are matched using [node-ignore](https://github.com/kaelzhang/node-ignore), so a number of features are available:

* Lines beginning with `#` are treated as comments and do not affect ignore patterns.
* Paths are relative to `.eslintignore` location or the current working directory. This is also true of paths passed in via the `--ignore-pattern` [command](./command-line-interface.md#--ignore-pattern).
* Paths are relative to the current working directory. This is also true of paths passed in via the `--ignore-pattern` [command](./command-line-interface.md#--ignore-pattern).
* Lines preceded by `!` are negated patterns that re-include a pattern that was ignored by an earlier pattern.
* Ignore patterns behave according to the `.gitignore` [specification](https://git-scm.com/docs/gitignore).

Expand Down
6 changes: 4 additions & 2 deletions lib/cli-engine/cascading-config-array-factory.js
Expand Up @@ -140,6 +140,7 @@ function createBaseConfigArray({
function createCLIConfigArray({
cliConfigData,
configArrayFactory,
cwd,
ignorePath,
specificConfigPath
}) {
Expand All @@ -158,7 +159,7 @@ function createCLIConfigArray({
cliConfigArray.unshift(
...configArrayFactory.loadFile(
specificConfigPath,
{ name: "--config" }
{ name: "--config", basePath: cwd }
)
);
}
Expand Down Expand Up @@ -198,7 +199,7 @@ class CascadingConfigArrayFactory {
cliConfig: cliConfigData = null,
cwd = process.cwd(),
ignorePath,
resolvePluginsRelativeTo = cwd,
resolvePluginsRelativeTo,
rulePaths = [],
specificConfigPath = null,
useEslintrc = true
Expand All @@ -220,6 +221,7 @@ class CascadingConfigArrayFactory {
cliConfigArray: createCLIConfigArray({
cliConfigData,
configArrayFactory,
cwd,
ignorePath,
specificConfigPath
}),
Expand Down
176 changes: 111 additions & 65 deletions lib/cli-engine/cli-engine.js
Expand Up @@ -20,12 +20,13 @@ const path = require("path");
const defaultOptions = require("../../conf/default-cli-options");
const pkg = require("../../package.json");
const ConfigOps = require("../shared/config-ops");
const { emitDeprecationWarning } = require("../shared/deprecation-warnings");
const naming = require("../shared/naming");
const ModuleResolver = require("../shared/relative-module-resolver");
const { Linter } = require("../linter");
const builtInRules = require("../rules");
const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory");
const { IgnorePattern, getUsedExtractedConfigs } = require("./config-array");
const { IgnorePattern } = require("./config-array");
const { FileEnumerator } = require("./file-enumerator");
const hash = require("./hash");
const LintResultCache = require("./lint-result-cache");
Expand Down Expand Up @@ -75,6 +76,13 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
* @property {string} resolvePluginsRelativeTo The folder where plugins should be resolved from, defaulting to the CWD
*/

/**
* Information of deprecated rules.
* @typedef {Object} DeprecatedRuleInfo
* @property {string} ruleId The rule ID.
* @property {string[]} replacedBy The rule IDs that replace this deprecated rule.
*/

/**
* A linting result.
* @typedef {Object} LintResult
Expand All @@ -86,13 +94,7 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
* @property {number} fixableWarningCount Number of fixable warnings for the result.
* @property {string} [source] The source code of the file that was linted.
* @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible.
*/

/**
* Information of deprecated rules.
* @typedef {Object} DeprecatedRuleInfo
* @property {string} ruleId The rule ID.
* @property {string[]} replacedBy The rule IDs that replace this deprecated rule.
* @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
*/

/**
Expand All @@ -103,7 +105,6 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
* @property {number} warningCount Number of warnings for the result.
* @property {number} fixableErrorCount Number of fixable errors for the result.
* @property {number} fixableWarningCount Number of fixable warnings for the result.
* @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
*/

/**
Expand Down Expand Up @@ -191,6 +192,59 @@ function calculateStatsPerRun(results) {
});
}

/**
* Collect used deprecated rules.
* @param {ExtractedConfig} config The config which were used.
* @param {Map<string, Rule>} pluginRules The rule definitions of loaded plugins.
* @returns {DeprecatedRuleInfo[]} Used deprecated rules.
*/
function getRuleDeprecationWarnings(config, pluginRules) {
const retv = [];

for (const [ruleId, ruleConfig] of Object.entries(config.rules)) {

// Skip if it's not used.
if (!ConfigOps.getRuleSeverity(ruleConfig)) {
continue;
}
const rule = pluginRules.get(ruleId) || builtInRules.get(ruleId);

// Skip if it's not deprecated.
if (!(rule && rule.meta && rule.meta.deprecated)) {
continue;
}

// This rule was used and deprecated.
retv.push({
ruleId,
replacedBy: rule.meta.replacedBy || []
});
}

return retv;
}

/**
* Merge used deprecated rules from all lint results.
* @param {LintResult[]} results The lint results.
* @returns {DeprecatedRuleInfo[]} Used deprecated rules.
*/
function mergeRuleDeprecationWarnings(results) {
const retv = [];
const ids = new Set();

for (const { usedDeprecatedRules } of results) {
for (const usedDeprecatedRule of usedDeprecatedRules) {
if (!ids.has(usedDeprecatedRule.ruleId)) {
ids.add(usedDeprecatedRule.ruleId);
retv.push(usedDeprecatedRule);
}
}
}

return retv;
}

/**
* Processes an source code using ESLint.
* @param {Object} config The config object.
Expand Down Expand Up @@ -264,6 +318,11 @@ function verifyText({
result.source = text;
}

result.usedDeprecatedRules = getRuleDeprecationWarnings(
config.extractConfig(filePathToVerify),
config.pluginRules
);

return result;
}

Expand Down Expand Up @@ -323,50 +382,6 @@ function getRule(ruleId, configArrays) {
return builtInRules.get(ruleId) || null;
}

/**
* Collect used deprecated rules.
* @param {ConfigArray[]} usedConfigArrays The config arrays which were used.
* @returns {IterableIterator<DeprecatedRuleInfo>} Used deprecated rules.
*/
function *iterateRuleDeprecationWarnings(usedConfigArrays) {
const processedRuleIds = new Set();

// Flatten used configs.
/** @type {ExtractedConfig[]} */
const configs = [].concat(
...usedConfigArrays.map(getUsedExtractedConfigs)
);

// Traverse rule configs.
for (const config of configs) {
for (const [ruleId, ruleConfig] of Object.entries(config.rules)) {

// Skip if it was processed.
if (processedRuleIds.has(ruleId)) {
continue;
}
processedRuleIds.add(ruleId);

// Skip if it's not used.
if (!ConfigOps.getRuleSeverity(ruleConfig)) {
continue;
}
const rule = getRule(ruleId, usedConfigArrays);

// Skip if it's not deprecated.
if (!(rule && rule.meta && rule.meta.deprecated)) {
continue;
}

// This rule was used and deprecated.
yield {
ruleId,
replacedBy: rule.meta.replacedBy || []
};
}
}
}

/**
* Checks if the given message is an error message.
* @param {LintMessage} message The message to check.
Expand Down Expand Up @@ -620,7 +635,14 @@ class CLIEngine {
}
}

/**
* Get rules that the last `executeOn*` methods used.
* @returns {Map<string, Rule>} A map of rule Id and rule implementation.
* @deprecated
*/
getRules() {
emitDeprecationWarning("getRules()", "ESLINT_LEGACY_GET_RULES");

const { lastConfigArrays } = internalSlotsMap.get(this);

return new Map(function *() {
Expand Down Expand Up @@ -823,16 +845,15 @@ class CLIEngine {
lintResultCache.reconcile();
}

// Collect used deprecated rules.
const usedDeprecatedRules = Array.from(
iterateRuleDeprecationWarnings(lastConfigArrays)
);

debug(`Linting complete in: ${Date.now() - startTime}ms`);
return {
results,
...calculateStatsPerRun(results),
usedDeprecatedRules

get usedDeprecatedRules() {
emitDeprecationWarning("executeOnFiles()", "ESLINT_LEGACY_USED_DEPRECATED_RULES");
return mergeRuleDeprecationWarnings(results);
}
};
}

Expand Down Expand Up @@ -894,16 +915,15 @@ class CLIEngine {
}));
}

// Collect used deprecated rules.
const usedDeprecatedRules = Array.from(
iterateRuleDeprecationWarnings(lastConfigArrays)
);

debug(`Linting complete in: ${Date.now() - startTime}ms`);
return {
results,
...calculateStatsPerRun(results),
usedDeprecatedRules

get usedDeprecatedRules() {
emitDeprecationWarning("executeOnText()", "ESLINT_LEGACY_USED_DEPRECATED_RULES");
return mergeRuleDeprecationWarnings(results);
}
};
}

Expand Down Expand Up @@ -931,6 +951,32 @@ class CLIEngine {
.toCompatibleObjectAsConfigFileContent();
}

/**
* Returns rule objects for the given file based on the CLI options.
* @param {string} filePath The path of the file to retrieve rules for.
* @returns {Map<string, Rule>} A map of rule ID and rule implementation that is used to lint the file.
*/
getRulesForFile(filePath) {
const { configArrayFactory, options } = internalSlotsMap.get(this);
const absolutePath = path.resolve(options.cwd, filePath);

if (directoryExists(absolutePath)) {
throw Object.assign(
new Error("'filePath' should not be a directory path."),
{ messageTemplate: "print-config-with-directory-path" }
);
}
const configArray = configArrayFactory.getConfigArrayForFile(
absolutePath,
{ ignoreNotFoundError: true }
);

return new Map(function *() {
yield* builtInRules;
yield* configArray.pluginRules;
}());
}

/**
* Checks if a given path is ignored by ESLint.
* @param {string} filePath The path of the file to check.
Expand Down