feat(47619): add support for extracting jsx string literal attribute to a constant (#47624)

This commit is contained in:
Oleksandr T
2022-02-17 21:30:52 +02:00
committed by GitHub
parent 66dba1331b
commit 5b947e6526
4 changed files with 103 additions and 4 deletions

View File

@@ -433,7 +433,7 @@ namespace ts.refactor.extractSymbol {
// For understanding how skipTrivia functioned:
Debug.assert(!positionIsSynthesized(nodeToCheck.pos), "This failure could trigger https://github.com/Microsoft/TypeScript/issues/20809 (2)");
if (!isStatement(nodeToCheck) && !(isExpressionNode(nodeToCheck) && isExtractableExpression(nodeToCheck))) {
if (!isStatement(nodeToCheck) && !(isExpressionNode(nodeToCheck) && isExtractableExpression(nodeToCheck)) && !isStringLiteralJsxAttribute(nodeToCheck)) {
return [createDiagnosticForNode(nodeToCheck, Messages.statementOrExpressionExpected)];
}
@@ -625,13 +625,16 @@ namespace ts.refactor.extractSymbol {
if (isStatement(node)) {
return [node];
}
else if (isExpressionNode(node)) {
if (isExpressionNode(node)) {
// If our selection is the expression in an ExpressionStatement, expand
// the selection to include the enclosing Statement (this stops us
// from trying to care about the return value of the extracted function
// and eliminates double semicolon insertion in certain scenarios)
return isExpressionStatement(node.parent) ? [node.parent] : node as Expression;
}
if (isStringLiteralJsxAttribute(node)) {
return node;
}
return undefined;
}
@@ -1649,7 +1652,7 @@ namespace ts.refactor.extractSymbol {
// Unfortunately, this code takes advantage of the knowledge that the generated method
// will use the contextual type of an expression as the return type of the extracted
// method (and will therefore "use" all the types involved).
if (inGenericContext && !isReadonlyArray(targetRange.range)) {
if (inGenericContext && !isReadonlyArray(targetRange.range) && !isJsxAttribute(targetRange.range)) {
const contextualType = checker.getContextualType(targetRange.range)!; // TODO: GH#18217
recordTypeParameterUsages(contextualType);
}
@@ -1997,6 +2000,11 @@ namespace ts.refactor.extractSymbol {
}
function isInJSXContent(node: Node) {
return (isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && (isJsxElement(node.parent) || isJsxFragment(node.parent));
return isStringLiteralJsxAttribute(node) ||
(isJsxElement(node) || isJsxSelfClosingElement(node) || isJsxFragment(node)) && (isJsxElement(node.parent) || isJsxFragment(node.parent));
}
function isStringLiteralJsxAttribute(node: Node): node is StringLiteral {
return isStringLiteral(node) && node.parent && isJsxAttribute(node.parent);
}
}

View File

@@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />
// @jsx: preserve
// @filename: a.tsx
////function Foo() {
//// return (
//// <div>
//// <a href=/*a*/"string"/*b*/></a>;
//// </div>
//// );
////}
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 = "string";
function Foo() {
return (
<div>
<a href={newLocal}></a>;
</div>
);
}`
});

View File

@@ -0,0 +1,28 @@
/// <reference path='fourslash.ts' />
// @jsx: preserve
// @filename: a.tsx
////function Foo() {
//// return (
//// <div>
//// <a href=/*a*/"string"/*b*/></a>;
//// </div>
//// );
////}
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 = "string";
return (
<div>
<a href={newLocal}></a>;
</div>
);
}`
});

View File

@@ -0,0 +1,35 @@
/// <reference path='fourslash.ts' />
// @jsx: preserve
// @filename: a.tsx
////declare var React: any;
////class Foo extends React.Component<{}, {}> {
//// render() {
//// return (
//// <div>
//// <a href=/*a*/"string"/*b*/></a>;
//// </div>
//// );
//// }
////}
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 = "string";
render() {
return (
<div>
<a href={this./*RENAME*/newProperty}></a>;
</div>
);
}
}`
});