From 01d7f0b699def1d9f9d3045bbe6fcb0c72079fb8 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 16 Aug 2017 17:50:50 -0700 Subject: [PATCH] Test that the return type of the extracted method counts as usage --- src/harness/unittests/extractMethods.ts | 5 ++++ src/services/refactors/extractMethod.ts | 30 +++++++++++++------ .../extractMethod/extractMethod16.ts | 19 ++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 tests/baselines/reference/extractMethod/extractMethod16.ts diff --git a/src/harness/unittests/extractMethods.ts b/src/harness/unittests/extractMethods.ts index 43984fc21b8..7d7125c5025 100644 --- a/src/harness/unittests/extractMethods.ts +++ b/src/harness/unittests/extractMethods.ts @@ -581,6 +581,11 @@ namespace A { function F(t2: U) { [#|t2.toString();|] } +}`); + // Confirm that the contextual type of an extracted expression counts as a use. + testExtractMethod("extractMethod16", + `function F() { + const array: T[] = [#|[]|]; }`); }); diff --git a/src/services/refactors/extractMethod.ts b/src/services/refactors/extractMethod.ts index ff376026e52..b02cb607e04 100644 --- a/src/services/refactors/extractMethod.ts +++ b/src/services/refactors/extractMethod.ts @@ -946,6 +946,7 @@ namespace ts.refactor.extractMethod { substitutionsPerScope.push(createMap()); errorsPerScope.push([]); } + const seenUsages = createMap(); const target = isReadonlyArray(targetRange.range) ? createBlock(targetRange.range) : targetRange.range; const containingLexicalScopeOfExtraction = isBlockScope(scopes[0], scopes[0].parent) ? scopes[0] : getEnclosingBlockScopeContainer(scopes[0]); @@ -955,6 +956,14 @@ namespace ts.refactor.extractMethod { collectUsages(target); + // 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)) { + const contextualType = checker.getContextualType(targetRange.range); + recordTypeParameterUsages(contextualType); + } + if (allTypeParameterUsages.size > 0) { const seenTypeParameterUsages = createMap(); // Key is type ID @@ -1032,18 +1041,21 @@ namespace ts.refactor.extractMethod { return false; } + function recordTypeParameterUsages(type: Type) { + const symbolWalker = checker.getSymbolWalker(); + const {visitedTypes} = symbolWalker.walkType(type); + + for (const visitedType of visitedTypes) { + if (visitedType.flags & TypeFlags.TypeParameter) { + allTypeParameterUsages.set(visitedType.id.toString(), visitedType as TypeParameter); + } + } + } + function collectUsages(node: Node, valueUsage = Usage.Read) { if (inGenericContext) { const type = checker.getTypeAtLocation(node); - - const symbolWalker = checker.getSymbolWalker(); - const {visitedTypes} = symbolWalker.walkType(type); - - for (const visitedType of visitedTypes) { - if (visitedType.flags & TypeFlags.TypeParameter) { - allTypeParameterUsages.set(visitedType.id.toString(), visitedType as TypeParameter); - } - } + recordTypeParameterUsages(type); } if (isDeclaration(node) && node.symbol) { diff --git a/tests/baselines/reference/extractMethod/extractMethod16.ts b/tests/baselines/reference/extractMethod/extractMethod16.ts new file mode 100644 index 00000000000..1ce88b93145 --- /dev/null +++ b/tests/baselines/reference/extractMethod/extractMethod16.ts @@ -0,0 +1,19 @@ +// ==ORIGINAL== +function F() { + const array: T[] = []; +} +// ==SCOPE::function 'F'== +function F() { + const array: T[] = newFunction(); + + function newFunction(): T[] { + return []; + } +} +// ==SCOPE::global scope== +function F() { + const array: T[] = newFunction(); +} +function newFunction(): T[] { + return []; +}