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
;
+ }
+}`
+});