diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 4fadc5fdefb..9efcce30066 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -111,6 +111,7 @@ import { LabeledStatement, last, map, + mapDefined, MethodDeclaration, Modifier, ModifierFlags, @@ -1070,12 +1071,12 @@ function extractFunctionInScope( callArguments.push(factory.createIdentifier(name)); }); - const typeParametersAndDeclarations = arrayFrom(typeParameterUsages.values(), type => ({ type, declaration: getFirstDeclaration(type) })); + const typeParametersAndDeclarations = arrayFrom(typeParameterUsages.values(), type => ({ type, declaration: getFirstDeclarationBeforePosition(type, context.startPosition) })); const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations.sort(compareTypesByDeclarationOrder); const typeParameters: readonly TypeParameterDeclaration[] | undefined = sortedTypeParametersAndDeclarations.length === 0 ? undefined - : sortedTypeParametersAndDeclarations.map(t => t.declaration as TypeParameterDeclaration); + : mapDefined(sortedTypeParametersAndDeclarations, ({ declaration }) => declaration as TypeParameterDeclaration); // Strictly speaking, we should check whether each name actually binds to the appropriate type // parameter. In cases of shadowing, they may not. @@ -1547,13 +1548,13 @@ function getContainingVariableDeclarationIfInList(node: Node, scope: Scope) { } } -function getFirstDeclaration(type: Type): Declaration | undefined { +function getFirstDeclarationBeforePosition(type: Type, position: number): Declaration | undefined { let firstDeclaration; const symbol = type.symbol; if (symbol && symbol.declarations) { for (const declaration of symbol.declarations) { - if (firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) { + if ((firstDeclaration === undefined || declaration.pos < firstDeclaration.pos) && declaration.pos < position) { firstDeclaration = declaration; } } diff --git a/tests/cases/fourslash/extract-method-generics-inside-extraction1.ts b/tests/cases/fourslash/extract-method-generics-inside-extraction1.ts new file mode 100644 index 00000000000..f585b96e918 --- /dev/null +++ b/tests/cases/fourslash/extract-method-generics-inside-extraction1.ts @@ -0,0 +1,22 @@ +/// + +////function f() { +//// let g = /*start*/(x: T) => x/*end*/; +//// return g; +////} + +goTo.select('start', 'end'); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to function in global scope", + newContent: +`function f() { + let g = /*RENAME*/newFunction(); + return g; +} + +function newFunction() { + return (x: T) => x; +} +`}); diff --git a/tests/cases/fourslash/extract-method-generics-inside-extraction2.ts b/tests/cases/fourslash/extract-method-generics-inside-extraction2.ts new file mode 100644 index 00000000000..62982480124 --- /dev/null +++ b/tests/cases/fourslash/extract-method-generics-inside-extraction2.ts @@ -0,0 +1,20 @@ +/// + +////function satisfies() { +//// return /*start*/(x: T) => x/*end*/; +////} + +goTo.select('start', 'end'); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to function in global scope", + newContent: +`function satisfies() { + return /*RENAME*/newFunction(); +} + +function newFunction() { + return (x: T) => x; +} +`});