-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat(eslint-plugin): [prefer-optional-chain] support logical with empty object #4430
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(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
packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts
Outdated
Show resolved
Hide resolved
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.
Looks great so far, thanks!
Just requesting changes on a bit more testing please ❤️
packages/eslint-plugin/tests/rules/prefer-optional-chain.test.ts
Outdated
Show resolved
Hide resolved
@@ -48,6 +48,48 @@ export default util.createRule({ | |||
create(context) { | |||
const sourceCode = context.getSourceCode(); | |||
return { | |||
'LogicalExpression[operator=||]'(node: TSESTree.LogicalExpression): void { |
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 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JoshuaKGoldberg Care to re-review this? I don't think I'm going to add object built-ins in the PR
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 got it, no problem!
- 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
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.
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 <[email protected]>
@bradzacher & @JoshuaKGoldberg any update about this PR? |
it's on the review queue for when the volunteer maintainers have time to review PRs! |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
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.
LGTM - thanks for thsi!
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 <[email protected]> Reviewed-on: https://codeberg.org/Calciumdibromid/CaBr2/pulls/1236 Reviewed-by: 6543 <[email protected]> Co-authored-by: Calciumdibromid Bot <[email protected]> Co-committed-by: Calciumdibromid Bot <[email protected]>
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 || {}).bar
or(foo ?? {}).bar
) as a match forprefer-optional-chain
rulePR Checklist
(foo || {}).bar
as 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 || {}).bar
and(foo ?? {}).bar
are roughly equivalent tofoo && foo.bar
orfoo?.bar
)Notes
(foo || {}).bar()
can cause a runtime error (unlike the match for&&
).