Skip to content

Commit 61056b8

Browse files
committed
New: Implement autofixing (fixes #3134)
1 parent c32fc2c commit 61056b8

34 files changed

Lines changed: 1648 additions & 258 deletions

docs/developer-guide/nodejs-api.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ The `verify()` method returns an array of objects containing information about t
6666
severity: 2,
6767
line: 1,
6868
column: 23,
69-
message: "Expected a semicolon."
69+
message: "Expected a semicolon.",
70+
fix: {
71+
range: [1, 15],
72+
text: ";"
73+
}
7074
}
7175
```
7276

@@ -80,6 +84,7 @@ The information available for each linting message is:
8084
* `ruleId` - the ID of the rule that triggered the messages (or null if `fatal` is true).
8185
* `severity` - either 1 or 2, depending on your configuration.
8286
* `source` - the line of code where the problem is (or empty string if it can't be found).
87+
* `fix` - an object describing the fix for the problem (this property is omitted if no fix is available).
8388

8489
You can also get an instance of the `SourceCode` object used inside of `linter` by using the `getSourceCode()` method:
8590

@@ -115,6 +120,7 @@ The `CLIEngine` is a constructor, and you can create a new instance by passing i
115120
* `envs` - An array of environments to load (default: empty array). Corresponds to `--env`.
116121
* `extensions` - An array of filename extensions that should be checked for code. The default is an array containing just `".js"`. Corresponds to `--ext`.
117122
* `globals` - An array of global variables to declare (default: empty array). Corresponds to `--global`.
123+
* `fix` - True indicates that fixes should be applied to the text when possible.
118124
* `ignore` - False disables use of `.eslintignore` (default: true). Corresponds to `--no-ignore`.
119125
* `ignorePath` - The ignore file to use instead of `.eslintignore` (default: null). Corresponds to `--ignore-path`.
120126
* `baseConfig` - Set to false to disable use of base config. Could be set to an object to override default base config as well.
@@ -167,6 +173,7 @@ The return value is an object containing the results of the linting operation. H
167173
results: [
168174
{
169175
filePath: "./myfile.js",
176+
output: "foo;",
170177
messages: [
171178
{
172179
fatal: false,
@@ -187,7 +194,7 @@ The return value is an object containing the results of the linting operation. H
187194
}
188195
```
189196

190-
The top-level report object has a `results` array containing all linting results for files that had warnings or errors (any files that did not produce a warning or error are omitted). Each file result includes the `filePath`, a `messages` array, `errorCount` and `warningCount`. The `messages` array contains the result of calling `linter.verify()` on the given file. The `errorCount` and `warningCount` give the exact number of errors and warnings respectively on the given file. The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files.
197+
The top-level report object has a `results` array containing all linting results for files that had warnings or errors (any files that did not produce a warning or error are omitted). Each file result includes the `filePath`, a `messages` array, `errorCount`, `warningCount`, and optionally `output`. The `messages` array contains the result of calling `linter.verify()` on the given file. The `errorCount` and `warningCount` give the exact number of errors and warnings respectively on the given file. The `output` property gives the source code for the file with as many fixes applied as possible, so you can use that to rewrite the files if necessary. The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files.
191198

192199
Once you get a report object, it's up to you to determine how to output the results.
193200

@@ -350,6 +357,28 @@ var errorReport = CLIEngine.getErrorResults(report.results)
350357

351358
**Important:** You must pass in the `results` property of the report. Passing in `report` directly will result in an error.
352359

360+
### outputFixes()
361+
362+
This is a static function on `CLIEngine` that is used to output fixes from `report` to disk. It does by looking for files that have an `output` property in their results. Here's an example:
363+
364+
```js
365+
var CLIEngine = require("eslint").CLIEngine;
366+
367+
var cli = new CLIEngine({
368+
envs: ["browser", "mocha"],
369+
useEslintrc: false,
370+
rules: {
371+
semi: 2
372+
}
373+
});
374+
375+
// lint myfile.js and all files in lib/
376+
var report = cli.executeOnFiles(["myfile.js", "lib/"]);
377+
378+
// output fixes to disk
379+
CLIEngine.outputFixes(report);
380+
```
381+
353382
## Deprecated APIs
354383

355384
* `cli` - the `cli` object has been deprecated in favor of `CLIEngine`. As of v1.0.0, `cli` is no longer exported and should not be used by external tools.

docs/developer-guide/working-with-rules.md

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Additionally, the `context` object has the following methods:
8383
* `getSourceCode()` - returns a `SourceCode` object that you can use to work with the source that was passed to ESLint
8484
* `isMarkedAsUsed(name)` - returns true if a given variable name has been marked as used.
8585
* `markVariableAsUsed(name)` - marks the named variable in scope as used. This affects the [no-unused-vars](../rules/no-unused-vars.md) rule.
86-
* `report(node, message)` - reports an error in the code.
86+
* `report(descriptor)` - reports a problem in the code.
8787

8888
**Deprecated:** The following methods on the `context` object are deprecated. Please use the corresponding methods on `SourceCode` instead:
8989

@@ -104,23 +104,82 @@ Additionally, the `context` object has the following methods:
104104
* `getTokensAfter(nodeOrToken, count)` - returns `count` tokens after the given node or token. Use `sourceCode.getTokensAfter(nodeOrToken, count)` instead.
105105
* `getTokensBefore(nodeOrToken, count)` - returns `count` tokens before the given node or token. Use `sourceCode.getTokensBefore(nodeOrToken, count)` instead.
106106
* `getTokensBetween(node1, node2)` - returns the tokens between two nodes. Use `sourceCode.getTokensBetween(node1, node2)` instead.
107+
* `report(node, [location], message)` - reports a problem in the code.
107108

108109
### context.report()
109110

110-
The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts three arguments: the AST node that caused the report, a message to display, and an optional object literal which is used to interpolate. For example:
111+
The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties:
111112

112-
context.report(node, "This is unexpected!");
113+
* `message` - the problem message.
114+
* `node` - (optional) the AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem.
115+
* `loc` - (optional) an object specifying the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`.
116+
* `line` - the 1-based line number at which the problem occurred.
117+
* `col` - the 0-based column number at which the problem occurred.
118+
* `data` - (optional) placeholder data for `message`.
119+
* `fix` - (optional) a function that applies a fix to resolve the problem.
113120

114-
or {% raw %}
121+
The simplest example is to use just `node` and `message`:
115122

116-
context.report(node, "`{{ identifier }}` is unexpected!", { identifier: node.name });
123+
```js
124+
context.report({
125+
node: node,
126+
message: "Unexpected identifier"
127+
});
128+
```
129+
130+
The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node.
131+
132+
You can also use placeholders in the message and provide `data`:
117133

134+
```js
135+
{% raw %}
136+
context.report({
137+
node: node,
138+
message: "Unexpected identifier: {{ identifier }}",
139+
data: {
140+
identifier: node.name
141+
}
142+
});
118143
{% endraw %}
144+
```
119145

120146
Note that leading and trailing whitespace is optional in message parameters.
121147

122148
The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node.
123149

150+
### Applying Fixes
151+
152+
If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example:
153+
154+
```js
155+
context.report({
156+
node: node,
157+
message: "Missing semicolon".
158+
fix: function(fixer) {
159+
return fixer.insertTextAfter(node, ";");
160+
}
161+
});
162+
```
163+
164+
Here, the `fix()` function is used to insert a semicolon after the node. Note that the fix is not immediately applied and may not be applied at all if there are conflicts with other fixes. If the fix cannot be applied, then the problem message is reported as usual; if the fix can be applied, then the problem message is not reported.
165+
166+
The `fixer` object has the following methods:
167+
168+
* `insertTextAfter(nodeOrToken, text)` - inserts text after the given node or token
169+
* `insertTextAfterRange(range, text)` - inserts text after the given range
170+
* `insertTextBefore(nodeOrToken, text)` - inserts text before the given node or token
171+
* `insertTextBeforeRange(range, text)` - inserts text before the given range
172+
* `remove(nodeOrToken, text)` - removes the given node or token
173+
* `removeRange(range, text)` - removes text in the given range
174+
* `replaceText(nodeOrToken, text)` - replaces the text in the given node or token
175+
* `replaceTextRange(range, text)` - replaces the text in the given range
176+
177+
Best practices for fixes:
178+
179+
1. Make fixes that are as small as possible. Anything more than a single character is risky and could prevent other, simpler fixes from being made.
180+
1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`.
181+
1. Fixes should not introduce clashes with other rules. You can accidentally introduce a new problem that won't be reported until ESLint is run again. Another good reason to make as small a fix as possible.
182+
124183
### context.options
125184

126185
Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example:

docs/rules/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Rules in ESLint are divided into several categories to help you better understand their value. Though none are enabled by default, you can turn on rules that ESLint recommends by specifying your configuration to inherit from `eslint:recommended`. The rules that will be enabled when you inherit from `eslint:recommended` are indicated below as "(recommended)". For more information on how to configure rules and inherit from `eslint:recommended`, please see the [configuration documentation](../user-guide/configuring.md).
44

5+
Some rules are fixable using the `--fix` command line flag. Those rules are marked as "(fixable)" below.
6+
57
## Possible Errors
68

79
The following rules point out areas where you might have made mistakes.
@@ -47,7 +49,7 @@ These are rules designed to prevent you from making mistakes. They either prescr
4749
* [default-case](default-case.md) - require `default` case in `switch` statements
4850
* [dot-notation](dot-notation.md) - encourages use of dot notation whenever possible
4951
* [dot-location](dot-location.md) - enforces consistent newlines before or after dots
50-
* [eqeqeq](eqeqeq.md) - require the use of `===` and `!==`
52+
* [eqeqeq](eqeqeq.md) - require the use of `===` and `!==` (fixable)
5153
* [guard-for-in](guard-for-in.md) - make sure `for-in` loops have an `if` statement
5254
* [no-alert](no-alert.md) - disallow the use of `alert`, `confirm`, and `prompt`
5355
* [no-caller](no-caller.md) - disallow use of `arguments.caller` or `arguments.callee`
@@ -177,10 +179,10 @@ These rules are purely matters of style and are quite subjective.
177179
* [operator-linebreak](operator-linebreak.md) - enforce operators to be placed before or after line breaks
178180
* [padded-blocks](padded-blocks.md) - enforce padding within blocks
179181
* [quote-props](quote-props.md) - require quotes around object literal property names
180-
* [quotes](quotes.md) - specify whether backticks, double or single quotes should be used
182+
* [quotes](quotes.md) - specify whether backticks, double or single quotes should be used (fixable)
181183
* [require-jsdoc](require-jsdoc.md) - Require JSDoc comment
182184
* [semi-spacing](semi-spacing.md) - enforce spacing before and after semicolons
183-
* [semi](semi.md) - require or disallow use of semicolons instead of ASI
185+
* [semi](semi.md) - require or disallow use of semicolons instead of ASI (fixable)
184186
* [sort-vars](sort-vars.md) - sort variables within the same declaration block
185187
* [space-after-keywords](space-after-keywords.md) - require a space after certain keywords
186188
* [space-before-keywords](space-before-keywords.md) - require a space before certain keywords

docs/rules/eqeqeq.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ If one of those occurs in an innocent-looking statement such as `a == b` the act
1515

1616
This rule is aimed at eliminating the type-unsafe equality operators.
1717

18+
**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
19+
1820
The following patterns are considered warnings:
1921

2022
```js

docs/rules/quotes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ Many codebases require strings to be defined in a consistent manner.
1616

1717
This rule is aimed at ensuring consistency of string quotes and as such will report a problem when an inconsistent style is found.
1818

19+
**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
20+
1921
The rule configuration takes up to two options:
2022

2123
1. The first option is `"double"`, `"single"` or `"backtick"` for double-quotes, single-quotes or backticks respectively. There is no default.

docs/rules/semi.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Enforce Semicolons (semi)
1+
# Enforce or Disallow Semicolons (semi)
22

33
JavaScript is unique amongst the C-like languages in that it doesn't require semicolons at the end of each statement. In many cases, the JavaScript engine can determine that a semicolon should be in a certain spot and will automatically add it. This feature is known as **automatic semicolon insertion (ASI)** and is considered one of the more controversial features of JavaScript. For example, the following lines are both valid:
44

@@ -40,6 +40,8 @@ On the other side of the argument are those who say ASI isn't magic, it follows
4040

4141
This rule is aimed at ensuring consistent use of semicolons. You can decide whether or not to require semicolons at the end of statements.
4242

43+
**Fixable:** This rule is automatically fixable using the `--fix` flag on the command line.
44+
4345
### Options
4446

4547
By using the default option, semicolons must be used any place where they are valid.

docs/user-guide/command-line-interface.md

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,15 @@ The command line utility has several options. You can view the options by runnin
2424

2525
```text
2626
Basic configuration:
27-
-c, --config path::String Use configuration from this file or sharable config
27+
-c, --config path::String Use configuration from this file or shareable
28+
config
2829
--no-eslintrc Disable use of configuration from .eslintrc
2930
--env [String] Specify environments
3031
--ext [String] Specify JavaScript file extensions - default: .js
3132
--global [String] Define global variables
3233
--parser String Specify the parser to be used - default: espree
34+
35+
Caching:
3336
--cache Only check changed files - default: false
3437
--cache-file String Path to the cache file - default: .eslintcache
3538
@@ -41,15 +44,17 @@ Specifying rules and plugins:
4144
Ignoring files:
4245
--ignore-path path::String Specify path of ignore file
4346
--no-ignore Disable use of .eslintignore
44-
--ignore-pattern String Pattern of files to ignore (in addition to those in .eslintignore)
47+
--ignore-pattern String Pattern of files to ignore (in addition to those
48+
in .eslintignore)
4549
4650
Using stdin:
4751
--stdin Lint code provided on <STDIN> - default: false
4852
--stdin-filename String Specify filename to process STDIN as
4953
5054
Handling warnings:
5155
--quiet Report errors only - default: false
52-
--max-warnings Number Number of warnings to trigger nonzero exit code - default: -1
56+
--max-warnings Number Number of warnings to trigger nonzero exit code
57+
- default: -1
5358
5459
Output:
5560
-o, --output-file path::String Specify file to write report to
@@ -58,10 +63,9 @@ Output:
5863
5964
Miscellaneous:
6065
--init Run config initialization wizard - default: false
66+
--fix Automatically fix problems
6167
-h, --help Show help
62-
-v, --version Outputs the version number
63-
64-
```
68+
-v, --version Outputs the version number```
6569
6670
### Basic configuration
6771
@@ -128,6 +132,16 @@ Examples:
128132
129133
This option allows you to specify a parser to be used by eslint. By default, `espree` will be used.
130134
135+
### Caching
136+
137+
#### `--cache`
138+
139+
Store the info about processed files in order to only operate on the changed ones.
140+
141+
#### `--cache-file`
142+
143+
Path to the cache file. If none specified `.eslintcache` will be used. The file will be created in the directory where the `eslint` command is executed.
144+
131145
### Specifying rules and plugins
132146
133147
#### `--rulesdir`
@@ -266,6 +280,13 @@ Example:
266280
267281
This option will start config initialization wizard. It's designed to help new users quickly create .eslintrc file by answering a few questions. File will be created in current directory.
268282
283+
#### `--fix`
284+
285+
This option instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. Not all problems are fixable using this flag, and the flag does not work in these situations:
286+
287+
1. This option throws an error when code is piped to ESLint.
288+
1. This option has no effect on code that uses processors.
289+
269290
#### `-h`, `--help`
270291
271292
This option outputs the help menu, displaying all of the available options. All other flags are ignored when this is present.
@@ -278,19 +299,9 @@ Example:
278299
279300
eslint -v
280301
281-
### `--cache`
282-
283-
Store the info about processed files in order to only operate on the changed ones.
284-
285-
### `--cache-file`
286-
287-
Path to the cache file. If none specified `.eslintcache` will be used. The file will be created in the directory where the `eslint` command is executed.
288-
289302
## Ignoring files from linting
290303
291304
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:
292305
293-
```text
294-
node_modules/*
295-
**/vendor/*.js
296-
```
306+
node_modules/*
307+
**/vendor/*.js

0 commit comments

Comments
 (0)