You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* New: Initial suggestions support in the linter
* Docs: Update some docs with info about suggestions
* Chore: Add some tests for suggestions
* New: Add support for suggestion messageId-based descriptions
* New: WIP add suggestions for no-useless-escape
* Chore: Update no-useless-escape suggestions to use messageIds
* New: Support output option for rule-tester tests of suggestions
* Chore: Respond to PR feedback
* Chore: Update rule-tester suggestion testing capabilities and tests
* Fix: Bug in no-useless-escape suggestions
* Chore: Use messageId more for suggestion example
* Chore: Update rule-tester suggestion support
* Chore: Remove rule-tester support for testing fix object
Copy file name to clipboardExpand all lines: docs/developer-guide/nodejs-api.md
+43-2Lines changed: 43 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -108,7 +108,7 @@ The most important method on `Linter` is `verify()`, which initiates linting of
108
108
*`preprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins.md#processors-in-plugins) documentation describes as the `preprocess` method.
109
109
*`postprocess` - (optional) A function that [Processors in Plugins](/docs/developer-guide/working-with-plugins.md#processors-in-plugins) documentation describes as the `postprocess` method.
110
110
*`filterCodeBlock` - (optional) A function that decides which code blocks the linter should adopt. The function receives two arguments. The first argument is the virtual filename of a code block. The second argument is the text of the code block. If the function returned `true` then the linter adopts the code block. If the function was omitted, the linter adopts only `*.js` code blocks. If you provided a `filterCodeBlock` function, it overrides this default behavior, so the linter doesn't adopt `*.js` code blocks automatically.
111
-
*`disableFixes` - (optional) when set to `true`, the linter doesn't make the `fix` property of the lint result.
111
+
*`disableFixes` - (optional) when set to `true`, the linter doesn't make either the `fix` or `suggestions` property of the lint result.
112
112
*`allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules.
113
113
*`reportUnusedDisableDirectives` - (optional) when set to `true`, adds reported errors for unused `eslint-disable` directives when no problems would be reported in the disabled area anyway.
114
114
@@ -170,6 +170,7 @@ The information available for each linting message is:
170
170
*`endColumn` - the end column of the range on which the error occurred (this property is omitted if it's not range).
171
171
*`endLine` - the end line of the range on which the error occurred (this property is omitted if it's not range).
172
172
*`fix` - an object describing the fix for the problem (this property is omitted if no fix is available).
173
+
*`suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](./working-with-rules.md#providing-suggestions)).
173
174
174
175
Linting message objects have a deprecated `source` property. This property **will be removed** from linting messages in an upcoming breaking release. If you depend on this property, you should now use the `SourceCode` instance provided by the linter.
175
176
@@ -437,9 +438,23 @@ The return value is an object containing the results of the linting operation. H
437
438
column:13,
438
439
nodeType:"ExpressionStatement",
439
440
fix: { range: [12, 12], text:";" }
441
+
}, {
442
+
ruleId:"no-useless-escape",
443
+
severity:1,
444
+
message:"disallow unnecessary escape characters",
445
+
line:1,
446
+
column:10,
447
+
nodeType:"ExpressionStatement",
448
+
suggestions: [{
449
+
desc:"Remove unnecessary escape. This maintains the current functionality.",
450
+
fix: { range: [9, 10], text:"" }
451
+
}, {
452
+
desc:"Escape backslash to include it in the RegExp.",
453
+
fix: { range: [9, 9], text:"\\" }
454
+
}]
440
455
}],
441
456
errorCount:1,
442
-
warningCount:0,
457
+
warningCount:1,
443
458
fixableErrorCount:1,
444
459
fixableWarningCount:0,
445
460
source:"\"use strict\"\n"
@@ -865,6 +880,7 @@ In addition to the properties above, invalid test cases can also have the follow
865
880
*`column` (number): The 1-based column number of the reported location
866
881
*`endLine` (number): The 1-based line number of the end of the reported location
867
882
*`endColumn` (number): The 1-based column number of the end of the reported location
883
+
*`suggestions` (array): An array of objects with suggestion details to check. See [Testing Suggestions](#testing-suggestions) for details
868
884
869
885
If a string is provided as an error instead of an object, the string is used to assert the `message` of the error.
870
886
*`output` (string, optional): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null`, asserts that none of the reported problems suggest autofixes.
@@ -880,6 +896,31 @@ Any additional properties of a test case will be passed directly to the linter a
880
896
881
897
If a valid test case only uses the `code` property, it can optionally be provided as a string containing the code, rather than an object with a `code` key.
882
898
899
+
#### Testing Suggestions
900
+
901
+
Suggestions can be tested by defining a `suggestions` key on an errors object. The options to check for the suggestions are the following (all are optional):
902
+
* `desc` (string): The suggestion `desc` value
903
+
* `messageId` (string): The suggestion `messageId` value for suggestions that use `messageId`s
904
+
* `output` (string): A code string representing the result of applying the suggestion fix to the input code
905
+
906
+
Example:
907
+
908
+
```js
909
+
ruleTester.run("my-rule-for-no-foo", rule, {
910
+
valid: [],
911
+
invalid: [{
912
+
code:"var foo;",
913
+
errors: [{
914
+
suggestions: [{
915
+
desc:"Rename identifier 'foo' to 'bar'",
916
+
messageId:"renameFoo",
917
+
output:"var bar;"
918
+
}]
919
+
}]
920
+
}]
921
+
})
922
+
```
923
+
883
924
### Customizing RuleTester
884
925
885
926
`RuleTester` depends on two functions to run tests: `describe` and `it`. These functions can come from various places:
Copy file name to clipboardExpand all lines: docs/developer-guide/unit-tests.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -12,10 +12,10 @@ This automatically starts Mocha and runs all tests in the `tests` directory. You
12
12
13
13
If you want to quickly run just one test, you can do so by running Mocha directly and passing in the filename. For example:
14
14
15
-
npm test:cli tests/lib/rules/no-wrap-func.js
15
+
npm run test:cli tests/lib/rules/no-wrap-func.js
16
16
17
17
Running individual tests is useful when you're working on a specific bug and iterating on the solution. You should be sure to run `npm test` before submitting a pull request.
18
18
19
19
## More Control on Unit Testing
20
20
21
-
`npm test:cli` is an alias of the Mocha cli in `./node_modules/.bin/mocha`. [Options](https://mochajs.org/#command-line-usage) are available to be provided to help to better control the test to run.
21
+
`npm run test:cli` is an alias of the Mocha cli in `./node_modules/.bin/mocha`. [Options](https://mochajs.org/#command-line-usage) are available to be provided to help to better control the test to run.
Copy file name to clipboardExpand all lines: docs/developer-guide/working-with-rules.md
+74Lines changed: 74 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -62,6 +62,7 @@ The source file for a rule exports an object with the following properties.
62
62
*`category` (string) specifies the heading under which the rule is listed in the [rules index](../rules/)
63
63
*`recommended` (boolean) is whether the `"extends": "eslint:recommended"` property in a [configuration file](../user-guide/configuring.md#extending-configuration-files) enables the rule
64
64
*`url` (string) specifies the URL at which the full documentation can be accessed
65
+
*`suggestion` (boolean) specifies whether rules can return suggestions (defaults to false if omitted)
65
66
66
67
In a custom rule or plugin, you can omit `docs` or include any properties that you need in it.
67
68
@@ -344,6 +345,79 @@ Best practices for fixes:
344
345
345
346
* This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](/docs/rules/quotes.md) rule.
346
347
348
+
### Providing Suggestions
349
+
350
+
In some cases fixes aren't appropriate to be automatically applied, for example, if a fix potentially changes functionality or if there are multiple valid ways to fix a rule depending on the implementation intent (see the best practices for [applying fixes](#applying-fixes) listed above). In these cases, there is an alternative `suggest` option on `context.report()` that allows other tools, such as editors, to expose helpers for users to manually apply a suggestion.
351
+
352
+
In order to provide suggestions, use the `suggest` key in the report argument with an array of suggestion objects. The suggestion objects represent individual suggestions that could be applied and require either a `desc` key string that describes what applying the suggestion would do or a `messageId` key (see [below](#suggestion-messageids)), and a `fix` key that is a function defining the suggestion result. This `fix` function follows the same API as regular fixes (described above in [applying fixes](#applying-fixes)).
desc: "Remove the `\\`. This maintains the current functionality.",
362
+
fix: function(fixer) {
363
+
return fixer.removeRange(range);
364
+
}
365
+
},
366
+
{
367
+
desc: "Replace the `\\` with `\\\\` to include the actual backslash character.",
368
+
fix: function(fixer) {
369
+
return fixer.insertTextBeforeRange(range, "\\");
370
+
}
371
+
}
372
+
]
373
+
});
374
+
```
375
+
376
+
Note: Suggestions will be applied as a stand-alone change, without triggering multipass fixes. Each suggestion should focus on a singular change in the code and should not try to conform to user defined styles. For example, if a suggestion is adding a new statement into the codebase, it should not try to match correct indentation, or confirm to user preferences on presence/absence of semicolumns. All of those things can be corrected by multipass autofix when the user triggers it.
377
+
378
+
Best practices for suggestions:
379
+
380
+
1. Don't try to do too much and suggest large refactors that could introduce a lot of breaking changes.
381
+
1. As noted above, don't try to conform to user-defined styles.
382
+
383
+
#### Suggestion `messageId`s
384
+
385
+
Instead of using a `desc` key for suggestions a `messageId` can be used instead. This works the same way as `messageId`s for the overall error (see [messageIds](#messageIds)). Here is an example of how to use it in a rule:
removeEscape: "Remove the `\\`. This maintains the current functionality.",
393
+
escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
394
+
}
395
+
},
396
+
create: function(context) {
397
+
// ...
398
+
context.report({
399
+
node: node,
400
+
messageId: 'unnecessaryEscape',
401
+
data: { character },
402
+
suggest: [
403
+
{
404
+
messageId: "removeEscape",
405
+
fix: function(fixer) {
406
+
return fixer.removeRange(range);
407
+
}
408
+
},
409
+
{
410
+
messageId: "escapeBackslash",
411
+
fix: function(fixer) {
412
+
return fixer.insertTextBeforeRange(range, "\\");
413
+
}
414
+
}
415
+
]
416
+
});
417
+
}
418
+
};
419
+
```
420
+
347
421
### context.options
348
422
349
423
Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example:
* @property {Object} [data] Optional data to use to fill in placeholders in the
27
27
* message.
28
28
* @property {Function} [fix] The function to call that creates a fix command.
29
+
* @property {Array<{desc?: string, messageId?: string, fix: Function}>} suggest Suggestion descriptions and functions to create a the associated fixes.
* @param {Object} messages Object of meta messages for the rule.
261
+
* @returns {void}
262
+
*/
263
+
functionvalidateSuggestions(suggest,messages){
264
+
if(suggest&&Array.isArray(suggest)){
265
+
suggest.forEach(suggestion=>{
266
+
if(suggestion.messageId){
267
+
const{ messageId }=suggestion;
268
+
269
+
if(!messages){
270
+
thrownewTypeError(`context.report() called with a suggest option with a messageId '${messageId}', but no messages were present in the rule metadata.`);
271
+
}
272
+
273
+
if(!messages[messageId]){
274
+
thrownewTypeError(`context.report() called with a suggest option with a messageId '${messageId}' which is not present in the 'messages' config: ${JSON.stringify(messages,null,2)}`);
275
+
}
276
+
277
+
if(suggestion.desc){
278
+
thrownewTypeError("context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one.");
279
+
}
280
+
}elseif(!suggestion.desc){
281
+
thrownewTypeError("context.report() called with a suggest option that doesn't have either a `desc` or `messageId`");
282
+
}
283
+
284
+
if(typeofsuggestion.fix!=="function"){
285
+
thrownewTypeError(`context.report() called with a suggest option without a fix function. See: ${suggestion}`);
286
+
}
287
+
});
288
+
}
289
+
}
290
+
227
291
/**
228
292
* Returns a function that converts the arguments of a `context.report` call from a rule into a reported
229
293
* problem for the Node.js API.
@@ -242,17 +306,17 @@ module.exports = function createReportTranslator(metadata) {
0 commit comments