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: Rule Performance Statistics for flat ESLint #17850

Merged
merged 55 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
e79b535
Add stats option
mnkiefer Feb 29, 2024
59792f3
Remove resetTimes, stats per file
mnkiefer Mar 6, 2024
f96ffbd
Add suggestions from review
mnkiefer Mar 6, 2024
d620fe7
Update types & docs
mnkiefer Mar 6, 2024
20e9612
Update times description
mnkiefer Mar 6, 2024
536bc5b
Add Stats Data page
mnkiefer Mar 12, 2024
b3d4850
Merge branch 'main' into htmlCharts
mnkiefer Mar 12, 2024
331b342
Fix file verification issues
mnkiefer Mar 12, 2024
30103e8
Use named exports
mnkiefer Mar 12, 2024
d03462b
Update docs/src/extend/stats.md
mnkiefer Mar 15, 2024
3f87efb
Update docs/src/extend/stats.md
mnkiefer Mar 15, 2024
5412682
Update docs/src/extend/stats.md
mnkiefer Mar 15, 2024
562caf3
Update docs/src/extend/stats.md
mnkiefer Mar 15, 2024
d3795e4
Update docs/src/extend/stats.md
mnkiefer Mar 15, 2024
0baf76c
Update docs/src/extend/stats.md
mnkiefer Mar 15, 2024
14dbe96
Update docs/src/extend/stats.md
mnkiefer Mar 15, 2024
f2bbbd6
Add suggestions and new data
mnkiefer Mar 15, 2024
38d2311
Merge branch 'main' into htmlCharts
mnkiefer Mar 15, 2024
6399dfc
Update lib/shared/stats.js
mnkiefer Mar 18, 2024
13228b4
Update lib/shared/stats.js
mnkiefer Mar 18, 2024
56ac36b
Update docs/src/extend/stats.md
mnkiefer Mar 18, 2024
596cc1a
Update lib/shared/types.js
mnkiefer Mar 18, 2024
c5d5afd
Update docs/src/integrate/nodejs-api.md
mnkiefer Mar 18, 2024
20b6b49
Update docs/src/extend/stats.md
mnkiefer Mar 18, 2024
697291f
Update docs/src/extend/stats.md
mnkiefer Mar 18, 2024
b70e81c
Update docs/src/extend/stats.md
mnkiefer Mar 18, 2024
d74f547
Update docs/src/extend/stats.md
mnkiefer Mar 18, 2024
6f5190e
Update lib/linter/linter.js
mnkiefer Mar 18, 2024
8c3c77d
Update lib/linter/linter.js
mnkiefer Mar 18, 2024
ff8ccc4
Update lib/linter/linter.js
mnkiefer Mar 18, 2024
1736efe
Update lib/linter/linter.js
mnkiefer Mar 18, 2024
048f9db
Update lib/linter/linter.js
mnkiefer Mar 18, 2024
7f0d205
Update tests/lib/eslint/eslint.js
mnkiefer Mar 18, 2024
331ff58
Update tests/fixtures/stats-example/file-to-fix.js
mnkiefer Mar 18, 2024
987523e
Update docs/src/integrate/nodejs-api.md
mnkiefer Mar 18, 2024
33792a3
Update docs/src/use/command-line-interface.md
mnkiefer Mar 18, 2024
66e92c8
Fix missing bracket
mnkiefer Mar 18, 2024
0002517
Update docs/src/use/command-line-interface.md
mnkiefer Mar 18, 2024
e954f44
Update stats tests
mnkiefer Mar 18, 2024
1cd3629
Merge branch 'htmlCharts' of https://github.com/mnkiefer/eslint into …
mnkiefer Mar 18, 2024
cf50d6e
Merge branch 'main' into htmlCharts
mnkiefer Mar 18, 2024
47abb04
Update tests/lib/eslint/eslint.js
mnkiefer Mar 22, 2024
6fde7a0
Update docs/src/integrate/nodejs-api.md
mnkiefer Mar 22, 2024
3aaf41a
Merge branch 'main' into htmlCharts
mnkiefer Mar 22, 2024
3bee435
Fix missing properties
mnkiefer Mar 22, 2024
3c7aabf
Remove trailing spaces
mnkiefer Mar 22, 2024
8721e12
Update nodejs-api.md
mnkiefer Mar 22, 2024
7492afd
Update docs/src/extend/stats.md
mnkiefer Mar 23, 2024
71a1b10
Update docs/src/extend/stats.md
mnkiefer Mar 23, 2024
6d3d65e
Update docs/src/extend/stats.md
mnkiefer Mar 23, 2024
8675ba0
Update docs/src/extend/stats.md
mnkiefer Mar 23, 2024
cefb5ba
Update stats.md
mnkiefer Mar 23, 2024
491d8ce
Add more docs
mnkiefer Mar 24, 2024
0faaa61
Merge branch 'htmlCharts' of https://github.com/mnkiefer/eslint into …
mnkiefer Mar 24, 2024
5b37bce
Merge branch 'main' into htmlCharts
mnkiefer Mar 24, 2024
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
1 change: 1 addition & 0 deletions docs/src/extend/custom-formatters.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ Each object in the `results` array is a `result` object. Each `result` object co
* **messages**: An array of [`message`](#the-message-object) objects. See below for more info about messages.
* **errorCount**: The number of errors for the given file.
* **warningCount**: The number of warnings for the given file.
* **stats**: The optional [`stats`](./stats#-stats-type) object that only exists when the `stats` option is used.
* **source**: The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present.
* **output**: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available.

Expand Down
2 changes: 2 additions & 0 deletions docs/src/extend/custom-rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -938,3 +938,5 @@ quotes | 18.066 | 100.0%
```

To see a longer list of results (more than 10), set the environment variable to another value such as `TIMING=50` or `TIMING=all`.

For more granular timing information (per file per rule), use the [`stats`](./stats) option instead.
139 changes: 139 additions & 0 deletions docs/src/extend/stats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
---
title: Stats Data
eleventyNavigation:
key: stats data
parent: extend eslint
title: Stats Data
order: 6
---

While an analysis of the overall rule performance for an ESLint run can be carried out by setting the [TIMING](./custom-rules#profile-rule-performance) environment variable, it can sometimes be useful to acquire more *granular* timing data (lint time per file per rule) or collect other measures of interest. In particular, when developing new [custom plugins](./plugins) and evaluating/benchmarking new languages or rule sets. For these use cases, you can optionally collect runtime statistics from ESLint.

## Enable stats collection

To enable collection of statistics, you can either:

1. Use the `--stats` CLI option. This will pass the stats data into the formatter used to output results from ESLint. (Note: not all formatters output stats data.)
1. Set `stats: true` as an option on the `ESLint` constructor.

Enabling stats data adds a new `stats` key to each [LintResult](../integrate/nodejs-api#-lintresult-type) object containing data such as parse times, fix times, lint times per rule.

As such, it is not available via stdout but made easily ingestible via a formatter using the CLI or via the Node.js API to cater to your specific needs.

## ◆ Stats type

The `Stats` value is the timing information of each lint run. The `stats` property of the [LintResult](../integrate/nodejs-api#-lintresult-type) type contains it. It has the following properties:

* `fixPasses` (`number`)<br>
The number of times ESLint has applied at least one fix after linting.
* `times` (`{ passes: TimePass[] }`)<br>
The times spent on (parsing, fixing, linting) a file, where the linting refers to the timing information for each rule.
* `TimePass` (`{ parse: ParseTime, rules?: Record<string, RuleTime>, fix: FixTime, total: number }`)<br>
An object containing the times spent on (parsing, fixing, linting)
* `ParseTime` (`{ total: number }`)<br>
The total time that is spent when parsing a file.
* `RuleTime` (`{ total: number }`)<be>
The total time that is spent on a rule.
* `FixTime` (`{ total: number }`)<be>
The total time that is spent on applying fixes to the code.

### CLI usage

Let's consider the following example:

```js [file-to-fix.js]
/*eslint no-regex-spaces: "error", wrap-regex: "error"*/

function a() {
return / foo/.test("bar");
}
```

Run ESLint with `--stats` and output to JSON via the built-in [`json` formatter](../use/formatters/):

```bash
npx eslint file-to-fix.js --fix --stats -f json
```

This yields the following `stats` entry as part of the formatted lint results object:

```json
{
"times": {
"passes": [
{
"parse": {
"total": 3.975959
},
"rules": {
"no-regex-spaces": {
"total": 0.160792
},
"wrap-regex": {
"total": 0.422626
}
},
"fix": {
"total": 0.080208
},
"total": 12.765959
},
{
"parse": {
"total": 0.623542
},
"rules": {
"no-regex-spaces": {
"total": 0.043084
},
"wrap-regex": {
"total": 0.007959
}
},
"fix": {
"total": 0
},
"total": 1.148875
}
]
},
"fixPasses": 1
}
```

Note, that for the simple example above, the sum of all rule times should be directly comparable to the first column of the TIMING output. Running the same command with `TIMING=all`, you can verify this:

```bash
$ TIMING=all npx eslint file-to-fix.js --fix --stats -f json
...
Rule | Time (ms) | Relative
:---------------|----------:|--------:
wrap-regex | 0.431 | 67.9%
no-regex-spaces | 0.204 | 32.1%
```

### API Usage

You can achieve the same thing using the Node.js API by passing`stats: true` as an option to the `ESLint` constructor. For example:

```js
const { ESLint } = require("eslint");

(async function main() {
// 1. Create an instance.
const eslint = new ESLint({ stats: true, fix: true });

// 2. Lint files.
const results = await eslint.lintFiles(["file-to-fix.js"]);

// 3. Format the results.
const formatter = await eslint.loadFormatter("json");
const resultText = formatter.format(results);

// 4. Output it.
console.log(resultText);
})().catch((error) => {
process.exitCode = 1;
console.error(error);
});
```
12 changes: 12 additions & 0 deletions docs/src/integrate/nodejs-api.md
mnkiefer marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob
Default is `null`. The plugin implementations that ESLint uses for the `plugins` setting of your configuration. This is a map-like object. Those keys are plugin IDs and each value is implementation.
* `options.ruleFilter` (`({ruleId: string, severity: number}) => boolean`)<br>
Default is `() => true`. A predicate function that filters rules to be run. This function is called with an object containing `ruleId` and `severity`, and returns `true` if the rule should be run.
* `options.stats` (`boolean`)<br>
Default is `false`. When set to `true`, additional statistics are added to the lint results (see [Stats type](../extend/stats#-stats-type)).

##### Autofix

Expand Down Expand Up @@ -367,6 +369,8 @@ The `LintResult` value is the information of the linting result of each file. Th
The modified source code text. This property is undefined if any fixable messages didn't exist.
* `source` (`string | undefined`)<br>
The original source code text. This property is undefined if any messages didn't exist or the `output` property exists.
* `stats` (`Stats | undefined`)<br>
The [Stats](../extend/stats#-stats-type) object. This contains the lint performance statistics collected with the `stats` option.
* `usedDeprecatedRules` (`{ ruleId: string; replacedBy: string[] }[]`)<br>
The information about the deprecated rules that were used to check this file.

Expand Down Expand Up @@ -715,6 +719,14 @@ const Linter = require("eslint").Linter;
Linter.version; // => '9.0.0'
```

### Linter#getTimes()

This method is used to get the times spent on (parsing, fixing, linting) a file. See `times` property of the [Stats](../extend/stats#-stats-type) object.

### Linter#getFixPassCount()

This method is used to get the number of autofix passes made. See `fixPasses` property of the [Stats](../extend/stats#-stats-type) object.

---

## RuleTester
Expand Down
15 changes: 15 additions & 0 deletions docs/src/use/command-line-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Miscellaneous:
-h, --help Show help
-v, --version Output the version number
--print-config path::String Print the configuration for the given file
--stats Add statistics to the lint report - default: false
```

### Basic Configuration
Expand Down Expand Up @@ -811,6 +812,20 @@ This option outputs the configuration to be used for the file passed. When prese
npx eslint --print-config file.js
```

#### `--stats`

This option adds a series of detailed performance statistics (see [Stats type](../extend/stats#-stats-type)) such as the *parse*-, *fix*- and *lint*-times (time per rule) to [`result`](../extend/custom-formatters#the-result-object) objects that are passed to the formatter (see [Stats CLI usage](../extend/stats#cli-usage)).

* **Argument Type**: No argument.

This option is intended for use with custom formatters that display statistics. It can also be used with the built-in `json` formatter.

##### `--stats` example

```shell
npx eslint --stats --format json file.js
```

## Exit Codes

When linting files, ESLint exits with one of the following exit codes:
Expand Down
2 changes: 2 additions & 0 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ async function translateOptions({
resolvePluginsRelativeTo,
rule,
rulesdir,
stats,
warnIgnored,
passOnNoPatterns,
maxWarnings
Expand Down Expand Up @@ -222,6 +223,7 @@ async function translateOptions({

if (configType === "flat") {
options.ignorePatterns = ignorePattern;
options.stats = stats;
options.warnIgnored = warnIgnored;

/*
Expand Down
5 changes: 5 additions & 0 deletions lib/eslint/eslint-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ function processOptions({
overrideConfig = null,
overrideConfigFile = null,
plugins = {},
stats = false,
warnIgnored = true,
passOnNoPatterns = false,
ruleFilter = () => true,
Expand Down Expand Up @@ -791,6 +792,9 @@ function processOptions({
if (Array.isArray(plugins)) {
errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
}
if (typeof stats !== "boolean") {
errors.push("'stats' must be a boolean.");
}
if (typeof warnIgnored !== "boolean") {
errors.push("'warnIgnored' must be a boolean.");
}
Expand Down Expand Up @@ -818,6 +822,7 @@ function processOptions({
globInputPaths,
ignore,
ignorePatterns,
stats,
passOnNoPatterns,
warnIgnored,
ruleFilter
Expand Down
17 changes: 16 additions & 1 deletion lib/eslint/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const LintResultCache = require("../cli-engine/lint-result-cache");
* doesn't do any config file lookup when `true`; considered to be a config filename
* when a string.
* @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
* @property {boolean} [stats] True enables added statistics on lint results.
* @property {boolean} warnIgnored Show warnings when the file list includes ignored files
* @property {boolean} [passOnNoPatterns=false] When set to true, missing patterns cause
* the linting operation to short circuit and not report any failures.
Expand Down Expand Up @@ -465,6 +466,7 @@ async function calculateConfigArray(eslint, {
* @param {boolean} config.fix If `true` then it does fix.
* @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
* @param {Function} config.ruleFilter A predicate function to filter which rules should be run.
* @param {boolean} config.stats If `true`, then if reports extra statistics with the lint results.
* @param {Linter} config.linter The linter instance to verify.
* @returns {LintResult} The result of linting.
* @private
Expand All @@ -477,6 +479,7 @@ function verifyText({
fix,
allowInlineConfig,
ruleFilter,
stats,
linter
}) {
const filePath = providedFilePath || "<text>";
Expand All @@ -497,6 +500,7 @@ function verifyText({
filename: filePathToVerify,
fix,
ruleFilter,
stats,

/**
* Check if the linter should adopt a given code block or not.
Expand Down Expand Up @@ -528,6 +532,13 @@ function verifyText({
result.source = text;
}

if (stats) {
result.stats = {
times: linter.getTimes(),
fixPasses: linter.getFixPassCount()
};
}

return result;
}

Expand Down Expand Up @@ -808,6 +819,7 @@ class ESLint {
fix,
fixTypes,
ruleFilter,
stats,
globInputPaths,
errorOnUnmatchedPattern,
warnIgnored
Expand Down Expand Up @@ -922,6 +934,7 @@ class ESLint {
fix: fixer,
allowInlineConfig,
ruleFilter,
stats,
linter
});

Expand Down Expand Up @@ -1010,7 +1023,8 @@ class ESLint {
cwd,
fix,
warnIgnored: constructorWarnIgnored,
ruleFilter
ruleFilter,
stats
} = eslintOptions;
const results = [];
const startTime = Date.now();
Expand All @@ -1034,6 +1048,7 @@ class ESLint {
fix,
allowInlineConfig,
ruleFilter,
stats,
linter
}));
}
Expand Down