Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(eslint-plugin): [prefer-optional-chain] handle cases where the first operands are unrelated to the rest of the chain and add type info#6397

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
bradzacher merged 32 commits intov6from6332-refactor-prefer-optional-chain
Jul 7, 2023

Conversation

bradzacher
Copy link
Member

@bradzacherbradzacher commentedJan 31, 2023
edited
Loading

PR Checklist

Overview

This is a big refactor of the lint rule so that it can be MUCH more robust and handle more cases.

The new algorithm is based off of flattening the logical expression to an array and then iterating over it. This new algorithm allows us to handle cases like this:

if(unrelated&&foo&&foo.bar){...}

The new algorithm also includes a beefy recursive node comparison function which clearly compares two nodes semantically, instead of comparing them loosely based on source code text. This should lead to fewer weird cases, and also allows us to handle cases like this:

if(foo.bar&&foo?.bar.baz){...}if(foo?.bar&&foo.bar.baz){...}

The TL;DR of the algorithm:

  1. Convert the logical chain to a list of logical operators in order
    foo && foo.bar && foo.bar.baz --> [foo, foo.bar, foo.bar.baz]
  2. Classify each operand based on whether or not it's in a form that does a "nullish comparison" that can be converted to optional chain.
    foo != null && foo.bar !== undefined && foo.bar.baz --> [foo<NotEqualNullOrUndefined>, foo.bar<NotStrictEqualUndefined>, foo.bar.baz<Boolean>]
  3. Further analyse the chain looking for pairs likefoo !== null && foo !== undefined and group those as one operand
  4. Analyse the chain looking for contiguous sub-chains of "subset comparisons" (egfoo.bar is a subset offoo.bar.baz.bam, but not offoo.baz)
  5. When a sub-chain ends, report on it.

TODO:

JoshuaKGoldberg, vuki656, and omril1 reacted with rocket emoji
@typescript-eslint
Copy link
Contributor

Thanks for the PR,@bradzacher!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently onhttps://opencollective.com/typescript-eslint.

@nx-cloud
Copy link

nx-cloudbot commentedJan 31, 2023
edited
Loading

☁️ Nx Cloud Report

CI is running/has finished running commands for commit3c387a9. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this branch


✅ Successfully ran 47 targets

Sent with 💌 fromNxCloud.

@netlify
Copy link

netlifybot commentedJan 31, 2023
edited
Loading

Deploy Preview fortypescript-eslint ready!

NameLink
🔨 Latest commit4c36891
🔍 Latest deploy loghttps://app.netlify.com/sites/typescript-eslint/deploys/64a7905efc36a000084beac7
😎 Deploy Previewhttps://deploy-preview-6397--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to yourNetlify site configuration.

@bradzacher
Copy link
MemberAuthor

This is now "check complete" - all existing tests are passing as are the new tests.
Need to rebuild the fixer logic though.

This is going to be a big chunk of the logic to figure out the correct places to put an optional chain 🤔

@bradzacherbradzacher added the enhancement: new plugin ruleNew rule request for eslint-plugin labelApr 17, 2023
@bradzacherbradzacher changed the base branch frommain tov6April 17, 2023 04:42
@nx-cloud
Copy link

nx-cloudbot commentedApr 17, 2023
edited
Loading

☁️ Nx Cloud Report

CI is running/has finished running commands for commit4c36891. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this branch


✅ Successfully ran 31 targets

Sent with 💌 fromNxCloud.

@bradzacher
Copy link
MemberAuthor

@typescript-eslint/triage-team - this is functionally complete in terms of raw logic.
However it is missing the fixer logic because:

  1. frankly it's not a small piece of work to re-implement it.
  2. it's the bit that made the rule unsafe to begin with (x != null && x.y returnsfalse | T, butx?.y returnsT | undefined)

In some future state I can definitely see us including:

  • an autofixer if we detect it's safe (eg if it doesn't change the types), or
  • a suggestion fixer in other cases

But I don't know if it's worth "holding up" this change to re-implement. Happy to do it if we think it's necessary, if not then we can review and land this as is.

@JoshuaKGoldberg
Copy link
Member

JoshuaKGoldberg commentedApr 17, 2023
edited
Loading

😞 I do dislike removing functionality. Maybe we could put this out as an experimental rule without deleting the existing one?

@bradzacher
Copy link
MemberAuthor

Maybe we could put this out as an experimental rule without deleting the existing one?

That would create a worse state, IMO - as we would have two nearly identical rules that are mostly differentiated by having a fixer or not. What would be the value prop for a user to switch to test the rule? We'd need a breaking change to remove the experimental rule name.

Also "experimental" implies instability or that it may not be adopted - but the rule is perfectly stable (passes all the existing tests, and more) andwill be adopted - it just doesn't have the previously available (unsafe) fixer.

armano2 reacted with thumbs up emoji

@JoshuaKGoldberg
Copy link
Member

Yeah that's a good point.

Heh, I'm torn here. Expecting previously (perhaps slightly incorrect) satisfied users to be annoyed by the lack of autofixer.

Given that the autofixer today is unsafe, I think it's reasonable for us to say"we've rewritten and removed it for safety - welcoming community PRs to add it back".

omril1 reacted with confused emoji

@omril1
Copy link
Contributor

It's really sad to see the fixer being removed, my team absolutely loved it when I turned the rule on since we worked on a code-base full of partial objects (and different patterns to deal with them).
I've never even once saw a case of false positive fix of the typeobject | false.
I did see a few false positives returningundefined instead ofnull (from nullish objects) but they only affected tests asserting on null, the real code had falsy checks catching both.

@JoshuaKGoldberg
Copy link
Member

...maybe we could expose the old one under a name like "deprecated"?

@bradzacher
Copy link
MemberAuthor

I've never even once saw a case of false positive fix of the type object | false.

Also applies to0 | object and'' | object.
It's rare, for sure, but we've had a few users report it - which means it's not as rare as you might hope in the wild.

The bigger issue is the conversion of return types -x != null && x.y returnsfalse | T, whereasx?.y returnsundefined | T. In a lot of cases this distinction isn't going to matter - eg in boolean locations likeif or logical expressions, or in expression statements. But there are places where this can matter and the autofix will cause errors. Again we've had some reports of this!

Honestly I'd be happy with having a flag likeallowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing which enables people to opt-in to an unsafe fixer - the issue is that it's just a bigger chunk of work to build the fixer.

JoshuaKGoldberg reacted with thumbs up emoji

@bradzacherbradzacher changed the titlefeat(eslint-plugin): [prefer-optional-chain] handle cases where the first operands are unrelated to the rest of the chainfeat(eslint-plugin): [prefer-optional-chain] handle cases where the first operands are unrelated to the rest of the chain and add type infoApr 23, 2023
@bradzacher
Copy link
MemberAuthor

Fun edge case I just discovered:

declareconstfoo:{bar:number}|null;foo===null||foo.bar;foo!==null&&!foo.bar;

In the first casefoo.bar is seen by my code as a "truthy boolean" check - which isn't a valid operand in an "OR" chain.
Similarly in the second case!foo.bar is seen as a "falsy boolean" check - which isn't a valid operand in an "AND" chain.

Which means that my code will ignore it and try not to include it in the chain to report and fix.

Will need to add special case logic to handle this as it's obviously intended to be chained together.


This also opens up another question about how this rule functions (that we can probably punt on for now, but worth mentioning).
Currently the rule will ignore operands with "invalid" checks. For example:

foo&&foo.bar&&foo.bar.baz===1// rule will currently fix tofoo?.bar&&foo.bar.baz===1

Should the rule inspect through these "invalid" checks? Or should it ignore them entirely?
I think there's a subset of them that it should include

  • !==/===/!=/== comparing against anything
  • not>/>=/</<= because TS will hard error if one of the operands is nullish.
  • nottypeof becausefoo && typeof foo.bar will returnundefined | typeof foo.bar whereastypeof foo?.bar will returntypeof foo.bar | 'undefined' - which is VERY different

@JoshuaKGoldberg
Copy link
Member

Should the rule inspect through these "invalid" checks? Or should it ignore them entirely?

...hmm. My instinct is to ignore "invalid" checks as an odd edge case. If users complain we can add it as a bugfix later on - but if the code is roughly not valid, I'd hesitate to feel confident in making changes.

@bradzacherbradzacher marked this pull request as ready for reviewJune 24, 2023 04:13
@bradzacher
Copy link
MemberAuthor

bradzacher commentedJun 24, 2023
edited
Loading

Note: I hate the tests with a passion because it's impossible for anyone to understand them as written without checking out and running the test 😢
However without utilities it'sreally hard to write and maintain the 700 cases with lots of repeated code.
Sigh this is a big pet peeve I have with ESLint rule tests. There has to be a better way...

Snapshot output would make it much simpler to review as you can see the expected reports and fixes properly rather than doing it opaquely.

The test coverage should show how much is covered overall - which should be all of the code!

omril1 and JoshuaKGoldberg reacted with thumbs up emojiJoshuaKGoldberg reacted with laugh emoji

Copy link
Member

@JoshuaKGoldbergJoshuaKGoldberg left a comment

Choose a reason for hiding this comment

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

This is really impressive code. Seriously awesome PR@bradzacher!

I've gone over it a few times and think I generally understand it. Particular ups on the approach of separating out the collection and analysis bits.

I say let's get this in for v6!

Comment on lines +722 to +723
// type parameters are considered
'foo.bar<a>() && foo.bar<a, b>().baz;',
Copy link
Contributor

Choose a reason for hiding this comment

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

type parameters were renamed to type arguments in this context, right?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

omril1 reacted with laugh emoji
},
],
},
// type parameters are considered
Copy link
Contributor

Choose a reason for hiding this comment

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

Same

@bradzacherbradzacher merged commit02a37c4 intov6Jul 7, 2023
@bradzacherbradzacher deleted the 6332-refactor-prefer-optional-chain branchJuly 7, 2023 04:28
@github-actionsgithub-actionsbot locked asresolvedand limited conversation to collaboratorsJul 15, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@omril1omril1omril1 left review comments

@JoshuaKGoldbergJoshuaKGoldbergJoshuaKGoldberg approved these changes

@Josh-CenaJosh-CenaAwaiting requested review from Josh-Cena

Assignees
No one assigned
Labels
breaking changeThis change will require a new major version to be releasedenhancement: new plugin ruleNew rule request for eslint-plugin
Projects
None yet
Milestone
6.0.0
4 participants
@bradzacher@JoshuaKGoldberg@omril1@Josh-Cena

[8]ページ先頭

©2009-2025 Movatter.jp