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

feat: lint files concurrently #15508

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions docs/developer-guide/nodejs-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob
Default is `[]`. An array of paths to directories to load custom rules from.
* `options.useEslintrc` (`boolean`)<br>
Default is `true`. If `false` is present, ESLint doesn't load configuration files (`.eslintrc.*` files). Only the configuration of the constructor options is valid.
* `options.concurrency` (`number`)<br>
Default is 1. Sets the concurrency level to use when linting files.

##### Autofix

Expand Down
5 changes: 5 additions & 0 deletions docs/user-guide/command-line-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ Miscellaneous:
-h, --help Show help
-v, --version Output the version number
--print-config path::String Print the configuration for the given file
--concurrency The concurrency level to use when linting files
```

Options that accept array values can be specified by repeating the option or with a comma-delimited list (other than `--ignore-pattern` which does not allow the second style).
Expand Down Expand Up @@ -494,6 +495,10 @@ Example:

eslint --print-config file.js

#### `--concurrency`

This option sets the concurrency level when linting files. Linting may be faster for large when this option is greater than 1.

## Ignoring files from linting

ESLint supports `.eslintignore` files to exclude files from the linting process when ESLint operates on a directory. Files given as individual CLI arguments will be exempt from exclusion. The `.eslintignore` file is a plain text file containing one pattern per line. It can be located in any of the target directory's ancestors; it will affect files in its containing directory as well as all sub-directories. Here's a simple example of a `.eslintignore` file:
Expand Down
195 changes: 137 additions & 58 deletions lib/cli-engine/cli-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
/** @typedef {import("../shared/types").Rule} Rule */
/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
/** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
/** @typedef {import("./file-enumerator").FileEnumerator} FileEnumerator */

/**
* The options to configure a CLI engine with.
Expand Down Expand Up @@ -558,6 +559,74 @@ function directoryExists(resolvedPath) {
}
}

/**
* Iterates over all of the files matching the pattern calling the appropriate callback
* for each file path. If a file is ignored or a cached result exists, `onResult()` is called,
* otherwise `onFile()` is called.
* @param {Object} options the iteration options.
* @param {string[]} options.patterns the file path patterns to be used.
* @param {FileEnumerator} options.fileEnumerator the file enumerator to use.
* @param {ConfigArray[]} options.lastConfigArrays an array of configuration arrays.
* @param {LintResultCache} options.lintResultCache the lint cache.
* @param {Object} options.options the configuration options.
* @param {string} options.options.cwd the current working directory.
* @param {boolean|Function} options.options.fix whether to fix lint errors.
* @param {Function} options.onResult called with the result when an ignored or previously cached file is found.
* @param {Function} options.onFile called with the file path and file config when a file to be linted is found.
* @returns {void}
*/
function visitFilesToLint({
patterns,
fileEnumerator,
lastConfigArrays,
lintResultCache,
options: {
cwd,
fix
},
onResult = () => {},
onFile = () => {}
}) {
for (const { config, filePath, ignored } of fileEnumerator.iterateFiles(patterns)) {
if (ignored) {
onResult(createIgnoreResult(filePath, cwd));
continue;
}

/*
* Store used configs for:
* - this method uses to collect used deprecated rules.
* - `getRules()` method uses to collect all loaded rules.
* - `--fix-type` option uses to get the loaded rule's meta data.
*/
if (!lastConfigArrays.includes(config)) {
lastConfigArrays.push(config);
}

// Skip if there is cached result.
if (lintResultCache) {
const cachedResult =
lintResultCache.getCachedLintResults(filePath, config);

if (cachedResult) {
const hadMessages =
cachedResult.messages &&
cachedResult.messages.length > 0;

if (hadMessages && fix) {
debug(`Reprocessing cached file to allow autofix: ${filePath}`);
} else {
debug(`Skipping file since it hasn't changed: ${filePath}`);
onResult(cachedResult);
continue;
}
}
}

onFile(filePath, config);
}
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
Expand Down Expand Up @@ -733,6 +802,39 @@ class CLIEngine {
});
}

resolveFilesToLint(patterns) {
const {
fileEnumerator,
lastConfigArrays,
lintResultCache,
options: {
cwd,
fix
}
} = internalSlotsMap.get(this);

const filesToLint = [];
const results = [];

visitFilesToLint({
patterns,
fileEnumerator,
lastConfigArrays,
lintResultCache,
options: {
cwd,
fix
},
onResult: result => results.push(result),
onFile: filePath => filesToLint.push(filePath)
});

return {
filesToLint,
results
};
}

/**
* Executes the current configuration on an array of file and directory names.
* @param {string[]} patterns An array of file and directory names.
Expand Down Expand Up @@ -774,68 +876,45 @@ class CLIEngine {
}
}

// Iterate source code files.
for (const { config, filePath, ignored } of fileEnumerator.iterateFiles(patterns)) {
if (ignored) {
results.push(createIgnoreResult(filePath, cwd));
continue;
}

/*
* Store used configs for:
* - this method uses to collect used deprecated rules.
* - `getRules()` method uses to collect all loaded rules.
* - `--fix-type` option uses to get the loaded rule's meta data.
*/
if (!lastConfigArrays.includes(config)) {
lastConfigArrays.push(config);
}

// Skip if there is cached result.
if (lintResultCache) {
const cachedResult =
lintResultCache.getCachedLintResults(filePath, config);

if (cachedResult) {
const hadMessages =
cachedResult.messages &&
cachedResult.messages.length > 0;

if (hadMessages && fix) {
debug(`Reprocessing cached file to allow autofix: ${filePath}`);
} else {
debug(`Skipping file since it hasn't changed: ${filePath}`);
results.push(cachedResult);
continue;
}
}
}

// Do lint.
const result = verifyText({
text: fs.readFileSync(filePath, "utf8"),
filePath,
config,
visitFilesToLint({
patterns,
fileEnumerator,
lastConfigArrays,
lintResultCache,
options: {
cwd,
fix,
allowInlineConfig,
reportUnusedDisableDirectives,
fileEnumerator,
linter
});
fix
},
onResult: result => results.push(result),
onFile: (filePath, config) => {

// Do lint.
const result = verifyText({
text: fs.readFileSync(filePath, "utf8"),
filePath,
config,
cwd,
fix,
allowInlineConfig,
reportUnusedDisableDirectives,
fileEnumerator,
linter
});

results.push(result);
results.push(result);

/*
* Store the lint result in the LintResultCache.
* NOTE: The LintResultCache will remove the file source and any
* other properties that are difficult to serialize, and will
* hydrate those properties back in on future lint runs.
*/
if (lintResultCache) {
lintResultCache.setCachedLintResults(filePath, config, result);

/*
* Store the lint result in the LintResultCache.
* NOTE: The LintResultCache will remove the file source and any
* other properties that are difficult to serialize, and will
* hydrate those properties back in on future lint runs.
*/
if (lintResultCache) {
lintResultCache.setCachedLintResults(filePath, config, result);
}
}
}
});

// Persist the cache to disk.
if (lintResultCache) {
Expand Down
6 changes: 4 additions & 2 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ function translateOptions({
reportUnusedDisableDirectives,
resolvePluginsRelativeTo,
rule,
rulesdir
rulesdir,
concurrency
}) {
return {
allowInlineConfig: inlineConfig,
Expand Down Expand Up @@ -120,7 +121,8 @@ function translateOptions({
reportUnusedDisableDirectives: reportUnusedDisableDirectives ? "error" : void 0,
resolvePluginsRelativeTo,
rulePaths: rulesdir,
useEslintrc: eslintrc
useEslintrc: eslintrc,
concurrency
};
}

Expand Down