Skip to content

Commit b4f8329

Browse files
authored
fix: ignore directives for no-fallthrough (#16757)
Fallthrough comment pattern detection was incorrectly matching to eslint directives. Fixes #16650
1 parent 5981296 commit b4f8329

File tree

6 files changed

+73
-5
lines changed

6 files changed

+73
-5
lines changed

docs/src/rules/no-fallthrough.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ switch(foo) {
3232
}
3333
```
3434

35-
That works fine when you don't want a fallthrough, but what if the fallthrough is intentional, there is no way to indicate that in the language. It's considered a best practice to always indicate when a fallthrough is intentional using a comment which matches the `/falls?\s?through/i` regular expression:
35+
That works fine when you don't want a fallthrough, but what if the fallthrough is intentional, there is no way to indicate that in the language. It's considered a best practice to always indicate when a fallthrough is intentional using a comment which matches the `/falls?\s?through/i` regular expression but isn't a directive:
3636

3737
```js
3838
switch(foo) {
@@ -169,7 +169,7 @@ Note that the last `case` statement in these examples does not cause a warning b
169169

170170
This rule has an object option:
171171

172-
* Set the `commentPattern` option to a regular expression string to change the test for intentional fallthrough comment.
172+
* Set the `commentPattern` option to a regular expression string to change the test for intentional fallthrough comment. If the fallthrough comment matches a directive, that takes precedence over `commentPattern`.
173173

174174
* Set the `allowEmptyCase` option to `true` to allow empty cases regardless of the layout. By default, this rule does not require a fallthrough comment after an empty `case` only if the empty `case` and the next `case` are on the same line or on consecutive lines.
175175

lib/linter/linter.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ const
1818
merge = require("lodash.merge"),
1919
pkg = require("../../package.json"),
2020
astUtils = require("../shared/ast-utils"),
21+
{
22+
directivesPattern
23+
} = require("../shared/directives"),
2124
{
2225
Legacy: {
2326
ConfigOps,
@@ -377,7 +380,7 @@ function getDirectiveComments(ast, ruleMapper, warnInlineConfig) {
377380
ast.comments.filter(token => token.type !== "Shebang").forEach(comment => {
378381
const { directivePart, justificationPart } = extractDirectiveComment(comment.value);
379382

380-
const match = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u.exec(directivePart);
383+
const match = directivesPattern.exec(directivePart);
381384

382385
if (!match) {
383386
return;

lib/rules/no-fallthrough.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,28 @@
44
*/
55
"use strict";
66

7+
//------------------------------------------------------------------------------
8+
// Requirements
9+
//------------------------------------------------------------------------------
10+
11+
const { directivesPattern } = require("../shared/directives");
12+
713
//------------------------------------------------------------------------------
814
// Helpers
915
//------------------------------------------------------------------------------
1016

1117
const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu;
1218

19+
/**
20+
* Checks whether or not a given comment string is really a fallthrough comment and not an ESLint directive.
21+
* @param {string} comment The comment string to check.
22+
* @param {RegExp} fallthroughCommentPattern The regular expression used for checking for fallthrough comments.
23+
* @returns {boolean} `true` if the comment string is truly a fallthrough comment.
24+
*/
25+
function isFallThroughComment(comment, fallthroughCommentPattern) {
26+
return fallthroughCommentPattern.test(comment) && !directivesPattern.test(comment.trim());
27+
}
28+
1329
/**
1430
* Checks whether or not a given case has a fallthrough comment.
1531
* @param {ASTNode} caseWhichFallsThrough SwitchCase node which falls through.
@@ -25,14 +41,14 @@ function hasFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f
2541
const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]);
2642
const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop();
2743

28-
if (commentInBlock && fallthroughCommentPattern.test(commentInBlock.value)) {
44+
if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) {
2945
return true;
3046
}
3147
}
3248

3349
const comment = sourceCode.getCommentsBefore(subsequentCase).pop();
3450

35-
return Boolean(comment && fallthroughCommentPattern.test(comment.value));
51+
return Boolean(comment && isFallThroughComment(comment.value, fallthroughCommentPattern));
3652
}
3753

3854
/**

lib/shared/directives.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @fileoverview Common utils for directives.
3+
*
4+
* This file contains only shared items for directives.
5+
* If you make a utility for rules, please see `../rules/utils/ast-utils.js`.
6+
*
7+
* @author gfyoung <https://github.com/gfyoung>
8+
*/
9+
"use strict";
10+
11+
const directivesPattern = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u;
12+
13+
module.exports = {
14+
directivesPattern
15+
};

tests/lib/linter/linter.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4189,6 +4189,27 @@ var a = "test2";
41894189
assert.strictEqual(suppressedMessages[0].ruleId, "no-alert");
41904190
});
41914191

4192+
it("reports no problems for no-fallthrough despite comment pattern match", () => {
4193+
const code = "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }";
4194+
const config = {
4195+
reportUnusedDisableDirectives: true,
4196+
rules: {
4197+
"no-fallthrough": 2
4198+
}
4199+
};
4200+
4201+
const messages = linter.verify(code, config, {
4202+
filename,
4203+
allowInlineConfig: true
4204+
});
4205+
const suppressedMessages = linter.getSuppressedMessages();
4206+
4207+
assert.strictEqual(messages.length, 0);
4208+
4209+
assert.strictEqual(suppressedMessages.length, 1);
4210+
assert.strictEqual(suppressedMessages[0].ruleId, "no-fallthrough");
4211+
});
4212+
41924213
describe("autofix", () => {
41934214
const alwaysReportsRule = {
41944215
create(context) {

tests/lib/rules/no-fallthrough.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ ruleTester.run("no-fallthrough", rule, {
6363
"switch (foo) { case 0: try {} finally { break; } default: b(); }",
6464
"switch (foo) { case 0: try { throw 0; } catch (err) { break; } default: b(); }",
6565
"switch (foo) { case 0: do { throw 0; } while(a); default: b(); }",
66+
"switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }",
6667
{
6768
code: "switch(foo) { case 0: a(); /* no break */ case 1: b(); }",
6869
options: [{
@@ -297,6 +298,18 @@ ruleTester.run("no-fallthrough", rule, {
297298
column: 34
298299
}
299300
]
301+
},
302+
{
303+
code: "switch (foo) { case 0: a(); \n// eslint-enable no-fallthrough\n case 1: }",
304+
options: [{}],
305+
errors: [
306+
{
307+
messageId: "case",
308+
type: "SwitchCase",
309+
line: 3,
310+
column: 2
311+
}
312+
]
300313
}
301314
]
302315
});

0 commit comments

Comments
 (0)