Skip to content

Enhancement: [prefer-nullish-coalescing] should allow if statement assignment (??=) to be optional #10995

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

Closed
4 tasks done
asselin opened this issue Mar 25, 2025 · 14 comments · Fixed by #11000
Closed
4 tasks done
Labels
accepting prs Go ahead, send a pull request that resolves this issue enhancement: plugin rule option New rule option for an existing eslint-plugin rule locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin

Comments

@asselin
Copy link

asselin commented Mar 25, 2025

Before You File a Proposal Please Confirm You Have Done The Following...

My proposal is suitable for this project

  • I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).

Link to the rule's documentation

https://typescript-eslint.io/rules/prefer-nullish-coalescing/

Description

This #10829 recent enhancement makes the rule check and recommend nullish coalescing for if() statements. IMO, this is not simpler to read, and won't include this to my organization's set of coding rules. The other recommendations from this rule are useful, and I'd like to keep it enabled, but this rule doesn't have a way to turn off the recommendation for the if() case specifically.

Please add an option to disable nullish coalescing checks for if() statements. Suggested name: ignoreIfStatements

Fail

// This code fails by default with the new version of this rule

if (!foo) {
  foo = callAFunctionToGetAValue();
}

Pass

// This code should PASS if the new option (suggested name: ignoreIfStatements) is set to TRUE

if (!foo) {
  foo = callAFunctionToGetAValue();
}

Additional Info

No response

@asselin asselin added enhancement: plugin rule option New rule option for an existing eslint-plugin rule package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin triage Waiting for team members to take a look labels Mar 25, 2025
@bobber205

This comment has been minimized.

@kirkwaiblinger
Copy link
Member

Hi @asselin ! Yep, that's a good point, this should be configurable with an option, like the rest of the checks in this rule.

@bobber205 I understand mistakes can be frustrating, but please avoid jumping straight to language like calling things "insane". This was nothing but a simple oversight which is easy to rectify. We, meaning us on the team as well as the generous community contributors who pitch and implement features, do our best to get things right the first time. But, sometimes things slip through the cracks, and we are grateful for community feedback such as this issue report to find out about them so that we can fix them.

@kirkwaiblinger kirkwaiblinger added accepting prs Go ahead, send a pull request that resolves this issue and removed triage Waiting for team members to take a look labels Mar 25, 2025
@kirkwaiblinger
Copy link
Member

cc @OlivierZal

@JoshuaKGoldberg
Copy link
Member

JoshuaKGoldberg commented Mar 25, 2025

I'm definitely +1 on giving an option for this, but am surprised at the immediate and strong pushback about how undesirable enforcing ??= over if is. Could someone please post a few "before and after" code snippets with an explanation of why they're so stylistically inferior?

This is not a request to engage angrily or debate, I just want to understand 🙂

@OlivierZal
Copy link
Contributor

@kirkwaiblinger, I can take it.

Do we go with an opt-in or an opt-out? Opt-in is less breaking-change.

@kirkwaiblinger
Copy link
Member

opt-in or an opt-out? Opt-in is less breaking-change.

Personally, I'd say just opt-out because

  • we're already here now
  • the rest of the options are opt-out

But not strongly opinionated, either way is fine by me 👍

@kirkwaiblinger
Copy link
Member

On the naming, the proposed ignoreIfStatements is also used in the optional chaining corollary PR, so I think let's definitely use that name.

@bradzacher
Copy link
Member

if (!foo) {
  foo = callAFunctionToGetAValue();
}

// vs

foo ??= callAFunctionToGetAValue();

🤷 Looks clearer and cleaner to me but hey it's a stylistic decision I guess.

@asselin
Copy link
Author

asselin commented Mar 25, 2025

I'm good with opt-out. I expect when upgrading eslint that there will be issues to deal with, either fixing code, or shutting off new rules / adding options to opt out of new behavior.

Thank you @kirkwaiblinger, @OlivierZal, @JoshuaKGoldberg, @bradzacher for all your work on eslint!

@asselin
Copy link
Author

asselin commented Mar 25, 2025

I'm definitely +1 on giving an option for this, but am surprised at the immediate and strong pushback about how undesirable enforcing ??= over if is. Could someone please post a few "before and after" code snippets with an explanation of why they're so stylistically inferior?

This is not a request to engage angrily or debate, I just want to understand 🙂

You asked for it, now you pay the penalty of reading my ramblings about how I read and understand code :)

When I read:

if (!foo) {
  foo = callAFunctionToGetAValue();
}

My internal dialog is "If not foo, then foo equals .......", which immediately triggers "this is setting a default". "??=" is a shorthand that I still need to mentally expand to understand what it's doing:

foo ??= callAFunctionToGetAValue();

"Foo question-question-equals callAFunctionToGetAValue.... that's the same as foo equals foo question-question callAFunctionToGetAValue". Then I have to picture that in my mind:

foo = foo ?? callAFunctionToGetAValue();

Then go back and start parsing again: "foo equals foo or* callAFunctionToGetAValue. ... ok, that's setting a default". "or*" is my mental "this is almost or (||) but not exactly... remember that in case it's important"

I think "??" is now getting to a point of general adoption with the vast population of JS/TS devs, and this rule (particularly with autofixes) helps drive it. But I don't think "??=" is there yet. Part of the problem may be that there's no good name to use when reading "??" or "??=" (contrast with e.g. "plus-equals" for +=, or "or-equals" for ||=). I also don't think just recognizing "??=" as a pattern and internalizing it as an idiom is widespread yet. So I would expect many other devs are going to go through the same mental expansion process, which is of course much slower than speed reading through an if statement.

My 2c

@bradzacher
Copy link
Member

Nitpick about the equivalence.

foo ??= callAFunctionToGetAValue();

// equivalent to

foo ?? (foo = callAFunctionToGetAValue());

This is an minor distinction from your interpretation - your interpretation always does an assignment, regardless of if foo is nullish. Whereas this representation only does the assignment if foo is nullish. A slight perf optimisation and also ensures that setters aren't triggered unless the value changes.

@neilenns

This comment has been minimized.

@JavaScriptBach
Copy link
Contributor

FWIW the new behavior of this rule was also polarizing at my company, so having an option to disable it sounds good. However, personally I don't think that the current behavior makes code any stylistically worse, and certainly not significantly worse. If one is willing to use ??, then it doesn't seem like ??= requires much additional cognitive overhead. I was not familiar with ??= before the latest release, but by the time I'd finished fixing the lint errors in my codebase, I felt like it was slightly more readable than if statements with nesting.

@kirkwaiblinger
Copy link
Member

Personally, I like that you only have to specify the assignee once 🤷‍♂

if (foo.bar == null) {
   foo.bat = 'typo, whoops';
}

// vs

foo.bar ??= 'all good';

but we're all on the same page that it's totally subjective

@github-actions github-actions bot added the locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. label Apr 8, 2025
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 8, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
accepting prs Go ahead, send a pull request that resolves this issue enhancement: plugin rule option New rule option for an existing eslint-plugin rule locked due to age Please open a new issue if you'd like to say more. See https://typescript-eslint.io/contributing. package: eslint-plugin Issues related to @typescript-eslint/eslint-plugin
Projects
None yet
8 participants