Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
fix(eslint-plugin): [no-unnecessary-template-expression] add missing parentheses in autofix#8673
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
fix(eslint-plugin): [no-unnecessary-template-expression] add missing parentheses in autofix#8673
Uh oh!
There was an error while loading.Please reload this page.
Conversation
Thanks for the PR,@developer-bandi! 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. |
netlifybot commentedMar 14, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
✅ Deploy Preview fortypescript-eslint ready!
To edit notification comments on pull requests, go to yourNetlify site configuration. |
nx-cloudbot commentedMar 14, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
☁️ Nx Cloud ReportCI is running/has finished running commands for commit1d536b6. As they complete they will appear below. Click to see the status, the terminal output, and the build insights. 📂 See all runs for this CI Pipeline Execution ✅ Successfully ran 2 targetsSent with 💌 fromNxCloud. |
codecovbot commentedMar 14, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@## main #8673 +/- ##==========================================- Coverage 88.02% 88.01% -0.01%========================================== Files 406 406 Lines 13871 13881 +10 Branches 4053 4056 +3 ==========================================+ Hits 12210 12218 +8- Misses 1352 1354 +2 Partials 309 309
Flags with carried forward coverage won't be shown.Click here to find out more.
|
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.
I like the approach here of usinggetWrappingFixer
! 👏
I also think the code can be refactored a bit to remove the extra layer of indirection & theas
. Requesting changes on that please. :)
Uh oh!
There was an error while loading.Please reload this page.
const replaceResult = getWrappingFixer({ | ||
sourceCode: context.sourceCode, | ||
node: node.expressions[0], | ||
wrap: (...code: string[]) => { | ||
return code.join(''); | ||
}, | ||
})(fixer); |
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.
[Refactor] Coming from#8673 (comment):
Instead of adding the overhead of the rest of
getWrappingFixer()
, suggestion: the specific text generation stuff should be extracted out and used here. Like, you could move the text generation from thegetWrappingFixer
into a separate function, and use that separate helper here.
I think this means that we should extract codegen partfromgetWrappingFixer
implementation to the separate utility function, not extract wholegetWrappingFixer
call to the separate function. WDYT?
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.
I think I misunderstood the explanation. thank you The code generation part that generates parentheses was separated and made into a function.
@@ -207,7 +207,7 @@ ruleTester.run('no-useless-template-literals', rule, { | |||
{ | |||
code: noFormat`\`\${ 'a' + 'b' }\`;`, | |||
output: `'a' + 'b';`, | |||
output: `('a' + 'b');`, |
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.
How come this test case gains parens? Is that just thegetWrappingFixer
being overly cautions?
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.
I also have this question. Feels like a bug to me?
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.
At first, I thought it would be good for the expression to be wrapped, but as you said, I think it's excessive and looks like a bug.
However, in order to restore the test code, the getWrappingFixer function must be modified. Would it be a good idea to modify this function?
export function getWrappingFixer( | ||
params: WrappingFixerParams, | ||
): TSESLint.ReportFixFunction { | ||
export function getWrappingCode(params: WrappingFixerParams): string { |
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.
One thing worth noting is that this uses the parent node to infer whether parens are needed (seeif (isWeakPrecedenceParent(node))
). So I'm wondering if that could give mistaken results for a scenario where we're reparenting code?
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.
I understand that you are using the above node to infer whether parentheses are needed, but I don't understand this part (So I'm wondering if that could give mistaken results for a scenario where we're reparenting code?). Can you tell me in more detail what the scenario is for reparenting the code?
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.
Sorry for taking a bit to get back to you - took me a while to figure out what I meant 😆 . Basically,getWrappingFixer()
allows you to transform a node whose parent did not change.
// input<stuff before> nodeToBeModified <stuff after>// output<stuff before> modifiedNode <stuff after>
But,now that getWrappingCode has been extracted, it can be used instead like
// input<stuff before> nodeToBeModified <stuff after>// getWrappingCode outputmodifiedNode// used in fixer as<DIFFERENT stuff before> modifiedNode <DIFFERENT stuff after>
In fact, that's the whole reason that you've extracted it in this PR.
// inputcondition ?`${'left'||'right'}`.trim() :'lol';// getWrappingCodeOutput('left'||'right')// used in fixer ascondition ?('left'||'right').trim() :'lol';
So,getWrappingCode
needs to deduce whether to wrap the expression in parentheses based on theNEW parent, not the existing one.
Used within thegetWrappingFixer
those were the one and the same, so the lineif (isWeakPrecedenceParent(node))
was justified, butnow that getWrappingCode may be used indepdently, we'd need something likeif (isWeakPrecedenceNewParent(node, newParent))
, in order for the function to give expected resultsin general.
I'd have to have a longer think to come up with test cases where this discrepancy would manifest, but hopefully this gets you started? If it's still unclear let me know and I can think up some test cases. The bottom line is that we need to be aware of whether parens are needed for thenew parent, not theexisting parent, in order forgetWrappingCode
to be safe for general use outside ofgetWrappingFixer
.
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.
this might well be related actually:https://github.com/typescript-eslint/typescript-eslint/pull/8673/files/5a9f63085e1a3b08ae1c63e29deb34ca93b5165d#r1573616588
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.
I apologize for not clear question. But the answers were the best!
If my understanding is correct, the parent that theisWeakPrecedenceNewParent
function checks must be the new parent. In fact, the previous code was checking the parent of the expression, so it was always checking the templete literal node as the parent, so it was not getting normal results.
However, if i slightly modify the code to satisfy the above conditions, i will get the following results depending on the conditions of innerNode and parents condition.
// beforetrue ? `${'test' || ''}`.trim() : undefined;// aftertrue ? (('test' || '')).trim() : undefined;
In my opinion, it seems better to use parentheses when both the logic of applying parentheses to innerNode and the logic of examining the state of the parent are satisfied. so i revertgetWrappingFixer
and make newgetWrappingCode
function
I think we're just getting started, so i appreciate your feedback.
export function getWrappingFixer( | ||
params: WrappingFixerParams, | ||
): TSESLint.ReportFixFunction { | ||
export function getWrappingCode(params: WrappingFixerParams): string { |
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.
Sorry for taking a bit to get back to you - took me a while to figure out what I meant 😆 . Basically,getWrappingFixer()
allows you to transform a node whose parent did not change.
// input<stuff before> nodeToBeModified <stuff after>// output<stuff before> modifiedNode <stuff after>
But,now that getWrappingCode has been extracted, it can be used instead like
// input<stuff before> nodeToBeModified <stuff after>// getWrappingCode outputmodifiedNode// used in fixer as<DIFFERENT stuff before> modifiedNode <DIFFERENT stuff after>
In fact, that's the whole reason that you've extracted it in this PR.
// inputcondition ?`${'left'||'right'}`.trim() :'lol';// getWrappingCodeOutput('left'||'right')// used in fixer ascondition ?('left'||'right').trim() :'lol';
So,getWrappingCode
needs to deduce whether to wrap the expression in parentheses based on theNEW parent, not the existing one.
Used within thegetWrappingFixer
those were the one and the same, so the lineif (isWeakPrecedenceParent(node))
was justified, butnow that getWrappingCode may be used indepdently, we'd need something likeif (isWeakPrecedenceNewParent(node, newParent))
, in order for the function to give expected resultsin general.
I'd have to have a longer think to come up with test cases where this discrepancy would manifest, but hopefully this gets you started? If it's still unclear let me know and I can think up some test cases. The bottom line is that we need to be aware of whether parens are needed for thenew parent, not theexisting parent, in order forgetWrappingCode
to be safe for general use outside ofgetWrappingFixer
.
kirkwaiblinger commentedJun 3, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
FYI@developer-bandi - This will have a semantic merge conflict with#8821, which has now been merged. When this is ready to go, you'll want to duplicate the fix to the Also, I'm sorry for taking so long to do another review pass. I'm setting an intention for myself to get another pass in this week. Please bug me if I don't get to it within that time! |
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.
I spent a long time playing around with this on my machine.... this is adifficult area of the code. Requesting a few changes for clarity, but I think the essential logic is in place! 🙂
Also, would like to tag in another member of @typescript-eslint/triage-team to help make sure we're on the right track.
const parent = node.parent!; | ||
function isWeakPrecedenceParent( | ||
node: TSESTree.Node, | ||
parent = node.parent, |
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.
Let's keep the parent deduced by thenode
... so,
functionisWeakPrecedenceParent(node:TSESTree.Node):boolean{constparent=node.parent;if(!parent){returnfalse;}
@@ -75,6 +75,23 @@ export function getWrappingFixer( | |||
}; | |||
} | |||
export function getWrappingCode(params: { |
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.
What do you think of reframing this function to look something like this?
/** * Useful jsdoc here */exportfunctiongetMovedNodeCode(params:{sourceCode:Readonly<TSESLint.SourceCode>;nodeToMove:TSESTree.Node;destinationNode:TSESTree.Node;}):string{const{ sourceCode,nodeToMove:existingNode, destinationNode}=params;constcode=sourceCode.getText(existingNode);if(isStrongPrecedenceNode(existingNode)){// Moved node never needs parensreturncode;}if(!isWeakPrecedenceParent(destinationNode)){// Destination would never needs parens, regardless what node moves therereturncode;}// Parens may be necessaryreturn`(${code})`;}
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.
It looks much more intuitive and good. We will reflect it appropriately
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.
I agree with@kirkwaiblinger 🙂
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.
]), | ||
]; | ||
fix(fixer): TSESLint.RuleFix | null { | ||
const wrappingCode = getMovedNodeCode({ |
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.
(nit) the namewrappingCode
is slightly stale with the rename, but I don't have many good ideas for a better name 🙃. Any ideas?codeToReplaceTemplate
? Up to you if you want to change this or leave it 🤷♂️
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.
Since the function name is getMovedNodeCode, how about changing the variable name to movedNodeCode? If you don't think it's appropriate, it might be a good idea to keep the current name.
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.
Makes sense to me 👍
kirkwaiblinger left a comment• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
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.
Ah - reminder to duplicate these changes to theno-unnecessary-template-expressions
copy of the rule. Otherwise I think I'm happy!
👋@developer-bandi just checking in, is this something you still have time and energy for? |
@JoshuaKGoldberg I haven't been able to proceed with the work recently due to lack of time. �I plan to start working on it this weekend. |
copy work to [no-unnecessary-template-expression] and delete no-useless-template-literals to resolve conflict |
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.
return code; | ||
} | ||
if (!isWeakPrecedenceParent(destinationNode)) { |
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.
[Praise] Great reuse of the existing functions!
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.
Nice!!
90655d1
intotypescript-eslint:mainUh oh!
There was an error while loading.Please reload this page.
PR Checklist
Overview
Fix no consider precedence when fixer function is excution using util function.