diff --git a/Jakefile.js b/Jakefile.js index 7b4991b74b6f7..8a4c67ac84bc4 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -138,7 +138,10 @@ var harnessSources = harnessCoreSources.concat([ "projectErrors.ts", "matchFiles.ts", "initializeTSConfig.ts", - "extractMethods.ts", + "extractConstants.ts", + "extractFunctions.ts", + "extractRanges.ts", + "extractTestHelpers.ts", "printer.ts", "textChanges.ts", "telemetry.ts", diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index bf8fcdc4f84b3..573993f1b5e29 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3703,7 +3703,7 @@ "code": 95002 }, - "Extract function": { + "Extract symbol": { "category": "Message", "code": 95003 }, @@ -3711,5 +3711,15 @@ "Extract to {0}": { "category": "Message", "code": 95004 + }, + + "Extract function": { + "category": "Message", + "code": 95005 + }, + + "Extract constant": { + "category": "Message", + "code": 95006 } } diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index c6e7813863852..88999b2d979ff 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -128,7 +128,10 @@ "./unittests/printer.ts", "./unittests/transform.ts", "./unittests/customTransforms.ts", - "./unittests/extractMethods.ts", + "./unittests/extractConstants.ts", + "./unittests/extractFunctions.ts", + "./unittests/extractRanges.ts", + "./unittests/extractTestHelpers.ts", "./unittests/textChanges.ts", "./unittests/telemetry.ts", "./unittests/languageService.ts", diff --git a/src/harness/unittests/extractConstants.ts b/src/harness/unittests/extractConstants.ts new file mode 100644 index 0000000000000..e2c64afb5261f --- /dev/null +++ b/src/harness/unittests/extractConstants.ts @@ -0,0 +1,87 @@ +/// + +namespace ts { + describe("extractConstants", () => { + testExtractConstant("extractConstant_TopLevel", + `let x = [#|1|];`); + + testExtractConstant("extractConstant_Namespace", + `namespace N { + let x = [#|1|]; +}`); + + testExtractConstant("extractConstant_Class", + `class C { + x = [#|1|]; +}`); + + testExtractConstant("extractConstant_Method", + `class C { + M() { + let x = [#|1|]; + } +}`); + + testExtractConstant("extractConstant_Function", + `function F() { + let x = [#|1|]; +}`); + + testExtractConstant("extractConstant_ExpressionStatement", + `[#|"hello";|]`); + + testExtractConstant("extractConstant_ExpressionStatementExpression", + `[#|"hello"|];`); + + testExtractConstant("extractConstant_BlockScopes_NoDependencies", + `for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + let x = [#|1|]; + } +}`); + + testExtractConstant("extractConstant_ClassInsertionPosition", + `class C { + a = 1; + b = 2; + M1() { } + M2() { } + M3() { + let x = [#|1|]; + } +}`); + + testExtractConstant("extractConstant_Parameters", + `function F() { + let w = 1; + let x = [#|w + 1|]; +}`); + + testExtractConstant("extractConstant_TypeParameters", + `function F(t: T) { + let x = [#|t + 1|]; +}`); + +// TODO (acasey): handle repeated substitution +// testExtractConstant("extractConstant_RepeatedSubstitution", +// `namespace X { +// export const j = 10; +// export const y = [#|j * j|]; +// }`); + + testExtractConstantFailed("extractConstant_BlockScopes_Dependencies", + `for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + let x = [#|i + 1|]; + } +}`); + }); + + function testExtractConstant(caption: string, text: string) { + testExtractSymbol(caption, text, "extractConstant", Diagnostics.Extract_constant); + } + + function testExtractConstantFailed(caption: string, text: string) { + testExtractSymbolFailed(caption, text, Diagnostics.Extract_constant); + } +} diff --git a/src/harness/unittests/extractFunctions.ts b/src/harness/unittests/extractFunctions.ts new file mode 100644 index 0000000000000..4e7cba9606c2c --- /dev/null +++ b/src/harness/unittests/extractFunctions.ts @@ -0,0 +1,379 @@ +/// + +namespace ts { + describe("extractMethods", () => { + testExtractMethod("extractMethod1", + `namespace A { + let x = 1; + function foo() { + } + namespace B { + function a() { + let a = 1; + [#| + let y = 5; + let z = x; + a = y; + foo();|] + } + } +}`); + testExtractMethod("extractMethod2", + `namespace A { + let x = 1; + function foo() { + } + namespace B { + function a() { + [#| + let y = 5; + let z = x; + return foo();|] + } + } +}`); + testExtractMethod("extractMethod3", + `namespace A { + function foo() { + } + namespace B { + function* a(z: number) { + [#| + let y = 5; + yield z; + return foo();|] + } + } +}`); + testExtractMethod("extractMethod4", + `namespace A { + function foo() { + } + namespace B { + async function a(z: number, z1: any) { + [#| + let y = 5; + if (z) { + await z1; + } + return foo();|] + } + } +}`); + testExtractMethod("extractMethod5", + `namespace A { + let x = 1; + export function foo() { + } + namespace B { + function a() { + let a = 1; + [#| + let y = 5; + let z = x; + a = y; + foo();|] + } + } +}`); + testExtractMethod("extractMethod6", + `namespace A { + let x = 1; + export function foo() { + } + namespace B { + function a() { + let a = 1; + [#| + let y = 5; + let z = x; + a = y; + return foo();|] + } + } +}`); + testExtractMethod("extractMethod7", + `namespace A { + let x = 1; + export namespace C { + export function foo() { + } + } + namespace B { + function a() { + let a = 1; + [#| + let y = 5; + let z = x; + a = y; + return C.foo();|] + } + } +}`); + testExtractMethod("extractMethod8", + `namespace A { + let x = 1; + namespace B { + function a() { + let a1 = 1; + return 1 + [#|a1 + x|] + 100; + } + } +}`); + testExtractMethod("extractMethod9", + `namespace A { + export interface I { x: number }; + namespace B { + function a() { + [#|let a1: I = { x: 1 }; + return a1.x + 10;|] + } + } +}`); + testExtractMethod("extractMethod10", + `namespace A { + export interface I { x: number }; + class C { + a() { + let z = 1; + [#|let a1: I = { x: 1 }; + return a1.x + 10;|] + } + } +}`); + testExtractMethod("extractMethod11", + `namespace A { + let y = 1; + class C { + a() { + let z = 1; + [#|let a1 = { x: 1 }; + y = 10; + z = 42; + return a1.x + 10;|] + } + } +}`); + testExtractMethod("extractMethod12", + `namespace A { + let y = 1; + class C { + b() {} + a() { + let z = 1; + [#|let a1 = { x: 1 }; + y = 10; + z = 42; + this.b(); + return a1.x + 10;|] + } + } +}`); + // The "b" type parameters aren't used and shouldn't be passed to the extracted function. + // Type parameters should be in syntactic order (i.e. in order or character offset from BOF). + // In all cases, we could use type inference, rather than passing explicit type arguments. + // Note the inclusion of arrow functions to ensure that some type parameters are not from + // targetable scopes. + testExtractMethod("extractMethod13", + `(u1a: U1a, u1b: U1b) => { + function F1(t1a: T1a, t1b: T1b) { + (u2a: U2a, u2b: U2b) => { + function F2(t2a: T2a, t2b: T2b) { + (u3a: U3a, u3b: U3b) => { + [#|t1a.toString(); + t2a.toString(); + u1a.toString(); + u2a.toString(); + u3a.toString();|] + } + } + } + } +}`); + // This test is descriptive, rather than normative. The current implementation + // doesn't handle type parameter shadowing. + testExtractMethod("extractMethod14", + `function F(t1: T) { + function G(t2: T) { + [#|t1.toString(); + t2.toString();|] + } +}`); + // Confirm that the constraint is preserved. + testExtractMethod("extractMethod15", + `function F(t1: T) { + function G(t2: U) { + [#|t2.toString();|] + } +}`); + // Confirm that the contextual type of an extracted expression counts as a use. + testExtractMethod("extractMethod16", + `function F() { + const array: T[] = [#|[]|]; +}`); + // Class type parameter + testExtractMethod("extractMethod17", + `class C { + M(t1: T1, t2: T2) { + [#|t1.toString()|]; + } +}`); + // Method type parameter + testExtractMethod("extractMethod18", + `class C { + M(t1: T1, t2: T2) { + [#|t1.toString()|]; + } +}`); + // Coupled constraints + testExtractMethod("extractMethod19", + `function F(v: V) { + [#|v.toString()|]; +}`); + + testExtractMethod("extractMethod20", + `const _ = class { + a() { + [#|let a1 = { x: 1 }; + return a1.x + 10;|] + } +}`); + // Write + void return + testExtractMethod("extractMethod21", + `function foo() { + let x = 10; + [#|x++; + return;|] +}`); + // Return in finally block + testExtractMethod("extractMethod22", + `function test() { + try { + } + finally { + [#|return 1;|] + } +}`); + // Extraction position - namespace + testExtractMethod("extractMethod23", + `namespace NS { + function M1() { } + function M2() { + [#|return 1;|] + } + function M3() { } +}`); + // Extraction position - function + testExtractMethod("extractMethod24", + `function Outer() { + function M1() { } + function M2() { + [#|return 1;|] + } + function M3() { } +}`); + // Extraction position - file + testExtractMethod("extractMethod25", + `function M1() { } +function M2() { + [#|return 1;|] +} +function M3() { }`); + // Extraction position - class without ctor + testExtractMethod("extractMethod26", + `class C { + M1() { } + M2() { + [#|return 1;|] + } + M3() { } +}`); + // Extraction position - class with ctor in middle + testExtractMethod("extractMethod27", + `class C { + M1() { } + M2() { + [#|return 1;|] + } + constructor() { } + M3() { } +}`); + // Extraction position - class with ctor at end + testExtractMethod("extractMethod28", + `class C { + M1() { } + M2() { + [#|return 1;|] + } + M3() { } + constructor() { } +}`); + // Shorthand property names + testExtractMethod("extractMethod29", + `interface UnaryExpression { + kind: "Unary"; + operator: string; + operand: any; +} + +function parseUnaryExpression(operator: string): UnaryExpression { + [#|return { + kind: "Unary", + operator, + operand: parsePrimaryExpression(), + };|] +} + +function parsePrimaryExpression(): any { + throw "Not implemented"; +}`); + // Type parameter as declared type + testExtractMethod("extractMethod30", + `function F() { + [#|let t: T;|] +}`); + // Return in nested function + testExtractMethod("extractMethod31", + `namespace N { + + export const value = 1; + + () => { + var f: () => number; + [#|f = function (): number { + return value; + }|] + } +}`); + // Return in nested class + testExtractMethod("extractMethod32", + `namespace N { + + export const value = 1; + + () => { + [#|var c = class { + M() { + return value; + } + }|] + } +}`); + // Selection excludes leading trivia of declaration + testExtractMethod("extractMethod33", + `function F() { + [#|function G() { }|] +}`); + +// TODO (acasey): handle repeated substitution +// testExtractMethod("extractMethod_RepeatedSubstitution", +// `namespace X { +// export const j = 10; +// export const y = [#|j * j|]; +// }`); + }); + + function testExtractMethod(caption: string, text: string) { + testExtractSymbol(caption, text, "extractMethod", Diagnostics.Extract_function); + } +} diff --git a/src/harness/unittests/extractMethods.ts b/src/harness/unittests/extractMethods.ts deleted file mode 100644 index 190cd1d5be0bc..0000000000000 --- a/src/harness/unittests/extractMethods.ts +++ /dev/null @@ -1,823 +0,0 @@ -/// -/// - -namespace ts { - interface Range { - start: number; - end: number; - name: string; - } - - interface Test { - source: string; - ranges: Map; - } - - function extractTest(source: string): Test { - const activeRanges: Range[] = []; - let text = ""; - let lastPos = 0; - let pos = 0; - const ranges = createMap(); - - while (pos < source.length) { - if (source.charCodeAt(pos) === CharacterCodes.openBracket && - (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) { - const saved = pos; - pos += 2; - const s = pos; - consumeIdentifier(); - const e = pos; - if (source.charCodeAt(pos) === CharacterCodes.bar) { - pos++; - text += source.substring(lastPos, saved); - const name = s === e - ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted" - : source.substring(s, e); - activeRanges.push({ name, start: text.length, end: undefined }); - lastPos = pos; - continue; - } - else { - pos = saved; - } - } - else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) { - text += source.substring(lastPos, pos); - activeRanges[activeRanges.length - 1].end = text.length; - const range = activeRanges.pop(); - if (range.name in ranges) { - throw new Error(`Duplicate name of range ${range.name}`); - } - ranges.set(range.name, range); - pos += 2; - lastPos = pos; - continue; - } - pos++; - } - text += source.substring(lastPos, pos); - - function consumeIdentifier() { - while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) { - pos++; - } - } - return { source: text, ranges }; - } - - const newLineCharacter = "\n"; - function getRuleProvider(action?: (opts: FormatCodeSettings) => void) { - const options = { - indentSize: 4, - tabSize: 4, - newLineCharacter, - convertTabsToSpaces: true, - indentStyle: ts.IndentStyle.Smart, - insertSpaceAfterConstructor: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - }; - if (action) { - action(options); - } - const rulesProvider = new formatting.RulesProvider(); - rulesProvider.ensureUpToDate(options); - return rulesProvider; - } - - function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) { - return it(caption, () => { - const t = extractTest(s); - const file = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${s} does not specify selection range`); - } - const result = refactor.extractMethod.getRangeToExtract(file, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); - assert(result.targetRange === undefined, "failure expected"); - const sortedErrors = result.errors.map(e => e.messageText).sort(); - assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors"); - }); - } - - function testExtractRange(s: string): void { - const t = extractTest(s); - const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${s} does not specify selection range`); - } - const result = refactor.extractMethod.getRangeToExtract(f, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); - const expectedRange = t.ranges.get("extracted"); - if (expectedRange) { - let start: number, end: number; - if (ts.isArray(result.targetRange.range)) { - start = result.targetRange.range[0].getStart(f); - end = ts.lastOrUndefined(result.targetRange.range).getEnd(); - } - else { - start = result.targetRange.range.getStart(f); - end = result.targetRange.range.getEnd(); - } - assert.equal(start, expectedRange.start, "incorrect start of range"); - assert.equal(end, expectedRange.end, "incorrect end of range"); - } - else { - assert.isTrue(!result.targetRange, `expected range to extract to be undefined`); - } - } - - describe("extractMethods", () => { - it("get extract range from selection", () => { - testExtractRange(` - [#| - [$|var x = 1; - var y = 2;|]|] - `); - testExtractRange(` - [#| - var x = 1; - var y = 2|]; - `); - testExtractRange(` - [#|var x = 1|]; - var y = 2; - `); - testExtractRange(` - if ([#|[#extracted|a && b && c && d|]|]) { - } - `); - testExtractRange(` - if [#|(a && b && c && d|]) { - } - `); - testExtractRange(` - if (a && b && c && d) { - [#| [$|var x = 1; - console.log(x);|] |] - } - `); - testExtractRange(` - [#| - if (a) { - return 100; - } |] - `); - testExtractRange(` - function foo() { - [#| [$|if (a) { - } - return 100|] |] - } - `); - testExtractRange(` - [#| - [$|l1: - if (x) { - break l1; - }|]|] - `); - testExtractRange(` - [#| - [$|l2: - { - if (x) { - } - break l2; - }|]|] - `); - testExtractRange(` - while (true) { - [#| if(x) { - } - break; |] - } - `); - testExtractRange(` - while (true) { - [#| if(x) { - } - continue; |] - } - `); - testExtractRange(` - l3: - { - [#| - if (x) { - } - break l3; |] - } - `); - testExtractRange(` - function f() { - while (true) { - [#| - if (x) { - return; - } |] - } - } - `); - testExtractRange(` - function f() { - while (true) { - [#| - [$|if (x) { - } - return;|] - |] - } - } - `); - testExtractRange(` - function f() { - return [#| [$|1 + 2|] |]+ 3; - } - } - `); - testExtractRange(` - function f() { - return [$|1 + [#|2 + 3|]|]; - } - } - `); - testExtractRange(` - function f() { - return [$|1 + 2 + [#|3 + 4|]|]; - } - } - `); - }); - - testExtractRangeFailed("extractRangeFailed1", - ` -namespace A { - function f() { - [#| - let x = 1 - if (x) { - return 10; - } - |] - } -} - `, - [ - "Cannot extract range containing conditional return statement." - ]); - - testExtractRangeFailed("extractRangeFailed2", - ` -namespace A { - function f() { - while (true) { - [#| - let x = 1 - if (x) { - break; - } - |] - } - } -} - `, - [ - "Cannot extract range containing conditional break or continue statements." - ]); - - testExtractRangeFailed("extractRangeFailed3", - ` -namespace A { - function f() { - while (true) { - [#| - let x = 1 - if (x) { - continue; - } - |] - } - } -} - `, - [ - "Cannot extract range containing conditional break or continue statements." - ]); - - testExtractRangeFailed("extractRangeFailed4", - ` -namespace A { - function f() { - l1: { - [#| - let x = 1 - if (x) { - break l1; - } - |] - } - } -} - `, - [ - "Cannot extract range containing labeled break or continue with target outside of the range." - ]); - - testExtractRangeFailed("extractRangeFailed5", - ` -namespace A { - function f() { - [#| - try { - f2() - return 10; - } - catch (e) { - } - |] - } - function f2() { - } -} - `, - [ - "Cannot extract range containing conditional return statement." - ]); - - testExtractRangeFailed("extractRangeFailed6", - ` -namespace A { - function f() { - [#| - try { - f2() - } - catch (e) { - return 10; - } - |] - } - function f2() { - } -} - `, - [ - "Cannot extract range containing conditional return statement." - ]); - - testExtractRangeFailed("extractRangeFailed7", - ` -function test(x: number) { - while (x) { - x--; - [#|break;|] - } -} - `, - [ - "Cannot extract range containing conditional break or continue statements." - ]); - - testExtractRangeFailed("extractRangeFailed8", - ` -function test(x: number) { - switch (x) { - case 1: - [#|break;|] - } -} - `, - [ - "Cannot extract range containing conditional break or continue statements." - ]); - - testExtractRangeFailed("extractRangeFailed9", - `var x = ([#||]1 + 2);`, - [ - "Statement or expression expected." - ]); - - testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, ["Select more than a single identifier."]); - - testExtractMethod("extractMethod1", - `namespace A { - let x = 1; - function foo() { - } - namespace B { - function a() { - let a = 1; - [#| - let y = 5; - let z = x; - a = y; - foo();|] - } - } -}`); - testExtractMethod("extractMethod2", - `namespace A { - let x = 1; - function foo() { - } - namespace B { - function a() { - [#| - let y = 5; - let z = x; - return foo();|] - } - } -}`); - testExtractMethod("extractMethod3", - `namespace A { - function foo() { - } - namespace B { - function* a(z: number) { - [#| - let y = 5; - yield z; - return foo();|] - } - } -}`); - testExtractMethod("extractMethod4", - `namespace A { - function foo() { - } - namespace B { - async function a(z: number, z1: any) { - [#| - let y = 5; - if (z) { - await z1; - } - return foo();|] - } - } -}`); - testExtractMethod("extractMethod5", - `namespace A { - let x = 1; - export function foo() { - } - namespace B { - function a() { - let a = 1; - [#| - let y = 5; - let z = x; - a = y; - foo();|] - } - } -}`); - testExtractMethod("extractMethod6", - `namespace A { - let x = 1; - export function foo() { - } - namespace B { - function a() { - let a = 1; - [#| - let y = 5; - let z = x; - a = y; - return foo();|] - } - } -}`); - testExtractMethod("extractMethod7", - `namespace A { - let x = 1; - export namespace C { - export function foo() { - } - } - namespace B { - function a() { - let a = 1; - [#| - let y = 5; - let z = x; - a = y; - return C.foo();|] - } - } -}`); - testExtractMethod("extractMethod8", - `namespace A { - let x = 1; - namespace B { - function a() { - let a1 = 1; - return 1 + [#|a1 + x|] + 100; - } - } -}`); - testExtractMethod("extractMethod9", - `namespace A { - export interface I { x: number }; - namespace B { - function a() { - [#|let a1: I = { x: 1 }; - return a1.x + 10;|] - } - } -}`); - testExtractMethod("extractMethod10", - `namespace A { - export interface I { x: number }; - class C { - a() { - let z = 1; - [#|let a1: I = { x: 1 }; - return a1.x + 10;|] - } - } -}`); - testExtractMethod("extractMethod11", - `namespace A { - let y = 1; - class C { - a() { - let z = 1; - [#|let a1 = { x: 1 }; - y = 10; - z = 42; - return a1.x + 10;|] - } - } -}`); - testExtractMethod("extractMethod12", - `namespace A { - let y = 1; - class C { - b() {} - a() { - let z = 1; - [#|let a1 = { x: 1 }; - y = 10; - z = 42; - this.b(); - return a1.x + 10;|] - } - } -}`); - // The "b" type parameters aren't used and shouldn't be passed to the extracted function. - // Type parameters should be in syntactic order (i.e. in order or character offset from BOF). - // In all cases, we could use type inference, rather than passing explicit type arguments. - // Note the inclusion of arrow functions to ensure that some type parameters are not from - // targetable scopes. - testExtractMethod("extractMethod13", - `(u1a: U1a, u1b: U1b) => { - function F1(t1a: T1a, t1b: T1b) { - (u2a: U2a, u2b: U2b) => { - function F2(t2a: T2a, t2b: T2b) { - (u3a: U3a, u3b: U3b) => { - [#|t1a.toString(); - t2a.toString(); - u1a.toString(); - u2a.toString(); - u3a.toString();|] - } - } - } - } -}`); - // This test is descriptive, rather than normative. The current implementation - // doesn't handle type parameter shadowing. - testExtractMethod("extractMethod14", - `function F(t1: T) { - function F(t2: T) { - [#|t1.toString(); - t2.toString();|] - } -}`); - // Confirm that the constraint is preserved. - testExtractMethod("extractMethod15", - `function F(t1: T) { - 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[] = [#|[]|]; -}`); - // Class type parameter - testExtractMethod("extractMethod17", - `class C { - M(t1: T1, t2: T2) { - [#|t1.toString()|]; - } -}`); - // Method type parameter - testExtractMethod("extractMethod18", - `class C { - M(t1: T1, t2: T2) { - [#|t1.toString()|]; - } -}`); - // Coupled constraints - testExtractMethod("extractMethod19", - `function F(v: V) { - [#|v.toString()|]; -}`); - - testExtractMethod("extractMethod20", - `const _ = class { - a() { - [#|let a1 = { x: 1 }; - return a1.x + 10;|] - } -}`); - // Write + void return - testExtractMethod("extractMethod21", - `function foo() { - let x = 10; - [#|x++; - return;|] -}`); - // Return in finally block - testExtractMethod("extractMethod22", - `function test() { - try { - } - finally { - [#|return 1;|] - } -}`); - // Extraction position - namespace - testExtractMethod("extractMethod23", - `namespace NS { - function M1() { } - function M2() { - [#|return 1;|] - } - function M3() { } -}`); - // Extraction position - function - testExtractMethod("extractMethod24", - `function Outer() { - function M1() { } - function M2() { - [#|return 1;|] - } - function M3() { } -}`); - // Extraction position - file - testExtractMethod("extractMethod25", - `function M1() { } -function M2() { - [#|return 1;|] -} -function M3() { }`); - // Extraction position - class without ctor - testExtractMethod("extractMethod26", - `class C { - M1() { } - M2() { - [#|return 1;|] - } - M3() { } -}`); - // Extraction position - class with ctor in middle - testExtractMethod("extractMethod27", - `class C { - M1() { } - M2() { - [#|return 1;|] - } - constructor() { } - M3() { } -}`); - // Extraction position - class with ctor at end - testExtractMethod("extractMethod28", - `class C { - M1() { } - M2() { - [#|return 1;|] - } - M3() { } - constructor() { } -}`); - // Shorthand property names - testExtractMethod("extractMethod29", - `interface UnaryExpression { - kind: "Unary"; - operator: string; - operand: any; -} - -function parseUnaryExpression(operator: string): UnaryExpression { - [#|return { - kind: "Unary", - operator, - operand: parsePrimaryExpression(), - };|] -} - -function parsePrimaryExpression(): any { - throw "Not implemented"; -}`); - // Type parameter as declared type - testExtractMethod("extractMethod30", - `function F() { - [#|let t: T;|] -}`); - // Return in nested function - testExtractMethod("extractMethod31", - `namespace N { - - export const value = 1; - - () => { - var f: () => number; - [#|f = function (): number { - return value; - }|] - } -}`); - // Return in nested class - testExtractMethod("extractMethod32", - `namespace N { - - export const value = 1; - - () => { - [#|var c = class { - M() { - return value; - } - }|] - } -}`); - // Selection excludes leading trivia of declaration - testExtractMethod("extractMethod33", - `function F() { - [#|function G() { }|] -}`); - }); - - - function testExtractMethod(caption: string, text: string) { - it(caption, () => { - Harness.Baseline.runBaseline(`extractMethod/${caption}.ts`, () => { - const t = extractTest(text); - const selectionRange = t.ranges.get("selection"); - if (!selectionRange) { - throw new Error(`Test ${caption} does not specify selection range`); - } - const f = { - path: "/a.ts", - content: t.source - }; - const host = projectSystem.createServerHost([f, projectSystem.libFile]); - const projectService = projectSystem.createProjectService(host); - projectService.openClientFile(f.path); - const program = projectService.inferredProjects[0].getLanguageService().getProgram(); - const sourceFile = program.getSourceFile(f.path); - const context: RefactorContext = { - cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } }, - newLineCharacter, - program, - file: sourceFile, - startPosition: -1, - rulesProvider: getRuleProvider() - }; - const result = refactor.extractMethod.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); - assert.equal(result.errors, undefined, "expect no errors"); - const results = refactor.extractMethod.getPossibleExtractions(result.targetRange, context); - const data: string[] = []; - data.push(`// ==ORIGINAL==`); - data.push(sourceFile.text); - for (const r of results) { - const { renameLocation, edits } = refactor.extractMethod.getExtractionAtIndex(result.targetRange, context, results.indexOf(r)); - assert.lengthOf(edits, 1); - data.push(`// ==SCOPE::${r.scopeDescription}==`); - const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges); - const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation); - data.push(newTextWithRename); - } - return data.join(newLineCharacter); - }); - }); - } -} diff --git a/src/harness/unittests/extractRanges.ts b/src/harness/unittests/extractRanges.ts new file mode 100644 index 0000000000000..fcffffeba9dbe --- /dev/null +++ b/src/harness/unittests/extractRanges.ts @@ -0,0 +1,319 @@ +/// + +namespace ts { + function testExtractRangeFailed(caption: string, s: string, expectedErrors: string[]) { + return it(caption, () => { + const t = extractTest(s); + const file = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${s} does not specify selection range`); + } + const result = refactor.extractSymbol.getRangeToExtract(file, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); + assert(result.targetRange === undefined, "failure expected"); + const sortedErrors = result.errors.map(e => e.messageText).sort(); + assert.deepEqual(sortedErrors, expectedErrors.sort(), "unexpected errors"); + }); + } + + function testExtractRange(s: string): void { + const t = extractTest(s); + const f = createSourceFile("a.ts", t.source, ScriptTarget.Latest, /*setParentNodes*/ true); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${s} does not specify selection range`); + } + const result = refactor.extractSymbol.getRangeToExtract(f, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); + const expectedRange = t.ranges.get("extracted"); + if (expectedRange) { + let start: number, end: number; + if (ts.isArray(result.targetRange.range)) { + start = result.targetRange.range[0].getStart(f); + end = ts.lastOrUndefined(result.targetRange.range).getEnd(); + } + else { + start = result.targetRange.range.getStart(f); + end = result.targetRange.range.getEnd(); + } + assert.equal(start, expectedRange.start, "incorrect start of range"); + assert.equal(end, expectedRange.end, "incorrect end of range"); + } + else { + assert.isTrue(!result.targetRange, `expected range to extract to be undefined`); + } + } + + describe("extractRanges", () => { + it("get extract range from selection", () => { + testExtractRange(` + [#| + [$|var x = 1; + var y = 2;|]|] + `); + testExtractRange(` + [#| + var x = 1; + var y = 2|]; + `); + testExtractRange(` + [#|var x = 1|]; + var y = 2; + `); + testExtractRange(` + if ([#|[#extracted|a && b && c && d|]|]) { + } + `); + testExtractRange(` + if [#|(a && b && c && d|]) { + } + `); + testExtractRange(` + if (a && b && c && d) { + [#| [$|var x = 1; + console.log(x);|] |] + } + `); + testExtractRange(` + [#| + if (a) { + return 100; + } |] + `); + testExtractRange(` + function foo() { + [#| [$|if (a) { + } + return 100|] |] + } + `); + testExtractRange(` + [#| + [$|l1: + if (x) { + break l1; + }|]|] + `); + testExtractRange(` + [#| + [$|l2: + { + if (x) { + } + break l2; + }|]|] + `); + testExtractRange(` + while (true) { + [#| if(x) { + } + break; |] + } + `); + testExtractRange(` + while (true) { + [#| if(x) { + } + continue; |] + } + `); + testExtractRange(` + l3: + { + [#| + if (x) { + } + break l3; |] + } + `); + testExtractRange(` + function f() { + while (true) { + [#| + if (x) { + return; + } |] + } + } + `); + testExtractRange(` + function f() { + while (true) { + [#| + [$|if (x) { + } + return;|] + |] + } + } + `); + testExtractRange(` + function f() { + return [#| [$|1 + 2|] |]+ 3; + } + } + `); + testExtractRange(` + function f() { + return [$|1 + [#|2 + 3|]|]; + } + } + `); + testExtractRange(` + function f() { + return [$|1 + 2 + [#|3 + 4|]|]; + } + } + `); + }); + + testExtractRangeFailed("extractRangeFailed1", + ` +namespace A { +function f() { + [#| + let x = 1 + if (x) { + return 10; + } + |] +} +} + `, + [ + refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalReturnStatement.message + ]); + + testExtractRangeFailed("extractRangeFailed2", + ` +namespace A { +function f() { + while (true) { + [#| + let x = 1 + if (x) { + break; + } + |] + } +} +} + `, + [ + refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalBreakOrContinueStatements.message + ]); + + testExtractRangeFailed("extractRangeFailed3", + ` +namespace A { +function f() { + while (true) { + [#| + let x = 1 + if (x) { + continue; + } + |] + } +} +} + `, + [ + refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalBreakOrContinueStatements.message + ]); + + testExtractRangeFailed("extractRangeFailed4", + ` +namespace A { +function f() { + l1: { + [#| + let x = 1 + if (x) { + break l1; + } + |] + } +} +} + `, + [ + refactor.extractSymbol.Messages.CannotExtractRangeContainingLabeledBreakOrContinueStatementWithTargetOutsideOfTheRange.message + ]); + + testExtractRangeFailed("extractRangeFailed5", + ` +namespace A { +function f() { + [#| + try { + f2() + return 10; + } + catch (e) { + } + |] +} +function f2() { +} +} + `, + [ + refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalReturnStatement.message + ]); + + testExtractRangeFailed("extractRangeFailed6", + ` +namespace A { +function f() { + [#| + try { + f2() + } + catch (e) { + return 10; + } + |] +} +function f2() { +} +} + `, + [ + refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalReturnStatement.message + ]); + + testExtractRangeFailed("extractRangeFailed7", + ` +function test(x: number) { +while (x) { + x--; + [#|break;|] +} +} + `, + [ + refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalBreakOrContinueStatements.message + ]); + + testExtractRangeFailed("extractRangeFailed8", + ` +function test(x: number) { +switch (x) { + case 1: + [#|break;|] +} +} + `, + [ + refactor.extractSymbol.Messages.CannotExtractRangeContainingConditionalBreakOrContinueStatements.message + ]); + + testExtractRangeFailed("extractRangeFailed9", + `var x = ([#||]1 + 2);`, + [ + "Cannot extract empty range." + ]); + + testExtractRangeFailed("extract-method-not-for-token-expression-statement", `[#|a|]`, [refactor.extractSymbol.Messages.CannotExtractIdentifier.message]); + }); +} \ No newline at end of file diff --git a/src/harness/unittests/extractTestHelpers.ts b/src/harness/unittests/extractTestHelpers.ts new file mode 100644 index 0000000000000..e619f9343d891 --- /dev/null +++ b/src/harness/unittests/extractTestHelpers.ts @@ -0,0 +1,177 @@ +/// +/// + +namespace ts { + export interface Range { + start: number; + end: number; + name: string; + } + + export interface Test { + source: string; + ranges: Map; + } + + export function extractTest(source: string): Test { + const activeRanges: Range[] = []; + let text = ""; + let lastPos = 0; + let pos = 0; + const ranges = createMap(); + + while (pos < source.length) { + if (source.charCodeAt(pos) === CharacterCodes.openBracket && + (source.charCodeAt(pos + 1) === CharacterCodes.hash || source.charCodeAt(pos + 1) === CharacterCodes.$)) { + const saved = pos; + pos += 2; + const s = pos; + consumeIdentifier(); + const e = pos; + if (source.charCodeAt(pos) === CharacterCodes.bar) { + pos++; + text += source.substring(lastPos, saved); + const name = s === e + ? source.charCodeAt(saved + 1) === CharacterCodes.hash ? "selection" : "extracted" + : source.substring(s, e); + activeRanges.push({ name, start: text.length, end: undefined }); + lastPos = pos; + continue; + } + else { + pos = saved; + } + } + else if (source.charCodeAt(pos) === CharacterCodes.bar && source.charCodeAt(pos + 1) === CharacterCodes.closeBracket) { + text += source.substring(lastPos, pos); + activeRanges[activeRanges.length - 1].end = text.length; + const range = activeRanges.pop(); + if (range.name in ranges) { + throw new Error(`Duplicate name of range ${range.name}`); + } + ranges.set(range.name, range); + pos += 2; + lastPos = pos; + continue; + } + pos++; + } + text += source.substring(lastPos, pos); + + function consumeIdentifier() { + while (isIdentifierPart(source.charCodeAt(pos), ScriptTarget.Latest)) { + pos++; + } + } + return { source: text, ranges }; + } + + export const newLineCharacter = "\n"; + export function getRuleProvider(action?: (opts: FormatCodeSettings) => void) { + const options = { + indentSize: 4, + tabSize: 4, + newLineCharacter, + convertTabsToSpaces: true, + indentStyle: ts.IndentStyle.Smart, + insertSpaceAfterConstructor: false, + insertSpaceAfterCommaDelimiter: true, + insertSpaceAfterSemicolonInForStatements: true, + insertSpaceBeforeAndAfterBinaryOperators: true, + insertSpaceAfterKeywordsInControlFlowStatements: true, + insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, + insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, + insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, + insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, + insertSpaceBeforeFunctionParenthesis: false, + placeOpenBraceOnNewLineForFunctions: false, + placeOpenBraceOnNewLineForControlBlocks: false, + }; + if (action) { + action(options); + } + const rulesProvider = new formatting.RulesProvider(); + rulesProvider.ensureUpToDate(options); + return rulesProvider; + } + + export function testExtractSymbol(caption: string, text: string, baselineFolder: string, description: DiagnosticMessage) { + it(caption, () => { + Harness.Baseline.runBaseline(`${baselineFolder}/${caption}.ts`, () => { + const t = extractTest(text); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); + } + const f = { + path: "/a.ts", + content: t.source + }; + const host = projectSystem.createServerHost([f, projectSystem.libFile]); + const projectService = projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const program = projectService.inferredProjects[0].getLanguageService().getProgram(); + const sourceFile = program.getSourceFile(f.path); + const context: RefactorContext = { + cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } }, + newLineCharacter, + program, + file: sourceFile, + startPosition: selectionRange.start, + endPosition: selectionRange.end, + rulesProvider: getRuleProvider() + }; + const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); + assert.equal(rangeToExtract.errors, undefined, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); + const infos = refactor.extractSymbol.getAvailableActions(context); + const actions = find(infos, info => info.description === description.message).actions; + const data: string[] = []; + data.push(`// ==ORIGINAL==`); + data.push(sourceFile.text); + for (const action of actions) { + const { renameLocation, edits } = refactor.extractSymbol.getEditsForAction(context, action.name); + assert.lengthOf(edits, 1); + data.push(`// ==SCOPE::${action.description}==`); + const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges); + const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation); + data.push(newTextWithRename); + } + return data.join(newLineCharacter); + }); + }); + } + + export function testExtractSymbolFailed(caption: string, text: string, description: DiagnosticMessage) { + it(caption, () => { + const t = extractTest(text); + const selectionRange = t.ranges.get("selection"); + if (!selectionRange) { + throw new Error(`Test ${caption} does not specify selection range`); + } + const f = { + path: "/a.ts", + content: t.source + }; + const host = projectSystem.createServerHost([f, projectSystem.libFile]); + const projectService = projectSystem.createProjectService(host); + projectService.openClientFile(f.path); + const program = projectService.inferredProjects[0].getLanguageService().getProgram(); + const sourceFile = program.getSourceFile(f.path); + const context: RefactorContext = { + cancellationToken: { throwIfCancellationRequested() { }, isCancellationRequested() { return false; } }, + newLineCharacter, + program, + file: sourceFile, + startPosition: selectionRange.start, + endPosition: selectionRange.end, + rulesProvider: getRuleProvider() + }; + const rangeToExtract = refactor.extractSymbol.getRangeToExtract(sourceFile, createTextSpanFromBounds(selectionRange.start, selectionRange.end)); + assert.isUndefined(rangeToExtract.errors, rangeToExtract.errors && "Range error: " + rangeToExtract.errors[0].messageText); + const infos = refactor.extractSymbol.getAvailableActions(context); + assert.isUndefined(find(infos, info => info.description === description.message)); + }); + } +} \ No newline at end of file diff --git a/src/services/refactors/extractMethod.ts b/src/services/refactors/extractSymbol.ts similarity index 73% rename from src/services/refactors/extractMethod.ts rename to src/services/refactors/extractSymbol.ts index 481834c5a094b..375acb7e5857d 100644 --- a/src/services/refactors/extractMethod.ts +++ b/src/services/refactors/extractSymbol.ts @@ -2,18 +2,21 @@ /// /* @internal */ -namespace ts.refactor.extractMethod { - const extractMethod: Refactor = { - name: "Extract Method", - description: Diagnostics.Extract_function.message, +namespace ts.refactor.extractSymbol { + const extractSymbol: Refactor = { + name: "Extract Symbol", + description: Diagnostics.Extract_symbol.message, getAvailableActions, getEditsForAction, }; - registerRefactor(extractMethod); + registerRefactor(extractSymbol); - /** Compute the associated code actions */ - function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { + /** + * Compute the associated code actions + * Exported for tests. + */ + export function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { const rangeToExtract = getRangeToExtract(context.file, { start: context.startPosition, length: getRefactorContextLength(context) }); const targetRange: TargetRange = rangeToExtract.targetRange; @@ -27,63 +30,103 @@ namespace ts.refactor.extractMethod { return undefined; } - const actions: RefactorActionInfo[] = []; - const usedNames: Map = createMap(); + const functionActions: RefactorActionInfo[] = []; + const usedFunctionNames: Map = createMap(); + + const constantActions: RefactorActionInfo[] = []; + const usedConstantNames: Map = createMap(); let i = 0; - for (const { scopeDescription, errors } of extractions) { + for (const extraction of extractions) { // Skip these since we don't have a way to report errors yet - if (errors.length) { - continue; + if (extraction.functionErrors.length === 0) { + // Don't issue refactorings with duplicated names. + // Scopes come back in "innermost first" order, so extractions will + // preferentially go into nearer scopes + const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [extraction.functionDescription]); + if (!usedFunctionNames.has(description)) { + usedFunctionNames.set(description, true); + functionActions.push({ + description, + name: `function_scope_${i}` + }); + } } - // Don't issue refactorings with duplicated names. - // Scopes come back in "innermost first" order, so extractions will - // preferentially go into nearer scopes - const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [scopeDescription]); - if (!usedNames.has(description)) { - usedNames.set(description, true); - actions.push({ - description, - name: `scope_${i}` - }); + // Skip these since we don't have a way to report errors yet + if (extraction.constantErrors.length === 0) { + // Don't issue refactorings with duplicated names. + // Scopes come back in "innermost first" order, so extractions will + // preferentially go into nearer scopes + const description = formatStringFromArgs(Diagnostics.Extract_to_0.message, [extraction.constantDescription]); + if (!usedConstantNames.has(description)) { + usedConstantNames.set(description, true); + constantActions.push({ + description, + name: `constant_scope_${i}` + }); + } } + // *do* increment i anyway because we'll look for the i-th scope // later when actually doing the refactoring if the user requests it i++; } - if (actions.length === 0) { - return undefined; + const infos: ApplicableRefactorInfo[] = []; + + if (functionActions.length) { + infos.push({ + name: extractSymbol.name, + description: Diagnostics.Extract_function.message, + actions: functionActions + }); } - return [{ - name: extractMethod.name, - description: extractMethod.description, - inlineable: true, - actions - }]; + if (constantActions.length) { + infos.push({ + name: extractSymbol.name, + description: Diagnostics.Extract_constant.message, + actions: constantActions + }); + } + + return infos.length ? infos : undefined; } - function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { + /* Exported for tests */ + export function getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined { const rangeToExtract = getRangeToExtract(context.file, { start: context.startPosition, length: getRefactorContextLength(context) }); const targetRange: TargetRange = rangeToExtract.targetRange; - const parsedIndexMatch = /^scope_(\d+)$/.exec(actionName); - Debug.assert(!!parsedIndexMatch, "Scope name should have matched the regexp"); - const index = +parsedIndexMatch[1]; - Debug.assert(isFinite(index), "Expected to parse a finite number from the scope index"); + const parsedFunctionIndexMatch = /^function_scope_(\d+)$/.exec(actionName); + if (parsedFunctionIndexMatch) { + const index = +parsedFunctionIndexMatch[1]; + Debug.assert(isFinite(index), "Expected to parse a finite number from the function scope index"); + return getFunctionExtractionAtIndex(targetRange, context, index); + } + + const parsedConstantIndexMatch = /^constant_scope_(\d+)$/.exec(actionName); + if (parsedConstantIndexMatch) { + const index = +parsedConstantIndexMatch[1]; + Debug.assert(isFinite(index), "Expected to parse a finite number from the constant scope index"); + return getConstantExtractionAtIndex(targetRange, context, index); + } - return getExtractionAtIndex(targetRange, context, index); + Debug.fail("Unrecognized action name"); } // Move these into diagnostic messages if they become user-facing - namespace Messages { + export namespace Messages { function createMessage(message: string): DiagnosticMessage { return { message, code: 0, category: DiagnosticCategory.Message, key: message }; } - export const CannotExtractFunction: DiagnosticMessage = createMessage("Cannot extract function."); + export const CannotExtractRange: DiagnosticMessage = createMessage("Cannot extract range."); + export const CannotExtractImport: DiagnosticMessage = createMessage("Cannot extract import statement."); + export const CannotExtractSuper: DiagnosticMessage = createMessage("Cannot extract super call."); + export const CannotExtractEmpty: DiagnosticMessage = createMessage("Cannot extract empty range."); + export const ExpressionExpected: DiagnosticMessage = createMessage("expression expected."); export const StatementOrExpressionExpected: DiagnosticMessage = createMessage("Statement or expression expected."); export const CannotExtractRangeContainingConditionalBreakOrContinueStatements: DiagnosticMessage = createMessage("Cannot extract range containing conditional break or continue statements."); export const CannotExtractRangeContainingConditionalReturnStatement: DiagnosticMessage = createMessage("Cannot extract range containing conditional return statement."); @@ -91,11 +134,14 @@ namespace ts.refactor.extractMethod { export const CannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators: DiagnosticMessage = createMessage("Cannot extract range containing writes to references located outside of the target range in generators."); export const TypeWillNotBeVisibleInTheNewScope = createMessage("Type will not visible in the new scope."); export const FunctionWillNotBeVisibleInTheNewScope = createMessage("Function will not visible in the new scope."); - export const InsufficientSelection = createMessage("Select more than a single identifier."); + export const CannotExtractIdentifier = createMessage("Select more than a single identifier."); export const CannotExtractExportedEntity = createMessage("Cannot extract exported declaration"); export const CannotCombineWritesAndReturns = createMessage("Cannot combine writes and returns"); export const CannotExtractReadonlyPropertyInitializerOutsideConstructor = createMessage("Cannot move initialization of read-only class property outside of the constructor"); export const CannotExtractAmbientBlock = createMessage("Cannot extract code from ambient contexts"); + export const CannotAccessVariablesFromNestedScopes = createMessage("Cannot access variables from nested scopes"); + export const CannotExtractToOtherFunctionLike = createMessage("Cannot extract method to a function-like scope that is not a function"); + export const CannotExtractToJSClass = createMessage("Cannot extract constant to a class scope in JS"); } enum RangeFacts { @@ -150,7 +196,7 @@ namespace ts.refactor.extractMethod { const { length } = span; if (length === 0) { - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.StatementOrExpressionExpected)] }; + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.CannotExtractEmpty)] }; } // Walk up starting from the the start position until we find a non-SourceFile node that subsumes the selected span. @@ -167,7 +213,7 @@ namespace ts.refactor.extractMethod { if (!start || !end) { // cannot find either start or end node - return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.CannotExtractFunction)] }; + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.CannotExtractRange)] }; } if (start.parent !== end.parent) { @@ -193,13 +239,13 @@ namespace ts.refactor.extractMethod { } else { // start and end nodes belong to different subtrees - return createErrorResult(sourceFile, span.start, length, Messages.CannotExtractFunction); + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.CannotExtractRange)] }; } } if (start !== end) { // start and end should be statements and parent should be either block or a source file if (!isBlockLike(start.parent)) { - return createErrorResult(sourceFile, span.start, length, Messages.CannotExtractFunction); + return { errors: [createFileDiagnostic(sourceFile, span.start, length, Messages.CannotExtractRange)] }; } const statements: Statement[] = []; for (const statement of (start.parent).statements) { @@ -216,22 +262,17 @@ namespace ts.refactor.extractMethod { } return { targetRange: { range: statements, facts: rangeFacts, declarations } }; } - else { - // We have a single node (start) - const errors = checkRootNode(start) || checkNode(start); - if (errors) { - return { errors }; - } - return { targetRange: { range: getStatementOrExpressionRange(start), facts: rangeFacts, declarations } }; - } - function createErrorResult(sourceFile: SourceFile, start: number, length: number, message: DiagnosticMessage): RangeToExtract { - return { errors: [createFileDiagnostic(sourceFile, start, length, message)] }; + // We have a single node (start) + const errors = checkRootNode(start) || checkNode(start); + if (errors) { + return { errors }; } + return { targetRange: { range: getStatementOrExpressionRange(start), facts: rangeFacts, declarations } }; function checkRootNode(node: Node): Diagnostic[] | undefined { if (isIdentifier(isExpressionStatement(node) ? node.expression : node)) { - return [createDiagnosticForNode(node, Messages.InsufficientSelection)]; + return [createDiagnosticForNode(node, Messages.CannotExtractIdentifier)]; } return undefined; } @@ -309,7 +350,7 @@ namespace ts.refactor.extractMethod { // Some things can't be extracted in certain situations switch (node.kind) { case SyntaxKind.ImportDeclaration: - (errors || (errors = [])).push(createDiagnosticForNode(node, Messages.CannotExtractFunction)); + (errors || (errors = [])).push(createDiagnosticForNode(node, Messages.CannotExtractImport)); return true; case SyntaxKind.SuperKeyword: // For a super *constructor call*, we have to be extracting the entire class, @@ -318,7 +359,7 @@ namespace ts.refactor.extractMethod { // Super constructor call const containingClass = getContainingClass(node); if (containingClass.pos < span.start || containingClass.end >= (span.start + span.length)) { - (errors || (errors = [])).push(createDiagnosticForNode(node, Messages.CannotExtractFunction)); + (errors || (errors = [])).push(createDiagnosticForNode(node, Messages.CannotExtractSuper)); return true; } } @@ -328,7 +369,7 @@ namespace ts.refactor.extractMethod { break; } - if (!node || isFunctionLike(node) || isClassLike(node)) { + if (!node || isFunctionLikeDeclaration(node) || isClassLike(node)) { switch (node.kind) { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: @@ -439,9 +480,8 @@ namespace ts.refactor.extractMethod { return undefined; } - function isValidExtractionTarget(node: Node): node is Scope { - // Note that we don't use isFunctionLike because we don't want to put the extracted closure *inside* a method - return (node.kind === SyntaxKind.FunctionDeclaration) || isSourceFile(node) || isModuleBlock(node) || isClassLike(node); + function isScope(node: Node): node is Scope { + return isFunctionLikeDeclaration(node) || isSourceFile(node) || isModuleBlock(node) || isClassLike(node); } /** @@ -468,14 +508,14 @@ namespace ts.refactor.extractMethod { // * Function declaration // * Class declaration or expression // * Module/namespace or source file - if (current !== start && isValidExtractionTarget(current)) { + if (current !== start && isScope(current)) { (scopes = scopes || []).push(current); } // A function parameter's initializer is actually in the outer scope, not the function declaration if (current && current.parent && current.parent.kind === SyntaxKind.Parameter) { // Skip all the way to the outer scope of the function that declared this parameter - current = findAncestor(current, parent => isFunctionLike(parent)).parent; + current = findAncestor(current, parent => isFunctionLikeDeclaration(parent)).parent; } else { current = current.parent; @@ -485,29 +525,44 @@ namespace ts.refactor.extractMethod { return scopes; } - // exported only for tests - export function getExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { - const { scopes, readsAndWrites: { target, usagesPerScope, errorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); - Debug.assert(!errorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + function getFunctionExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { + const { scopes, readsAndWrites: { target, usagesPerScope, functionErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); + Debug.assert(!functionErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); context.cancellationToken.throwIfCancellationRequested(); return extractFunctionInScope(target, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange, context); } + function getConstantExtractionAtIndex(targetRange: TargetRange, context: RefactorContext, requestedChangesIndex: number): RefactorEditInfo { + const { scopes, readsAndWrites: { target, usagesPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); + Debug.assert(!constantErrorsPerScope[requestedChangesIndex].length, "The extraction went missing? How?"); + context.cancellationToken.throwIfCancellationRequested(); + const expression = isExpression(target) + ? target + : (target.statements[0] as ExpressionStatement).expression; + return extractConstantInScope(expression, scopes[requestedChangesIndex], usagesPerScope[requestedChangesIndex], targetRange.facts, context); + } + interface PossibleExtraction { - readonly scopeDescription: string; - readonly errors: ReadonlyArray; + readonly functionDescription: string; + readonly functionErrors: ReadonlyArray; + readonly constantDescription: string; + readonly constantErrors: ReadonlyArray; } /** * Given a piece of text to extract ('targetRange'), computes a list of possible extractions. * Each returned ExtractResultForScope corresponds to a possible target scope and is either a set of changes * or an error explaining why we can't extract into that scope. */ - // exported only for tests - export function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): ReadonlyArray | undefined { - const { scopes, readsAndWrites: { errorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); + function getPossibleExtractions(targetRange: TargetRange, context: RefactorContext): ReadonlyArray | undefined { + const { scopes, readsAndWrites: { functionErrorsPerScope, constantErrorsPerScope } } = getPossibleExtractionsWorker(targetRange, context); // Need the inner type annotation to avoid https://github.com/Microsoft/TypeScript/issues/7547 - return scopes.map((scope, i): PossibleExtraction => - ({ scopeDescription: getDescriptionForScope(scope), errors: errorsPerScope[i] })); + const extractions = scopes.map((scope, i): PossibleExtraction => ({ + functionDescription: getDescriptionForFunctionInScope(scope), + functionErrors: functionErrorsPerScope[i], + constantDescription: getDescriptionForConstantInScope(scope), + constantErrors: constantErrorsPerScope[i], + })); + return extractions; } function getPossibleExtractionsWorker(targetRange: TargetRange, context: RefactorContext): { readonly scopes: Scope[], readonly readsAndWrites: ReadsAndWrites } { @@ -533,13 +588,20 @@ namespace ts.refactor.extractMethod { return { scopes, readsAndWrites }; } - function getDescriptionForScope(scope: Scope): string { + function getDescriptionForFunctionInScope(scope: Scope): string { return isFunctionLikeDeclaration(scope) ? `inner function in ${getDescriptionForFunctionLikeDeclaration(scope)}` : isClassLike(scope) ? `method in ${getDescriptionForClassLikeDeclaration(scope)}` : `function in ${getDescriptionForModuleLikeDeclaration(scope)}`; } + function getDescriptionForConstantInScope(scope: Scope): string { + return isFunctionLikeDeclaration(scope) + ? `constant in ${getDescriptionForFunctionLikeDeclaration(scope)}` + : isClassLike(scope) + ? `readonly field in ${getDescriptionForClassLikeDeclaration(scope)}` + : `constant in ${getDescriptionForModuleLikeDeclaration(scope)}`; + } function getDescriptionForFunctionLikeDeclaration(scope: FunctionLikeDeclaration): string { switch (scope.kind) { case SyntaxKind.Constructor: @@ -573,12 +635,12 @@ namespace ts.refactor.extractMethod { : scope.externalModuleIndicator ? "module scope" : "global scope"; } - function getUniqueName(fileText: string): string { - let functionNameText = "newFunction"; - for (let i = 1; fileText.indexOf(functionNameText) !== -1; i++) { - functionNameText = `newFunction_${i}`; + function getUniqueName(baseName: string, fileText: string): string { + let nameText = baseName; + for (let i = 1; fileText.indexOf(nameText) !== -1; i++) { + nameText = `${baseName}_${i}`; } - return functionNameText; + return nameText; } /** @@ -596,7 +658,7 @@ namespace ts.refactor.extractMethod { // Make a unique name for the extracted function const file = scope.getSourceFile(); - const functionNameText = getUniqueName(file.text); + const functionNameText = getUniqueName(isClassLike(scope) ? "newMethod" : "newFunction", file.text); const isJS = isInJavaScriptFile(scope); const functionName = createIdentifier(functionNameText); @@ -688,7 +750,7 @@ namespace ts.refactor.extractMethod { const changeTracker = textChanges.ChangeTracker.fromContext(context); const minInsertionPos = (isReadonlyArray(range.range) ? lastOrUndefined(range.range) : range.range).end; - const nodeToInsertBefore = getNodeToInsertBefore(minInsertionPos, scope); + const nodeToInsertBefore = getNodeToInsertFunctionBefore(minInsertionPos, scope); if (nodeToInsertBefore) { changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newFunction, { suffix: context.newLineCharacter + context.newLineCharacter }); } @@ -774,27 +836,126 @@ namespace ts.refactor.extractMethod { const renameRange = isReadonlyArray(range.range) ? range.range[0] : range.range; const renameFilename = renameRange.getSourceFile().fileName; - const renameLocation = getRenameLocation(edits, renameFilename, functionNameText); + const renameLocation = getRenameLocation(edits, renameFilename, functionNameText, /*isDeclaredBeforeUse*/ false); return { renameFilename, renameLocation, edits }; } - function getRenameLocation(edits: ReadonlyArray, renameFilename: string, functionNameText: string): number { + /** + * Result of 'extractRange' operation for a specific scope. + * Stores either a list of changes that should be applied to extract a range or a list of errors + */ + function extractConstantInScope( + node: Expression, + scope: Scope, + { substitutions }: ScopeUsages, + rangeFacts: RangeFacts, + context: RefactorContext): RefactorEditInfo { + + const checker = context.program.getTypeChecker(); + + // Make a unique name for the extracted variable + const file = scope.getSourceFile(); + const localNameText = getUniqueName(isClassLike(scope) ? "newProperty" : "newLocal", file.text); + const isJS = isInJavaScriptFile(scope); + + const variableType = isJS + ? undefined + : checker.typeToTypeNode(checker.getContextualType(node)); + + const initializer = transformConstantInitializer(node, substitutions); + + const changeTracker = textChanges.ChangeTracker.fromContext(context); + + if (isClassLike(scope)) { + Debug.assert(!isJS); // See CannotExtractToJSClass + const modifiers: Modifier[] = []; + modifiers.push(createToken(SyntaxKind.PrivateKeyword)); + if (rangeFacts & RangeFacts.InStaticRegion) { + modifiers.push(createToken(SyntaxKind.StaticKeyword)); + } + modifiers.push(createToken(SyntaxKind.ReadonlyKeyword)); + + const newVariable = createProperty( + /*decorators*/ undefined, + modifiers, + localNameText, + /*questionToken*/ undefined, + variableType, + initializer); + + const localReference = createPropertyAccess( + rangeFacts & RangeFacts.InStaticRegion + ? createIdentifier(scope.name.getText()) + : createThis(), + createIdentifier(localNameText)); + + // Declare + const minInsertionPos = node.end; + const nodeToInsertBefore = getNodeToInsertConstantBefore(minInsertionPos, scope); + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter }); + + // Consume + changeTracker.replaceNodeWithNodes(context.file, node, [localReference], { nodeSeparator: context.newLineCharacter }); + } + else { + const newVariable = createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList( + [createVariableDeclaration(localNameText, variableType, initializer)], + NodeFlags.Const)); + + // If the parent is an expression statement, replace the statement with the declaration + if (node.parent.kind === SyntaxKind.ExpressionStatement) { + changeTracker.replaceNodeWithNodes(context.file, node.parent, [newVariable], { nodeSeparator: context.newLineCharacter }); + } + else { + // Declare + const minInsertionPos = node.end; + const nodeToInsertBefore = getNodeToInsertConstantBefore(minInsertionPos, scope); + changeTracker.insertNodeBefore(context.file, nodeToInsertBefore, newVariable, { suffix: context.newLineCharacter + context.newLineCharacter }); + + // Consume + const localReference = createIdentifier(localNameText); + changeTracker.replaceNodeWithNodes(context.file, node, [localReference], { nodeSeparator: context.newLineCharacter }); + } + } + + const edits = changeTracker.getChanges(); + + const renameFilename = node.getSourceFile().fileName; + const renameLocation = getRenameLocation(edits, renameFilename, localNameText, /*isDeclaredBeforeUse*/ true); + return { renameFilename, renameLocation, edits }; + } + + /** + * @return The index of the (only) reference to the extracted symbol. We want the cursor + * to be on the reference, rather than the declaration, because it's closer to where the + * user was before extracting it. + */ + function getRenameLocation(edits: ReadonlyArray, renameFilename: string, functionNameText: string, isDeclaredBeforeUse: boolean): number { let delta = 0; + let lastPos = -1; for (const { fileName, textChanges } of edits) { Debug.assert(fileName === renameFilename); for (const change of textChanges) { const { span, newText } = change; - // TODO(acasey): We are assuming that the call expression comes before the function declaration, - // because we want the new cursor to be on the call expression, - // which is closer to where the user was before extracting the function. const index = newText.indexOf(functionNameText); if (index !== -1) { - return span.start + delta + index; + lastPos = span.start + delta + index; + + // If the reference comes first, return immediately. + if (!isDeclaredBeforeUse) { + return lastPos; + } } delta += newText.length - span.length; } } - throw new Error(); // Didn't find the text we inserted? + + // If the declaration comes first, return the position of the last occurrence. + Debug.assert(isDeclaredBeforeUse); + Debug.assert(lastPos >= 0); + return lastPos; } function getFirstDeclaration(type: Type): Declaration | undefined { @@ -899,7 +1060,7 @@ namespace ts.refactor.extractMethod { } else { const oldIgnoreReturns = ignoreReturns; - ignoreReturns = ignoreReturns || isFunctionLike(node) || isClassLike(node); + ignoreReturns = ignoreReturns || isFunctionLikeDeclaration(node) || isClassLike(node); const substitution = substitutions.get(getNodeId(node).toString()); const result = substitution || visitEachChild(node, visitor, nullTransformationContext); ignoreReturns = oldIgnoreReturns; @@ -908,8 +1069,19 @@ namespace ts.refactor.extractMethod { } } + function transformConstantInitializer(initializer: Expression, substitutions: ReadonlyMap): Expression { + return substitutions.size + ? visitor(initializer) as Expression + : initializer; + + function visitor(node: Node): VisitResult { + const substitution = substitutions.get(getNodeId(node).toString()); + return substitution || visitEachChild(node, visitor, nullTransformationContext); + } + } + function getStatementsOrClassElements(scope: Scope): ReadonlyArray | ReadonlyArray { - if (isFunctionLike(scope)) { + if (isFunctionLikeDeclaration(scope)) { const body = scope.body; if (isBlock(body)) { return body.statements; @@ -932,9 +1104,31 @@ namespace ts.refactor.extractMethod { * If `scope` contains a function after `minPos`, then return the first such function. * Otherwise, return `undefined`. */ - function getNodeToInsertBefore(minPos: number, scope: Scope): Node | undefined { + function getNodeToInsertFunctionBefore(minPos: number, scope: Scope): Node | undefined { return find(getStatementsOrClassElements(scope), child => - child.pos >= minPos && isFunctionLike(child) && !isConstructorDeclaration(child)); + child.pos >= minPos && isFunctionLikeDeclaration(child) && !isConstructorDeclaration(child)); + } + + // TODO (acasey): need to dig into nested statements + // TODO (acasey): don't insert before pinned comments, directives, or triple-slash references + function getNodeToInsertConstantBefore(maxPos: number, scope: Scope): Node { + const children = getStatementsOrClassElements(scope); + Debug.assert(children.length > 0); // There must be at least one child, since we extracted from one. + + const isClassLikeScope = isClassLike(scope); + let prevChild: Statement | ClassElement | undefined = undefined; + for (const child of children) { + if (child.pos >= maxPos) { + break; + } + prevChild = child; + if (isClassLikeScope && !isPropertyDeclaration(child)) { + break; + } + } + + Debug.assert(prevChild !== undefined); + return prevChild; } function getPropertyAssignmentsForWrites(writes: ReadonlyArray): ShorthandPropertyAssignment[] { @@ -982,7 +1176,8 @@ namespace ts.refactor.extractMethod { interface ReadsAndWrites { readonly target: Expression | Block; readonly usagesPerScope: ReadonlyArray; - readonly errorsPerScope: ReadonlyArray>; + readonly functionErrorsPerScope: ReadonlyArray>; + readonly constantErrorsPerScope: ReadonlyArray>; } function collectReadsAndWrites( targetRange: TargetRange, @@ -995,14 +1190,33 @@ namespace ts.refactor.extractMethod { const allTypeParameterUsages = createMap(); // Key is type ID const usagesPerScope: ScopeUsages[] = []; const substitutionsPerScope: Map[] = []; - const errorsPerScope: Diagnostic[][] = []; + const functionErrorsPerScope: Diagnostic[][] = []; + const constantErrorsPerScope: Diagnostic[][] = []; const visibleDeclarationsInExtractedRange: Symbol[] = []; + const expressionDiagnostic = + isReadonlyArray(targetRange.range) && !(targetRange.range.length === 1 && isExpressionStatement(targetRange.range[0])) + ? ((start, end) => createFileDiagnostic(sourceFile, start, end - start, Messages.ExpressionExpected))(firstOrUndefined(targetRange.range).getStart(), lastOrUndefined(targetRange.range).end) + : undefined; + // initialize results - for (const _ of scopes) { + for (const scope of scopes) { usagesPerScope.push({ usages: createMap(), typeParameterUsages: createMap(), substitutions: createMap() }); substitutionsPerScope.push(createMap()); - errorsPerScope.push([]); + + functionErrorsPerScope.push( + isFunctionLikeDeclaration(scope) && scope.kind !== SyntaxKind.FunctionDeclaration + ? [createDiagnosticForNode(scope, Messages.CannotExtractToOtherFunctionLike)] + : []); + + const constantErrors = []; + if (expressionDiagnostic) { + constantErrors.push(expressionDiagnostic); + } + if (isClassLike(scope) && isInJavaScriptFile(scope)) { + constantErrors.push(createDiagnosticForNode(scope, Messages.CannotExtractToJSClass)); + } + constantErrorsPerScope.push(constantErrors); } const seenUsages = createMap(); @@ -1054,6 +1268,13 @@ namespace ts.refactor.extractMethod { } for (let i = 0; i < scopes.length; i++) { + if (!isReadonlyArray(targetRange.range)) { + const scopeUsages = usagesPerScope[i]; + if (scopeUsages.usages.size > 0 || scopeUsages.typeParameterUsages.size > 0) { + constantErrorsPerScope[i].push(createDiagnosticForNode(targetRange.range, Messages.CannotAccessVariablesFromNestedScopes)); + } + } + let hasWrite = false; let readonlyClassPropertyWrite: Declaration | undefined = undefined; usagesPerScope[i].usages.forEach(value => { @@ -1068,10 +1289,14 @@ namespace ts.refactor.extractMethod { }); if (hasWrite && !isReadonlyArray(targetRange.range) && isExpression(targetRange.range)) { - errorsPerScope[i].push(createDiagnosticForNode(targetRange.range, Messages.CannotCombineWritesAndReturns)); + const diag = createDiagnosticForNode(targetRange.range, Messages.CannotCombineWritesAndReturns); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); } else if (readonlyClassPropertyWrite && i > 0) { - errorsPerScope[i].push(createDiagnosticForNode(readonlyClassPropertyWrite, Messages.CannotExtractReadonlyPropertyInitializerOutsideConstructor)); + const diag = createDiagnosticForNode(readonlyClassPropertyWrite, Messages.CannotExtractReadonlyPropertyInitializerOutsideConstructor); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); } } @@ -1081,7 +1306,7 @@ namespace ts.refactor.extractMethod { forEachChild(containingLexicalScopeOfExtraction, checkForUsedDeclarations); } - return { target, usagesPerScope, errorsPerScope }; + return { target, usagesPerScope, functionErrorsPerScope, constantErrorsPerScope }; function hasTypeParameters(node: Node) { return isDeclarationWithTypeParameters(node) && @@ -1157,9 +1382,9 @@ namespace ts.refactor.extractMethod { if (symbolId) { for (let i = 0; i < scopes.length; i++) { // push substitution from map to map to simplify rewriting - const substitition = substitutionsPerScope[i].get(symbolId); - if (substitition) { - usagesPerScope[i].substitutions.set(getNodeId(n).toString(), substitition); + const substitution = substitutionsPerScope[i].get(symbolId); + if (substitution) { + usagesPerScope[i].substitutions.set(getNodeId(n).toString(), substitution); } } } @@ -1211,8 +1436,12 @@ namespace ts.refactor.extractMethod { if (targetRange.facts & RangeFacts.IsGenerator && usage === Usage.Write) { // this is write to a reference located outside of the target scope and range is extracted into generator // currently this is unsupported scenario - for (const errors of errorsPerScope) { - errors.push(createDiagnosticForNode(identifier, Messages.CannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators)); + const diag = createDiagnosticForNode(identifier, Messages.CannotExtractRangeThatContainsWritesToReferencesLocatedOutsideOfTheTargetRangeInGenerators); + for (const errors of functionErrorsPerScope) { + errors.push(diag); + } + for (const errors of constantErrorsPerScope) { + errors.push(diag); } } for (let i = 0; i < scopes.length; i++) { @@ -1230,7 +1459,9 @@ namespace ts.refactor.extractMethod { // If the symbol is a type parameter that won't be in scope, we'll pass it as a type argument // so there's no problem. if (!(symbol.flags & SymbolFlags.TypeParameter)) { - errorsPerScope[i].push(createDiagnosticForNode(identifier, Messages.TypeWillNotBeVisibleInTheNewScope)); + const diag = createDiagnosticForNode(identifier, Messages.TypeWillNotBeVisibleInTheNewScope); + functionErrorsPerScope[i].push(diag); + constantErrorsPerScope[i].push(diag); } } else { @@ -1250,8 +1481,12 @@ namespace ts.refactor.extractMethod { // Otherwise check and recurse. const sym = checker.getSymbolAtLocation(node); if (sym && visibleDeclarationsInExtractedRange.some(d => d === sym)) { - for (const scope of errorsPerScope) { - scope.push(createDiagnosticForNode(node, Messages.CannotExtractExportedEntity)); + const diag = createDiagnosticForNode(node, Messages.CannotExtractExportedEntity); + for (const errors of functionErrorsPerScope) { + errors.push(diag); + } + for (const errors of constantErrorsPerScope) { + errors.push(diag); } return true; } diff --git a/src/services/refactors/refactors.ts b/src/services/refactors/refactors.ts index 3a33ccc83c2b8..680b7f8b02fbe 100644 --- a/src/services/refactors/refactors.ts +++ b/src/services/refactors/refactors.ts @@ -1,2 +1,2 @@ /// -/// +/// diff --git a/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts new file mode 100644 index 0000000000000..25609a3a8010a --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_BlockScopes_NoDependencies.ts @@ -0,0 +1,14 @@ +// ==ORIGINAL== +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + let x = 1; + } +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +for (let i = 0; i < 10; i++) { + for (let j = 0; j < 10; j++) { + let x = /*RENAME*/newLocal; + } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_Class.ts b/tests/baselines/reference/extractConstant/extractConstant_Class.ts new file mode 100644 index 0000000000000..eb06cf6c0cf7d --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_Class.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== +class C { + x = 1; +} +// ==SCOPE::Extract to readonly field in class 'C'== +class C { + private readonly newProperty = 1; + + x = this./*RENAME*/newProperty; +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +class C { + x = /*RENAME*/newLocal; +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.ts b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.ts new file mode 100644 index 0000000000000..e024fda34bcb1 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_ClassInsertionPosition.ts @@ -0,0 +1,46 @@ +// ==ORIGINAL== +class C { + a = 1; + b = 2; + M1() { } + M2() { } + M3() { + let x = 1; + } +} +// ==SCOPE::Extract to constant in method 'M3== +class C { + a = 1; + b = 2; + M1() { } + M2() { } + M3() { + const newLocal = 1; + + let x = /*RENAME*/newLocal; + } +} +// ==SCOPE::Extract to readonly field in class 'C'== +class C { + a = 1; + b = 2; + private readonly newProperty = 1; + + M1() { } + M2() { } + M3() { + let x = this./*RENAME*/newProperty; + } +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +class C { + a = 1; + b = 2; + M1() { } + M2() { } + M3() { + let x = /*RENAME*/newLocal; + } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.ts b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.ts new file mode 100644 index 0000000000000..6bf35cd17e125 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatement.ts @@ -0,0 +1,4 @@ +// ==ORIGINAL== +"hello"; +// ==SCOPE::Extract to constant in global scope== +const /*RENAME*/newLocal = "hello"; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.ts b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.ts new file mode 100644 index 0000000000000..6bf35cd17e125 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_ExpressionStatementExpression.ts @@ -0,0 +1,4 @@ +// ==ORIGINAL== +"hello"; +// ==SCOPE::Extract to constant in global scope== +const /*RENAME*/newLocal = "hello"; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_Function.ts b/tests/baselines/reference/extractConstant/extractConstant_Function.ts new file mode 100644 index 0000000000000..67c8255c4b45d --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_Function.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== +function F() { + let x = 1; +} +// ==SCOPE::Extract to constant in function 'F'== +function F() { + const newLocal = 1; + + let x = /*RENAME*/newLocal; +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +function F() { + let x = /*RENAME*/newLocal; +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_Method.ts b/tests/baselines/reference/extractConstant/extractConstant_Method.ts new file mode 100644 index 0000000000000..1ae4c8b1cb5ac --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_Method.ts @@ -0,0 +1,30 @@ +// ==ORIGINAL== +class C { + M() { + let x = 1; + } +} +// ==SCOPE::Extract to constant in method 'M== +class C { + M() { + const newLocal = 1; + + let x = /*RENAME*/newLocal; + } +} +// ==SCOPE::Extract to readonly field in class 'C'== +class C { + private readonly newProperty = 1; + + M() { + let x = this./*RENAME*/newProperty; + } +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +class C { + M() { + let x = /*RENAME*/newLocal; + } +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_Namespace.ts b/tests/baselines/reference/extractConstant/extractConstant_Namespace.ts new file mode 100644 index 0000000000000..8f25847165f8a --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_Namespace.ts @@ -0,0 +1,16 @@ +// ==ORIGINAL== +namespace N { + let x = 1; +} +// ==SCOPE::Extract to constant in namespace 'N'== +namespace N { + const newLocal = 1; + + let x = /*RENAME*/newLocal; +} +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +namespace N { + let x = /*RENAME*/newLocal; +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_Parameters.ts b/tests/baselines/reference/extractConstant/extractConstant_Parameters.ts new file mode 100644 index 0000000000000..e6c9c474fbcb8 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_Parameters.ts @@ -0,0 +1,12 @@ +// ==ORIGINAL== +function F() { + let w = 1; + let x = w + 1; +} +// ==SCOPE::Extract to constant in function 'F'== +function F() { + let w = 1; + const newLocal = w + 1; + + let x = /*RENAME*/newLocal; +} \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_TopLevel.ts b/tests/baselines/reference/extractConstant/extractConstant_TopLevel.ts new file mode 100644 index 0000000000000..fb0447583ff8f --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_TopLevel.ts @@ -0,0 +1,6 @@ +// ==ORIGINAL== +let x = 1; +// ==SCOPE::Extract to constant in global scope== +const newLocal = 1; + +let x = /*RENAME*/newLocal; \ No newline at end of file diff --git a/tests/baselines/reference/extractConstant/extractConstant_TypeParameters.ts b/tests/baselines/reference/extractConstant/extractConstant_TypeParameters.ts new file mode 100644 index 0000000000000..a323e98817651 --- /dev/null +++ b/tests/baselines/reference/extractConstant/extractConstant_TypeParameters.ts @@ -0,0 +1,10 @@ +// ==ORIGINAL== +function F(t: T) { + let x = t + 1; +} +// ==SCOPE::Extract to constant in function 'F'== +function F(t: T) { + const newLocal = t + 1; + + let x = /*RENAME*/newLocal; +} \ No newline at end of file diff --git a/tests/baselines/reference/extractMethod/extractMethod1.ts b/tests/baselines/reference/extractMethod/extractMethod1.ts index 86c28b5f4d288..74303bbf3e189 100644 --- a/tests/baselines/reference/extractMethod/extractMethod1.ts +++ b/tests/baselines/reference/extractMethod/extractMethod1.ts @@ -14,7 +14,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { let x = 1; function foo() { @@ -34,7 +34,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { let x = 1; function foo() { @@ -55,7 +55,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { let x = 1; function foo() { @@ -76,7 +76,7 @@ namespace A { return a; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { let x = 1; function foo() { diff --git a/tests/baselines/reference/extractMethod/extractMethod10.ts b/tests/baselines/reference/extractMethod/extractMethod10.ts index e3eb73b661ed1..65fbc9fb4c936 100644 --- a/tests/baselines/reference/extractMethod/extractMethod10.ts +++ b/tests/baselines/reference/extractMethod/extractMethod10.ts @@ -9,22 +9,22 @@ namespace A { } } } -// ==SCOPE::method in class 'C'== +// ==SCOPE::Extract to method in class 'C'== namespace A { export interface I { x: number }; class C { a() { let z = 1; - return this./*RENAME*/newFunction(); + return this./*RENAME*/newMethod(); } - private newFunction() { + private newMethod() { let a1: I = { x: 1 }; return a1.x + 10; } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { export interface I { x: number }; class C { @@ -39,7 +39,7 @@ namespace A { return a1.x + 10; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { export interface I { x: number }; class C { diff --git a/tests/baselines/reference/extractMethod/extractMethod11.ts b/tests/baselines/reference/extractMethod/extractMethod11.ts index 43fdd75b76f93..55472063055ee 100644 --- a/tests/baselines/reference/extractMethod/extractMethod11.ts +++ b/tests/baselines/reference/extractMethod/extractMethod11.ts @@ -11,18 +11,18 @@ namespace A { } } } -// ==SCOPE::method in class 'C'== +// ==SCOPE::Extract to method in class 'C'== namespace A { let y = 1; class C { a() { let z = 1; var __return: any; - ({ __return, z } = this./*RENAME*/newFunction(z)); + ({ __return, z } = this./*RENAME*/newMethod(z)); return __return; } - private newFunction(z: number) { + private newMethod(z: number) { let a1 = { x: 1 }; y = 10; z = 42; @@ -30,7 +30,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { let y = 1; class C { @@ -49,7 +49,7 @@ namespace A { return { __return: a1.x + 10, z }; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { let y = 1; class C { diff --git a/tests/baselines/reference/extractMethod/extractMethod12.ts b/tests/baselines/reference/extractMethod/extractMethod12.ts index 2f3082cf28008..c8f066657a218 100644 --- a/tests/baselines/reference/extractMethod/extractMethod12.ts +++ b/tests/baselines/reference/extractMethod/extractMethod12.ts @@ -13,7 +13,7 @@ namespace A { } } } -// ==SCOPE::method in class 'C'== +// ==SCOPE::Extract to method in class 'C'== namespace A { let y = 1; class C { @@ -21,11 +21,11 @@ namespace A { a() { let z = 1; var __return: any; - ({ __return, z } = this./*RENAME*/newFunction(z)); + ({ __return, z } = this./*RENAME*/newMethod(z)); return __return; } - private newFunction(z: number) { + private newMethod(z: number) { let a1 = { x: 1 }; y = 10; z = 42; diff --git a/tests/baselines/reference/extractMethod/extractMethod13.ts b/tests/baselines/reference/extractMethod/extractMethod13.ts index 121d7eeecfa7e..c40f29711f482 100644 --- a/tests/baselines/reference/extractMethod/extractMethod13.ts +++ b/tests/baselines/reference/extractMethod/extractMethod13.ts @@ -14,7 +14,7 @@ } } } -// ==SCOPE::inner function in function 'F2'== +// ==SCOPE::Extract to inner function in function 'F2'== (u1a: U1a, u1b: U1b) => { function F1(t1a: T1a, t1b: T1b) { (u2a: U2a, u2b: U2b) => { @@ -34,7 +34,7 @@ } } } -// ==SCOPE::inner function in function 'F1'== +// ==SCOPE::Extract to inner function in function 'F1'== (u1a: U1a, u1b: U1b) => { function F1(t1a: T1a, t1b: T1b) { (u2a: U2a, u2b: U2b) => { @@ -54,7 +54,7 @@ } } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== (u1a: U1a, u1b: U1b) => { function F1(t1a: T1a, t1b: T1b) { (u2a: U2a, u2b: U2b) => { diff --git a/tests/baselines/reference/extractMethod/extractMethod14.ts b/tests/baselines/reference/extractMethod/extractMethod14.ts index d3dcded2c42da..943c8250edd44 100644 --- a/tests/baselines/reference/extractMethod/extractMethod14.ts +++ b/tests/baselines/reference/extractMethod/extractMethod14.ts @@ -1,13 +1,13 @@ // ==ORIGINAL== function F(t1: T) { - function F(t2: T) { + function G(t2: T) { t1.toString(); t2.toString(); } } -// ==SCOPE::inner function in function 'F'== +// ==SCOPE::Extract to inner function in function 'G'== function F(t1: T) { - function F(t2: T) { + function G(t2: T) { /*RENAME*/newFunction(); function newFunction() { @@ -16,9 +16,9 @@ function F(t1: T) { } } } -// ==SCOPE::inner function in function 'F'== +// ==SCOPE::Extract to inner function in function 'F'== function F(t1: T) { - function F(t2: T) { + function G(t2: T) { /*RENAME*/newFunction(t2); } @@ -27,9 +27,9 @@ function F(t1: T) { t2.toString(); } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function F(t1: T) { - function F(t2: T) { + function G(t2: T) { /*RENAME*/newFunction(t1, t2); } } diff --git a/tests/baselines/reference/extractMethod/extractMethod15.ts b/tests/baselines/reference/extractMethod/extractMethod15.ts index 50516445c8762..b6e3e0f04b6a7 100644 --- a/tests/baselines/reference/extractMethod/extractMethod15.ts +++ b/tests/baselines/reference/extractMethod/extractMethod15.ts @@ -1,12 +1,12 @@ // ==ORIGINAL== function F(t1: T) { - function F(t2: U) { + function G(t2: U) { t2.toString(); } } -// ==SCOPE::inner function in function 'F'== +// ==SCOPE::Extract to inner function in function 'G'== function F(t1: T) { - function F(t2: U) { + function G(t2: U) { /*RENAME*/newFunction(); function newFunction() { @@ -14,9 +14,9 @@ function F(t1: T) { } } } -// ==SCOPE::inner function in function 'F'== +// ==SCOPE::Extract to inner function in function 'F'== function F(t1: T) { - function F(t2: U) { + function G(t2: U) { /*RENAME*/newFunction(t2); } @@ -24,9 +24,9 @@ function F(t1: T) { t2.toString(); } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function F(t1: T) { - function F(t2: U) { + function G(t2: U) { /*RENAME*/newFunction(t2); } } diff --git a/tests/baselines/reference/extractMethod/extractMethod16.ts b/tests/baselines/reference/extractMethod/extractMethod16.ts index e58bbf576c6f8..223307ff349e1 100644 --- a/tests/baselines/reference/extractMethod/extractMethod16.ts +++ b/tests/baselines/reference/extractMethod/extractMethod16.ts @@ -2,7 +2,7 @@ function F() { const array: T[] = []; } -// ==SCOPE::inner function in function 'F'== +// ==SCOPE::Extract to inner function in function 'F'== function F() { const array: T[] = /*RENAME*/newFunction(); @@ -10,7 +10,7 @@ function F() { return []; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function F() { const array: T[] = /*RENAME*/newFunction(); } diff --git a/tests/baselines/reference/extractMethod/extractMethod17.ts b/tests/baselines/reference/extractMethod/extractMethod17.ts index f79abcca7924f..e1f20af68671a 100644 --- a/tests/baselines/reference/extractMethod/extractMethod17.ts +++ b/tests/baselines/reference/extractMethod/extractMethod17.ts @@ -4,17 +4,17 @@ class C { t1.toString(); } } -// ==SCOPE::method in class 'C'== +// ==SCOPE::Extract to method in class 'C'== class C { M(t1: T1, t2: T2) { - this./*RENAME*/newFunction(t1); + this./*RENAME*/newMethod(t1); } - private newFunction(t1: T1) { + private newMethod(t1: T1) { t1.toString(); } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== class C { M(t1: T1, t2: T2) { /*RENAME*/newFunction(t1); diff --git a/tests/baselines/reference/extractMethod/extractMethod18.ts b/tests/baselines/reference/extractMethod/extractMethod18.ts index 122eced75d5f2..775a1f3ed15e9 100644 --- a/tests/baselines/reference/extractMethod/extractMethod18.ts +++ b/tests/baselines/reference/extractMethod/extractMethod18.ts @@ -4,17 +4,17 @@ class C { t1.toString(); } } -// ==SCOPE::method in class 'C'== +// ==SCOPE::Extract to method in class 'C'== class C { M(t1: T1, t2: T2) { - this./*RENAME*/newFunction(t1); + this./*RENAME*/newMethod(t1); } - private newFunction(t1: T1) { + private newMethod(t1: T1) { t1.toString(); } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== class C { M(t1: T1, t2: T2) { /*RENAME*/newFunction(t1); diff --git a/tests/baselines/reference/extractMethod/extractMethod19.ts b/tests/baselines/reference/extractMethod/extractMethod19.ts index 61d35f97db5bc..e9107198b186b 100644 --- a/tests/baselines/reference/extractMethod/extractMethod19.ts +++ b/tests/baselines/reference/extractMethod/extractMethod19.ts @@ -2,7 +2,7 @@ function F(v: V) { v.toString(); } -// ==SCOPE::inner function in function 'F'== +// ==SCOPE::Extract to inner function in function 'F'== function F(v: V) { /*RENAME*/newFunction(); @@ -10,7 +10,7 @@ function F(v: V) { v.toString(); } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function F(v: V) { /*RENAME*/newFunction(v); } diff --git a/tests/baselines/reference/extractMethod/extractMethod2.ts b/tests/baselines/reference/extractMethod/extractMethod2.ts index b83cc6f32c6b1..201dd173adc04 100644 --- a/tests/baselines/reference/extractMethod/extractMethod2.ts +++ b/tests/baselines/reference/extractMethod/extractMethod2.ts @@ -12,7 +12,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { let x = 1; function foo() { @@ -30,7 +30,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { let x = 1; function foo() { @@ -48,7 +48,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { let x = 1; function foo() { @@ -66,7 +66,7 @@ namespace A { return foo(); } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { let x = 1; function foo() { diff --git a/tests/baselines/reference/extractMethod/extractMethod20.ts b/tests/baselines/reference/extractMethod/extractMethod20.ts index 6e0148e099197..723d8a99ca69e 100644 --- a/tests/baselines/reference/extractMethod/extractMethod20.ts +++ b/tests/baselines/reference/extractMethod/extractMethod20.ts @@ -5,18 +5,18 @@ const _ = class { return a1.x + 10; } } -// ==SCOPE::method in anonymous class expression== +// ==SCOPE::Extract to method in anonymous class expression== const _ = class { a() { - return this./*RENAME*/newFunction(); + return this./*RENAME*/newMethod(); } - private newFunction() { + private newMethod() { let a1 = { x: 1 }; return a1.x + 10; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== const _ = class { a() { return /*RENAME*/newFunction(); diff --git a/tests/baselines/reference/extractMethod/extractMethod21.ts b/tests/baselines/reference/extractMethod/extractMethod21.ts index 2c4ffd1bdb831..b0469d81384e7 100644 --- a/tests/baselines/reference/extractMethod/extractMethod21.ts +++ b/tests/baselines/reference/extractMethod/extractMethod21.ts @@ -4,7 +4,7 @@ function foo() { x++; return; } -// ==SCOPE::inner function in function 'foo'== +// ==SCOPE::Extract to inner function in function 'foo'== function foo() { let x = 10; return /*RENAME*/newFunction(); @@ -14,7 +14,7 @@ function foo() { return; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function foo() { let x = 10; x = /*RENAME*/newFunction(x); diff --git a/tests/baselines/reference/extractMethod/extractMethod22.ts b/tests/baselines/reference/extractMethod/extractMethod22.ts index 990bfdf0575b9..09fbc7a5b82d0 100644 --- a/tests/baselines/reference/extractMethod/extractMethod22.ts +++ b/tests/baselines/reference/extractMethod/extractMethod22.ts @@ -6,7 +6,7 @@ function test() { return 1; } } -// ==SCOPE::inner function in function 'test'== +// ==SCOPE::Extract to inner function in function 'test'== function test() { try { } @@ -18,7 +18,7 @@ function test() { return 1; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function test() { try { } diff --git a/tests/baselines/reference/extractMethod/extractMethod23.ts b/tests/baselines/reference/extractMethod/extractMethod23.ts index b9bc4264ea625..1a0eaf098fd99 100644 --- a/tests/baselines/reference/extractMethod/extractMethod23.ts +++ b/tests/baselines/reference/extractMethod/extractMethod23.ts @@ -6,7 +6,7 @@ namespace NS { } function M3() { } } -// ==SCOPE::inner function in function 'M2'== +// ==SCOPE::Extract to inner function in function 'M2'== namespace NS { function M1() { } function M2() { @@ -18,7 +18,7 @@ namespace NS { } function M3() { } } -// ==SCOPE::function in namespace 'NS'== +// ==SCOPE::Extract to function in namespace 'NS'== namespace NS { function M1() { } function M2() { @@ -30,7 +30,7 @@ namespace NS { function M3() { } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace NS { function M1() { } function M2() { diff --git a/tests/baselines/reference/extractMethod/extractMethod24.ts b/tests/baselines/reference/extractMethod/extractMethod24.ts index a9dc25d32ea89..7b80180c5d3cb 100644 --- a/tests/baselines/reference/extractMethod/extractMethod24.ts +++ b/tests/baselines/reference/extractMethod/extractMethod24.ts @@ -6,7 +6,7 @@ function Outer() { } function M3() { } } -// ==SCOPE::inner function in function 'M2'== +// ==SCOPE::Extract to inner function in function 'M2'== function Outer() { function M1() { } function M2() { @@ -18,7 +18,7 @@ function Outer() { } function M3() { } } -// ==SCOPE::inner function in function 'Outer'== +// ==SCOPE::Extract to inner function in function 'Outer'== function Outer() { function M1() { } function M2() { @@ -30,7 +30,7 @@ function Outer() { function M3() { } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function Outer() { function M1() { } function M2() { diff --git a/tests/baselines/reference/extractMethod/extractMethod25.ts b/tests/baselines/reference/extractMethod/extractMethod25.ts index dc376781346c3..3233c4043077d 100644 --- a/tests/baselines/reference/extractMethod/extractMethod25.ts +++ b/tests/baselines/reference/extractMethod/extractMethod25.ts @@ -4,7 +4,7 @@ function M2() { return 1; } function M3() { } -// ==SCOPE::inner function in function 'M2'== +// ==SCOPE::Extract to inner function in function 'M2'== function M1() { } function M2() { return /*RENAME*/newFunction(); @@ -14,7 +14,7 @@ function M2() { } } function M3() { } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function M1() { } function M2() { return /*RENAME*/newFunction(); diff --git a/tests/baselines/reference/extractMethod/extractMethod26.ts b/tests/baselines/reference/extractMethod/extractMethod26.ts index b49e8ab950838..6f4d85c78018c 100644 --- a/tests/baselines/reference/extractMethod/extractMethod26.ts +++ b/tests/baselines/reference/extractMethod/extractMethod26.ts @@ -6,19 +6,19 @@ class C { } M3() { } } -// ==SCOPE::method in class 'C'== +// ==SCOPE::Extract to method in class 'C'== class C { M1() { } M2() { - return this./*RENAME*/newFunction(); + return this./*RENAME*/newMethod(); } - private newFunction() { + private newMethod() { return 1; } M3() { } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== class C { M1() { } M2() { diff --git a/tests/baselines/reference/extractMethod/extractMethod27.ts b/tests/baselines/reference/extractMethod/extractMethod27.ts index 0ec214bb5ed18..3426c4ec5c306 100644 --- a/tests/baselines/reference/extractMethod/extractMethod27.ts +++ b/tests/baselines/reference/extractMethod/extractMethod27.ts @@ -7,20 +7,20 @@ class C { constructor() { } M3() { } } -// ==SCOPE::method in class 'C'== +// ==SCOPE::Extract to method in class 'C'== class C { M1() { } M2() { - return this./*RENAME*/newFunction(); + return this./*RENAME*/newMethod(); } constructor() { } - private newFunction() { + private newMethod() { return 1; } M3() { } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== class C { M1() { } M2() { diff --git a/tests/baselines/reference/extractMethod/extractMethod28.ts b/tests/baselines/reference/extractMethod/extractMethod28.ts index ab6ce220bd76a..b6e94cb47c590 100644 --- a/tests/baselines/reference/extractMethod/extractMethod28.ts +++ b/tests/baselines/reference/extractMethod/extractMethod28.ts @@ -7,20 +7,20 @@ class C { M3() { } constructor() { } } -// ==SCOPE::method in class 'C'== +// ==SCOPE::Extract to method in class 'C'== class C { M1() { } M2() { - return this./*RENAME*/newFunction(); + return this./*RENAME*/newMethod(); } - private newFunction() { + private newMethod() { return 1; } M3() { } constructor() { } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== class C { M1() { } M2() { diff --git a/tests/baselines/reference/extractMethod/extractMethod29.ts b/tests/baselines/reference/extractMethod/extractMethod29.ts index aa7004d254e87..9baea5870f520 100644 --- a/tests/baselines/reference/extractMethod/extractMethod29.ts +++ b/tests/baselines/reference/extractMethod/extractMethod29.ts @@ -16,7 +16,7 @@ function parseUnaryExpression(operator: string): UnaryExpression { function parsePrimaryExpression(): any { throw "Not implemented"; } -// ==SCOPE::inner function in function 'parseUnaryExpression'== +// ==SCOPE::Extract to inner function in function 'parseUnaryExpression'== interface UnaryExpression { kind: "Unary"; operator: string; @@ -38,7 +38,7 @@ function parseUnaryExpression(operator: string): UnaryExpression { function parsePrimaryExpression(): any { throw "Not implemented"; } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== interface UnaryExpression { kind: "Unary"; operator: string; diff --git a/tests/baselines/reference/extractMethod/extractMethod3.ts b/tests/baselines/reference/extractMethod/extractMethod3.ts index 0af79791fe7cb..b5ae759215794 100644 --- a/tests/baselines/reference/extractMethod/extractMethod3.ts +++ b/tests/baselines/reference/extractMethod/extractMethod3.ts @@ -11,7 +11,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { function foo() { } @@ -28,7 +28,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { function foo() { } @@ -45,7 +45,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { function foo() { } @@ -62,7 +62,7 @@ namespace A { return foo(); } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { function foo() { } diff --git a/tests/baselines/reference/extractMethod/extractMethod30.ts b/tests/baselines/reference/extractMethod/extractMethod30.ts index 67dc1208abcfe..35b86b668ff1b 100644 --- a/tests/baselines/reference/extractMethod/extractMethod30.ts +++ b/tests/baselines/reference/extractMethod/extractMethod30.ts @@ -2,7 +2,7 @@ function F() { let t: T; } -// ==SCOPE::inner function in function 'F'== +// ==SCOPE::Extract to inner function in function 'F'== function F() { /*RENAME*/newFunction(); @@ -10,7 +10,7 @@ function F() { let t: T; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function F() { /*RENAME*/newFunction(); } diff --git a/tests/baselines/reference/extractMethod/extractMethod31.ts b/tests/baselines/reference/extractMethod/extractMethod31.ts index 754814dcc266d..7712009182841 100644 --- a/tests/baselines/reference/extractMethod/extractMethod31.ts +++ b/tests/baselines/reference/extractMethod/extractMethod31.ts @@ -10,7 +10,7 @@ namespace N { } } } -// ==SCOPE::function in namespace 'N'== +// ==SCOPE::Extract to function in namespace 'N'== namespace N { export const value = 1; @@ -27,7 +27,7 @@ namespace N { return f; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace N { export const value = 1; diff --git a/tests/baselines/reference/extractMethod/extractMethod32.ts b/tests/baselines/reference/extractMethod/extractMethod32.ts index b9b870a08fa7a..529b81e8f9b2b 100644 --- a/tests/baselines/reference/extractMethod/extractMethod32.ts +++ b/tests/baselines/reference/extractMethod/extractMethod32.ts @@ -11,7 +11,7 @@ namespace N { } } } -// ==SCOPE::function in namespace 'N'== +// ==SCOPE::Extract to function in namespace 'N'== namespace N { export const value = 1; @@ -28,7 +28,7 @@ namespace N { }; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace N { export const value = 1; diff --git a/tests/baselines/reference/extractMethod/extractMethod33.ts b/tests/baselines/reference/extractMethod/extractMethod33.ts index 79a6626a535c2..fd716646e6610 100644 --- a/tests/baselines/reference/extractMethod/extractMethod33.ts +++ b/tests/baselines/reference/extractMethod/extractMethod33.ts @@ -2,7 +2,7 @@ function F() { function G() { } } -// ==SCOPE::inner function in function 'F'== +// ==SCOPE::Extract to inner function in function 'F'== function F() { /*RENAME*/newFunction(); @@ -10,7 +10,7 @@ function F() { function G() { } } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== function F() { /*RENAME*/newFunction(); } diff --git a/tests/baselines/reference/extractMethod/extractMethod4.ts b/tests/baselines/reference/extractMethod/extractMethod4.ts index e0a636135d45d..6ef3e843ee128 100644 --- a/tests/baselines/reference/extractMethod/extractMethod4.ts +++ b/tests/baselines/reference/extractMethod/extractMethod4.ts @@ -13,7 +13,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { function foo() { } @@ -32,7 +32,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { function foo() { } @@ -51,7 +51,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { function foo() { } @@ -70,7 +70,7 @@ namespace A { return foo(); } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { function foo() { } diff --git a/tests/baselines/reference/extractMethod/extractMethod5.ts b/tests/baselines/reference/extractMethod/extractMethod5.ts index fc15f6762e568..e2f72721b950d 100644 --- a/tests/baselines/reference/extractMethod/extractMethod5.ts +++ b/tests/baselines/reference/extractMethod/extractMethod5.ts @@ -14,7 +14,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { let x = 1; export function foo() { @@ -34,7 +34,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { let x = 1; export function foo() { @@ -55,7 +55,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { let x = 1; export function foo() { @@ -76,7 +76,7 @@ namespace A { return a; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { let x = 1; export function foo() { diff --git a/tests/baselines/reference/extractMethod/extractMethod6.ts b/tests/baselines/reference/extractMethod/extractMethod6.ts index 37112b80e9e13..6b4852dc722a6 100644 --- a/tests/baselines/reference/extractMethod/extractMethod6.ts +++ b/tests/baselines/reference/extractMethod/extractMethod6.ts @@ -14,7 +14,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { let x = 1; export function foo() { @@ -34,7 +34,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { let x = 1; export function foo() { @@ -56,7 +56,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { let x = 1; export function foo() { @@ -78,7 +78,7 @@ namespace A { return { __return: foo(), a }; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { let x = 1; export function foo() { diff --git a/tests/baselines/reference/extractMethod/extractMethod7.ts b/tests/baselines/reference/extractMethod/extractMethod7.ts index 8859b7b4fdd1d..9712cedda8d5d 100644 --- a/tests/baselines/reference/extractMethod/extractMethod7.ts +++ b/tests/baselines/reference/extractMethod/extractMethod7.ts @@ -16,7 +16,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { let x = 1; export namespace C { @@ -38,7 +38,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { let x = 1; export namespace C { @@ -62,7 +62,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { let x = 1; export namespace C { @@ -86,7 +86,7 @@ namespace A { return { __return: C.foo(), a }; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { let x = 1; export namespace C { diff --git a/tests/baselines/reference/extractMethod/extractMethod8.ts b/tests/baselines/reference/extractMethod/extractMethod8.ts index cb06470d3855f..adb8adbe56ab0 100644 --- a/tests/baselines/reference/extractMethod/extractMethod8.ts +++ b/tests/baselines/reference/extractMethod/extractMethod8.ts @@ -8,7 +8,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { let x = 1; namespace B { @@ -22,7 +22,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { let x = 1; namespace B { @@ -36,7 +36,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { let x = 1; namespace B { @@ -50,7 +50,7 @@ namespace A { return 1 + a1 + x; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { let x = 1; namespace B { diff --git a/tests/baselines/reference/extractMethod/extractMethod9.ts b/tests/baselines/reference/extractMethod/extractMethod9.ts index 022dab8236397..9c1f81edf452d 100644 --- a/tests/baselines/reference/extractMethod/extractMethod9.ts +++ b/tests/baselines/reference/extractMethod/extractMethod9.ts @@ -8,7 +8,7 @@ namespace A { } } } -// ==SCOPE::inner function in function 'a'== +// ==SCOPE::Extract to inner function in function 'a'== namespace A { export interface I { x: number }; namespace B { @@ -22,7 +22,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'B'== +// ==SCOPE::Extract to function in namespace 'B'== namespace A { export interface I { x: number }; namespace B { @@ -36,7 +36,7 @@ namespace A { } } } -// ==SCOPE::function in namespace 'A'== +// ==SCOPE::Extract to function in namespace 'A'== namespace A { export interface I { x: number }; namespace B { @@ -50,7 +50,7 @@ namespace A { return a1.x + 10; } } -// ==SCOPE::function in global scope== +// ==SCOPE::Extract to function in global scope== namespace A { export interface I { x: number }; namespace B { diff --git a/tests/cases/fourslash/extract-method-empty-namespace.ts b/tests/cases/fourslash/extract-method-empty-namespace.ts index 3a29992350df5..bef4cdd12fe4e 100644 --- a/tests/cases/fourslash/extract-method-empty-namespace.ts +++ b/tests/cases/fourslash/extract-method-empty-namespace.ts @@ -6,8 +6,8 @@ goTo.select('start', 'end') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_1", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to function in global scope", newContent: `function f() { /*RENAME*/newFunction(); diff --git a/tests/cases/fourslash/extract-method-formatting.ts b/tests/cases/fourslash/extract-method-formatting.ts index e4193fd8db390..d4c2836e815e6 100644 --- a/tests/cases/fourslash/extract-method-formatting.ts +++ b/tests/cases/fourslash/extract-method-formatting.ts @@ -7,8 +7,8 @@ goTo.select('start', 'end') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_1", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to function in global scope", newContent: `function f(x: number): number { return /*RENAME*/newFunction(x); diff --git a/tests/cases/fourslash/extract-method-not-for-empty.ts b/tests/cases/fourslash/extract-method-not-for-empty.ts index 756716441cd6c..253614d316065 100644 --- a/tests/cases/fourslash/extract-method-not-for-empty.ts +++ b/tests/cases/fourslash/extract-method-not-for-empty.ts @@ -3,4 +3,4 @@ ////"/**/foo"; goTo.marker(""); -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); diff --git a/tests/cases/fourslash/extract-method-not-for-import.ts b/tests/cases/fourslash/extract-method-not-for-import.ts index a72d793611d62..2852d856264d8 100644 --- a/tests/cases/fourslash/extract-method-not-for-import.ts +++ b/tests/cases/fourslash/extract-method-not-for-import.ts @@ -7,4 +7,4 @@ ////export default function f() {} goTo.marker(""); -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); diff --git a/tests/cases/fourslash/extract-method-uniqueName.ts b/tests/cases/fourslash/extract-method-uniqueName.ts index 8f024ad6a4799..4271d9e84d2f2 100644 --- a/tests/cases/fourslash/extract-method-uniqueName.ts +++ b/tests/cases/fourslash/extract-method-uniqueName.ts @@ -7,8 +7,8 @@ // it's omitted right now. goTo.select('start', 'end') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_0", actionDescription: "Extract to function in global scope", newContent: `/*RENAME*/newFunction_1(); diff --git a/tests/cases/fourslash/extract-method1.ts b/tests/cases/fourslash/extract-method1.ts index a8d421923b14e..f4693877ed8b9 100644 --- a/tests/cases/fourslash/extract-method1.ts +++ b/tests/cases/fourslash/extract-method1.ts @@ -14,18 +14,18 @@ goTo.select('start', 'end') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to method in class 'Foo'", newContent: `class Foo { someMethod(m: number) { - this./*RENAME*/newFunction(m); + this./*RENAME*/newMethod(m); var q = 10; return q; } - private newFunction(m: number) { + private newMethod(m: number) { var x = m; x = x * 3; var y = 30; diff --git a/tests/cases/fourslash/extract-method10.ts b/tests/cases/fourslash/extract-method10.ts index 215196d6c51dc..ed92e6b06ac8d 100644 --- a/tests/cases/fourslash/extract-method10.ts +++ b/tests/cases/fourslash/extract-method10.ts @@ -5,8 +5,8 @@ goTo.select('1', '2'); edit.applyRefactor({ - refactorName: "Extract Method", - actionName: 'scope_0', + refactorName: "Extract Symbol", + actionName: 'function_scope_0', actionDescription: "Extract to function in module scope", newContent: `export {}; // Make this a module diff --git a/tests/cases/fourslash/extract-method11.ts b/tests/cases/fourslash/extract-method11.ts index 705f2373104de..a7d90f04f42eb 100644 --- a/tests/cases/fourslash/extract-method11.ts +++ b/tests/cases/fourslash/extract-method11.ts @@ -20,9 +20,9 @@ for (const m of ['1', '2', '3', '4', '5']) { goTo.select(m + 'a', m + 'b'); - verify.not.refactorAvailable('Extract Method'); + verify.not.refactorAvailable('Extract Symbol'); } // Verify we can still extract the entire class goTo.select('oka', 'okb'); -verify.refactorAvailable('Extract Method'); +verify.refactorAvailable('Extract Symbol', 'function_scope_0'); diff --git a/tests/cases/fourslash/extract-method13.ts b/tests/cases/fourslash/extract-method13.ts index 409b5f890ad9b..fc0b22f3d48e8 100644 --- a/tests/cases/fourslash/extract-method13.ts +++ b/tests/cases/fourslash/extract-method13.ts @@ -11,16 +11,16 @@ goTo.select('a', 'b'); edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_0", actionDescription: "Extract to method in class 'C'", newContent: `class C { static j = 1 + 1; - constructor(q: string = C./*RENAME*/newFunction()) { + constructor(q: string = C./*RENAME*/newMethod()) { } - private static newFunction(): string { + private static newMethod(): string { return "hello"; } }` @@ -28,30 +28,30 @@ edit.applyRefactor({ verify.currentFileContentIs(`class C { static j = 1 + 1; - constructor(q: string = C.newFunction()) { + constructor(q: string = C.newMethod()) { } - private static newFunction(): string { + private static newMethod(): string { return "hello"; } }`); goTo.select('c', 'd'); edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_0", actionDescription: "Extract to method in class 'C'", newContent: `class C { - static j = C./*RENAME*/newFunction_1(); - constructor(q: string = C.newFunction()) { + static j = C./*RENAME*/newMethod_1(); + constructor(q: string = C.newMethod()) { } - private static newFunction_1() { + private static newMethod_1() { return 1 + 1; } - private static newFunction(): string { + private static newMethod(): string { return "hello"; } }` diff --git a/tests/cases/fourslash/extract-method14.ts b/tests/cases/fourslash/extract-method14.ts index e2b58a36450c8..ea051aabbe75b 100644 --- a/tests/cases/fourslash/extract-method14.ts +++ b/tests/cases/fourslash/extract-method14.ts @@ -12,8 +12,8 @@ goTo.select('a', 'b'); edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_1", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to function in global scope", newContent: `function foo() { diff --git a/tests/cases/fourslash/extract-method15.ts b/tests/cases/fourslash/extract-method15.ts index c3db3186cdf9d..e46ff39ad6a49 100644 --- a/tests/cases/fourslash/extract-method15.ts +++ b/tests/cases/fourslash/extract-method15.ts @@ -10,8 +10,8 @@ goTo.select('a', 'b'); edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_1", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to function in global scope", newContent: `function foo() { diff --git a/tests/cases/fourslash/extract-method17.ts b/tests/cases/fourslash/extract-method17.ts index ce54896604dd9..467ff91ebb282 100644 --- a/tests/cases/fourslash/extract-method17.ts +++ b/tests/cases/fourslash/extract-method17.ts @@ -6,5 +6,5 @@ //// } goTo.select('start', 'end') -verify.refactorAvailable('Extract Method', 'scope_0'); -verify.not.refactorAvailable('Extract Method', 'scope_1'); +verify.refactorAvailable('Extract Symbol', 'function_scope_0'); +verify.not.refactorAvailable('Extract Symbol', 'function_scope_1'); diff --git a/tests/cases/fourslash/extract-method18.ts b/tests/cases/fourslash/extract-method18.ts index 8ff1cc3028df8..d53e79f493000 100644 --- a/tests/cases/fourslash/extract-method18.ts +++ b/tests/cases/fourslash/extract-method18.ts @@ -10,8 +10,8 @@ goTo.select('a', 'b') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_1", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to function in global scope", newContent: `function fn() { diff --git a/tests/cases/fourslash/extract-method19.ts b/tests/cases/fourslash/extract-method19.ts index 56d6b02560fdd..87534a17d4a4a 100644 --- a/tests/cases/fourslash/extract-method19.ts +++ b/tests/cases/fourslash/extract-method19.ts @@ -10,8 +10,8 @@ goTo.select('a', 'b') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_0", actionDescription: "Extract to inner function in function 'fn'", newContent: `function fn() { diff --git a/tests/cases/fourslash/extract-method2.ts b/tests/cases/fourslash/extract-method2.ts index 6fbe7394c2f84..7f197d4775b0d 100644 --- a/tests/cases/fourslash/extract-method2.ts +++ b/tests/cases/fourslash/extract-method2.ts @@ -11,8 +11,8 @@ //// } goTo.select('start', 'end') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_2", + refactorName: "Extract Symbol", + actionName: "function_scope_3", actionDescription: "Extract to function in global scope", newContent: `namespace NS { diff --git a/tests/cases/fourslash/extract-method20.ts b/tests/cases/fourslash/extract-method20.ts index 04990001b5f0e..75927f0fd5c9e 100644 --- a/tests/cases/fourslash/extract-method20.ts +++ b/tests/cases/fourslash/extract-method20.ts @@ -10,5 +10,5 @@ //// } goTo.select('a', 'b') -verify.refactorAvailable('Extract Method', 'scope_0'); -verify.not.refactorAvailable('Extract Method', 'scope_1'); +verify.refactorAvailable('Extract Symbol', 'function_scope_0'); +verify.not.refactorAvailable('Extract Symbol', 'function_scope_1'); diff --git a/tests/cases/fourslash/extract-method21.ts b/tests/cases/fourslash/extract-method21.ts index 8e3baf619496f..cbb1f55e071f0 100644 --- a/tests/cases/fourslash/extract-method21.ts +++ b/tests/cases/fourslash/extract-method21.ts @@ -10,19 +10,19 @@ goTo.select('start', 'end') -verify.refactorAvailable('Extract Method'); +verify.refactorAvailable('Extract Symbol', 'function_scope_1'); edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to method in class 'Foo'", newContent: `class Foo { static method() { - return Foo./*RENAME*/newFunction(); + return Foo./*RENAME*/newMethod(); } - private static newFunction() { + private static newMethod() { return 1; } }` diff --git a/tests/cases/fourslash/extract-method22.ts b/tests/cases/fourslash/extract-method22.ts index 7486520e700f7..85f88f3952cf8 100644 --- a/tests/cases/fourslash/extract-method22.ts +++ b/tests/cases/fourslash/extract-method22.ts @@ -7,4 +7,4 @@ //// } goTo.select('start', 'end') -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); diff --git a/tests/cases/fourslash/extract-method23.ts b/tests/cases/fourslash/extract-method23.ts index 7da8f175f1353..ad5248a37e295 100644 --- a/tests/cases/fourslash/extract-method23.ts +++ b/tests/cases/fourslash/extract-method23.ts @@ -5,4 +5,4 @@ //// } goTo.select('start', 'end') -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); diff --git a/tests/cases/fourslash/extract-method24.ts b/tests/cases/fourslash/extract-method24.ts index 5706c5c7a5453..8c750edd9f906 100644 --- a/tests/cases/fourslash/extract-method24.ts +++ b/tests/cases/fourslash/extract-method24.ts @@ -8,8 +8,8 @@ goTo.select('a', 'b') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_1", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to function in global scope", newContent: `function M() { diff --git a/tests/cases/fourslash/extract-method25.ts b/tests/cases/fourslash/extract-method25.ts index 4fb2193adf3bf..a06262ca47510 100644 --- a/tests/cases/fourslash/extract-method25.ts +++ b/tests/cases/fourslash/extract-method25.ts @@ -9,8 +9,8 @@ goTo.select('a', 'b') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_0", actionDescription: "Extract to inner function in function 'fn'", newContent: `function fn() { diff --git a/tests/cases/fourslash/extract-method26.ts b/tests/cases/fourslash/extract-method26.ts index 68982eda86cd8..2915118fd8e18 100644 --- a/tests/cases/fourslash/extract-method26.ts +++ b/tests/cases/fourslash/extract-method26.ts @@ -13,17 +13,17 @@ goTo.select('a', 'b') edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_1", actionDescription: "Extract to method in class 'C'", newContent: `class C { M() { - const q = this./*RENAME*/newFunction(); + const q = this./*RENAME*/newMethod(); q.toString(); } - newFunction() { + newMethod() { return 1 + 2; } }` diff --git a/tests/cases/fourslash/extract-method3.ts b/tests/cases/fourslash/extract-method3.ts index 27a520d0555a4..43402a7ac209f 100644 --- a/tests/cases/fourslash/extract-method3.ts +++ b/tests/cases/fourslash/extract-method3.ts @@ -10,9 +10,9 @@ //// } //// } -// Don't offer to 'extract method' a single identifier +// Don't offer to 'extract symbol' a single identifier goTo.marker('a'); -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); goTo.select('a', 'b'); -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); diff --git a/tests/cases/fourslash/extract-method4.ts b/tests/cases/fourslash/extract-method4.ts index ec8f39f354189..93eabbc0ae5d6 100644 --- a/tests/cases/fourslash/extract-method4.ts +++ b/tests/cases/fourslash/extract-method4.ts @@ -11,4 +11,4 @@ // Should rewrite to a = newFunc(); function() { return b = c = d; } goTo.select('1', '2'); -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); diff --git a/tests/cases/fourslash/extract-method5.ts b/tests/cases/fourslash/extract-method5.ts index b27d9a8209bda..ce164d43c6e3f 100644 --- a/tests/cases/fourslash/extract-method5.ts +++ b/tests/cases/fourslash/extract-method5.ts @@ -10,8 +10,8 @@ goTo.select('start', 'end'); edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_0", actionDescription: "Extract to inner function in function 'f'", newContent: `function f() { diff --git a/tests/cases/fourslash/extract-method6.ts b/tests/cases/fourslash/extract-method6.ts index f188ab319e9b8..be21505ac1341 100644 --- a/tests/cases/fourslash/extract-method6.ts +++ b/tests/cases/fourslash/extract-method6.ts @@ -10,7 +10,7 @@ //// }/*f1b*/ goTo.select('f1a', 'f1b'); -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); goTo.select('g1a', 'g1b'); -verify.not.refactorAvailable('Extract Method'); +verify.not.refactorAvailable('Extract Symbol'); diff --git a/tests/cases/fourslash/extract-method7.ts b/tests/cases/fourslash/extract-method7.ts index 0ce39c5a3095c..2998b6bbee31c 100644 --- a/tests/cases/fourslash/extract-method7.ts +++ b/tests/cases/fourslash/extract-method7.ts @@ -1,15 +1,15 @@ /// // You cannot extract a function initializer into the function's body. -// The innermost scope (scope_0) is the sibling of the function, not the function itself. +// The innermost scope (function_scope_0) is the sibling of the function, not the function itself. //// function fn(x = /*a*/3/*b*/) { //// } goTo.select('a', 'b'); edit.applyRefactor({ - refactorName: "Extract Method", - actionName: "scope_0", + refactorName: "Extract Symbol", + actionName: "function_scope_0", actionDescription: "Extract to function in global scope", newContent: `function fn(x = /*RENAME*/newFunction()) { diff --git a/tests/cases/fourslash/extract-method8.ts b/tests/cases/fourslash/extract-method8.ts index 28068dd7c783b..12d1dab282861 100644 --- a/tests/cases/fourslash/extract-method8.ts +++ b/tests/cases/fourslash/extract-method8.ts @@ -4,14 +4,14 @@ //// namespace ns { //// /*a*/export function fn() { -//// +//// //// } //// fn(); //// /*b*/ //// } goTo.select('a', 'b'); -verify.not.refactorAvailable("Extract Method"); +verify.not.refactorAvailable("Extract Symbol"); edit.deleteAtCaret('export'.length); goTo.select('a', 'b'); -verify.refactorAvailable("Extract Method"); +verify.refactorAvailable("Extract Symbol", 'function_scope_0'); diff --git a/tests/cases/fourslash/extract-method9.ts b/tests/cases/fourslash/extract-method9.ts index f70ef20a87b56..41af6cbf5f45c 100644 --- a/tests/cases/fourslash/extract-method9.ts +++ b/tests/cases/fourslash/extract-method9.ts @@ -1,11 +1,11 @@ /// //// function f() { -//// /*a*/function q() { } +//// /*a*/function q() { } //// q();/*b*/ //// q(); //// } goTo.select('a', 'b'); -verify.not.refactorAvailable("Extract Method"); +verify.not.refactorAvailable("Extract Symbol");