Skip to content
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

Merged

Conversation

lutaok
Copy link
Contributor

@lutaok lutaok commented Apr 24, 2024

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.

@github-actions github-actions bot added A-CLI Area: CLI A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Diagnostic Area: diagnostocis labels Apr 24, 2024
@lutaok lutaok changed the title Feature/no useless undefined initialization feat: no useless undefined initialization Apr 24, 2024
Copy link

codspeed-hq bot commented Apr 24, 2024

CodSpeed Performance Report

Merging #2591 will not alter performance

Comparing lutaok:feature/no-useless-undefined-initialization (0080714) with main (ce4979d)

Summary

✅ 90 untouched benchmarks

/// 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
Copy link
Member

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

Copy link
Contributor Author

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?

Copy link
Member

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.

Copy link
Contributor Author

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Disallow initializing variables to undefined.
/// Disallow initializing variables to `undefined`.

Comment on lines 15 to 16
/// 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// 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`.

Comment on lines 24 to 32
/// ```js,expect_diagnostic
/// var a = undefined;
///
/// let b = undefined, c = 1, d = undefined;
///
/// for (let i = 0; i < 100; i++) {
/// let i = undefined;
/// }
/// ```
Copy link
Member

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.

Suggested change
/// ```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;
/// }
/// ```

Comment on lines 69 to 82
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,
};
Copy link
Member

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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"It's not necessary to initialize "<Emphasis>{state.0}</Emphasis>" to undefined"
"It's not necessary to initialize "<Emphasis>{state.0}</Emphasis>" to undefined."

Copy link
Member

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.

Copy link
Contributor Author

@lutaok lutaok Apr 25, 2024

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?

Copy link
Member

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

Copy link
Contributor Author

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!

@ematipico
Copy link
Member

One more thing: remember to run just gen-lint to generate some missing code files. It's all written in the code contribution guide.

@lutaok
Copy link
Contributor Author

lutaok commented Apr 25, 2024

One more thing: remember to run just gen-lint to generate some missing code files. It's all written in the code contribution guide.

Hi @ematipico thanks for the feedbacks, I read the contribution guide and ran just ready which should have included just gen-lint.
I'm looking forward to change what you point out and I'm sorry if I missed something from the guide.
I'm not familiar with many of these concepts nor with Rust.
Thanks again for the time you put into the review 😃

@lutaok lutaok requested a review from ematipico April 25, 2024 11:13
/// 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.
Copy link
Member

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")],
Copy link
Member

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;
Copy link
Member

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

Copy link
Contributor Author

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!

Copy link
Member

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 :)

Copy link
Contributor Author

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(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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();
Copy link
Member

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

@lutaok lutaok requested a review from ematipico April 26, 2024 09:34
Copy link
Member

@ematipico ematipico left a 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! ❤️

@ematipico ematipico merged commit a13d36d into biomejs:main Apr 26, 2024
12 checks passed
@Conaclos Conaclos added the A-Changelog Area: changelog label Apr 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Changelog Area: changelog A-CLI Area: CLI A-Diagnostic Area: diagnostocis A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages
Projects
None yet
Development

Successfully merging this pull request may close these issues.

📎 Implement noUselessUndefinedInitialization - eslint/no-undef-init
3 participants