-
-
Notifications
You must be signed in to change notification settings - Fork 482
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
feat: no useless undefined initialization #2591
feat: no useless undefined initialization #2591
Conversation
CodSpeed Performance ReportMerging #2591 will not alter performanceComparing Summary
|
/// A variable that is declared and not initialized to any value automatically gets the value of undefined. | ||
/// It’s considered a best practice to avoid initializing variables to undefined. | ||
/// | ||
/// Source: https://eslint.org/docs/latest/rules/no-undef-init |
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 line isn't needed, the code automation takes care of it
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 you mean the whole block you highlighted or just the Source
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.
I think Ema means the source
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.
Okay thanks, I'll remove it!
use crate::JsRuleAction; | ||
|
||
declare_rule! { | ||
/// Disallow initializing variables to undefined. |
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.
/// Disallow initializing variables to undefined. | |
/// Disallow initializing variables to `undefined`. |
/// A variable that is declared and not initialized to any value automatically gets the value of undefined. | ||
/// It’s considered a best practice to avoid initializing variables to undefined. |
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.
/// A variable that is declared and not initialized to any value automatically gets the value of undefined. | |
/// It’s considered a best practice to avoid initializing variables to undefined. | |
/// A variable that is declared and not initialized to any value automatically gets the value of `undefined`. | |
/// It’s considered a best practice to avoid initializing variables to `undefined`. |
/// ```js,expect_diagnostic | ||
/// var a = undefined; | ||
/// | ||
/// let b = undefined, c = 1, d = undefined; | ||
/// | ||
/// for (let i = 0; i < 100; i++) { | ||
/// let i = undefined; | ||
/// } | ||
/// ``` |
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.
I am not sure if you read the contribution guide. Each snippet must emit only one diagnostic.
/// ```js,expect_diagnostic | |
/// var a = undefined; | |
/// | |
/// let b = undefined, c = 1, d = undefined; | |
/// | |
/// for (let i = 0; i < 100; i++) { | |
/// let i = undefined; | |
/// } | |
/// ``` | |
/// ```js,expect_diagnostic | |
/// var a = undefined; | |
/// ``` | |
/// ```js,expect_diagnostic | |
/// let b = undefined, c = 1, d = undefined; | |
/// ``` | |
/// ```js,expect_diagnostic | |
/// for (let i = 0; i < 100; i++) { | |
/// let i = undefined; | |
/// } | |
/// ``` |
let decl = match declarator { | ||
Ok(item) => item, | ||
_ => continue, | ||
}; | ||
|
||
let initializer = match decl.initializer() { | ||
Some(init) => init, | ||
_ => continue, | ||
}; | ||
|
||
let expression = match initializer.expression() { | ||
Ok(expr) => expr, | ||
_ => continue, | ||
}; |
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.
All of this can be chained using a combination of ok()
, .and_then()
and .map()
.
It that's not possible, you can use the let-else
syntax:
let Ok(decl) = declarator else {
continue
}
rule_category!(), | ||
state.1, | ||
markup! { | ||
"It's not necessary to initialize "<Emphasis>{state.0}</Emphasis>" to undefined" |
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.
"It's not necessary to initialize "<Emphasis>{state.0}</Emphasis>" to undefined" | |
"It's not necessary to initialize "<Emphasis>{state.0}</Emphasis>" to undefined." |
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.
I am not sure what's your plan about the rule, but these cases are incomplete. We miss cases with comments attached to the tokens we're removing, e.g.:
let a = undefined/**/ ;
let a = /**/undefined/**/ ;
let a
/**/= /**/undefined/**/ ;
The last declaration is tricky.
We need to decide what to do with these comments and where to place them. We can also choose to remove them. If we decide to remove some of them, we need to document it.
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.
Right, my bad, these are not cases I considered unfortunately.
If I understand correctly if I decide to remove comments I should also change fix_kind
into Unsafe
and document it.
I read on Eslint's rule tests sources, though, that they don't auto-fix the rule if there are comments attached.
Would you like it to replicate Eslint's behaviour?
EDIT: Removing comments would also require to change the Applicability
on the JsRuleAction
return right?
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.
We have a different point of view on the code actions. We think code actions should be safe/unsafe regardless of how the code is, because this gives certain and correct expectations to the user.
A code action can start as unsafe and then become safe once we are certain it covers most cases.
Now, this is a nursery rule, so we are allowed to ship a half-baked rule, and then make new PRs to improve it. My suggestion to you, is to cut short the scope of the PR and handle comments in another PR. It will be less overwhelming for now and it gives you time to think about what we want to do.
FYI, we have rules that remove comments and are safe, as long as removing comments makes sense.
EDIT: Removing comments would also require to change the Applicability on the JsRuleAction return right?
Yes, we would need to change the metadata fix_kind
and Applicability
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.
Okay I'll take your suggestion and will modify fix_kind
and Applicability
and I'll also add comments and a single diagnostic on this case to make it clear.
Thank you for the explanation!
One more thing: remember to run |
Hi @ematipico thanks for the feedbacks, I read the contribution guide and ran |
/// It’s considered a best practice to avoid initializing variables to `undefined`. | ||
/// | ||
/// Please note that any inline comments attached to the initialization value or variable will be removed on auto-fix. | ||
/// Please be also aware that this differs from Eslint's behaviour and we are still discussing on how to properly handle this 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.
No need for this line, the website will take care of it for you
pub NoUselessUndefinedInitialization { | ||
version: "next", | ||
name: "noUselessUndefinedInitialization", | ||
sources: &[RuleSource::Eslint("no-undef-init")], |
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.
Since we decided to diverge, you need to add a new metadata called source_kind
and mark it as Inspired
. Look how other rules do it
|
||
2 │ inline2 = undefined; | ||
3 │ let test1, | ||
> 4 │ test2 = undefined; |
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.
The diagnostic is highlighting the whole assignment, but it's incorrect. The diagnostic should only align starting from =
.
You can use the state of the rule to save the TextRange
of the right hand side of the assignment
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.
I replicated Eslint's behaviour on this matter (Eslint's Playground).
If you think we should diverge on this too, I'll gladly change the logic for the diagnostic!
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.
We don't usually check their diagnostics, our "parity" is around the logic of the rule (what to trigger and what to not trigger).
Code fixes and diagnostics are our own :)
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.
Oh okay, thanks!
I'll change it in order to make the diagnostic start from the =
token!
Some(JsRuleAction { | ||
category: ActionCategory::QuickFix, | ||
applicability: Applicability::MaybeIncorrect, | ||
message: markup! { "Remove undefined initialization" }.to_owned(), |
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.
message: markup! { "Remove undefined initialization" }.to_owned(), | |
message: markup! { "Remove undefined initialization." }.to_owned(), |
.map(|decl| decl.ok())? | ||
.and_then(|declarator| declarator.initializer())?; | ||
|
||
let mut mutation = declarators.begin(); |
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 shouldn't create a mutation this way.
let mut mutation = ctx.root().begin();
Can't remember the exact syntax. I suggest you look at other rules
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.
Thank you, @lutaok, for your contribution! ❤️
Summary
Closes #2533.
Implements eslint
no-undef-init
rule with similar diagnostic and quick action.EDIT: After some explanation on the quick action behaviour, I decided to make it diverge from Eslint's behaviour.
My implementation removes any inline comments attached to the initialization clause (variable or value)
I added some line of comments to document it and a test case that shows this behaviour.
Test Plan
Added snapshots, valid and invalid cases.