From c75af697afcfcddcbf08366752a8088ff08028e4 Mon Sep 17 00:00:00 2001 From: Alexander T Date: Sun, 28 Jun 2020 10:51:11 +0300 Subject: [PATCH] fix(29890): wrap variable/method/property to jsx expression --- src/services/refactors/extractSymbol.ts | 19 ++++++---- .../fourslash/extract-const_jsxElement1.ts | 28 ++++++++++++++ .../fourslash/extract-const_jsxElement2.ts | 28 ++++++++++++++ .../fourslash/extract-const_jsxElement3.ts | 35 ++++++++++++++++++ .../fourslash/extract-const_jsxFragment1.ts | 28 ++++++++++++++ .../fourslash/extract-const_jsxFragment2.ts | 28 ++++++++++++++ .../fourslash/extract-const_jsxFragment3.ts | 35 ++++++++++++++++++ .../extract-const_jsxSelfClosingElement1.ts | 28 ++++++++++++++ .../extract-const_jsxSelfClosingElement2.ts | 28 ++++++++++++++ .../extract-const_jsxSelfClosingElement3.ts | 35 ++++++++++++++++++ .../fourslash/extract-method_jsxElement1.ts | 32 ++++++++++++++++ .../fourslash/extract-method_jsxElement2.ts | 31 ++++++++++++++++ .../fourslash/extract-method_jsxElement3.ts | 37 +++++++++++++++++++ .../fourslash/extract-method_jsxFragment1.ts | 32 ++++++++++++++++ .../fourslash/extract-method_jsxFragment2.ts | 31 ++++++++++++++++ .../fourslash/extract-method_jsxFragment3.ts | 37 +++++++++++++++++++ .../extract-method_jsxSelfClosingElement1.ts | 32 ++++++++++++++++ .../extract-method_jsxSelfClosingElement2.ts | 31 ++++++++++++++++ .../extract-method_jsxSelfClosingElement3.ts | 37 +++++++++++++++++++ 19 files changed, 585 insertions(+), 7 deletions(-) create mode 100644 tests/cases/fourslash/extract-const_jsxElement1.ts create mode 100644 tests/cases/fourslash/extract-const_jsxElement2.ts create mode 100644 tests/cases/fourslash/extract-const_jsxElement3.ts create mode 100644 tests/cases/fourslash/extract-const_jsxFragment1.ts create mode 100644 tests/cases/fourslash/extract-const_jsxFragment2.ts create mode 100644 tests/cases/fourslash/extract-const_jsxFragment3.ts create mode 100644 tests/cases/fourslash/extract-const_jsxSelfClosingElement1.ts create mode 100644 tests/cases/fourslash/extract-const_jsxSelfClosingElement2.ts create mode 100644 tests/cases/fourslash/extract-const_jsxSelfClosingElement3.ts create mode 100644 tests/cases/fourslash/extract-method_jsxElement1.ts create mode 100644 tests/cases/fourslash/extract-method_jsxElement2.ts create mode 100644 tests/cases/fourslash/extract-method_jsxElement3.ts create mode 100644 tests/cases/fourslash/extract-method_jsxFragment1.ts create mode 100644 tests/cases/fourslash/extract-method_jsxFragment2.ts create mode 100644 tests/cases/fourslash/extract-method_jsxFragment3.ts create mode 100644 tests/cases/fourslash/extract-method_jsxSelfClosingElement1.ts create mode 100644 tests/cases/fourslash/extract-method_jsxSelfClosingElement2.ts create mode 100644 tests/cases/fourslash/extract-method_jsxSelfClosingElement3.ts diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 451ac6359b9ab..d3d7660b400f4 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -915,6 +915,9 @@ namespace ts.refactor.extractSymbol { if (range.facts & RangeFacts.IsAsyncFunction) { call = factory.createAwaitExpression(call); } + if (isInJSXContent(node)) { + call = factory.createJsxExpression(/*dotDotDotToken*/ undefined, call); + } if (exposedVariableDeclarations.length && !writes) { // No need to mix declarations and writes. @@ -1118,12 +1121,16 @@ namespace ts.refactor.extractSymbol { variableType, initializer); - const localReference = factory.createPropertyAccessExpression( + let localReference: Expression = factory.createPropertyAccessExpression( rangeFacts & RangeFacts.InStaticRegion ? factory.createIdentifier(scope.name!.getText()) // TODO: GH#18217 : factory.createThis(), factory.createIdentifier(localNameText)); + if (isInJSXContent(node)) { + localReference = factory.createJsxExpression(/*dotDotDotToken*/ undefined, localReference); + } + // Declare const maxInsertionPos = node.pos; const nodeToInsertBefore = getNodeToInsertPropertyBefore(maxInsertionPos, scope); @@ -1194,12 +1201,6 @@ namespace ts.refactor.extractSymbol { const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true); return { renameFilename, renameLocation, edits }; - function isInJSXContent(node: Node) { - if (!isJsxElement(node)) return false; - if (isJsxElement(node.parent)) return true; - return false; - } - function transformFunctionInitializerAndType(variableType: TypeNode | undefined, initializer: Expression): { variableType: TypeNode | undefined, initializer: Expression } { // If no contextual type exists there is nothing to transfer to the function signature if (variableType === undefined) return { variableType, initializer }; @@ -1953,4 +1954,8 @@ namespace ts.refactor.extractSymbol { return false; } } + + function isInJSXContent(node: Node) { + return (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && isJsxElement(node.parent); + } } diff --git a/tests/cases/fourslash/extract-const_jsxElement1.ts b/tests/cases/fourslash/extract-const_jsxElement1.ts new file mode 100644 index 0000000000000..3018aa0cadd09 --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxElement1.ts @@ -0,0 +1,28 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*//*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_1", + actionDescription: "Extract to constant in global scope", + newContent: +`const /*RENAME*/newLocal = ; +function Foo() { + return ( +
+ {newLocal} +
+ ); +}` +}); diff --git a/tests/cases/fourslash/extract-const_jsxElement2.ts b/tests/cases/fourslash/extract-const_jsxElement2.ts new file mode 100644 index 0000000000000..eb1bd23473a4a --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxElement2.ts @@ -0,0 +1,28 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*//*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_0", + actionDescription: "Extract to constant in enclosing scope", + newContent: +`function Foo() { + const /*RENAME*/newLocal = ; + return ( +
+ {newLocal} +
+ ); +}` +}); diff --git a/tests/cases/fourslash/extract-const_jsxElement3.ts b/tests/cases/fourslash/extract-const_jsxElement3.ts new file mode 100644 index 0000000000000..ef9f9ff543411 --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxElement3.ts @@ -0,0 +1,35 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////declare var React: any; +////class Foo extends React.Component<{}, {}> { +//// render() { +//// return ( +////
+//// /*a*//*b*/ +////
+//// ); +//// } +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_1", + actionDescription: "Extract to readonly field in class 'Foo'", + newContent: +`declare var React: any; +class Foo extends React.Component<{}, {}> { + private readonly newProperty = ; + + render() { + return ( +
+ {this./*RENAME*/newProperty} +
+ ); + } +}` +}); diff --git a/tests/cases/fourslash/extract-const_jsxFragment1.ts b/tests/cases/fourslash/extract-const_jsxFragment1.ts new file mode 100644 index 0000000000000..d67432f382ffd --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxFragment1.ts @@ -0,0 +1,28 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*/<>/*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_1", + actionDescription: "Extract to constant in global scope", + newContent: +`const /*RENAME*/newLocal = <>; +function Foo() { + return ( +
+ {newLocal} +
+ ); +}` +}); diff --git a/tests/cases/fourslash/extract-const_jsxFragment2.ts b/tests/cases/fourslash/extract-const_jsxFragment2.ts new file mode 100644 index 0000000000000..ba4a67bfa3862 --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxFragment2.ts @@ -0,0 +1,28 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*/<>/*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_0", + actionDescription: "Extract to constant in enclosing scope", + newContent: +`function Foo() { + const /*RENAME*/newLocal = <>; + return ( +
+ {newLocal} +
+ ); +}` +}); diff --git a/tests/cases/fourslash/extract-const_jsxFragment3.ts b/tests/cases/fourslash/extract-const_jsxFragment3.ts new file mode 100644 index 0000000000000..8a5faf4457f14 --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxFragment3.ts @@ -0,0 +1,35 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////declare var React: any; +////class Foo extends React.Component<{}, {}> { +//// render() { +//// return ( +////
+//// /*a*/<>/*b*/ +////
+//// ); +//// } +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_1", + actionDescription: "Extract to readonly field in class 'Foo'", + newContent: +`declare var React: any; +class Foo extends React.Component<{}, {}> { + private readonly newProperty = <>; + + render() { + return ( +
+ {this./*RENAME*/newProperty} +
+ ); + } +}` +}); diff --git a/tests/cases/fourslash/extract-const_jsxSelfClosingElement1.ts b/tests/cases/fourslash/extract-const_jsxSelfClosingElement1.ts new file mode 100644 index 0000000000000..0a37c10763c12 --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxSelfClosingElement1.ts @@ -0,0 +1,28 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*/
/*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_1", + actionDescription: "Extract to constant in global scope", + newContent: +`const /*RENAME*/newLocal =
; +function Foo() { + return ( +
+ {newLocal} +
+ ); +}` +}); diff --git a/tests/cases/fourslash/extract-const_jsxSelfClosingElement2.ts b/tests/cases/fourslash/extract-const_jsxSelfClosingElement2.ts new file mode 100644 index 0000000000000..33dd2c6347b34 --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxSelfClosingElement2.ts @@ -0,0 +1,28 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*/
/*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_0", + actionDescription: "Extract to constant in enclosing scope", + newContent: +`function Foo() { + const /*RENAME*/newLocal =
; + return ( +
+ {newLocal} +
+ ); +}` +}); diff --git a/tests/cases/fourslash/extract-const_jsxSelfClosingElement3.ts b/tests/cases/fourslash/extract-const_jsxSelfClosingElement3.ts new file mode 100644 index 0000000000000..5d9569819aa0b --- /dev/null +++ b/tests/cases/fourslash/extract-const_jsxSelfClosingElement3.ts @@ -0,0 +1,35 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////declare var React: any; +////class Foo extends React.Component<{}, {}> { +//// render() { +//// return ( +////
+//// /*a*/
/*b*/ +////
+//// ); +//// } +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "constant_scope_1", + actionDescription: "Extract to readonly field in class 'Foo'", + newContent: +`declare var React: any; +class Foo extends React.Component<{}, {}> { + private readonly newProperty =
; + + render() { + return ( +
+ {this./*RENAME*/newProperty} +
+ ); + } +}` +}); diff --git a/tests/cases/fourslash/extract-method_jsxElement1.ts b/tests/cases/fourslash/extract-method_jsxElement1.ts new file mode 100644 index 0000000000000..1c5d2225a3d59 --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxElement1.ts @@ -0,0 +1,32 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*//*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to function in global scope", + newContent: +`function Foo() { + return ( +
+ {newFunction()} +
+ ); +} + +function /*RENAME*/newFunction() { + return ; +} +` +}); diff --git a/tests/cases/fourslash/extract-method_jsxElement2.ts b/tests/cases/fourslash/extract-method_jsxElement2.ts new file mode 100644 index 0000000000000..d02b55e1c13a7 --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxElement2.ts @@ -0,0 +1,31 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*//*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_0", + actionDescription: "Extract to inner function in function 'Foo'", + newContent: +`function Foo() { + return ( +
+ {newFunction()} +
+ ); + + function /*RENAME*/newFunction() { + return ; + } +}` +}); diff --git a/tests/cases/fourslash/extract-method_jsxElement3.ts b/tests/cases/fourslash/extract-method_jsxElement3.ts new file mode 100644 index 0000000000000..7e0b4cc098ffc --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxElement3.ts @@ -0,0 +1,37 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////declare var React: any; +////class Foo extends React.Component<{}, {}> { +//// render() { +//// return ( +////
+//// /*a*//*b*/ +////
+//// ); +//// } +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to method in class 'Foo'", + newContent: +`declare var React: any; +class Foo extends React.Component<{}, {}> { + render() { + return ( +
+ {this./*RENAME*/newMethod()} +
+ ); + } + + private newMethod() { + return ; + } +}` +}); diff --git a/tests/cases/fourslash/extract-method_jsxFragment1.ts b/tests/cases/fourslash/extract-method_jsxFragment1.ts new file mode 100644 index 0000000000000..30a2426e01d04 --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxFragment1.ts @@ -0,0 +1,32 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*/<>/*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to function in global scope", + newContent: +`function Foo() { + return ( +
+ {newFunction()} +
+ ); +} + +function /*RENAME*/newFunction() { + return <>; +} +` +}); diff --git a/tests/cases/fourslash/extract-method_jsxFragment2.ts b/tests/cases/fourslash/extract-method_jsxFragment2.ts new file mode 100644 index 0000000000000..254924c51ee36 --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxFragment2.ts @@ -0,0 +1,31 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*/<>/*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_0", + actionDescription: "Extract to inner function in function 'Foo'", + newContent: +`function Foo() { + return ( +
+ {newFunction()} +
+ ); + + function /*RENAME*/newFunction() { + return <>; + } +}` +}); diff --git a/tests/cases/fourslash/extract-method_jsxFragment3.ts b/tests/cases/fourslash/extract-method_jsxFragment3.ts new file mode 100644 index 0000000000000..e8983f6524d3c --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxFragment3.ts @@ -0,0 +1,37 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////declare var React: any; +////class Foo extends React.Component<{}, {}> { +//// render() { +//// return ( +////
+//// /*a*/<>/*b*/ +////
+//// ); +//// } +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to method in class 'Foo'", + newContent: +`declare var React: any; +class Foo extends React.Component<{}, {}> { + render() { + return ( +
+ {this./*RENAME*/newMethod()} +
+ ); + } + + private newMethod() { + return <>; + } +}` +}); diff --git a/tests/cases/fourslash/extract-method_jsxSelfClosingElement1.ts b/tests/cases/fourslash/extract-method_jsxSelfClosingElement1.ts new file mode 100644 index 0000000000000..041b4462bb6f0 --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxSelfClosingElement1.ts @@ -0,0 +1,32 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*/
/*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to function in global scope", + newContent: +`function Foo() { + return ( +
+ {newFunction()} +
+ ); +} + +function /*RENAME*/newFunction() { + return
; +} +` +}); diff --git a/tests/cases/fourslash/extract-method_jsxSelfClosingElement2.ts b/tests/cases/fourslash/extract-method_jsxSelfClosingElement2.ts new file mode 100644 index 0000000000000..de96c61f8f728 --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxSelfClosingElement2.ts @@ -0,0 +1,31 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////function Foo() { +//// return ( +////
+//// /*a*/
/*b*/ +////
+//// ); +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_0", + actionDescription: "Extract to inner function in function 'Foo'", + newContent: +`function Foo() { + return ( +
+ {newFunction()} +
+ ); + + function /*RENAME*/newFunction() { + return
; + } +}` +}); diff --git a/tests/cases/fourslash/extract-method_jsxSelfClosingElement3.ts b/tests/cases/fourslash/extract-method_jsxSelfClosingElement3.ts new file mode 100644 index 0000000000000..0b6f48253b96a --- /dev/null +++ b/tests/cases/fourslash/extract-method_jsxSelfClosingElement3.ts @@ -0,0 +1,37 @@ +/// + +// @jsx: preserve +// @filename: a.tsx +////declare var React: any; +////class Foo extends React.Component<{}, {}> { +//// render() { +//// return ( +////
+//// /*a*/
/*b*/ +////
+//// ); +//// } +////} + +goTo.file("a.tsx"); +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to method in class 'Foo'", + newContent: +`declare var React: any; +class Foo extends React.Component<{}, {}> { + render() { + return ( +
+ {this./*RENAME*/newMethod()} +
+ ); + } + + private newMethod() { + return
; + } +}` +});