feat(eslint-plugin): [prefer-optional-chain] support logical with empty object#4430
Conversation
Empty object as optional chaining
|
Thanks for the PR, @omril1! 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 on https://opencollective.com/typescript-eslint. As a thank you, your profile/company logo will be added to our main README which receives thousands of unique visitors per day. |
|
✅ Deploy Preview for typescript-eslint ready! 🔨 Explore the source changes: 44236bb 🔍 Inspect the deploy log: https://app.netlify.com/sites/typescript-eslint/deploys/6234ebec3a1fff0008fe7c9d 😎 Browse the preview: https://deploy-preview-4430--typescript-eslint.netlify.app |
Codecov Report
@@ Coverage Diff @@
## main #4430 +/- ##
==========================================
+ Coverage 94.25% 94.30% +0.05%
==========================================
Files 151 151
Lines 7971 7991 +20
Branches 2573 2578 +5
==========================================
+ Hits 7513 7536 +23
+ Misses 262 258 -4
- Partials 196 197 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Empty object as optional chaining
const sourceCode already available in the upper scope
JoshuaKGoldberg
left a comment
There was a problem hiding this comment.
Looks great so far, thanks!
Just requesting changes on a bit more testing please ❤️
| create(context) { | ||
| const sourceCode = context.getSourceCode(); | ||
| return { | ||
| 'LogicalExpression[operator=||]'(node: TSESTree.LogicalExpression): void { |
There was a problem hiding this comment.
The feature does not handle function calls in this way as
(foo || {}).bar()can cause a runtime error.
Hmm, interesting point. My hunch would be that we probably want to handle function calls for cases like this:
(someContainer || {}).hasOwnProperty("someKey")...but I wouldn't consider it a blocker / must-have for this PR. Up to you!
There was a problem hiding this comment.
@JoshuaKGoldberg Care to re-review this? I don't think I'm going to add object built-ins in the PR
- Wrap left node if it's ternary or await expressions - Early exit if left node can't be a nullish object - Add More UT cases
JoshuaKGoldberg
left a comment
There was a problem hiding this comment.
Looks great to me, thanks so much for the thorough implementation & discussion! 🎉
@bradzacher is all over the history for this already-complex file so I'll just want to get a second opinion before merging.
Co-authored-by: Josh Goldberg <me@joshuakgoldberg.com>
|
@bradzacher & @JoshuaKGoldberg any update about this PR? |
|
it's on the review queue for when the volunteer maintainers have time to review PRs! |
bradzacher
left a comment
There was a problem hiding this comment.
looking good! Just one comment
| // Any node that is made of an operator with higher or equal precedence, | ||
| const maybeWrappedLeftNode = | ||
| leftNode.type === AST_NODE_TYPES.BinaryExpression || | ||
| leftNode.type === AST_NODE_TYPES.TSAsExpression || | ||
| leftNode.type === AST_NODE_TYPES.UnaryExpression || | ||
| leftNode.type === AST_NODE_TYPES.LogicalExpression || | ||
| leftNode.type === AST_NODE_TYPES.ConditionalExpression || | ||
| leftNode.type === AST_NODE_TYPES.AwaitExpression |
There was a problem hiding this comment.
We have a module which supports getting the precedence!
We should use this so that we handle all of the cases properly, considering it's a fixer.
It's a tiny bit cumbersome because we need to get the ts node first.
const parserServices = util.getParserServices(context, true);
// ...
function isHigherPrecedence() {
const logicalTsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const logicalPrecedence = util.getOperatorPrecedence(node.kind, node.operatorToken.kind);
const leftTsNode = parserServices.esTreeNodeToTSNodeMap.get(leftNode);
const operator = isBinaryExpression(node)
? node.operatorToken.kind
: ts.SyntaxKind.Unknown;
const leftPrecedence = util.getOperatorPrecedence(leftTsNode.kind, operator);
return leftPrecedence > logicalPrecedence;
}(Eventually we should make this API work with ESTree nodes so it's nicer)
There was a problem hiding this comment.
OK, I did that with a slight alteration because it didn't work (even after fixing the variable names from the snippet)
* Use util.getOperatorPrecedence instead of hard coding the precedence
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint) | devDependencies | minor | [`5.15.0` -> `5.16.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2feslint-plugin/5.15.0/5.16.0) | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint) | devDependencies | minor | [`5.15.0` -> `5.16.0`](https://renovatebot.com/diffs/npm/@typescript-eslint%2fparser/5.15.0/5.16.0) | --- ### Release Notes <details> <summary>typescript-eslint/typescript-eslint (@​typescript-eslint/eslint-plugin)</summary> ### [`v5.16.0`](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/eslint-plugin/CHANGELOG.md#​5160-httpsgithubcomtypescript-eslinttypescript-eslintcomparev5150v5160-2022-03-21) [Compare Source](typescript-eslint/typescript-eslint@v5.15.0...v5.16.0) ##### Bug Fixes - **eslint-plugin:** \[consistent-type-assertions] enforce assertionStyle for `const` assertions ([#​4685](typescript-eslint/typescript-eslint#4685)) ([8ec05be](typescript-eslint/typescript-eslint@8ec05be)) ##### Features - **eslint-plugin:** \[prefer-optional-chain] support logical with empty object ([#​4430](typescript-eslint/typescript-eslint#4430)) ([d21cfe0](typescript-eslint/typescript-eslint@d21cfe0)) </details> <details> <summary>typescript-eslint/typescript-eslint (@​typescript-eslint/parser)</summary> ### [`v5.16.0`](https://github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/parser/CHANGELOG.md#​5160-httpsgithubcomtypescript-eslinttypescript-eslintcomparev5150v5160-2022-03-21) [Compare Source](typescript-eslint/typescript-eslint@v5.15.0...v5.16.0) **Note:** Version bump only for package [@​typescript-eslint/parser](https://github.com/typescript-eslint/parser) </details> --- ### Configuration 📅 **Schedule**: At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox. --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). Co-authored-by: cabr2-bot <cabr2.help@gmail.com> Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1236 Reviewed-by: 6543 <6543@noreply.codeberg.org> Co-authored-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org> Co-committed-by: Calciumdibromid Bot <cabr2_bot@noreply.codeberg.org>
|
This change seems to be a breaking change because this rule now requires type information. |
|
@avaly As per our contributing guide. Please don't comment on closed PRs. |
Adds empty object pattern (
(foo || {}).baror(foo ?? {}).bar) as a match forprefer-optional-chainrulePR Checklist
(foo || {}).baras a valid match for the rule #4395Overview
Adds support for suggesting optional chaining refactor when an empty object is used to mimic optional chaining in addition to the existing
&&pattern ((foo || {}).barand(foo ?? {}).barare roughly equivalent tofoo && foo.barorfoo?.bar)Notes
(foo || {}).bar()can cause a runtime error (unlike the match for&&).