Skip to content

Commit ea4fdc5

Browse files
authored
Merge pull request #17988 from amcasey/ExtractGeneric
Handle loose type parameters in Extract Method
2 parents 450c32a + a816079 commit ea4fdc5

35 files changed

+547
-1043
lines changed

src/compiler/types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,20 @@ namespace ts {
651651
}
652652

653653
export interface SignatureDeclaration extends NamedDeclaration {
654+
kind: SyntaxKind.CallSignature
655+
| SyntaxKind.ConstructSignature
656+
| SyntaxKind.MethodSignature
657+
| SyntaxKind.IndexSignature
658+
| SyntaxKind.FunctionType
659+
| SyntaxKind.ConstructorType
660+
| SyntaxKind.JSDocFunctionType
661+
| SyntaxKind.FunctionDeclaration
662+
| SyntaxKind.MethodDeclaration
663+
| SyntaxKind.Constructor
664+
| SyntaxKind.GetAccessor
665+
| SyntaxKind.SetAccessor
666+
| SyntaxKind.FunctionExpression
667+
| SyntaxKind.ArrowFunction;
654668
name?: PropertyName;
655669
typeParameters?: NodeArray<TypeParameterDeclaration>;
656670
parameters: NodeArray<ParameterDeclaration>;
@@ -1816,6 +1830,7 @@ namespace ts {
18161830
export type DeclarationWithTypeParameters = SignatureDeclaration | ClassLikeDeclaration | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag;
18171831

18181832
export interface ClassLikeDeclaration extends NamedDeclaration {
1833+
kind: SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression;
18191834
name?: Identifier;
18201835
typeParameters?: NodeArray<TypeParameterDeclaration>;
18211836
heritageClauses?: NodeArray<HeritageClause>;

src/compiler/utilities.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,38 @@ namespace ts {
477477
return false;
478478
}
479479

480+
/* @internal */
481+
export function isDeclarationWithTypeParameters(node: Node): node is DeclarationWithTypeParameters;
482+
export function isDeclarationWithTypeParameters(node: DeclarationWithTypeParameters): node is DeclarationWithTypeParameters {
483+
switch (node.kind) {
484+
case SyntaxKind.CallSignature:
485+
case SyntaxKind.ConstructSignature:
486+
case SyntaxKind.MethodSignature:
487+
case SyntaxKind.IndexSignature:
488+
case SyntaxKind.FunctionType:
489+
case SyntaxKind.ConstructorType:
490+
case SyntaxKind.JSDocFunctionType:
491+
case SyntaxKind.ClassDeclaration:
492+
case SyntaxKind.ClassExpression:
493+
case SyntaxKind.InterfaceDeclaration:
494+
case SyntaxKind.TypeAliasDeclaration:
495+
case SyntaxKind.JSDocTemplateTag:
496+
case SyntaxKind.FunctionDeclaration:
497+
case SyntaxKind.MethodDeclaration:
498+
case SyntaxKind.Constructor:
499+
case SyntaxKind.GetAccessor:
500+
case SyntaxKind.SetAccessor:
501+
case SyntaxKind.FunctionExpression:
502+
case SyntaxKind.ArrowFunction:
503+
return true;
504+
default:
505+
staticAssertNever(node);
506+
return false;
507+
}
508+
}
509+
510+
function staticAssertNever(_: never): void {}
511+
480512
// Gets the nearest enclosing block scope container that has the provided node
481513
// as a descendant, that is not the provided node.
482514
export function getEnclosingBlockScopeContainer(node: Node): Node {

src/harness/unittests/extractMethods.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -544,13 +544,74 @@ namespace A {
544544
return a1.x + 10;|]
545545
}
546546
}
547+
}`);
548+
// The "b" type parameters aren't used and shouldn't be passed to the extracted function.
549+
// Type parameters should be in syntactic order (i.e. in order or character offset from BOF).
550+
// In all cases, we could use type inference, rather than passing explicit type arguments.
551+
// Note the inclusion of arrow functions to ensure that some type parameters are not from
552+
// targetable scopes.
553+
testExtractMethod("extractMethod13",
554+
`<U1a, U1b>(u1a: U1a, u1b: U1b) => {
555+
function F1<T1a, T1b>(t1a: T1a, t1b: T1b) {
556+
<U2a, U2b>(u2a: U2a, u2b: U2b) => {
557+
function F2<T2a, T2b>(t2a: T2a, t2b: T2b) {
558+
<U3a, U3b>(u3a: U3a, u3b: U3b) => {
559+
[#|t1a.toString();
560+
t2a.toString();
561+
u1a.toString();
562+
u2a.toString();
563+
u3a.toString();|]
564+
}
565+
}
566+
}
567+
}
568+
}`);
569+
// This test is descriptive, rather than normative. The current implementation
570+
// doesn't handle type parameter shadowing.
571+
testExtractMethod("extractMethod14",
572+
`function F<T>(t1: T) {
573+
function F<T>(t2: T) {
574+
[#|t1.toString();
575+
t2.toString();|]
576+
}
577+
}`);
578+
// Confirm that the constraint is preserved.
579+
testExtractMethod("extractMethod15",
580+
`function F<T>(t1: T) {
581+
function F<U extends T[]>(t2: U) {
582+
[#|t2.toString();|]
583+
}
584+
}`);
585+
// Confirm that the contextual type of an extracted expression counts as a use.
586+
testExtractMethod("extractMethod16",
587+
`function F<T>() {
588+
const array: T[] = [#|[]|];
589+
}`);
590+
// Class type parameter
591+
testExtractMethod("extractMethod17",
592+
`class C<T1, T2> {
593+
M(t1: T1, t2: T2) {
594+
[#|t1.toString()|];
595+
}
596+
}`);
597+
// Method type parameter
598+
testExtractMethod("extractMethod18",
599+
`class C {
600+
M<T1, T2>(t1: T1, t2: T2) {
601+
[#|t1.toString()|];
602+
}
603+
}`);
604+
// Coupled constraints
605+
testExtractMethod("extractMethod19",
606+
`function F<T, U extends T[], V extends U[]>(v: V) {
607+
[#|v.toString()|];
547608
}`);
548609
});
549610

550611

551612
function testExtractMethod(caption: string, text: string) {
552613
it(caption, () => {
553-
Harness.Baseline.runBaseline(`extractMethod/${caption}.js`, () => {
614+
Harness.Baseline.runBaseline(`extractMethod/${caption}.ts`, () => {
554615
const t = extractTest(text);
555616
const selectionRange = t.ranges.get("selection");
556617
if (!selectionRange) {
@@ -560,7 +621,7 @@ namespace A {
560621
path: "/a.ts",
561622
content: t.source
562623
};
563-
const host = projectSystem.createServerHost([f]);
624+
const host = projectSystem.createServerHost([f, projectSystem.libFile]);
564625
const projectService = projectSystem.createProjectService(host);
565626
projectService.openClientFile(f.path);
566627
const program = projectService.inferredProjects[0].getLanguageService().getProgram();
@@ -577,11 +638,11 @@ namespace A {
577638
assert.equal(result.errors, undefined, "expect no errors");
578639
const results = refactor.extractMethod.getPossibleExtractions(result.targetRange, context);
579640
const data: string[] = [];
580-
data.push(`==ORIGINAL==`);
641+
data.push(`// ==ORIGINAL==`);
581642
data.push(sourceFile.text);
582643
for (const r of results) {
583644
const changes = refactor.extractMethod.getPossibleExtractions(result.targetRange, context, results.indexOf(r))[0].changes;
584-
data.push(`==SCOPE::${r.scopeDescription}==`);
645+
data.push(`// ==SCOPE::${r.scopeDescription}==`);
585646
data.push(textChanges.applyChanges(sourceFile.text, changes[0].textChanges));
586647
}
587648
return data.join(newLineCharacter);

0 commit comments

Comments
 (0)