This repository has been archived by the owner on Aug 31, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 664
fix(rome_js_analyze): only emit control flow side-effects for variable declarators with an initializer #2870
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use rome_js_syntax::JsVariableStatement; | ||
use rome_rowan::{AstNode, SyntaxResult}; | ||
|
||
use crate::control_flow::{ | ||
visitor::{NodeVisitor, StatementStack}, | ||
FunctionBuilder, | ||
}; | ||
|
||
pub(in crate::control_flow) struct VariableVisitor; | ||
|
||
impl<B> NodeVisitor<B> for VariableVisitor { | ||
type Node = JsVariableStatement; | ||
|
||
fn enter( | ||
node: Self::Node, | ||
builder: &mut FunctionBuilder, | ||
_: StatementStack, | ||
) -> SyntaxResult<Self> { | ||
let declaration = node.declaration()?; | ||
for declarator in declaration.declarators() { | ||
if let Some(initializer) = declarator?.initializer() { | ||
let expr = initializer.expression()?; | ||
builder.append_statement().with_node(expr.into_syntax()); | ||
} | ||
} | ||
|
||
Ok(Self) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
crates/rome_js_analyze/tests/specs/noDeadCode/JsVariableStatement.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
function JsVariableStatement1() { | ||
return; | ||
var variable; | ||
} | ||
|
||
function JsVariableStatement2() { | ||
return; | ||
var variable = initializer(); | ||
} | ||
|
||
function JsVariableStatement3() { | ||
return; | ||
let variable; | ||
} | ||
|
||
function JsVariableStatement4() { | ||
return; | ||
let variable = initializer(); | ||
} | ||
|
||
function JsVariableStatement5() { | ||
return; | ||
const variable = initializer(); | ||
} | ||
|
||
function JsVariableStatement6() { | ||
return; | ||
var variable1 = initializer(), | ||
variable2 = initializer(); | ||
} |
103 changes: 103 additions & 0 deletions
103
crates/rome_js_analyze/tests/specs/noDeadCode/JsVariableStatement.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
--- | ||
source: crates/rome_js_analyze/tests/spec_tests.rs | ||
assertion_line: 98 | ||
expression: JsVariableStatement.js | ||
--- | ||
# Input | ||
```js | ||
function JsVariableStatement1() { | ||
return; | ||
var variable; | ||
} | ||
|
||
function JsVariableStatement2() { | ||
return; | ||
var variable = initializer(); | ||
} | ||
|
||
function JsVariableStatement3() { | ||
return; | ||
let variable; | ||
} | ||
|
||
function JsVariableStatement4() { | ||
return; | ||
let variable = initializer(); | ||
} | ||
|
||
function JsVariableStatement5() { | ||
return; | ||
const variable = initializer(); | ||
} | ||
|
||
function JsVariableStatement6() { | ||
return; | ||
var variable1 = initializer(), | ||
variable2 = initializer(); | ||
} | ||
|
||
``` | ||
|
||
# Diagnostics | ||
``` | ||
warning[noDeadCode]: This code is unreachable | ||
┌─ JsVariableStatement.js:8:20 | ||
│ | ||
7 │ return; | ||
│ ------- This statement will return from the function ... | ||
8 │ var variable = initializer(); | ||
│ ------------- ... before it can reach this code | ||
|
||
|
||
``` | ||
|
||
``` | ||
warning[noDeadCode]: This code is unreachable | ||
┌─ JsVariableStatement.js:18:20 | ||
│ | ||
17 │ return; | ||
│ ------- This statement will return from the function ... | ||
18 │ let variable = initializer(); | ||
│ ------------- ... before it can reach this code | ||
|
||
|
||
``` | ||
|
||
``` | ||
warning[noDeadCode]: This code is unreachable | ||
┌─ JsVariableStatement.js:23:22 | ||
│ | ||
22 │ return; | ||
│ ------- This statement will return from the function ... | ||
23 │ const variable = initializer(); | ||
│ ------------- ... before it can reach this code | ||
|
||
|
||
``` | ||
|
||
``` | ||
warning[noDeadCode]: This code is unreachable | ||
┌─ JsVariableStatement.js:28:21 | ||
│ | ||
27 │ return; | ||
│ ------- This statement will return from the function ... | ||
28 │ var variable1 = initializer(), | ||
│ ------------- ... before it can reach this code | ||
|
||
|
||
``` | ||
|
||
``` | ||
warning[noDeadCode]: This code is unreachable | ||
┌─ JsVariableStatement.js:29:21 | ||
│ | ||
27 │ return; | ||
│ ------- This statement will return from the function ... | ||
28 │ var variable1 = initializer(), | ||
29 │ variable2 = initializer(); | ||
│ ------------- ... before it can reach this code | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was expecting this PR to do something like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is due to a limitation in the Language Server Protocol: when the diagnostic is displayed in the editor only the primary range will be rendered as "unnecessary", and since diagnostics can only have a single primary label the linter needs to emit a new diagnostic for each non-contiguous range of code that's unreachable |
||
|
||
|
||
``` | ||
|
||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This case should throw a diagnostic, even when it gets assigned. Same for
const
.Examples
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.
As I mentioned in the PR description, I expect a diagnostic for this case will be emitted by a future
noUnusedVariable
lint rule based on the semantic analysis instead. Any access to the variable before the return statement is invalid and will emit an error (since reading or writing alet
variable before its declared is not permitted), while any access to the variable coming after the declaration will also be marked as unreachable and should be ignored by thenoUnusedVariable
rule. Since the variable will be marked as unused by the semantic analysis I wanted to avoid a duplicate diagnostic flagging it as unreachable again in the control flow analysis, so the control flow diagnostic only covers the initializer expressionThere 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.
I thought you were just covering the
var
declarations, because also thelet
don't need an initializers. Thank you.Although also
const
would be affected by thenoUnusedVariable
, because it requires a initializer, but they are not hoisted to the gloabl scope. Which means that we could have double diagnostics on the same line.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.
Do we want that? This would imply a rule that needs the
SemanticModel
and theCFG
. This means that we need to check if every reference is reachable or not.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.
There would indeed be two diagnostics on the same line but they would not necessarily overlap:
noDeadCode
will only highlight the initializer expression, andnoUnusedVariable
could highlight only the identifier in the declaration:I don't think it's a requirement for the initial version of
noUnusedVariable
but it does seem like something we'll want to have eventually (and an good use case to exercise the capability of the analyzer infrastructure to orchestrate the execution of rules to fulfill complex query requirements)