-
Notifications
You must be signed in to change notification settings - Fork 12.9k
add refactoring: string concatenation to template literals #30565
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
add refactoring: string concatenation to template literals #30565
Conversation
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.
Seems good except for some minor things.
Another thing I've noticed is that this refactor has the same problem as other ones regarding comments that disappear. For example, in the code above both "comment b" occurrences go missing after refactoring:
const a = 2;
const comment1 = `test ${ /*comment b*/ a /*comment e*/ }!`;
const comment2 = "test " + /* comment b*/ a /*comment e*/ + "!";
becomes:
const a = 2;
const comment1 = "test " + a /*comment e*/ + "!";
const comment2 = `test ${a /*comment e*/}!`;
I've had the same problem in this refactor so I had to explicitly handle the copying of comments from old nodes to new ones. I'm writing about that problem and solution and will link to it here when I finish.
Update: wrote about comments here: https://gist.github.com/gabritto/fa489e48e09ce6bc089955abba78d91a#trivia
tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts
Outdated
Show resolved
Hide resolved
|
||
function getNodeOrParentOfParentheses(file: SourceFile, startPosition: number) { | ||
const node = getTokenAtPosition(file, startPosition); | ||
if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) return node.parent.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.
could you explain why this is necessary?
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 idea behind is that
even if your cursor is inside a parentheses, you can still invoke this refactoring (as long as it's not a string binary expression)
both examples were invoked inside the parentheses:
// before
const foo = "foobar is " + (42 + 6) + " years old"
const fooStr = "foobar is " + (42 + 6 + "years") + " old"
// after
const foo = `foobar is ${42 + 6} years old`
const fooStr = "foobar is " + (`${42 + 6}years`) + " old"
It is not necessary but nice to have
let text = ""; | ||
|
||
while (index < nodes.length && isStringLiteral(nodes[index])) { | ||
text = text + decodeRawString(nodes[index].getText()); |
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 could cast nodes[index]
to StringLiteral
and use nodes[index].text
instead of using getText
. then decodeRawString
becomes unnecessary i think.
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 first used as StringLiteral.
but last time @ajafff pointed out that Octal escapes are disallowed inside template literal
If I use (nodes[index] as StringLiteral).text
then happens that:
// before
const foo = "Unicode \u0023 \u{0023} " + "Hex \x23 " + "Octal \43";
// after
const foo = `Unicode # # Hex # Octal 43`;
As you can see the octal escape is not supported with .text
That's why I created a suboptimal function decodeRawString
. It seems that the parser has a bug.
What do you think?
Should I switch back to: (nodes[index] as StringLiteral).text
?
and provide a fix for the bug (as a separate 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.
I switched back to (nodes[index] as StringLiteral).text
@RyanCavanaugh
Is there a reason why octal escape sequence is not interpreted from scanner (even when strict mode is off)
// before
const foo = "Unicode \u0023 \u{0023} " + "Hex \x23 " + "Octal \43";
// after
const foo = `Unicode # # Hex # Octal 43`;
|
||
function escapeText(content: string) { | ||
return content.replace("`", "\`") // back-tick | ||
.replace("${", "$\\{"); // placeholder alike beginning |
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 there's an extra backslash here. maybe it should be .replace("${", "$\{")
?
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.
Unfortunately if I pass as $\{
, the backslash will not be preserved.
because the parser optimize this \{
to {
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.
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 "$\\{"
changes the external behavior
to explain my dilemma
// before
const escape = "with ${dollar}"
// after using .replace("${", "$\{")
const escape = `with ${dollar}`
// after using .replace("${", "\${")
const escape = `with ${dollar}`
// after using .replace("${", "$\\{")
const escape = `with $\\{dollar}`
// after using .replace("${", "\\${")
const escape = `with \\${dollar}`
using $\{
or \${
- the backslash completely disappears after refactoring
- and therefore, it leaves the template literal in an invalid state
using $\\{
or \\${
- there is one backslash too much
- however, the template literal is still in valid state
Both approaches change the external behavior.
As it seems, neither $
nor {
can be escaped with single backslash.
However, the backtick `
can be escaped with just a single backslash.
What do you think?
How should I deal with this matter?
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 external behavior cannot be preserved with one or two backslash, I've decided to remove escaping of opening placeholder. .replace("${", "$\\{")
tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts
Outdated
Show resolved
Hide resolved
tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts
Outdated
Show resolved
Hide resolved
...ses/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityPrecedingMinus.ts
Outdated
Show resolved
Hide resolved
...cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityReassignment.ts
Outdated
Show resolved
Hide resolved
My inclination here is that concat -> template is a good refactor, but it's not clear to me why you'd want the reverse direction. @DanielRosenwasser thoughts? |
@typescript-bot pack this |
Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at 35a3a5f. You can monitor the build here. It should now contribute to this PR's status checks. |
Okay now @typescript-bot pack this |
Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at 88795e2. You can monitor the build here. It should now contribute to this PR's status checks. |
Hey @DanielRosenwasser, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build. |
Hey @bigaru, I spoke with Ryan and at this time we think convert to template expression is clearly useful from users. On the other hand, we didn't have consensus that users wanted to go the other way. I've removed those bits and pushed up a commit to remove that functionality. In the future, we'll be able to revert ad0f006 and ff4fa1f if we change our minds. |
Nit-pick: At the moment, this refactoring seems to be called “Convert to template string”. The more correct name would be “Convert to template literal”. |
Fixes #18267
@ajafff have a look