From 6cc8029a6e00311989b3ca6a80ac554c266334a6 Mon Sep 17 00:00:00 2001 From: BigAru Date: Wed, 5 Dec 2018 05:39:11 +0100 Subject: [PATCH 01/29] add skeleton --- .../convertStringOrTemplateLiteral.ts | 24 +++++++++++++++++++ src/services/tsconfig.json | 1 + 2 files changed, 25 insertions(+) create mode 100644 src/services/refactors/convertStringOrTemplateLiteral.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts new file mode 100644 index 0000000000000..025b1cbd912de --- /dev/null +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -0,0 +1,24 @@ +/* @internal */ +namespace ts.refactor.convertStringOrTemplateLiteral { + const refactorName = "Convert string concatenation or template literal"; + const refactorDescription = "Convert string concatenation or template literal"; + const toTemplateLiteralActionName = "Convert to template literal"; + const toStringConcatenationActionName = "Convert to string concatenation"; + const toTemplateLiteralDescription = "Convert to template literal"; + const toStringConcatenationDescription = "Convert to string concatenation"; + + // TODO convert Description to DiagnosticMsg + registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); + + function getAvailableActions(context: RefactorContext): ReadonlyArray { + const { file, startPosition } = context; file; startPosition; + + return emptyArray; + } + + function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + const { file, startPosition } = context; file; startPosition; + return undefined; + } + +} diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 15044416f8426..6a23ea94ea563 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -83,6 +83,7 @@ "refactors/generateGetAccessorAndSetAccessor.ts", "refactors/moveToNewFile.ts", "refactors/addOrRemoveBracesToArrowFunction.ts", + "refactors/convertStringOrTemplateLiteral.ts", "services.ts", "breakpoints.ts", "transform.ts", From 50e130a8e4ce54f66779d9b3aec9b91a92871dad Mon Sep 17 00:00:00 2001 From: BigAru Date: Wed, 5 Dec 2018 06:00:28 +0100 Subject: [PATCH 02/29] add test cases --- ...StringOrTemplateLiteral_ToTemplateCalcExpr.ts | 12 ++++++++++++ ...tringOrTemplateLiteral_ToTemplateMultiExpr.ts | 16 ++++++++++++++++ ...tStringOrTemplateLiteral_ToTemplateOneExpr.ts | 14 ++++++++++++++ ...rtStringOrTemplateLiteral_ToTemplateSimple.ts | 12 ++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateCalcExpr.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateCalcExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateCalcExpr.ts new file mode 100644 index 0000000000000..54b3f98591035 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateCalcExpr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar is " + (42 + 6) + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${ 42 + 6 } years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts new file mode 100644 index 0000000000000..6aa095d67d3cc --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts @@ -0,0 +1,16 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const foo = /*x*/n/*y*/ame + " is " + age + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const age = 22 +const name = "Eddy" +const foo = \`\${ name } is \${ age } years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts new file mode 100644 index 0000000000000..8899f99c864e8 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = "/*x*/f/*y*/oobar is " + age + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const age = 42 +const foo = \`foobar is \${ age } years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts new file mode 100644 index 0000000000000..35b3e17519dd7 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar rocks" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar rocks\``, +}); From 949262330a2da2482da265ce653d346721d7d164 Mon Sep 17 00:00:00 2001 From: BigAru Date: Wed, 5 Dec 2018 08:30:22 +0100 Subject: [PATCH 03/29] add test cases --- ...StringOrTemplateLiteral_ToStringBackTick.ts | 12 ++++++++++++ ...ringOrTemplateLiteral_ToStringBinaryExpr.ts | 12 ++++++++++++ ...rtStringOrTemplateLiteral_ToStringDollar.ts | 12 ++++++++++++ ...tringOrTemplateLiteral_ToStringExprInRow.ts | 12 ++++++++++++ ...tringOrTemplateLiteral_ToStringMultiExpr.ts | 16 ++++++++++++++++ ...rtStringOrTemplateLiteral_ToStringNested.ts | 14 ++++++++++++++ ...tStringOrTemplateLiteral_ToStringOneExpr.ts | 14 ++++++++++++++ ...rtStringOrTemplateLiteral_ToStringSimple.ts | 12 ++++++++++++ ...ringOrTemplateLiteral_ToTemplateBackTick.ts | 12 ++++++++++++ ...gOrTemplateLiteral_ToTemplateBinaryExpr.ts} | 0 ...StringOrTemplateLiteral_ToTemplateDollar.ts | 12 ++++++++++++ ...ingOrTemplateLiteral_ToTemplateExprInRow.ts | 12 ++++++++++++ ...ngOrTemplateLiteral_ToTemplateMultiLines.ts | 18 ++++++++++++++++++ ...tringOrTemplateLiteral_ToTemplateNewLine.ts | 14 ++++++++++++++ 14 files changed, 172 insertions(+) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts rename tests/cases/fourslash/{refactorConvertStringOrTemplateLiteral_ToTemplateCalcExpr.ts => refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts} (100%) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts new file mode 100644 index 0000000000000..5025c5df7c7b0 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/w/*y*/ith back\`tick` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +"const foo = \"with back`tick\"", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts new file mode 100644 index 0000000000000..b545b7861c354 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/f/*y*/oobar is ${ 42 + 6 } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar is " + (42 + 6) + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts new file mode 100644 index 0000000000000..1629f7e7df96a --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/w/*y*/ith \${dollar}` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +"const foo = \"with ${dollar}\"", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts new file mode 100644 index 0000000000000..93a3897f67cb5 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/f/*y*/oobar is ${ 42 }${ 6 } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar is " + 42 + 6 + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts new file mode 100644 index 0000000000000..8e1daf79c58ae --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts @@ -0,0 +1,16 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const foo = \`\${ /*x*/n/*y*/ame } is \${ age } years old\` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 22 +const name = "Eddy" +const foo = name + " is " + age + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts new file mode 100644 index 0000000000000..f3240a10b929a --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `/*x*/f/*y*/oobar is a ${ age < 18 ? 'child' : `grown-up ${ age > 40 ? 'who needs probaply assistance': ''}` }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is a " + ( age < 18 ? 'child' : \`grown-up \${ age > 40 ? 'who needs probaply assistance': ''}\` ) `, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts new file mode 100644 index 0000000000000..a6bf5c4d192e5 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `/*x*/f/*y*/oobar is ${ age } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is " + age + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts new file mode 100644 index 0000000000000..7c6496be7b5d1 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = `/*x*/f/*y*/oobar rocks` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar rocks"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts new file mode 100644 index 0000000000000..8c7b2a9383303 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/w/*y*/ith back`tick" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `with back\`tick`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateCalcExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts similarity index 100% rename from tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateCalcExpr.ts rename to tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts new file mode 100644 index 0000000000000..884b30202351b --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/w/*y*/ith ${dollar}" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `with \${dollar}`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts new file mode 100644 index 0000000000000..1c3af8543090f --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar is " + 42 + 6 + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${ 42 }\${ 6 } years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts new file mode 100644 index 0000000000000..e58111ebfb222 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts @@ -0,0 +1,18 @@ +/// + +//// const foo = "/*x*/w/*y*/ait for others\n" +//// + "D'oh!\n" +//// + ""Yada, yada, yada\n" +//// + "Hasta la vista, baby!" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`wait for others +D'oh! +Yada, yada, yada +Hasta la vista, baby!\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts new file mode 100644 index 0000000000000..577c1de4efb98 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts @@ -0,0 +1,14 @@ +/// + +//// const foo = "/*x*/w/*y*/ait for new line\n" +//// + "bada bum!" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`wait for new line +bada bum!\``, +}); From 1046eb6bdc89b6ef49741627379981f4fbc0f261 Mon Sep 17 00:00:00 2001 From: BigAru Date: Wed, 5 Dec 2018 08:59:18 +0100 Subject: [PATCH 04/29] add visibility tests --- ...gOrTemplateLiteral_ToStringAvailability.ts | 30 +++++++++++++++++++ ...ringOrTemplateLiteral_ToStringMultiExpr.ts | 2 +- ...rTemplateLiteral_ToTemplateAvailability.ts | 30 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts new file mode 100644 index 0000000000000..0ebde2b46ad7d --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts @@ -0,0 +1,30 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const /*z*/f/*y*/oo = /*x*/`/*w*/M/*v*/r/*u*/ /*t*/$/*s*/{ /*r*/n/*q*/ame } is $/*p*/{/*o*/ age } years old` + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("x", "w"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("v", "u"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("t", "s"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("r", "q"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("p", "o"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts index 8e1daf79c58ae..7f4255b6ef466 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts @@ -2,7 +2,7 @@ //// const age = 22 //// const name = "Eddy" -//// const foo = \`\${ /*x*/n/*y*/ame } is \${ age } years old\` +//// const foo = `${ /*x*/n/*y*/ame } is ${ age } years old` goTo.select("x", "y"); edit.applyRefactor({ diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts new file mode 100644 index 0000000000000..ab26de51270a0 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts @@ -0,0 +1,30 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const /*z*/f/*y*/oo = /*x*/"/*w*/M/*v*/r/*u*/ " /*t*/+/*s*/ /*r*/n/*q*/ame + " is " + /*p*/a/*o*/ge + " years old" + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("r", "q"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("p", "o"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + From 8510e25bf53d9acb9e2fba39dd728d8ecec60a5e Mon Sep 17 00:00:00 2001 From: BigAru Date: Wed, 5 Dec 2018 09:14:23 +0100 Subject: [PATCH 05/29] add diagnostic messages --- src/compiler/diagnosticMessages.json | 12 ++++++++++++ .../refactors/convertStringOrTemplateLiteral.ts | 14 +++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e3f8e1a7ad5a6..0372150ed8c8f 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4799,5 +4799,17 @@ "Add names to all parameters without names": { "category": "Message", "code": 95073 + }, + "Convert string concatenation or template literal": { + "category": "Message", + "code": 95074 + }, + "Convert to template literal": { + "category": "Message", + "code": 95075 + }, + "Convert to string concatenation": { + "category": "Message", + "code": 95076 } } diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 025b1cbd912de..fcad0b734836e 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -1,13 +1,13 @@ /* @internal */ namespace ts.refactor.convertStringOrTemplateLiteral { const refactorName = "Convert string concatenation or template literal"; - const refactorDescription = "Convert string concatenation or template literal"; - const toTemplateLiteralActionName = "Convert to template literal"; - const toStringConcatenationActionName = "Convert to string concatenation"; - const toTemplateLiteralDescription = "Convert to template literal"; - const toStringConcatenationDescription = "Convert to string concatenation"; + // const toTemplateLiteralActionName = "Convert to template literal"; + // const toStringConcatenationActionName = "Convert to string concatenation"; + + // const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_string_concatenation_or_template_literal); + // const toTemplateLiteralDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_literal); + // const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); - // TODO convert Description to DiagnosticMsg registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); function getAvailableActions(context: RefactorContext): ReadonlyArray { @@ -17,7 +17,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const { file, startPosition } = context; file; startPosition; + const { file, startPosition } = context; file; startPosition; actionName; return undefined; } From f15fcb7980412ef74e2c7e2af80a8ab41ce37b52 Mon Sep 17 00:00:00 2001 From: BigAru Date: Thu, 6 Dec 2018 08:05:03 +0100 Subject: [PATCH 06/29] add working conversion to template literal --- .../convertStringOrTemplateLiteral.ts | 119 +++++++++++++++++- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index fcad0b734836e..0036fc9f68d7c 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -1,11 +1,11 @@ /* @internal */ namespace ts.refactor.convertStringOrTemplateLiteral { const refactorName = "Convert string concatenation or template literal"; - // const toTemplateLiteralActionName = "Convert to template literal"; + const toTemplateLiteralActionName = "Convert to template literal"; // const toStringConcatenationActionName = "Convert to string concatenation"; - // const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_string_concatenation_or_template_literal); - // const toTemplateLiteralDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_literal); + const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_string_concatenation_or_template_literal); + const toTemplateLiteralDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_literal); // const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); @@ -13,12 +13,121 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function getAvailableActions(context: RefactorContext): ReadonlyArray { const { file, startPosition } = context; file; startPosition; - return emptyArray; + const node = getTokenAtPosition(file, startPosition); + const maybeBinary = getParentBinaryExpression(node); containsString(maybeBinary); + if (!(isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) || !containsString(maybeBinary)) return emptyArray; + + // let str = "untouched"; + // if (isBinaryExpression(maybeBinary)) str = maybeBinary.right.getText(); + // if (isStringLiteral(maybeBinary)) str = maybeBinary.getText(); + + return [{ + name: refactorName, + description: refactorDescription, + actions: [ + { + name: toTemplateLiteralActionName, + description: toTemplateLiteralDescription + } + ] + }]; } function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const { file, startPosition } = context; file; startPosition; actionName; - return undefined; + const node = getTokenAtPosition(file, startPosition); + + + switch (actionName) { + case toTemplateLiteralActionName: + const maybeBinary = getParentBinaryExpression(node); + let templateLiteral: TemplateExpression | NoSubstitutionTemplateLiteral; + if (isStringLiteral(maybeBinary)) { + templateLiteral = createNoSubstitutionTemplateLiteral(maybeBinary.text); + } + else { + const arrayOfNodes = treeToArray(maybeBinary); arrayOfNodes; + templateLiteral = nodesToTemplate(arrayOfNodes); + } + + const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); + return {edits}; + + default: + return Debug.fail("invalid action"); + } + } + + function getParentBinaryExpression(expr: Node) { + while (isBinaryExpression(expr.parent)) { + expr = expr.parent; + } + return expr; + } + + function containsString(node: Node): boolean { + if (isBinaryExpression(node)) { + return containsString(node.left) || containsString(node.right); + } + + if (isStringLiteral(node)) return true; + return false; + } + + function treeToArray(node: Node): Node[] { + if (isBinaryExpression(node)) { + return treeToArray(node.left).concat(treeToArray(node.right)) + } + return [node]; + } + + function nodesToTemplate(nodes: Node[]) { + let begin = 0; + + const head = createTemplateHead(""); + const firstNode = nodes[0]; + const spans: TemplateSpan[] = []; + + if (isStringLiteral(firstNode)){ + head.text = firstNode.text; + begin++; + + while(begin < nodes.length && isStringLiteral(nodes[begin])){ + + let next = nodes[begin] as StringLiteral; + head.text = head.text + next.text; + begin++; + } + } + + if(begin === nodes.length){ + return createNoSubstitutionTemplateLiteral(head.text); + } + + for(let i = begin; i < nodes.length; i++){ + const current = nodes[i]; + + if (i+1 < nodes.length && isStringLiteral(nodes[i+1])){ + let next = nodes[i+1] as StringLiteral; + let text = next.text; + i++; + + while(i+1 < nodes.length && isStringLiteral(nodes[i+1])){ + next = nodes[i+1] as StringLiteral; + text = text + next.text; + i++; + } + + const templatePart = i === nodes.length-1 ? createTemplateTail(text) : createTemplateMiddle(text); + spans.push(createTemplateSpan(current as Expression, templatePart)); + } + else { + const templatePart = i === nodes.length-1 ? createTemplateTail("") : createTemplateMiddle(""); + spans.push(createTemplateSpan(current as Expression, templatePart)); + } + } + + return createTemplateExpression(head, spans); } } From 3b99801139e2f9b7203260b3ffe8494dc0c47550 Mon Sep 17 00:00:00 2001 From: BigAru Date: Thu, 6 Dec 2018 10:54:23 +0100 Subject: [PATCH 07/29] add test cases --- .../convertStringOrTemplateLiteral.ts | 53 +++++++++------- ...gOrTemplateLiteral_ToStringAvailability.ts | 60 +++++++++---------- ...tringOrTemplateLiteral_ToStringBackTick.ts | 24 ++++---- ...ingOrTemplateLiteral_ToStringBinaryExpr.ts | 24 ++++---- ...tStringOrTemplateLiteral_ToStringDollar.ts | 24 ++++---- ...ringOrTemplateLiteral_ToStringExprInRow.ts | 24 ++++---- ...ringOrTemplateLiteral_ToStringMultiExpr.ts | 32 +++++----- ...tStringOrTemplateLiteral_ToStringNested.ts | 28 ++++----- ...StringOrTemplateLiteral_ToStringOneExpr.ts | 28 ++++----- ...tStringOrTemplateLiteral_ToStringSimple.ts | 24 ++++---- ...rTemplateLiteral_ToTemplateAvailability.ts | 60 +++++++++---------- ...ingOrTemplateLiteral_ToTemplateBackTick.ts | 24 ++++---- ...gOrTemplateLiteral_ToTemplateBinaryExpr.ts | 24 ++++---- ...emplateLiteral_ToTemplateConsecutiveStr.ts | 12 ++++ ...tringOrTemplateLiteral_ToTemplateDollar.ts | 24 ++++---- ...ngOrTemplateLiteral_ToTemplateExprInRow.ts | 24 ++++---- ...ngOrTemplateLiteral_ToTemplateMultiExpr.ts | 32 +++++----- ...gOrTemplateLiteral_ToTemplateMultiLines.ts | 36 +++++------ ...ringOrTemplateLiteral_ToTemplateNewLine.ts | 28 ++++----- ...ringOrTemplateLiteral_ToTemplateOneExpr.ts | 28 ++++----- ...ringOrTemplateLiteral_ToTemplateOnlyStr.ts | 12 ++++ ...tringOrTemplateLiteral_ToTemplateSimple.ts | 24 ++++---- 22 files changed, 341 insertions(+), 308 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateConsecutiveStr.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOnlyStr.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 0036fc9f68d7c..e63ab98e6e5dc 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -3,6 +3,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const refactorName = "Convert string concatenation or template literal"; const toTemplateLiteralActionName = "Convert to template literal"; // const toStringConcatenationActionName = "Convert to string concatenation"; + // let str = ""; str; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_string_concatenation_or_template_literal); const toTemplateLiteralDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_literal); @@ -11,8 +12,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); function getAvailableActions(context: RefactorContext): ReadonlyArray { - const { file, startPosition } = context; file; startPosition; - + const { file, startPosition } = context; const node = getTokenAtPosition(file, startPosition); const maybeBinary = getParentBinaryExpression(node); containsString(maybeBinary); if (!(isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) || !containsString(maybeBinary)) return emptyArray; @@ -34,24 +34,23 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { - const { file, startPosition } = context; file; startPosition; actionName; + const { file, startPosition } = context; const node = getTokenAtPosition(file, startPosition); - switch (actionName) { case toTemplateLiteralActionName: const maybeBinary = getParentBinaryExpression(node); let templateLiteral: TemplateExpression | NoSubstitutionTemplateLiteral; if (isStringLiteral(maybeBinary)) { - templateLiteral = createNoSubstitutionTemplateLiteral(maybeBinary.text); + templateLiteral = createNoSubstitutionTemplateLiteral(cleanString(maybeBinary.text)); } else { - const arrayOfNodes = treeToArray(maybeBinary); arrayOfNodes; + const arrayOfNodes = treeToArray(maybeBinary); templateLiteral = nodesToTemplate(arrayOfNodes); } const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); - return {edits}; + return { edits }; default: return Debug.fail("invalid action"); @@ -76,7 +75,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function treeToArray(node: Node): Node[] { if (isBinaryExpression(node)) { - return treeToArray(node.left).concat(treeToArray(node.right)) + return treeToArray(node.left).concat(treeToArray(node.right)); } return [node]; } @@ -88,46 +87,56 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const firstNode = nodes[0]; const spans: TemplateSpan[] = []; - if (isStringLiteral(firstNode)){ + if (isStringLiteral(firstNode)) { head.text = firstNode.text; begin++; - while(begin < nodes.length && isStringLiteral(nodes[begin])){ + while (begin < nodes.length && isStringLiteral(nodes[begin])) { - let next = nodes[begin] as StringLiteral; + const next = nodes[begin] as StringLiteral; head.text = head.text + next.text; begin++; } + + head.text = cleanString(head.text); } - if(begin === nodes.length){ + if (begin === nodes.length) { return createNoSubstitutionTemplateLiteral(head.text); } - for(let i = begin; i < nodes.length; i++){ - const current = nodes[i]; + for (let i = begin; i < nodes.length; i++) { + let current = nodes[i]; + let templatePart: TemplateMiddle | TemplateTail; - if (i+1 < nodes.length && isStringLiteral(nodes[i+1])){ - let next = nodes[i+1] as StringLiteral; + if (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { + let next = nodes[i + 1] as StringLiteral; let text = next.text; i++; - while(i+1 < nodes.length && isStringLiteral(nodes[i+1])){ - next = nodes[i+1] as StringLiteral; + while (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { + next = nodes[i + 1] as StringLiteral; text = text + next.text; i++; } - const templatePart = i === nodes.length-1 ? createTemplateTail(text) : createTemplateMiddle(text); - spans.push(createTemplateSpan(current as Expression, templatePart)); + if (isParenthesizedExpression(current)) current = current.expression; + text = cleanString(text); + + templatePart = i === nodes.length - 1 ? createTemplateTail(text) : createTemplateMiddle(text); } else { - const templatePart = i === nodes.length-1 ? createTemplateTail("") : createTemplateMiddle(""); - spans.push(createTemplateSpan(current as Expression, templatePart)); + templatePart = i === nodes.length - 1 ? createTemplateTail("") : createTemplateMiddle(""); } + + spans.push(createTemplateSpan(current as Expression, templatePart)); } return createTemplateExpression(head, spans); } + function cleanString(content: string) { + return content.replace("`", "\`").replace("\${", `$\\{`); + } + } diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts index 0ebde2b46ad7d..439b5cd80e7f2 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts @@ -1,30 +1,30 @@ -/// - -//// const age = 22 -//// const name = "Eddy" -//// const /*z*/f/*y*/oo = /*x*/`/*w*/M/*v*/r/*u*/ /*t*/$/*s*/{ /*r*/n/*q*/ame } is $/*p*/{/*o*/ age } years old` - -goTo.select("z", "y"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("x", "w"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("v", "u"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("t", "s"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("r", "q"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("p", "o"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - +/// + +//// const age = 22 +//// const name = "Eddy" +//// const /*z*/f/*y*/oo = /*x*/`/*w*/M/*v*/r/*u*/ /*t*/$/*s*/{ /*r*/n/*q*/ame } is $/*p*/{/*o*/ age } years old` + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("x", "w"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("v", "u"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("t", "s"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("r", "q"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + +goTo.select("p", "o"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") + diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts index 5025c5df7c7b0..6587f7037f057 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBackTick.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = `/*x*/w/*y*/ith back\`tick` - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to string concatenation", - actionDescription: "Convert to string concatenation", - newContent: -"const foo = \"with back`tick\"", -}); +/// + +//// const foo = `/*x*/w/*y*/ith back\`tick` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +"const foo = \"with back`tick\"", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts index b545b7861c354..1850b40f22c26 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringBinaryExpr.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = `/*x*/f/*y*/oobar is ${ 42 + 6 } years old` - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to string concatenation", - actionDescription: "Convert to string concatenation", - newContent: -`const foo = "foobar is " + (42 + 6) + " years old"`, -}); +/// + +//// const foo = `/*x*/f/*y*/oobar is ${ 42 + 6 } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar is " + (42 + 6) + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts index 1629f7e7df96a..3b19470d7a77b 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = `/*x*/w/*y*/ith \${dollar}` - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to string concatenation", - actionDescription: "Convert to string concatenation", - newContent: -"const foo = \"with ${dollar}\"", -}); +/// + +//// const foo = `/*x*/w/*y*/ith \${dollar}` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +"const foo = \"with ${dollar}\"", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts index 93a3897f67cb5..4b8e581b8c69b 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringExprInRow.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = `/*x*/f/*y*/oobar is ${ 42 }${ 6 } years old` - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to string concatenation", - actionDescription: "Convert to string concatenation", - newContent: -`const foo = "foobar is " + 42 + 6 + " years old"`, -}); +/// + +//// const foo = `/*x*/f/*y*/oobar is ${ 42 }${ 6 } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar is " + 42 + 6 + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts index 7f4255b6ef466..79f624118d7e0 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts @@ -1,16 +1,16 @@ -/// - -//// const age = 22 -//// const name = "Eddy" -//// const foo = `${ /*x*/n/*y*/ame } is ${ age } years old` - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to string concatenation", - actionDescription: "Convert to string concatenation", - newContent: -`const age = 22 -const name = "Eddy" -const foo = name + " is " + age + " years old"`, -}); +/// + +//// const age = 22 +//// const name = "Eddy" +//// const foo = `${ /*x*/n/*y*/ame } is ${ age } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 22 +const name = "Eddy" +const foo = name + " is " + age + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts index f3240a10b929a..d7a8f52b374a0 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts @@ -1,14 +1,14 @@ -/// - -//// const age = 42 -//// const foo = `/*x*/f/*y*/oobar is a ${ age < 18 ? 'child' : `grown-up ${ age > 40 ? 'who needs probaply assistance': ''}` }` - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to string concatenation", - actionDescription: "Convert to string concatenation", - newContent: -`const age = 42 -const foo = "foobar is a " + ( age < 18 ? 'child' : \`grown-up \${ age > 40 ? 'who needs probaply assistance': ''}\` ) `, -}); +/// + +//// const age = 42 +//// const foo = `/*x*/f/*y*/oobar is a ${ age < 18 ? 'child' : `grown-up ${ age > 40 ? 'who needs probaply assistance': ''}` }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is a " + ( age < 18 ? 'child' : \`grown-up \${ age > 40 ? 'who needs probaply assistance': ''}\` ) `, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts index a6bf5c4d192e5..0e5cb6ae2573b 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringOneExpr.ts @@ -1,14 +1,14 @@ -/// - -//// const age = 42 -//// const foo = `/*x*/f/*y*/oobar is ${ age } years old` - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to string concatenation", - actionDescription: "Convert to string concatenation", - newContent: -`const age = 42 -const foo = "foobar is " + age + " years old"`, -}); +/// + +//// const age = 42 +//// const foo = `/*x*/f/*y*/oobar is ${ age } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is " + age + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts index 7c6496be7b5d1..3da488fcf2515 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSimple.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = `/*x*/f/*y*/oobar rocks` - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to string concatenation", - actionDescription: "Convert to string concatenation", - newContent: -`const foo = "foobar rocks"`, -}); +/// + +//// const foo = `/*x*/f/*y*/oobar rocks` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const foo = "foobar rocks"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts index ab26de51270a0..fa58f01392d79 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailability.ts @@ -1,30 +1,30 @@ -/// - -//// const age = 22 -//// const name = "Eddy" -//// const /*z*/f/*y*/oo = /*x*/"/*w*/M/*v*/r/*u*/ " /*t*/+/*s*/ /*r*/n/*q*/ame + " is " + /*p*/a/*o*/ge + " years old" - -goTo.select("z", "y"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("x", "w"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("v", "u"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("t", "s"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("r", "q"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - -goTo.select("p", "o"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") - +/// + +//// const age = 22 +//// const name = "Eddy" +//// const /*z*/f/*y*/oo = /*x*/"/*w*/M/*v*/r/*u*/ " /*t*/+/*s*/ /*r*/n/*q*/ame + " is " + /*p*/a/*o*/ge + " years old" + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("r", "q"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("p", "o"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts index 8c7b2a9383303..205f84c19a111 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBackTick.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = "/*x*/w/*y*/ith back`tick" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -"const foo = `with back\`tick`", -}); +/// + +//// const foo = "/*x*/w/*y*/ith back`tick" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `with back\\`tick`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts index 54b3f98591035..2519ac34664e2 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExpr.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = "/*x*/f/*y*/oobar is " + (42 + 6) + " years old" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const foo = \`foobar is \${ 42 + 6 } years old\``, -}); +/// + +//// const foo = "/*x*/f/*y*/oobar is " + (42 + 6) + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${42 + 6} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateConsecutiveStr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateConsecutiveStr.ts new file mode 100644 index 0000000000000..a053a6c5e3bca --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateConsecutiveStr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar is " + 42 + " years" + " old" + " and " + 6 + " cars" + " are" + " missing" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${42} years old and \${6} cars are missing\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts index 884b30202351b..bfad8d47cd1b2 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = "/*x*/w/*y*/ith ${dollar}" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -"const foo = `with \${dollar}`", -}); +/// + +//// const foo = "/*x*/w/*y*/ith ${dollar}" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `with $\\{dollar}`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts index 1c3af8543090f..aadc6068a68ed 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprInRow.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = "/*x*/f/*y*/oobar is " + 42 + 6 + " years old" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const foo = \`foobar is \${ 42 }\${ 6 } years old\``, -}); +/// + +//// const foo = "/*x*/f/*y*/oobar is " + 42 + 6 + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${42}\${6} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts index 6aa095d67d3cc..e781bae6634b3 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiExpr.ts @@ -1,16 +1,16 @@ -/// - -//// const age = 22 -//// const name = "Eddy" -//// const foo = /*x*/n/*y*/ame + " is " + age + " years old" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const age = 22 -const name = "Eddy" -const foo = \`\${ name } is \${ age } years old\``, -}); +/// + +//// const age = 22 +//// const name = "Eddy" +//// const foo = /*x*/n/*y*/ame + " is " + age + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const age = 22 +const name = "Eddy" +const foo = \`\${name} is \${age} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts index e58111ebfb222..94cd3202512ed 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts @@ -1,18 +1,18 @@ -/// - -//// const foo = "/*x*/w/*y*/ait for others\n" -//// + "D'oh!\n" -//// + ""Yada, yada, yada\n" -//// + "Hasta la vista, baby!" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const foo = \`wait for others -D'oh! -Yada, yada, yada -Hasta la vista, baby!\``, -}); +/// + +//// const foo = "/*x*/w/*y*/ait for others\n" +//// + "D'oh!\n" +//// + ""Yada, yada, yada\n" +//// + "Hasta la vista, baby!" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`wait for others +D'oh! +Yada, yada, yada +Hasta la vista, baby!\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts index 577c1de4efb98..836b06f98d495 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts @@ -1,14 +1,14 @@ -/// - -//// const foo = "/*x*/w/*y*/ait for new line\n" -//// + "bada bum!" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const foo = \`wait for new line -bada bum!\``, -}); +/// + +//// const foo = "/*x*/w/*y*/ait for new line\n" +//// + "bada bum!" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`wait for new line +bada bum!\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts index 8899f99c864e8..a790a623aaa77 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOneExpr.ts @@ -1,14 +1,14 @@ -/// - -//// const age = 42 -//// const foo = "/*x*/f/*y*/oobar is " + age + " years old" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const age = 42 -const foo = \`foobar is \${ age } years old\``, -}); +/// + +//// const age = 42 +//// const foo = "/*x*/f/*y*/oobar is " + age + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const age = 42 +const foo = \`foobar is \${age} years old\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOnlyStr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOnlyStr.ts new file mode 100644 index 0000000000000..bd1fa6009fc16 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOnlyStr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar " + "rocks" + " fantastically" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `foobar rocks fantastically`", +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts index 35b3e17519dd7..2c5663b07dc2a 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateSimple.ts @@ -1,12 +1,12 @@ -/// - -//// const foo = "/*x*/f/*y*/oobar rocks" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const foo = \`foobar rocks\``, -}); +/// + +//// const foo = "/*x*/f/*y*/oobar rocks" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar rocks\``, +}); From 671eb547f4d0a8bd661d05a2d16e623809e8e82b Mon Sep 17 00:00:00 2001 From: BigAru Date: Thu, 6 Dec 2018 13:23:47 +0100 Subject: [PATCH 08/29] complete toTemplate --- ...StringOrTemplateLiteral_ToTemplateDollar.ts | 2 +- ...ngOrTemplateLiteral_ToTemplateMultiLines.ts | 18 ------------------ ...tringOrTemplateLiteral_ToTemplateNewLine.ts | 14 -------------- 3 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts delete mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts index bfad8d47cd1b2..a073b6838f585 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateDollar.ts @@ -8,5 +8,5 @@ edit.applyRefactor({ actionName: "Convert to template literal", actionDescription: "Convert to template literal", newContent: -"const foo = `with $\\{dollar}`", +"const foo = `with $\\\\{dollar}`", }); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts deleted file mode 100644 index 94cd3202512ed..0000000000000 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateMultiLines.ts +++ /dev/null @@ -1,18 +0,0 @@ -/// - -//// const foo = "/*x*/w/*y*/ait for others\n" -//// + "D'oh!\n" -//// + ""Yada, yada, yada\n" -//// + "Hasta la vista, baby!" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const foo = \`wait for others -D'oh! -Yada, yada, yada -Hasta la vista, baby!\``, -}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts deleted file mode 100644 index 836b06f98d495..0000000000000 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateNewLine.ts +++ /dev/null @@ -1,14 +0,0 @@ -/// - -//// const foo = "/*x*/w/*y*/ait for new line\n" -//// + "bada bum!" - -goTo.select("x", "y"); -edit.applyRefactor({ - refactorName: "Convert string concatenation or template literal", - actionName: "Convert to template literal", - actionDescription: "Convert to template literal", - newContent: -`const foo = \`wait for new line -bada bum!\``, -}); From f1d28c93aa61b064af2d4b509b6b3b4db0c4c04a Mon Sep 17 00:00:00 2001 From: BigAru Date: Thu, 6 Dec 2018 14:42:33 +0100 Subject: [PATCH 09/29] complete toString --- .../convertStringOrTemplateLiteral.ts | 91 +++++++++++++------ ...gOrTemplateLiteral_ToStringAvailability.ts | 24 ++--- ...tStringOrTemplateLiteral_ToStringDollar.ts | 2 +- ...ringOrTemplateLiteral_ToStringMultiExpr.ts | 4 +- ...tStringOrTemplateLiteral_ToStringNested.ts | 2 +- 5 files changed, 80 insertions(+), 43 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index e63ab98e6e5dc..9b4b754d5d96b 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -2,12 +2,13 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const refactorName = "Convert string concatenation or template literal"; const toTemplateLiteralActionName = "Convert to template literal"; - // const toStringConcatenationActionName = "Convert to string concatenation"; - // let str = ""; str; + const toStringConcatenationActionName = "Convert to string concatenation"; const refactorDescription = getLocaleSpecificMessage(Diagnostics.Convert_string_concatenation_or_template_literal); const toTemplateLiteralDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_literal); - // const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); + const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); + + // TODO let a = 45 + 45 + " ee" + 33; registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); @@ -15,22 +16,22 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const { file, startPosition } = context; const node = getTokenAtPosition(file, startPosition); const maybeBinary = getParentBinaryExpression(node); containsString(maybeBinary); - if (!(isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) || !containsString(maybeBinary)) return emptyArray; - - // let str = "untouched"; - // if (isBinaryExpression(maybeBinary)) str = maybeBinary.right.getText(); - // if (isStringLiteral(maybeBinary)) str = maybeBinary.getText(); - - return [{ - name: refactorName, - description: refactorDescription, - actions: [ - { - name: toTemplateLiteralActionName, - description: toTemplateLiteralDescription - } - ] - }]; + const actions: RefactorActionInfo[] = []; + + if (!isTemplateLike(node) && (isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && containsString(maybeBinary)) { + actions.push({ name: toTemplateLiteralActionName, description: toTemplateLiteralDescription }); + } + + + if (isTemplateLike(node)) { + actions.push({ name: toStringConcatenationActionName, description: toStringConcatenationDescription }); + } + + return [{ name: refactorName, description: refactorDescription, actions }]; + } + + function isTemplateLike(node: Node): boolean { + return isNoSubstitutionTemplateLiteral(node) || isTemplateHead(node) || isTemplateMiddleOrTemplateTail(node); } function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { @@ -40,18 +41,38 @@ namespace ts.refactor.convertStringOrTemplateLiteral { switch (actionName) { case toTemplateLiteralActionName: const maybeBinary = getParentBinaryExpression(node); - let templateLiteral: TemplateExpression | NoSubstitutionTemplateLiteral; - if (isStringLiteral(maybeBinary)) { - templateLiteral = createNoSubstitutionTemplateLiteral(cleanString(maybeBinary.text)); - } - else { - const arrayOfNodes = treeToArray(maybeBinary); - templateLiteral = nodesToTemplate(arrayOfNodes); - } + const arrayOfNodes = treeToArray(maybeBinary); + const templateLiteral = nodesToTemplate(arrayOfNodes); const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); return { edits }; + case toStringConcatenationActionName: + if (isNoSubstitutionTemplateLiteral(node)) { + const stringLiteral = createStringLiteral(node.text); + + return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, node, stringLiteral)) }; + + } + if (isTemplateExpression(node.parent)) { + const templateLiteralExpression = node.parent; + const nodesArray: Expression[] = []; + + nodesArray.push(createStringLiteral(templateLiteralExpression.head.text)); + templateLiteralExpression.templateSpans.forEach(ts => { + nodesArray.push(ts.expression); + const str = ts.literal.text; + if (str.length !== 0) { + nodesArray.push(createStringLiteral(str)); + } + }); + + const binaryExpression = arrayToTree(nodesArray, /* binaryExpression*/ undefined); + return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, templateLiteralExpression, binaryExpression)) }; + } + + break; + default: return Debug.fail("invalid action"); } @@ -73,6 +94,22 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return false; } + function arrayToTree(nodes: Expression[], bexpr: BinaryExpression | undefined): BinaryExpression { + if (nodes.length === 0) return bexpr!; + + if (bexpr === undefined) { + const left = nodes[0]; + const right = nodes[1]; + + const binary = createBinary (left, SyntaxKind.PlusToken, right); + return arrayToTree(nodes.slice(2), binary); + } + + const right = nodes[0]; + const binary = createBinary (bexpr, SyntaxKind.PlusToken, right); + return arrayToTree(nodes.slice(1), binary); + } + function treeToArray(node: Node): Node[] { if (isBinaryExpression(node)) { return treeToArray(node.left).concat(treeToArray(node.right)); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts index 439b5cd80e7f2..6ce188ac4383c 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts @@ -5,26 +5,26 @@ //// const /*z*/f/*y*/oo = /*x*/`/*w*/M/*v*/r/*u*/ /*t*/$/*s*/{ /*r*/n/*q*/ame } is $/*p*/{/*o*/ age } years old` goTo.select("z", "y"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("x", "w"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("v", "u"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("t", "s"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("r", "q"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("p", "o"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation") -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal") +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts index 3b19470d7a77b..667580796717a 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringDollar.ts @@ -8,5 +8,5 @@ edit.applyRefactor({ actionName: "Convert to string concatenation", actionDescription: "Convert to string concatenation", newContent: -"const foo = \"with ${dollar}\"", +"const foo = \"with \${dollar}\"", }); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts index 79f624118d7e0..b75af11d8bc40 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts @@ -2,7 +2,7 @@ //// const age = 22 //// const name = "Eddy" -//// const foo = `${ /*x*/n/*y*/ame } is ${ age } years old` +//// const foo = `/*x*/$/*y*/{ name } is ${ age } years old` goTo.select("x", "y"); edit.applyRefactor({ @@ -12,5 +12,5 @@ edit.applyRefactor({ newContent: `const age = 22 const name = "Eddy" -const foo = name + " is " + age + " years old"`, +const foo = "" + name + " is " + age + " years old"`, }); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts index d7a8f52b374a0..53477456f3f18 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts @@ -10,5 +10,5 @@ edit.applyRefactor({ actionDescription: "Convert to string concatenation", newContent: `const age = 42 -const foo = "foobar is a " + ( age < 18 ? 'child' : \`grown-up \${ age > 40 ? 'who needs probaply assistance': ''}\` ) `, +const·foo·=·"foobar·is·a·"·+·(age·<·18·?·'child'·:·\`grown-up·\${age·>·40·?·'who·needs·probaply·assistance'·:·''}\`)`, }); From 9bce643b2885e33ed8416bbdf9436412a6578e35 Mon Sep 17 00:00:00 2001 From: BigAru Date: Thu, 6 Dec 2018 16:09:43 +0100 Subject: [PATCH 10/29] catch empty head of template literal --- src/services/refactors/convertStringOrTemplateLiteral.ts | 4 +++- ...efactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 9b4b754d5d96b..bbc825c3d008d 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -9,6 +9,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); // TODO let a = 45 + 45 + " ee" + 33; + // TODO let a = tag `aaa`; registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); @@ -58,7 +59,8 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const templateLiteralExpression = node.parent; const nodesArray: Expression[] = []; - nodesArray.push(createStringLiteral(templateLiteralExpression.head.text)); + if (templateLiteralExpression.head.text.length !== 0) nodesArray.push(createStringLiteral(templateLiteralExpression.head.text)); + templateLiteralExpression.templateSpans.forEach(ts => { nodesArray.push(ts.expression); const str = ts.literal.text; diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts index b75af11d8bc40..3fdd7fb6362a9 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringMultiExpr.ts @@ -12,5 +12,5 @@ edit.applyRefactor({ newContent: `const age = 22 const name = "Eddy" -const foo = "" + name + " is " + age + " years old"`, +const foo = name + " is " + age + " years old"`, }); From 97cee680b247cfaa3d45532068ab7f454346db75 Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 04:24:01 +0100 Subject: [PATCH 11/29] add toString visibility from expression and from middle part --- .../refactors/convertStringOrTemplateLiteral.ts | 14 +++++--------- ...StringOrTemplateLiteral_ToStringAvailability.ts | 2 +- ...ngOrTemplateLiteral_ToStringSelectedFromExpr.ts | 14 ++++++++++++++ ...OrTemplateLiteral_ToStringSelectedFromMiddle.ts | 14 ++++++++++++++ 4 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromExpr.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromMiddle.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index bbc825c3d008d..423e15b88f130 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -9,6 +9,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); // TODO let a = 45 + 45 + " ee" + 33; + // TODO let a = 45 - 45 + " ee" - 33; // TODO let a = tag `aaa`; registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); @@ -19,22 +20,17 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const maybeBinary = getParentBinaryExpression(node); containsString(maybeBinary); const actions: RefactorActionInfo[] = []; - if (!isTemplateLike(node) && (isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && containsString(maybeBinary)) { + if ((isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && containsString(maybeBinary)) { actions.push({ name: toTemplateLiteralActionName, description: toTemplateLiteralDescription }); } - - if (isTemplateLike(node)) { + if (isNoSubstitutionTemplateLiteral(node) || isTemplateHead(node) || isTemplateSpan(node.parent)) { actions.push({ name: toStringConcatenationActionName, description: toStringConcatenationDescription }); } return [{ name: refactorName, description: refactorDescription, actions }]; } - function isTemplateLike(node: Node): boolean { - return isNoSubstitutionTemplateLiteral(node) || isTemplateHead(node) || isTemplateMiddleOrTemplateTail(node); - } - function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const { file, startPosition } = context; const node = getTokenAtPosition(file, startPosition); @@ -55,8 +51,8 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, node, stringLiteral)) }; } - if (isTemplateExpression(node.parent)) { - const templateLiteralExpression = node.parent; + if (isTemplateExpression(node.parent) || isTemplateSpan(node.parent)) { + const templateLiteralExpression = isTemplateSpan(node.parent) ? node.parent.parent : node.parent; const nodesArray: Expression[] = []; if (templateLiteralExpression.head.text.length !== 0) nodesArray.push(createStringLiteral(templateLiteralExpression.head.text)); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts index 6ce188ac4383c..3831d4437355b 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts @@ -21,7 +21,7 @@ verify.refactorAvailable("Convert string concatenation or template literal", "Co verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("r", "q"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("p", "o"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromExpr.ts new file mode 100644 index 0000000000000..7229e9e9f7a44 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromExpr.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is ${ /*x*/a/*y*/ge } years old` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is " + age + " years old"`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromMiddle.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromMiddle.ts new file mode 100644 index 0000000000000..c44e4ee646f1d --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringSelectedFromMiddle.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is ${ age } /*x*/y/*y*/ears old ${ false }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = "foobar is " + age + " years old " + false`, +}); From 5e6e1fb6c69823d8ea956b68c4c5e80cfc73d656 Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 04:28:02 +0100 Subject: [PATCH 12/29] fix test case --- .../refactorConvertStringOrTemplateLiteral_ToStringNested.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts index 53477456f3f18..f579508df2357 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts @@ -10,5 +10,5 @@ edit.applyRefactor({ actionDescription: "Convert to string concatenation", newContent: `const age = 42 -const·foo·=·"foobar·is·a·"·+·(age·<·18·?·'child'·:·\`grown-up·\${age·>·40·?·'who·needs·probaply·assistance'·:·''}\`)`, +const foo = "foobar is a " + (age < 18 ? 'child' : \`grown-up \${age > 40 ? 'who needs probaply assistance' : ''}\`)`, }); From 91cfca3066e7d9769d94567e903eab51fdf005df Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 05:06:52 +0100 Subject: [PATCH 13/29] combine preceding expressions to one --- .../refactors/convertStringOrTemplateLiteral.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 423e15b88f130..be6d088ff2928 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -8,7 +8,6 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const toTemplateLiteralDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_literal); const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); - // TODO let a = 45 + 45 + " ee" + 33; // TODO let a = 45 - 45 + " ee" - 33; // TODO let a = tag `aaa`; @@ -144,6 +143,19 @@ namespace ts.refactor.convertStringOrTemplateLiteral { let current = nodes[i]; let templatePart: TemplateMiddle | TemplateTail; + if (head.text.length === 0 && i + 1 < nodes.length && !isStringLiteral(nodes[i + 1])) { + let binary = createBinary(current as Expression, SyntaxKind.PlusToken, nodes[i + 1] as Expression); + current = binary; + i++; + + while (i + 1 < nodes.length && !isStringLiteral(nodes[i + 1])) { + binary = createBinary(binary, SyntaxKind.PlusToken, nodes[i + 1] as Expression); + i++; + } + + current = binary; + } + if (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { let next = nodes[i + 1] as StringLiteral; let text = next.text; From ec69194034413a4ebedce7cd7cb898ad53927877 Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 05:58:00 +0100 Subject: [PATCH 14/29] do not offer refactoring for tagged templates --- .../refactors/convertStringOrTemplateLiteral.ts | 7 +++---- ...ingOrTemplateLiteral_ToStringAvailability.ts | 1 - ...emplateLiteral_ToStringTaggedAvailability.ts | 17 +++++++++++++++++ ...ingOrTemplateLiteral_ToTemplatePrefixExpr.ts | 12 ++++++++++++ 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringTaggedAvailability.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplatePrefixExpr.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index be6d088ff2928..08f45aacb2461 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -9,7 +9,6 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); // TODO let a = 45 - 45 + " ee" - 33; - // TODO let a = tag `aaa`; registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); @@ -17,13 +16,14 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const { file, startPosition } = context; const node = getTokenAtPosition(file, startPosition); const maybeBinary = getParentBinaryExpression(node); containsString(maybeBinary); + const maybeTemplateExpression = findAncestor(node, n => isTemplateExpression(n)); const actions: RefactorActionInfo[] = []; if ((isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && containsString(maybeBinary)) { actions.push({ name: toTemplateLiteralActionName, description: toTemplateLiteralDescription }); } - if (isNoSubstitutionTemplateLiteral(node) || isTemplateHead(node) || isTemplateSpan(node.parent)) { + if ((isNoSubstitutionTemplateLiteral(node) && !isTaggedTemplateExpression(node.parent)) || (maybeTemplateExpression && !isTaggedTemplateExpression(maybeTemplateExpression.parent))) { actions.push({ name: toStringConcatenationActionName, description: toStringConcatenationDescription }); } @@ -46,8 +46,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { case toStringConcatenationActionName: if (isNoSubstitutionTemplateLiteral(node)) { const stringLiteral = createStringLiteral(node.text); - - return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, node, stringLiteral)) }; + return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, node, stringLiteral)) }; } if (isTemplateExpression(node.parent) || isTemplateSpan(node.parent)) { diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts index 3831d4437355b..a4b6be7463129 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts @@ -27,4 +27,3 @@ verify.not.refactorAvailable("Convert string concatenation or template literal", goTo.select("p", "o"); verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); - diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringTaggedAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringTaggedAvailability.ts new file mode 100644 index 0000000000000..9327ff0911cbe --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringTaggedAvailability.ts @@ -0,0 +1,17 @@ +/// + +//// function tag(literals: TemplateStringsArray, ...placeholders: string[]) { return "tagged" } +//// const alpha = tag/*z*/`/*y*/foobar` +//// const beta = tag/*x*/`/*w*/foobar ${/*v*/4/*u*/2}` + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplatePrefixExpr.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplatePrefixExpr.ts new file mode 100644 index 0000000000000..dfaa3ab8d66ec --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplatePrefixExpr.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = /*x*/4/*y*/2 + 6 + 23 + 12 +" years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`\${42 + 6 + 23 + 12} years old\``, +}); From 4d522c335949e87e0042a1eb4e3e9afa6182d741 Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 08:29:01 +0100 Subject: [PATCH 15/29] optimize preceding expression --- .../convertStringOrTemplateLiteral.ts | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 08f45aacb2461..6d9f7e46b0bc5 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -37,7 +37,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { switch (actionName) { case toTemplateLiteralActionName: const maybeBinary = getParentBinaryExpression(node); - const arrayOfNodes = treeToArray(maybeBinary); + const arrayOfNodes = treeToArray(maybeBinary)[0]; const templateLiteral = nodesToTemplate(arrayOfNodes); const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); @@ -106,11 +106,16 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return arrayToTree(nodes.slice(1), binary); } - function treeToArray(node: Node): Node[] { + function treeToArray(node: Node): [Node[], boolean] { if (isBinaryExpression(node)) { - return treeToArray(node.left).concat(treeToArray(node.right)); + const [leftNodes, leftHasString] = treeToArray(node.left); + const [rightNodes, rightHasString] = treeToArray(node.right); + + if (!leftHasString && !rightHasString) return [[node], false]; + return [leftNodes.concat(rightNodes), true]; } - return [node]; + + return [[node], isStringLiteral(node)]; } function nodesToTemplate(nodes: Node[]) { @@ -142,19 +147,6 @@ namespace ts.refactor.convertStringOrTemplateLiteral { let current = nodes[i]; let templatePart: TemplateMiddle | TemplateTail; - if (head.text.length === 0 && i + 1 < nodes.length && !isStringLiteral(nodes[i + 1])) { - let binary = createBinary(current as Expression, SyntaxKind.PlusToken, nodes[i + 1] as Expression); - current = binary; - i++; - - while (i + 1 < nodes.length && !isStringLiteral(nodes[i + 1])) { - binary = createBinary(binary, SyntaxKind.PlusToken, nodes[i + 1] as Expression); - i++; - } - - current = binary; - } - if (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { let next = nodes[i + 1] as StringLiteral; let text = next.text; From 7441c3519b00d3d2d769f01181e524876ce7e5f0 Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 11:43:15 +0100 Subject: [PATCH 16/29] treat corner cases --- .../convertStringOrTemplateLiteral.ts | 28 +++++++++++-------- ...lateLiteral_ToStringAvailabilityTagged.ts} | 0 ...lateLiteral_ToTemplateAvailabilityMinus.ts | 25 +++++++++++++++++ ...Literal_ToTemplateAvailabilityNoStrings.ts | 19 +++++++++++++ ...al_ToTemplateAvailabilityPrecedingMinus.ts | 24 ++++++++++++++++ 5 files changed, 85 insertions(+), 11 deletions(-) rename tests/cases/fourslash/{refactorConvertStringOrTemplateLiteral_ToStringTaggedAvailability.ts => refactorConvertStringOrTemplateLiteral_ToStringAvailabilityTagged.ts} (100%) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityMinus.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityNoStrings.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityPrecedingMinus.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 6d9f7e46b0bc5..a35f921c867e5 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -8,8 +8,6 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const toTemplateLiteralDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_template_literal); const toStringConcatenationDescription = getLocaleSpecificMessage(Diagnostics.Convert_to_string_concatenation); - // TODO let a = 45 - 45 + " ee" - 33; - registerRefactor(refactorName, { getEditsForAction, getAvailableActions }); function getAvailableActions(context: RefactorContext): ReadonlyArray { @@ -19,7 +17,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const maybeTemplateExpression = findAncestor(node, n => isTemplateExpression(n)); const actions: RefactorActionInfo[] = []; - if ((isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && containsString(maybeBinary)) { + if ((isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && isStringConcatenationValid(maybeBinary)) { actions.push({ name: toTemplateLiteralActionName, description: toTemplateLiteralDescription }); } @@ -39,7 +37,6 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const maybeBinary = getParentBinaryExpression(node); const arrayOfNodes = treeToArray(maybeBinary)[0]; const templateLiteral = nodesToTemplate(arrayOfNodes); - const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); return { edits }; @@ -97,7 +94,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { const left = nodes[0]; const right = nodes[1]; - const binary = createBinary (left, SyntaxKind.PlusToken, right); + const binary = createBinary(left, SyntaxKind.PlusToken, right); return arrayToTree(nodes.slice(2), binary); } @@ -106,16 +103,25 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return arrayToTree(nodes.slice(1), binary); } - function treeToArray(node: Node): [Node[], boolean] { + function isStringConcatenationValid(node: Node): boolean { + const [, containsString, areOperatorsValid] = treeToArray(node); + return containsString && areOperatorsValid; + } + + function treeToArray(node: Node): [Node[], /* containsString */ boolean, /* areOperatorsValid */ boolean] { if (isBinaryExpression(node)) { - const [leftNodes, leftHasString] = treeToArray(node.left); - const [rightNodes, rightHasString] = treeToArray(node.right); + const [leftNodes, leftHasString, leftOp] = treeToArray(node.left); + const [rightNodes, rightHasString, rightOp] = treeToArray(node.right); + + if (!leftHasString && !rightHasString) return [[node], false, true]; + + const currentOp = node.operatorToken.kind === SyntaxKind.PlusToken; + const isPlus = leftOp && currentOp && rightOp; - if (!leftHasString && !rightHasString) return [[node], false]; - return [leftNodes.concat(rightNodes), true]; + return [leftNodes.concat(rightNodes), true, isPlus]; } - return [[node], isStringLiteral(node)]; + return [[node], isStringLiteral(node), true]; } function nodesToTemplate(nodes: Node[]) { diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringTaggedAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailabilityTagged.ts similarity index 100% rename from tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringTaggedAvailability.ts rename to tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailabilityTagged.ts diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityMinus.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityMinus.ts new file mode 100644 index 0000000000000..50c7921836c22 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityMinus.ts @@ -0,0 +1,25 @@ +/// + +//// const age = 22 +//// const name = "Eddy" +//// const /*z*/f/*y*/oo = /*x*/"/*w*/M/*v*/r/*u*/ " /*t*/+/*s*/ name + " is " - /*r*/a/*q*/ge - " years old" + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("r", "q"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityNoStrings.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityNoStrings.ts new file mode 100644 index 0000000000000..f015af3b3c75e --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityNoStrings.ts @@ -0,0 +1,19 @@ +/// + +//// const /*z*/f/*y*/oo = /*x*/4/*w*/2 /*v*/-/*u*/ 56 + /*t*/2/*s*/2 * 4 / 33 + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityPrecedingMinus.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityPrecedingMinus.ts new file mode 100644 index 0000000000000..04f24dffc8f0f --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAvailabilityPrecedingMinus.ts @@ -0,0 +1,24 @@ +/// + +//// const age = 22 +//// const /*z*/f/*y*/oo = /*x*/a/*w*/ge * 4 /*v*/-/*u*/ 2 / 4 /*t*/+/*s*/ " /*r*/y/*q*/ears old" + +goTo.select("z", "y"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("x", "w"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("v", "u"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("t", "s"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); + +goTo.select("r", "q"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); From 7d8fccdbac963db746a90c2d6df0cc459fcfeb65 Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 12:14:23 +0100 Subject: [PATCH 17/29] remove parentheses also when expression at ending --- .../refactors/convertStringOrTemplateLiteral.ts | 3 +-- ...OrTemplateLiteral_ToTemplateBinaryExprInEnding.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExprInEnding.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index a35f921c867e5..e8b8768362c64 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -164,15 +164,14 @@ namespace ts.refactor.convertStringOrTemplateLiteral { i++; } - if (isParenthesizedExpression(current)) current = current.expression; text = cleanString(text); - templatePart = i === nodes.length - 1 ? createTemplateTail(text) : createTemplateMiddle(text); } else { templatePart = i === nodes.length - 1 ? createTemplateTail("") : createTemplateMiddle(""); } + if (isParenthesizedExpression(current)) current = current.expression; spans.push(createTemplateSpan(current as Expression, templatePart)); } diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExprInEnding.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExprInEnding.ts new file mode 100644 index 0000000000000..a7ab291657b17 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateBinaryExprInEnding.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "/*x*/f/*y*/oobar is " + (42 + 6) + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `foobar is \${42 + 6}`", +}); From e567e53a0ea88d2efe3b4639f68d06bcc4e66ecd Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 12:58:35 +0100 Subject: [PATCH 18/29] add possibility to invoke from parentheses --- .../convertStringOrTemplateLiteral.ts | 18 ++++++------------ ...rTemplateLiteral_ToTemplateExprFromBrace.ts | 12 ++++++++++++ 2 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprFromBrace.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index e8b8768362c64..b926ce65f2858 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -12,8 +12,9 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function getAvailableActions(context: RefactorContext): ReadonlyArray { const { file, startPosition } = context; - const node = getTokenAtPosition(file, startPosition); - const maybeBinary = getParentBinaryExpression(node); containsString(maybeBinary); + let node = getTokenAtPosition(file, startPosition); + if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) node = node.parent.parent; + const maybeBinary = getParentBinaryExpression(node); const maybeTemplateExpression = findAncestor(node, n => isTemplateExpression(n)); const actions: RefactorActionInfo[] = []; @@ -30,11 +31,13 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const { file, startPosition } = context; - const node = getTokenAtPosition(file, startPosition); + let node = getTokenAtPosition(file, startPosition); switch (actionName) { case toTemplateLiteralActionName: + if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) node = node.parent.parent; const maybeBinary = getParentBinaryExpression(node); + const arrayOfNodes = treeToArray(maybeBinary)[0]; const templateLiteral = nodesToTemplate(arrayOfNodes); const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); @@ -78,15 +81,6 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return expr; } - function containsString(node: Node): boolean { - if (isBinaryExpression(node)) { - return containsString(node.left) || containsString(node.right); - } - - if (isStringLiteral(node)) return true; - return false; - } - function arrayToTree(nodes: Expression[], bexpr: BinaryExpression | undefined): BinaryExpression { if (nodes.length === 0) return bexpr!; diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprFromBrace.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprFromBrace.ts new file mode 100644 index 0000000000000..5924da73ebc20 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateExprFromBrace.ts @@ -0,0 +1,12 @@ +/// + +//// const foo = "foobar is " + /*x*/(/*y*/42 + 6) + " years old" + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`const foo = \`foobar is \${42 + 6} years old\``, +}); From e0fdf742eda5e2837366c2e33e77c0100983d953 Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 13:37:38 +0100 Subject: [PATCH 19/29] only show toString if expression is not binary --- .../refactors/convertStringOrTemplateLiteral.ts | 12 ++++++++++-- ...rtStringOrTemplateLiteral_ToStringAvailability.ts | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index b926ce65f2858..cc360e5b991c8 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -15,20 +15,28 @@ namespace ts.refactor.convertStringOrTemplateLiteral { let node = getTokenAtPosition(file, startPosition); if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) node = node.parent.parent; const maybeBinary = getParentBinaryExpression(node); - const maybeTemplateExpression = findAncestor(node, n => isTemplateExpression(n)); + // const maybeTemplateExpression = findAncestor(node, n => isTemplateExpression(n)); const actions: RefactorActionInfo[] = []; if ((isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && isStringConcatenationValid(maybeBinary)) { actions.push({ name: toTemplateLiteralActionName, description: toTemplateLiteralDescription }); } - if ((isNoSubstitutionTemplateLiteral(node) && !isTaggedTemplateExpression(node.parent)) || (maybeTemplateExpression && !isTaggedTemplateExpression(maybeTemplateExpression.parent))) { + // if ((isNoSubstitutionTemplateLiteral(node) && !isTaggedTemplateExpression(node.parent)) || (maybeTemplateExpression && !isTaggedTemplateExpression(maybeTemplateExpression.parent))) { + if (isTemplateLike(node)) { actions.push({ name: toStringConcatenationActionName, description: toStringConcatenationDescription }); } return [{ name: refactorName, description: refactorDescription, actions }]; } + function isTemplateLike(node: Node) { + const isEmptyTL = isNoSubstitutionTemplateLiteral(node) && !isTaggedTemplateExpression(node.parent); + const is = (isTemplateHead(node) || isTemplateMiddleOrTemplateTail(node)) && !isTaggedTemplateExpression(node.parent.parent); + const ise = (isTemplateSpan(node.parent)) && !isTaggedTemplateExpression(node.parent.parent.parent); + return isEmptyTL || is || ise; + } + function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const { file, startPosition } = context; let node = getTokenAtPosition(file, startPosition); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts index a4b6be7463129..2725cd75aaf30 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts @@ -2,7 +2,7 @@ //// const age = 22 //// const name = "Eddy" -//// const /*z*/f/*y*/oo = /*x*/`/*w*/M/*v*/r/*u*/ /*t*/$/*s*/{ /*r*/n/*q*/ame } is $/*p*/{/*o*/ age } years old` +//// const /*z*/f/*y*/oo = /*x*/`/*w*/M/*v*/r/*u*/ /*t*/$/*s*/{ /*r*/n/*q*/ame } is ${ /*p*/a/*o*/ge + 34 } years old` goTo.select("z", "y"); verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); @@ -25,5 +25,5 @@ verify.refactorAvailable("Convert string concatenation or template literal", "Co verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("p", "o"); -verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); From bea12a1e0791c466bdd4ce4ef611328064b7b89b Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 14:06:41 +0100 Subject: [PATCH 20/29] extract creation of templateHead --- .../convertStringOrTemplateLiteral.ts | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index cc360e5b991c8..eebb27c676c0d 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -15,14 +15,12 @@ namespace ts.refactor.convertStringOrTemplateLiteral { let node = getTokenAtPosition(file, startPosition); if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) node = node.parent.parent; const maybeBinary = getParentBinaryExpression(node); - // const maybeTemplateExpression = findAncestor(node, n => isTemplateExpression(n)); const actions: RefactorActionInfo[] = []; if ((isBinaryExpression(maybeBinary) || isStringLiteral(maybeBinary)) && isStringConcatenationValid(maybeBinary)) { actions.push({ name: toTemplateLiteralActionName, description: toTemplateLiteralDescription }); } - // if ((isNoSubstitutionTemplateLiteral(node) && !isTaggedTemplateExpression(node.parent)) || (maybeTemplateExpression && !isTaggedTemplateExpression(maybeTemplateExpression.parent))) { if (isTemplateLike(node)) { actions.push({ name: toStringConcatenationActionName, description: toStringConcatenationDescription }); } @@ -31,10 +29,14 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } function isTemplateLike(node: Node) { - const isEmptyTL = isNoSubstitutionTemplateLiteral(node) && !isTaggedTemplateExpression(node.parent); - const is = (isTemplateHead(node) || isTemplateMiddleOrTemplateTail(node)) && !isTaggedTemplateExpression(node.parent.parent); - const ise = (isTemplateSpan(node.parent)) && !isTaggedTemplateExpression(node.parent.parent.parent); - return isEmptyTL || is || ise; + const isEmptyTemplate = isNoSubstitutionTemplateLiteral(node) && isNotTagged(node); + const isTemplate = (isTemplateHead(node) || isTemplateMiddleOrTemplateTail(node)) && isNotTagged(node.parent); + const isTemplateFromExpression = isTemplateSpan(node.parent) && isNotTagged(node.parent.parent); + return isEmptyTemplate || isTemplate || isTemplateFromExpression; + } + + function isNotTagged(templateExpression: Node) { + return !isTaggedTemplateExpression(templateExpression.parent); } function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { @@ -126,26 +128,23 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return [[node], isStringLiteral(node), true]; } - function nodesToTemplate(nodes: Node[]) { + function createHead(nodes: Node[]): [number, TemplateHead] { let begin = 0; - const head = createTemplateHead(""); - const firstNode = nodes[0]; - const spans: TemplateSpan[] = []; - if (isStringLiteral(firstNode)) { - head.text = firstNode.text; + while (begin < nodes.length && isStringLiteral(nodes[begin])) { + const next = nodes[begin] as StringLiteral; + head.text = head.text + next.text; begin++; + } - while (begin < nodes.length && isStringLiteral(nodes[begin])) { - - const next = nodes[begin] as StringLiteral; - head.text = head.text + next.text; - begin++; - } + head.text = escapeText(head.text); + return [begin, head]; + } - head.text = cleanString(head.text); - } + function nodesToTemplate(nodes: Node[]) { + const spans: TemplateSpan[] = []; + const [begin, head] = createHead(nodes); if (begin === nodes.length) { return createNoSubstitutionTemplateLiteral(head.text); @@ -166,7 +165,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { i++; } - text = cleanString(text); + text = escapeText(text); templatePart = i === nodes.length - 1 ? createTemplateTail(text) : createTemplateMiddle(text); } else { @@ -180,7 +179,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return createTemplateExpression(head, spans); } - function cleanString(content: string) { + function escapeText(content: string) { return content.replace("`", "\`").replace("\${", `$\\{`); } From 2a38eef914e1120d6c9593e4fe67252fd943526b Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 14:46:42 +0100 Subject: [PATCH 21/29] optimize nodesToTemplate --- .../convertStringOrTemplateLiteral.ts | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index eebb27c676c0d..7c9c191f4eab1 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -48,7 +48,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) node = node.parent.parent; const maybeBinary = getParentBinaryExpression(node); - const arrayOfNodes = treeToArray(maybeBinary)[0]; + const arrayOfNodes = transformTreeToArray(maybeBinary); const templateLiteral = nodesToTemplate(arrayOfNodes); const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); return { edits }; @@ -73,7 +73,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } }); - const binaryExpression = arrayToTree(nodesArray, /* binaryExpression*/ undefined); + const binaryExpression = arrayToTree(nodesArray); return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, templateLiteralExpression, binaryExpression)) }; } @@ -91,10 +91,10 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return expr; } - function arrayToTree(nodes: Expression[], bexpr: BinaryExpression | undefined): BinaryExpression { - if (nodes.length === 0) return bexpr!; + function arrayToTree(nodes: Expression[], accumulator?: BinaryExpression): BinaryExpression { + if (nodes.length === 0) return accumulator!; - if (bexpr === undefined) { + if (!accumulator) { const left = nodes[0]; const right = nodes[1]; @@ -103,7 +103,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } const right = nodes[0]; - const binary = createBinary (bexpr, SyntaxKind.PlusToken, right); + const binary = createBinary(accumulator, SyntaxKind.PlusToken, right); return arrayToTree(nodes.slice(1), binary); } @@ -112,20 +112,24 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return containsString && areOperatorsValid; } - function treeToArray(node: Node): [Node[], /* containsString */ boolean, /* areOperatorsValid */ boolean] { + function transformTreeToArray(node: Node): Expression[] { + return treeToArray(node)[0]; + } + + function treeToArray(node: Node): [Expression[], /* containsString */ boolean, /* areOperatorsValid */ boolean] { if (isBinaryExpression(node)) { - const [leftNodes, leftHasString, leftOp] = treeToArray(node.left); - const [rightNodes, rightHasString, rightOp] = treeToArray(node.right); + const [leftNodes, leftHasString, leftOperatorValid] = treeToArray(node.left); + const [rightNodes, rightHasString, rightOperatorValid] = treeToArray(node.right); if (!leftHasString && !rightHasString) return [[node], false, true]; - const currentOp = node.operatorToken.kind === SyntaxKind.PlusToken; - const isPlus = leftOp && currentOp && rightOp; + const nodeOperatorValid = node.operatorToken.kind === SyntaxKind.PlusToken; + const isPlus = leftOperatorValid && nodeOperatorValid && rightOperatorValid; return [leftNodes.concat(rightNodes), true, isPlus]; } - return [[node], isStringLiteral(node), true]; + return [[node as Expression], isStringLiteral(node), true]; } function createHead(nodes: Node[]): [number, TemplateHead] { @@ -142,8 +146,8 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return [begin, head]; } - function nodesToTemplate(nodes: Node[]) { - const spans: TemplateSpan[] = []; + function nodesToTemplate(nodes: Expression[]) { + const templateSpans: TemplateSpan[] = []; const [begin, head] = createHead(nodes); if (begin === nodes.length) { @@ -152,35 +156,27 @@ namespace ts.refactor.convertStringOrTemplateLiteral { for (let i = begin; i < nodes.length; i++) { let current = nodes[i]; - let templatePart: TemplateMiddle | TemplateTail; + let text = ""; - if (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { - let next = nodes[i + 1] as StringLiteral; - let text = next.text; + while (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { + const next = nodes[i + 1] as StringLiteral; + text = text + next.text; i++; - - while (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { - next = nodes[i + 1] as StringLiteral; - text = text + next.text; - i++; - } - - text = escapeText(text); - templatePart = i === nodes.length - 1 ? createTemplateTail(text) : createTemplateMiddle(text); - } - else { - templatePart = i === nodes.length - 1 ? createTemplateTail("") : createTemplateMiddle(""); } + text = escapeText(text); + const templatePart = i === nodes.length - 1 ? createTemplateTail(text) : createTemplateMiddle(text); + if (isParenthesizedExpression(current)) current = current.expression; - spans.push(createTemplateSpan(current as Expression, templatePart)); + templateSpans.push(createTemplateSpan(current, templatePart)); } - return createTemplateExpression(head, spans); + return createTemplateExpression(head, templateSpans); } function escapeText(content: string) { - return content.replace("`", "\`").replace("\${", `$\\{`); + return content.replace("`", "\`") + .replace("\${", `$\\{`); } } From 59008eb1efb4d7bcae9e18fdf8d9f98d49fa7463 Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 15:24:58 +0100 Subject: [PATCH 22/29] extract getEdits for string concatenation --- .../convertStringOrTemplateLiteral.ts | 62 ++++++++++--------- 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 7c9c191f4eab1..a0f6775caa65c 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -12,8 +12,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function getAvailableActions(context: RefactorContext): ReadonlyArray { const { file, startPosition } = context; - let node = getTokenAtPosition(file, startPosition); - if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) node = node.parent.parent; + const node = getNodeOrParentOfBraces(file, startPosition); const maybeBinary = getParentBinaryExpression(node); const actions: RefactorActionInfo[] = []; @@ -28,11 +27,17 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return [{ name: refactorName, description: refactorDescription, actions }]; } + function getNodeOrParentOfBraces(file: SourceFile, startPosition: number) { + const node = getTokenAtPosition(file, startPosition); + if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) return node.parent.parent; + return node; + } + function isTemplateLike(node: Node) { - const isEmptyTemplate = isNoSubstitutionTemplateLiteral(node) && isNotTagged(node); + const isTemplateWithoutSubstitution = isNoSubstitutionTemplateLiteral(node) && isNotTagged(node); const isTemplate = (isTemplateHead(node) || isTemplateMiddleOrTemplateTail(node)) && isNotTagged(node.parent); const isTemplateFromExpression = isTemplateSpan(node.parent) && isNotTagged(node.parent.parent); - return isEmptyTemplate || isTemplate || isTemplateFromExpression; + return isTemplateWithoutSubstitution || isTemplate || isTemplateFromExpression; } function isNotTagged(templateExpression: Node) { @@ -41,46 +46,45 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const { file, startPosition } = context; - let node = getTokenAtPosition(file, startPosition); + const node = getNodeOrParentOfBraces(file, startPosition); switch (actionName) { case toTemplateLiteralActionName: - if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) node = node.parent.parent; const maybeBinary = getParentBinaryExpression(node); - const arrayOfNodes = transformTreeToArray(maybeBinary); const templateLiteral = nodesToTemplate(arrayOfNodes); const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); return { edits }; case toStringConcatenationActionName: - if (isNoSubstitutionTemplateLiteral(node)) { - const stringLiteral = createStringLiteral(node.text); - return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, node, stringLiteral)) }; + return { edits: getEditsForToStringConcatenation(context, node) }; - } - if (isTemplateExpression(node.parent) || isTemplateSpan(node.parent)) { - const templateLiteralExpression = isTemplateSpan(node.parent) ? node.parent.parent : node.parent; - const nodesArray: Expression[] = []; - - if (templateLiteralExpression.head.text.length !== 0) nodesArray.push(createStringLiteral(templateLiteralExpression.head.text)); + default: + return Debug.fail("invalid action"); + } + } - templateLiteralExpression.templateSpans.forEach(ts => { - nodesArray.push(ts.expression); - const str = ts.literal.text; - if (str.length !== 0) { - nodesArray.push(createStringLiteral(str)); - } - }); + function getEditsForToStringConcatenation(context: RefactorContext, node: Node) { + if (isTemplateExpression(node.parent) || isTemplateSpan(node.parent)) { + const templateLiteralExpression = isTemplateSpan(node.parent) ? node.parent.parent : node.parent; + const { head, templateSpans } = templateLiteralExpression; + const arrayOfNodes: Expression[] = []; - const binaryExpression = arrayToTree(nodesArray); - return { edits: textChanges.ChangeTracker.with(context, t => t.replaceNode(file, templateLiteralExpression, binaryExpression)) }; - } + if (head.text.length !== 0) arrayOfNodes.push(createStringLiteral(head.text)); - break; + templateSpans.forEach(ts => { + arrayOfNodes.push(ts.expression); + const text = ts.literal.text; + if (text.length !== 0) arrayOfNodes.push(createStringLiteral(text)); + }); - default: - return Debug.fail("invalid action"); + const binaryExpression = arrayToTree(arrayOfNodes); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, templateLiteralExpression, binaryExpression)); + } + else { + const templateWithoutSubstitution = node as NoSubstitutionTemplateLiteral; + const stringLiteral = createStringLiteral(templateWithoutSubstitution.text); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, node, stringLiteral)); } } From 997f3e353d73636e01927b464874be45fb0b434f Mon Sep 17 00:00:00 2001 From: BigAru Date: Fri, 7 Dec 2018 15:41:04 +0100 Subject: [PATCH 23/29] optimize getEdits string concatenation --- .../refactors/convertStringOrTemplateLiteral.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index a0f6775caa65c..8874c0479bed7 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -68,15 +68,10 @@ namespace ts.refactor.convertStringOrTemplateLiteral { if (isTemplateExpression(node.parent) || isTemplateSpan(node.parent)) { const templateLiteralExpression = isTemplateSpan(node.parent) ? node.parent.parent : node.parent; const { head, templateSpans } = templateLiteralExpression; - const arrayOfNodes: Expression[] = []; + const arrayOfNodes = templateSpans.map(templateSpanToExpressions) + .reduce((accumulator, nextArray) => accumulator.concat(nextArray)); - if (head.text.length !== 0) arrayOfNodes.push(createStringLiteral(head.text)); - - templateSpans.forEach(ts => { - arrayOfNodes.push(ts.expression); - const text = ts.literal.text; - if (text.length !== 0) arrayOfNodes.push(createStringLiteral(text)); - }); + if (head.text.length !== 0) arrayOfNodes.unshift(createStringLiteral(head.text)); const binaryExpression = arrayToTree(arrayOfNodes); return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, templateLiteralExpression, binaryExpression)); @@ -88,6 +83,12 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } } + function templateSpanToExpressions(templateSpan: TemplateSpan): Expression[] { + const { expression, literal } = templateSpan; + const text = literal.text; + return text.length === 0 ? [expression] : [expression, createStringLiteral(text)]; + } + function getParentBinaryExpression(expr: Node) { while (isBinaryExpression(expr.parent)) { expr = expr.parent; From 81804c2dde94aee90d9644125af51a65e9f390d2 Mon Sep 17 00:00:00 2001 From: BigAru Date: Sat, 8 Dec 2018 06:57:32 +0100 Subject: [PATCH 24/29] change from tuple to object literal --- .../convertStringOrTemplateLiteral.ts | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 8874c0479bed7..6b0fd4f7dfa00 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -96,7 +96,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return expr; } - function arrayToTree(nodes: Expression[], accumulator?: BinaryExpression): BinaryExpression { + function arrayToTree(nodes: ReadonlyArray, accumulator?: BinaryExpression): BinaryExpression { if (nodes.length === 0) return accumulator!; if (!accumulator) { @@ -113,31 +113,33 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } function isStringConcatenationValid(node: Node): boolean { - const [, containsString, areOperatorsValid] = treeToArray(node); + const { containsString, areOperatorsValid } = treeToArray(node); return containsString && areOperatorsValid; } - function transformTreeToArray(node: Node): Expression[] { - return treeToArray(node)[0]; + function transformTreeToArray(node: Node): ReadonlyArray { + return treeToArray(node).nodes; } - function treeToArray(node: Node): [Expression[], /* containsString */ boolean, /* areOperatorsValid */ boolean] { + function treeToArray(node: Node): { nodes: ReadonlyArray, containsString: boolean, areOperatorsValid: boolean} { if (isBinaryExpression(node)) { - const [leftNodes, leftHasString, leftOperatorValid] = treeToArray(node.left); - const [rightNodes, rightHasString, rightOperatorValid] = treeToArray(node.right); + const { nodes: leftNodes, containsString: leftHasString, areOperatorsValid: leftOperatorValid } = treeToArray(node.left); + const { nodes: rightNodes, containsString: rightHasString, areOperatorsValid: rightOperatorValid } = treeToArray(node.right); - if (!leftHasString && !rightHasString) return [[node], false, true]; + if (!leftHasString && !rightHasString) { + return { nodes: [node], containsString: false, areOperatorsValid: true }; + } const nodeOperatorValid = node.operatorToken.kind === SyntaxKind.PlusToken; const isPlus = leftOperatorValid && nodeOperatorValid && rightOperatorValid; - return [leftNodes.concat(rightNodes), true, isPlus]; + return { nodes: leftNodes.concat(rightNodes), containsString: true, areOperatorsValid: isPlus }; } - return [[node as Expression], isStringLiteral(node), true]; + return { nodes: [node as Expression], containsString: isStringLiteral(node), areOperatorsValid: true }; } - function createHead(nodes: Node[]): [number, TemplateHead] { + function createHead(nodes: ReadonlyArray): [number, TemplateHead] { let begin = 0; const head = createTemplateHead(""); @@ -151,7 +153,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return [begin, head]; } - function nodesToTemplate(nodes: Expression[]) { + function nodesToTemplate(nodes: ReadonlyArray) { const templateSpans: TemplateSpan[] = []; const [begin, head] = createHead(nodes); From 3f5761f40e38e7f505e586d6b034b9eacd8047d1 Mon Sep 17 00:00:00 2001 From: BigAru Date: Sun, 9 Dec 2018 00:04:50 +0100 Subject: [PATCH 25/29] optimize templateLiteral check --- .../convertStringOrTemplateLiteral.ts | 33 +++++++------------ ...gOrTemplateLiteral_ToStringAvailability.ts | 2 +- ...ngOrTemplateLiteral_ToStringNestedInner.ts | 14 ++++++++ ...mplateLiteral_ToStringNestedInnerNonSub.ts | 14 ++++++++ ...gOrTemplateLiteral_ToStringNestedOuter.ts} | 2 +- 5 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInner.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInnerNonSub.ts rename tests/cases/fourslash/{refactorConvertStringOrTemplateLiteral_ToStringNested.ts => refactorConvertStringOrTemplateLiteral_ToStringNestedOuter.ts} (86%) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 6b0fd4f7dfa00..c63a4f48f1e2c 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -12,7 +12,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function getAvailableActions(context: RefactorContext): ReadonlyArray { const { file, startPosition } = context; - const node = getNodeOrParentOfBraces(file, startPosition); + const node = getNodeOrParentOfParentheses(file, startPosition); const maybeBinary = getParentBinaryExpression(node); const actions: RefactorActionInfo[] = []; @@ -20,33 +20,24 @@ namespace ts.refactor.convertStringOrTemplateLiteral { actions.push({ name: toTemplateLiteralActionName, description: toTemplateLiteralDescription }); } - if (isTemplateLike(node)) { + const templateLiteral = findAncestor(node, n => isTemplateLiteral(n)); + + if (templateLiteral && !isTaggedTemplateExpression(templateLiteral.parent)) { actions.push({ name: toStringConcatenationActionName, description: toStringConcatenationDescription }); } return [{ name: refactorName, description: refactorDescription, actions }]; } - function getNodeOrParentOfBraces(file: SourceFile, startPosition: number) { + function getNodeOrParentOfParentheses(file: SourceFile, startPosition: number) { const node = getTokenAtPosition(file, startPosition); if (isParenthesizedExpression(node.parent) && isBinaryExpression(node.parent.parent)) return node.parent.parent; return node; } - function isTemplateLike(node: Node) { - const isTemplateWithoutSubstitution = isNoSubstitutionTemplateLiteral(node) && isNotTagged(node); - const isTemplate = (isTemplateHead(node) || isTemplateMiddleOrTemplateTail(node)) && isNotTagged(node.parent); - const isTemplateFromExpression = isTemplateSpan(node.parent) && isNotTagged(node.parent.parent); - return isTemplateWithoutSubstitution || isTemplate || isTemplateFromExpression; - } - - function isNotTagged(templateExpression: Node) { - return !isTaggedTemplateExpression(templateExpression.parent); - } - function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const { file, startPosition } = context; - const node = getNodeOrParentOfBraces(file, startPosition); + const node = getNodeOrParentOfParentheses(file, startPosition); switch (actionName) { case toTemplateLiteralActionName: @@ -65,20 +56,20 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } function getEditsForToStringConcatenation(context: RefactorContext, node: Node) { - if (isTemplateExpression(node.parent) || isTemplateSpan(node.parent)) { - const templateLiteralExpression = isTemplateSpan(node.parent) ? node.parent.parent : node.parent; - const { head, templateSpans } = templateLiteralExpression; + const templateLiteral = findAncestor(node, n => isTemplateLiteral(n))! as TemplateLiteral; + + if (isTemplateExpression(templateLiteral)) { + const { head, templateSpans } = templateLiteral; const arrayOfNodes = templateSpans.map(templateSpanToExpressions) .reduce((accumulator, nextArray) => accumulator.concat(nextArray)); if (head.text.length !== 0) arrayOfNodes.unshift(createStringLiteral(head.text)); const binaryExpression = arrayToTree(arrayOfNodes); - return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, templateLiteralExpression, binaryExpression)); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, templateLiteral, binaryExpression)); } else { - const templateWithoutSubstitution = node as NoSubstitutionTemplateLiteral; - const stringLiteral = createStringLiteral(templateWithoutSubstitution.text); + const stringLiteral = createStringLiteral(templateLiteral.text); return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, node, stringLiteral)); } } diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts index 2725cd75aaf30..03e0882bb2f58 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAvailability.ts @@ -25,5 +25,5 @@ verify.refactorAvailable("Convert string concatenation or template literal", "Co verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); goTo.select("p", "o"); -verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); +verify.refactorAvailable("Convert string concatenation or template literal", "Convert to string concatenation"); verify.not.refactorAvailable("Convert string concatenation or template literal", "Convert to template literal"); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInner.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInner.ts new file mode 100644 index 0000000000000..a094d5847c433 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInner.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is a ${ age < 18 ? 'child' : /*x*/`/*y*/grown-up ${ age > 40 ? 'who needs probaply assistance' : ''}` }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = \`foobar is a \${ age < 18 ? 'child' : "grown-up " + (age > 40 ? 'who needs probaply assistance' : '') }\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInnerNonSub.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInnerNonSub.ts new file mode 100644 index 0000000000000..83783e4d658e8 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedInnerNonSub.ts @@ -0,0 +1,14 @@ +/// + +//// const age = 42 +//// const foo = `foobar is a ${ `/*x*/3/*y*/4` }` + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`const age = 42 +const foo = \`foobar is a \${ "34" }\``, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedOuter.ts similarity index 86% rename from tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts rename to tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedOuter.ts index f579508df2357..fb09d7876b9da 100644 --- a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNested.ts +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringNestedOuter.ts @@ -1,7 +1,7 @@ /// //// const age = 42 -//// const foo = `/*x*/f/*y*/oobar is a ${ age < 18 ? 'child' : `grown-up ${ age > 40 ? 'who needs probaply assistance': ''}` }` +//// const foo = `foobar is a ${ /*x*/a/*y*/ge < 18 ? 'child' : `grown-up ${ age > 40 ? 'who needs probaply assistance': ''}` }` goTo.select("x", "y"); edit.applyRefactor({ From 8c9903ab59d67c624259d43c6772050e8a55f3e4 Mon Sep 17 00:00:00 2001 From: BigAru Date: Sun, 9 Dec 2018 01:07:05 +0100 Subject: [PATCH 26/29] extract getEdits for template literal --- .../refactors/convertStringOrTemplateLiteral.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index c63a4f48f1e2c..5246054363e85 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -41,11 +41,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { switch (actionName) { case toTemplateLiteralActionName: - const maybeBinary = getParentBinaryExpression(node); - const arrayOfNodes = transformTreeToArray(maybeBinary); - const templateLiteral = nodesToTemplate(arrayOfNodes); - const edits = textChanges.ChangeTracker.with(context, t => t.replaceNode(file, maybeBinary, templateLiteral)); - return { edits }; + return { edits: getEditsForToTemplateLiteral(context, node) }; case toStringConcatenationActionName: return { edits: getEditsForToStringConcatenation(context, node) }; @@ -55,6 +51,13 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } } + function getEditsForToTemplateLiteral(context: RefactorContext, node: Node) { + const maybeBinary = getParentBinaryExpression(node); + const arrayOfNodes = transformTreeToArray(maybeBinary); + const templateLiteral = nodesToTemplate(arrayOfNodes); + return textChanges.ChangeTracker.with(context, t => t.replaceNode(context.file, maybeBinary, templateLiteral)); + } + function getEditsForToStringConcatenation(context: RefactorContext, node: Node) { const templateLiteral = findAncestor(node, n => isTemplateLiteral(n))! as TemplateLiteral; From dbd31cc45f10550de202d69d9730ef62c66e9a3b Mon Sep 17 00:00:00 2001 From: BigAru Date: Sun, 9 Dec 2018 01:20:33 +0100 Subject: [PATCH 27/29] add test cases --- ...rtStringOrTemplateLiteral_ToStringAsFnArgument.ts | 12 ++++++++++++ ...StringOrTemplateLiteral_ToTemplateAsFnArgument.ts | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAsFnArgument.ts create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAsFnArgument.ts diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAsFnArgument.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAsFnArgument.ts new file mode 100644 index 0000000000000..de79522269a1c --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToStringAsFnArgument.ts @@ -0,0 +1,12 @@ +/// + +//// console.log(`/*x*/f/*y*/oobar is ${ 32 } years old`) + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to string concatenation", + actionDescription: "Convert to string concatenation", + newContent: +`console.log("foobar is " + 32 + " years old")`, +}); diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAsFnArgument.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAsFnArgument.ts new file mode 100644 index 0000000000000..a41eef34b41e5 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateAsFnArgument.ts @@ -0,0 +1,12 @@ +/// + +//// console.log("/*x*/f/*y*/oobar is " + 32 + " years old") + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +`console.log(\`foobar is \${32} years old\`)`, +}); From 85dbb274c52ca7b3eab95bd0d18d6d674244ecc7 Mon Sep 17 00:00:00 2001 From: BigAru Date: Sun, 9 Dec 2018 16:19:21 +0100 Subject: [PATCH 28/29] add skeleton for handling octal escape --- .../refactors/convertStringOrTemplateLiteral.ts | 14 +++++++++----- ...tringOrTemplateLiteral_ToTemplateOctalEscape.ts | 13 +++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOctalEscape.ts diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index 5246054363e85..dde46dd924ca4 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -135,16 +135,16 @@ namespace ts.refactor.convertStringOrTemplateLiteral { function createHead(nodes: ReadonlyArray): [number, TemplateHead] { let begin = 0; - const head = createTemplateHead(""); + let text = ""; while (begin < nodes.length && isStringLiteral(nodes[begin])) { const next = nodes[begin] as StringLiteral; - head.text = head.text + next.text; + text = text + next.text; begin++; } - head.text = escapeText(head.text); - return [begin, head]; + text = escapeText(text); + return [begin, createTemplateHead(text)]; } function nodesToTemplate(nodes: ReadonlyArray) { @@ -176,8 +176,12 @@ namespace ts.refactor.convertStringOrTemplateLiteral { } function escapeText(content: string) { + // back-tick return content.replace("`", "\`") - .replace("\${", `$\\{`); + // placeholder alike beginning + .replace("\${", `$\\{`) + // octal escape + .replace(/\\([0-7]+)/g, (_whole, n) => "\\x" + parseInt(n, 8).toString(16)); } } diff --git a/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOctalEscape.ts b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOctalEscape.ts new file mode 100644 index 0000000000000..e607240f39fcf --- /dev/null +++ b/tests/cases/fourslash/refactorConvertStringOrTemplateLiteral_ToTemplateOctalEscape.ts @@ -0,0 +1,13 @@ +/// + +debugger; +//// const foo = "/*x*/U/*y*/nicode \u0023 \u{0023} " + "Hex \x23 " + "Octal \43"; + +goTo.select("x", "y"); +edit.applyRefactor({ + refactorName: "Convert string concatenation or template literal", + actionName: "Convert to template literal", + actionDescription: "Convert to template literal", + newContent: +"const foo = `Unicode # # Hex # Octal #\`;", +}); From 2d7a48fc3cc4c72adc703f588cd2b7c4f0488daf Mon Sep 17 00:00:00 2001 From: BigAru Date: Mon, 10 Dec 2018 00:24:23 +0100 Subject: [PATCH 29/29] complete handling for octal escape --- .../convertStringOrTemplateLiteral.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/services/refactors/convertStringOrTemplateLiteral.ts b/src/services/refactors/convertStringOrTemplateLiteral.ts index dde46dd924ca4..80d5fac3708e9 100644 --- a/src/services/refactors/convertStringOrTemplateLiteral.ts +++ b/src/services/refactors/convertStringOrTemplateLiteral.ts @@ -139,7 +139,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { while (begin < nodes.length && isStringLiteral(nodes[begin])) { const next = nodes[begin] as StringLiteral; - text = text + next.text; + text = text + decodeRawString(next.getText()); begin++; } @@ -161,7 +161,7 @@ namespace ts.refactor.convertStringOrTemplateLiteral { while (i + 1 < nodes.length && isStringLiteral(nodes[i + 1])) { const next = nodes[i + 1] as StringLiteral; - text = text + next.text; + text = text + decodeRawString(next.getText()); i++; } @@ -175,13 +175,27 @@ namespace ts.refactor.convertStringOrTemplateLiteral { return createTemplateExpression(head, templateSpans); } + const hexToUnicode = (_match: string, grp: string) => String.fromCharCode(parseInt(grp, 16)); + const octalToUnicode = (_match: string, grp: string) => String.fromCharCode(parseInt(grp, 8)); + + function decodeRawString(content: string) { + const outerQuotes = /"((.|\s)*)"/; + const unicodeEscape = /\\u([\d\w]+)/gi; + const unicodeEscapeWithBraces = /\\u\{([\d\w]+\})/gi; + const hexEscape = /\\x([\d\w]+)/gi; + const octalEscape = /\\([0-7]+)/g; + + return content.replace(outerQuotes, (_match, grp) => grp) + .replace(unicodeEscape, hexToUnicode) + .replace(unicodeEscapeWithBraces, hexToUnicode) + .replace(hexEscape, hexToUnicode) + .replace(octalEscape, octalToUnicode); + + } + function escapeText(content: string) { - // back-tick - return content.replace("`", "\`") - // placeholder alike beginning - .replace("\${", `$\\{`) - // octal escape - .replace(/\\([0-7]+)/g, (_whole, n) => "\\x" + parseInt(n, 8).toString(16)); + return content.replace("`", "\`") // back-tick + .replace("\${", `$\\{`); // placeholder alike beginning } }