-
-
Notifications
You must be signed in to change notification settings - Fork 192
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
Feature/warn missing fallthrough #1094
Feature/warn missing fallthrough #1094
Conversation
…ly introduced diag E0423
Hash_Set<String8_View> cases; | ||
while (keep_going) { | ||
auto is_comment_line = [&]() -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@strager the function to find the comment, is_comment_line
skips all whitespace, which is similar to the lexer function. However, the lexer handles many more cases of characters. Do you think all those cases should be included within is_comment_line
as well ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must fix: We should not repeat comment-parsing code like you have done here.
I see two options:
- Call
Lexer
's code directly instead of reimplementing or copy-pasting it here; or - Teach the lexer to communicate when a comment was seen before a token.
We should try the latter solution as I think it will be more reliable and useful. I think adding abool has_leading_comment;
into theToken
class should be simple enough for our needs.Token::has_leading_newline
already exists, and the code forhas_leading_comment
might be very similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. I went with the suggestion of adding has_leading_comment
and it worked very well 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must fix: The following code should not report any diagnostic, but your code reports several diagnostics for it:
function f(a) {
for (let c in a) {
switch (c) {
case 1:
return 42;
case 2:
break;
case 3:
continue;
case 4:
throw new Error();
case 5:
case 6:
if (c > 5) {
return 1;
} else {
return 2;
}
case 7:
}
}
}
Fixing most of these cases should be easy, but the if
looks challenging. For now, I think it's fine if we allow some false negatives by turning off the diagnostic if we see an if
, for
, while
, try
, etc.
@@ -759,6 +759,13 @@ struct Diag_Duplicated_Cases_In_Switch_Statement { | |||
Source_Code_Span duplicated_switch_case; | |||
}; | |||
|
|||
struct Diag_Explicit_Fallthrough_Comment_In_Switch { | |||
[[qljs::diag("E0423", Diagnostic_Severity::warning)]] // | |||
[[qljs::message("missing fallthrough comment", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor: This message and the message in docs/errors/E0423.md should match.
@@ -2766,15 +2766,51 @@ void Parser::parse_and_visit_switch(Parse_Visitor_Base &v) { | |||
|
|||
bool keep_going = true; | |||
bool is_before_first_switch_case = true; | |||
Token prev_token; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think this variable's name is bad. prev_token
does not refer to the token just before the current token; it refers to a prior case
or default
token or the first token of a statement.
Maybe previous_statement_first_token
is a better name?
@@ -759,6 +759,13 @@ struct Diag_Duplicated_Cases_In_Switch_Statement { | |||
Source_Code_Span duplicated_switch_case; | |||
}; | |||
|
|||
struct Diag_Explicit_Fallthrough_Comment_In_Switch { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: We normally name the diagnostic struct after the problem, not the solution. Therefore this should be named something like Diag_Fallthrough_Without_Comment_In_Switch
.
u8"switch(cond1){case 1:\nfoo()\ncase 2:\nlongBarFn()\ndefault:}"_sv, // | ||
u8" ^^^^^^^^^ Diag_Explicit_Fallthrough_Comment_In_Switch"_diag, // | ||
u8" ^^^ Diag_Explicit_Fallthrough_Comment_In_Switch"_diag); | ||
// check for false positive |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Hash_Set<String8_View> cases; | ||
while (keep_going) { | ||
auto is_comment_line = [&]() -> bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must fix: We should not repeat comment-parsing code like you have done here.
I see two options:
- Call
Lexer
's code directly instead of reimplementing or copy-pasting it here; or - Teach the lexer to communicate when a comment was seen before a token.
We should try the latter solution as I think it will be more reliable and useful. I think adding abool has_leading_comment;
into theToken
class should be simple enough for our needs.Token::has_leading_newline
already exists, and the code forhas_leading_comment
might be very similar.
test/test-parse-warning.cpp
Outdated
u8"switch(cond1){case 1:\nfoo()\ncase 2:\nbar() //fallthrough\ndefault:}"_sv, // | ||
u8" ^^^ Diag_Explicit_Fallthrough_Comment_In_Switch"_diag); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must fix: I think the diagnostic is in the wrong place. As a user, I would expect the diagnostic to be between the statement and the following case
, not at the beginning of the statement. In other words, the diagnostic should point me to where I should fix the problem. For example, the diagnostic could be just before case
:
u8"switch(cond1){case 1:\nfoo()\ncase 2:\nbar() //fallthrough\ndefault:}"_sv, //
u8" ` Diag_Explicit_Fallthrough_Comment_In_Switch"_diag);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. I went with the suggestion of adding has_leading_comment
👍
Also switched the diag to be just before case
or default
.
docs/errors/E0423.md
Outdated
@@ -0,0 +1,29 @@ | |||
# E0423: missing fallthrough comment in switch case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"missing fallthrough comment" is good, but if the programmer intended to write a 'break', this diagnostic message is misleading.
Maybe this is a better message:
missing 'break;' or '// fallthrough' comment between statement and 'case'
For reference, here is the message from ESLint's no-fallthrough rule:
Expected a 'break' statement before 'case'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
Switched to missing 'break;' or '// fallthrough' comment between statement and 'case'
FYI: Also had to switch diag code to E0427 since E0423 was used recently.
Thanks for the pull request. I think you're on the right track. There are a lot of things to consider, though, which is one reason I didn't mark the task as for-hire or good-first-issue. |
Thank you for the review. All comments were very clear and concise so I could make the changes easily. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must fix: Your patch introduces a false positive diagnostic in the example for E0140:
function getText(node) {
switch (node.nodeType) {
case document.TEXT_NODE:
return node.nodeValue;
case: {
let result = "";
for (let child of node.childNodes) {
result += getText(child, document);
}
return result;
}
default:
throw new Error("Unsupported DOM node type");
}
}
docs/errors/E0427.md
Outdated
foo(); | ||
default: | ||
bar(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These examples assume that globals foo
and bar
exist, but they don't. This causes warning E0057 ('use of undeclared variable: foo').
To fix this, I suggest writing console.log
calls instead. (The console
global should exist in these examples.)
Hash_Set<String8_View> cases; | ||
auto is_valid_end_of_case = [](Token_Type tk) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Nice helper function.
@@ -2824,6 +2843,13 @@ void Parser::parse_and_visit_switch(Parse_Visitor_Base &v) { | |||
break; | |||
|
|||
case Token_Type::kw_case: { | |||
if (!is_before_first_switch_case && | |||
!is_valid_end_of_case(previous_statement_first_token.type) && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GCC reports a warning because it thinks previous_statement_first_token
might be uninitialized here.
To fix the warning, I think making previous_statement_first_token
a std::optional
is a decent solution.
@@ -263,6 +263,7 @@ TEST_F(Test_Parse, asi_between_expression_statement_and_switch_label) { | |||
switch (x) { | |||
case a: | |||
f() | |||
//fallthrough |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
If you want, I can fix the remaining issues. Let me know. |
I addressed the remaining issues. Thanks! |
Landed as commit 422f3b4. 👍 |
Add and report a diagnostic for missing fallthrough within switch statements.
Fixes #1081