From 0b66cfc4a615c00b483112e1a5ace7cc4c4e8633 Mon Sep 17 00:00:00 2001 From: kingwl Date: Sun, 24 Mar 2019 18:34:14 +0800 Subject: [PATCH 01/19] add basically implement --- src/compiler/diagnosticMessages.json | 4 ++ src/services/refactors/extractType.ts | 48 +++++++++++++++++++ src/services/tsconfig.json | 1 + tests/cases/fourslash/refactorExtractType1.ts | 16 +++++++ 4 files changed, 69 insertions(+) create mode 100644 src/services/refactors/extractType.ts create mode 100644 tests/cases/fourslash/refactorExtractType1.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 9ae3e1ca77eae..69e1f2afafffc 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4930,5 +4930,9 @@ "Convert parameters to destructured object": { "category": "Message", "code": 95075 + }, + "Extract type": { + "category": "Message", + "code": 95076 } } diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts new file mode 100644 index 0000000000000..4d60398c8499d --- /dev/null +++ b/src/services/refactors/extractType.ts @@ -0,0 +1,48 @@ +/* @internal */ +namespace ts.refactor { + const refactorName = "Extract type"; + registerRefactor(refactorName, { + getAvailableActions(context): ReadonlyArray { + if (!getRangeToExtract(context)) return emptyArray; + const description = getLocaleSpecificMessage(Diagnostics.Extract_type); + return [{ name: refactorName, description, actions: [{ name: refactorName, description }] }]; + }, + getEditsForAction(context, actionName): RefactorEditInfo { + Debug.assert(actionName === refactorName); + const { file } = context; + const { selection, firstStatement } = Debug.assertDefined(getRangeToExtract(context)); + const edits = textChanges.ChangeTracker.with(context, changes => { + const name = getUniqueName("NewType", file); + changes.insertNodeBefore(file, firstStatement, generateTypeAlias(name, selection), /* blankLineBetween */ true); + changes.replaceNode(file, selection, createTypeReferenceNode(name, /* typeArguments */ undefined)); + }); + + return { edits, renameFilename: undefined, renameLocation: undefined }; + } + }); + + interface Info { selection: TypeNode; firstStatement: Statement; } + function getRangeToExtract(context: RefactorContext): Info | undefined { + const { file, startPosition } = context; + const current = getTokenAtPosition(file, startPosition); + const range = createTextRangeFromSpan(getRefactorContextSpan(context)); + const selection = findAncestor(current, (node => node.parent && rangeContainsSkipTrivia(range, node, file) && !rangeContainsSkipTrivia(range, node.parent, file))); + if (!selection || isInJSFile(selection) || !isTypeNode(selection)) return undefined; + const firstStatement = Debug.assertDefined((findAncestor(selection, n => isStatement(n) && !isBlock(n)))) as Statement; + return { selection, firstStatement }; + } + + function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { + return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); + } + + function generateTypeAlias(name: string, typeNode: TypeNode) { + return createTypeAliasDeclaration( + /* decorators */ undefined, + /* monifiers */ undefined, + name, + /* typeArguments */ undefined, + typeNode + ); + } +} \ No newline at end of file diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index e3f2358be1059..60e6f8da08bb1 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -81,6 +81,7 @@ "refactors/convertExport.ts", "refactors/convertImport.ts", "refactors/extractSymbol.ts", + "refactors/extractType.ts", "refactors/generateGetAccessorAndSetAccessor.ts", "refactors/moveToNewFile.ts", "refactors/addOrRemoveBracesToArrowFunction.ts", diff --git a/tests/cases/fourslash/refactorExtractType1.ts b/tests/cases/fourslash/refactorExtractType1.ts new file mode 100644 index 0000000000000..a6e1db4eb1ca4 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType1.ts @@ -0,0 +1,16 @@ +/// + +//// var x: /*a*/{ a?: number, b?: string }/*b*/ = { }; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type NewType = { + a?: number; + b?: string; +}; + +var x: NewType = { };`, +}); From 8a6f1edc651f7e4e9d04f69f4a4b9ff5f6b2b8df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 25 Mar 2019 14:00:51 +0800 Subject: [PATCH 02/19] add rename location and add testcase --- src/services/refactors/extractType.ts | 10 ++++++--- tests/cases/fourslash/refactorExtractType1.ts | 2 +- .../cases/fourslash/refactorExtractType10.ts | 17 +++++++++++++++ .../cases/fourslash/refactorExtractType11.ts | 17 +++++++++++++++ .../cases/fourslash/refactorExtractType12.ts | 21 +++++++++++++++++++ .../cases/fourslash/refactorExtractType13.ts | 21 +++++++++++++++++++ .../cases/fourslash/refactorExtractType14.ts | 13 ++++++++++++ .../cases/fourslash/refactorExtractType15.ts | 13 ++++++++++++ .../cases/fourslash/refactorExtractType16.ts | 13 ++++++++++++ tests/cases/fourslash/refactorExtractType2.ts | 13 ++++++++++++ tests/cases/fourslash/refactorExtractType3.ts | 13 ++++++++++++ tests/cases/fourslash/refactorExtractType4.ts | 13 ++++++++++++ tests/cases/fourslash/refactorExtractType5.ts | 13 ++++++++++++ tests/cases/fourslash/refactorExtractType6.ts | 13 ++++++++++++ tests/cases/fourslash/refactorExtractType7.ts | 17 +++++++++++++++ tests/cases/fourslash/refactorExtractType8.ts | 17 +++++++++++++++ tests/cases/fourslash/refactorExtractType9.ts | 17 +++++++++++++++ 17 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType10.ts create mode 100644 tests/cases/fourslash/refactorExtractType11.ts create mode 100644 tests/cases/fourslash/refactorExtractType12.ts create mode 100644 tests/cases/fourslash/refactorExtractType13.ts create mode 100644 tests/cases/fourslash/refactorExtractType14.ts create mode 100644 tests/cases/fourslash/refactorExtractType15.ts create mode 100644 tests/cases/fourslash/refactorExtractType16.ts create mode 100644 tests/cases/fourslash/refactorExtractType2.ts create mode 100644 tests/cases/fourslash/refactorExtractType3.ts create mode 100644 tests/cases/fourslash/refactorExtractType4.ts create mode 100644 tests/cases/fourslash/refactorExtractType5.ts create mode 100644 tests/cases/fourslash/refactorExtractType6.ts create mode 100644 tests/cases/fourslash/refactorExtractType7.ts create mode 100644 tests/cases/fourslash/refactorExtractType8.ts create mode 100644 tests/cases/fourslash/refactorExtractType9.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 4d60398c8499d..bc6bd1c6035a0 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -11,13 +11,17 @@ namespace ts.refactor { Debug.assert(actionName === refactorName); const { file } = context; const { selection, firstStatement } = Debug.assertDefined(getRangeToExtract(context)); + + const name = getUniqueName("NewType", file); + const newTypeNode = generateTypeAlias(name, selection); const edits = textChanges.ChangeTracker.with(context, changes => { - const name = getUniqueName("NewType", file); - changes.insertNodeBefore(file, firstStatement, generateTypeAlias(name, selection), /* blankLineBetween */ true); + changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true); changes.replaceNode(file, selection, createTypeReferenceNode(name, /* typeArguments */ undefined)); }); - return { edits, renameFilename: undefined, renameLocation: undefined }; + const renameFilename = file.fileName; + const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false); + return { edits, renameFilename, renameLocation }; } }); diff --git a/tests/cases/fourslash/refactorExtractType1.ts b/tests/cases/fourslash/refactorExtractType1.ts index a6e1db4eb1ca4..e6809459939f6 100644 --- a/tests/cases/fourslash/refactorExtractType1.ts +++ b/tests/cases/fourslash/refactorExtractType1.ts @@ -7,7 +7,7 @@ edit.applyRefactor({ refactorName: "Extract type", actionName: "Extract type", actionDescription: "Extract type", - newContent: `type NewType = { + newContent: `type /*RENAME*/NewType = { a?: number; b?: string; }; diff --git a/tests/cases/fourslash/refactorExtractType10.ts b/tests/cases/fourslash/refactorExtractType10.ts new file mode 100644 index 0000000000000..1b5f085ab452d --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType10.ts @@ -0,0 +1,17 @@ +/// + +//// function foo(a: number, b?: number, ...c: number[]): /*a*/boolean/*b*/ { +//// return false as boolean +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = boolean; + +function foo(a: number, b?: number, ...c: number[]): NewType { + return false as boolean +}`, +}); diff --git a/tests/cases/fourslash/refactorExtractType11.ts b/tests/cases/fourslash/refactorExtractType11.ts new file mode 100644 index 0000000000000..051fff6f44b41 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType11.ts @@ -0,0 +1,17 @@ +/// + +//// function foo(a: number, b?: number, ...c: number[]): boolean { +//// return false as /*a*/boolean/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `function foo(a: number, b?: number, ...c: number[]): boolean { + type /*RENAME*/NewType = boolean; + + return false as NewType +}`, +}); diff --git a/tests/cases/fourslash/refactorExtractType12.ts b/tests/cases/fourslash/refactorExtractType12.ts new file mode 100644 index 0000000000000..206f059294683 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType12.ts @@ -0,0 +1,21 @@ +/// + +//// interface A { +//// a: boolean +//// b: number +//// c: T +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = string; + +interface A { + a: boolean + b: number + c: T +}`, +}); diff --git a/tests/cases/fourslash/refactorExtractType13.ts b/tests/cases/fourslash/refactorExtractType13.ts new file mode 100644 index 0000000000000..2686ef39e236d --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType13.ts @@ -0,0 +1,21 @@ +/// + +//// interface A { +//// a: /*a*/boolean/*b*/ +//// b: number +//// c: T +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = boolean; + +interface A { + a: NewType + b: number + c: T +}`, +}); diff --git a/tests/cases/fourslash/refactorExtractType14.ts b/tests/cases/fourslash/refactorExtractType14.ts new file mode 100644 index 0000000000000..a5b100725bb52 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType14.ts @@ -0,0 +1,13 @@ +/// + +//// type A = string | number | T + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = boolean; + +type A = string | number | T`, +}); diff --git a/tests/cases/fourslash/refactorExtractType15.ts b/tests/cases/fourslash/refactorExtractType15.ts new file mode 100644 index 0000000000000..700b8b7750298 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType15.ts @@ -0,0 +1,13 @@ +/// + +//// type A = /*a*/string/*b*/ | number | T + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = string; + +type A = NewType | number | T`, +}); diff --git a/tests/cases/fourslash/refactorExtractType16.ts b/tests/cases/fourslash/refactorExtractType16.ts new file mode 100644 index 0000000000000..d6bc19bf50b5e --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType16.ts @@ -0,0 +1,13 @@ +/// + +//// var x: { a?: /*a*/number/*b*/, b?: string } = { }; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = number; + +var x: { a?: NewType, b?: string } = { };`, +}); diff --git a/tests/cases/fourslash/refactorExtractType2.ts b/tests/cases/fourslash/refactorExtractType2.ts new file mode 100644 index 0000000000000..c2f15bafe3a17 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType2.ts @@ -0,0 +1,13 @@ +/// + +//// var x: /*a*/string/*b*/ = ''; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = string; + +var x: NewType = '';`, +}); diff --git a/tests/cases/fourslash/refactorExtractType3.ts b/tests/cases/fourslash/refactorExtractType3.ts new file mode 100644 index 0000000000000..341dbaf6a6d04 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType3.ts @@ -0,0 +1,13 @@ +/// + +//// var x: /*a*/string | number | boolean/*b*/ = ''; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = string | number | boolean; + +var x: NewType = '';`, +}); diff --git a/tests/cases/fourslash/refactorExtractType4.ts b/tests/cases/fourslash/refactorExtractType4.ts new file mode 100644 index 0000000000000..e1acc09733967 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType4.ts @@ -0,0 +1,13 @@ +/// + +//// var x: /*a*/1/*b*/ = 1; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = 1; + +var x: NewType = 1;`, +}); diff --git a/tests/cases/fourslash/refactorExtractType5.ts b/tests/cases/fourslash/refactorExtractType5.ts new file mode 100644 index 0000000000000..fbc82d93f7e6c --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType5.ts @@ -0,0 +1,13 @@ +/// + +//// var x: /*a*/1 | 2/*b*/ = 1; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = 1 | 2; + +var x: NewType = 1;`, +}); diff --git a/tests/cases/fourslash/refactorExtractType6.ts b/tests/cases/fourslash/refactorExtractType6.ts new file mode 100644 index 0000000000000..0f50b061541e6 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType6.ts @@ -0,0 +1,13 @@ +/// + +//// var x: 1 | /*a*/2/*b*/ = 1; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = 2; + +var x: 1 | NewType = 1;`, +}); diff --git a/tests/cases/fourslash/refactorExtractType7.ts b/tests/cases/fourslash/refactorExtractType7.ts new file mode 100644 index 0000000000000..7e2e11c37faf7 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType7.ts @@ -0,0 +1,17 @@ +/// + +//// function foo(a: /*a*/number/*b*/, b?: number, ...c: number[]): boolean { +//// return false as boolean +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = number; + +function foo(a: NewType, b?: number, ...c: number[]): boolean { + return false as boolean +}`, +}); diff --git a/tests/cases/fourslash/refactorExtractType8.ts b/tests/cases/fourslash/refactorExtractType8.ts new file mode 100644 index 0000000000000..413125924f533 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType8.ts @@ -0,0 +1,17 @@ +/// + +//// function foo(a: number, b?: /*a*/number/*b*/, ...c: number[]): boolean { +//// return false as boolean +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = number; + +function foo(a: number, b?: NewType, ...c: number[]): boolean { + return false as boolean +}`, +}); diff --git a/tests/cases/fourslash/refactorExtractType9.ts b/tests/cases/fourslash/refactorExtractType9.ts new file mode 100644 index 0000000000000..d13ede841395b --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType9.ts @@ -0,0 +1,17 @@ +/// + +//// function foo(a: number, b?: number, ...c: /*a*/number[]/*b*/): boolean { +//// return false as boolean +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = number[]; + +function foo(a: number, b?: number, ...c: NewType): boolean { + return false as boolean +}`, +}); From 3948938b9802220aaafe2c31e7578648fa450284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 25 Mar 2019 15:22:03 +0800 Subject: [PATCH 03/19] collection type arguments --- src/services/refactors/extractType.ts | 35 ++++++++++++++----- .../cases/fourslash/refactorExtractType17.ts | 13 +++++++ .../cases/fourslash/refactorExtractType18.ts | 14 ++++++++ .../cases/fourslash/refactorExtractType19.ts | 14 ++++++++ .../cases/fourslash/refactorExtractType20.ts | 13 +++++++ .../cases/fourslash/refactorExtractType21.ts | 13 +++++++ .../cases/fourslash/refactorExtractType22.ts | 13 +++++++ .../cases/fourslash/refactorExtractType23.ts | 13 +++++++ .../cases/fourslash/refactorExtractType24.ts | 13 +++++++ .../cases/fourslash/refactorExtractType25.ts | 13 +++++++ .../cases/fourslash/refactorExtractType26.ts | 13 +++++++ .../cases/fourslash/refactorExtractType27.ts | 13 +++++++ .../cases/fourslash/refactorExtractType28.ts | 13 +++++++ .../cases/fourslash/refactorExtractType29.ts | 13 +++++++ .../cases/fourslash/refactorExtractType30.ts | 13 +++++++ 15 files changed, 211 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType17.ts create mode 100644 tests/cases/fourslash/refactorExtractType18.ts create mode 100644 tests/cases/fourslash/refactorExtractType19.ts create mode 100644 tests/cases/fourslash/refactorExtractType20.ts create mode 100644 tests/cases/fourslash/refactorExtractType21.ts create mode 100644 tests/cases/fourslash/refactorExtractType22.ts create mode 100644 tests/cases/fourslash/refactorExtractType23.ts create mode 100644 tests/cases/fourslash/refactorExtractType24.ts create mode 100644 tests/cases/fourslash/refactorExtractType25.ts create mode 100644 tests/cases/fourslash/refactorExtractType26.ts create mode 100644 tests/cases/fourslash/refactorExtractType27.ts create mode 100644 tests/cases/fourslash/refactorExtractType28.ts create mode 100644 tests/cases/fourslash/refactorExtractType29.ts create mode 100644 tests/cases/fourslash/refactorExtractType30.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index bc6bd1c6035a0..a47698c9fb884 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -10,13 +10,13 @@ namespace ts.refactor { getEditsForAction(context, actionName): RefactorEditInfo { Debug.assert(actionName === refactorName); const { file } = context; - const { selection, firstStatement } = Debug.assertDefined(getRangeToExtract(context)); + const { selection, firstStatement, typeParameters } = Debug.assertDefined(getRangeToExtract(context)); const name = getUniqueName("NewType", file); - const newTypeNode = generateTypeAlias(name, selection); + const newTypeNode = generateTypeAlias(name, selection, typeParameters); const edits = textChanges.ChangeTracker.with(context, changes => { changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true); - changes.replaceNode(file, selection, createTypeReferenceNode(name, /* typeArguments */ undefined)); + changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id, /* typeArguments */ undefined)))); }); const renameFilename = file.fileName; @@ -25,7 +25,7 @@ namespace ts.refactor { } }); - interface Info { selection: TypeNode; firstStatement: Statement; } + interface Info { selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray; } function getRangeToExtract(context: RefactorContext): Info | undefined { const { file, startPosition } = context; const current = getTokenAtPosition(file, startPosition); @@ -33,20 +33,39 @@ namespace ts.refactor { const selection = findAncestor(current, (node => node.parent && rangeContainsSkipTrivia(range, node, file) && !rangeContainsSkipTrivia(range, node.parent, file))); if (!selection || isInJSFile(selection) || !isTypeNode(selection)) return undefined; const firstStatement = Debug.assertDefined((findAncestor(selection, n => isStatement(n) && !isBlock(n)))) as Statement; - return { selection, firstStatement }; + const typeParameters = collectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); + return { selection, firstStatement, typeParameters }; } function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); } - function generateTypeAlias(name: string, typeNode: TypeNode) { + function collectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): string[] { + const result: string[] = []; + visitor(selection); + return result; + + function visitor(node: Node) { + if (isTypeReferenceNode(node)) { + if (isIdentifier(node.typeName)) { + const symbol = checker.resolveName(node.typeName.text, node.typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); + if (symbol && rangeContainsSkipTrivia(statement, first(symbol.declarations), file) && !rangeContainsSkipTrivia(selection, first(symbol.declarations), file)) { + result.push(node.typeName.text); + } + } + } + forEachChild(node, visitor); + } + } + + function generateTypeAlias(name: string, typeNode: TypeNode, typeParameters: ReadonlyArray) { return createTypeAliasDeclaration( /* decorators */ undefined, /* monifiers */ undefined, name, - /* typeArguments */ undefined, + typeParameters.map(id => createTypeParameterDeclaration(id)), typeNode ); } -} \ No newline at end of file +} diff --git a/tests/cases/fourslash/refactorExtractType17.ts b/tests/cases/fourslash/refactorExtractType17.ts new file mode 100644 index 0000000000000..b3c3774dd331d --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType17.ts @@ -0,0 +1,13 @@ +/// + +//// type A = string | number | /*a*/T/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = T; + +type A = string | number | NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType18.ts b/tests/cases/fourslash/refactorExtractType18.ts new file mode 100644 index 0000000000000..9956422a8f1db --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType18.ts @@ -0,0 +1,14 @@ +/// + +//// type A = /*a*/Partial/*b*/ & D | C + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = Partial; + +type A = NewType & D | C`, +}); + diff --git a/tests/cases/fourslash/refactorExtractType19.ts b/tests/cases/fourslash/refactorExtractType19.ts new file mode 100644 index 0000000000000..955aeeaeadd2a --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType19.ts @@ -0,0 +1,14 @@ +/// + +//// type A = /*a*/Partial/*b*/ & D | C + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = Partial; + +type A = NewType & D | C`, +}); + diff --git a/tests/cases/fourslash/refactorExtractType20.ts b/tests/cases/fourslash/refactorExtractType20.ts new file mode 100644 index 0000000000000..5fbd6f6aa29e1 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType20.ts @@ -0,0 +1,13 @@ +/// + +//// type A = () => (v: /*a*/T/*b*/) => (v: T) => (v: T) => U + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = T; + +type A = () => (v: NewType) => (v: T) => (v: T) => U`, +}); diff --git a/tests/cases/fourslash/refactorExtractType21.ts b/tests/cases/fourslash/refactorExtractType21.ts new file mode 100644 index 0000000000000..cffbb56a50ebd --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType21.ts @@ -0,0 +1,13 @@ +/// + +//// type A = () => (v: T) => (v: /*a*/T/*b*/) => (v: T) => U + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = T; + +type A = () => (v: T) => (v: NewType) => (v: T) => U`, +}); diff --git a/tests/cases/fourslash/refactorExtractType22.ts b/tests/cases/fourslash/refactorExtractType22.ts new file mode 100644 index 0000000000000..1fc17e95a77a3 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType22.ts @@ -0,0 +1,13 @@ +/// + +//// type A = () => (v: T) => (v: T) => (v: /*a*/T/*b*/) => U + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = T; + +type A = () => (v: T) => (v: T) => (v: NewType) => U`, +}); diff --git a/tests/cases/fourslash/refactorExtractType23.ts b/tests/cases/fourslash/refactorExtractType23.ts new file mode 100644 index 0000000000000..53fd166e03c12 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType23.ts @@ -0,0 +1,13 @@ +/// + +//// type A = () => (v: T) => (v: T) => (v: T) => /*a*/U/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = U; + +type A = () => (v: T) => (v: T) => (v: T) => NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType24.ts b/tests/cases/fourslash/refactorExtractType24.ts new file mode 100644 index 0000000000000..c3ebce743d8b4 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType24.ts @@ -0,0 +1,13 @@ +/// + +//// type A = () => (v: T) => (v: T) => /*a*/(v: T) => U/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = (v: T) => U; + +type A = () => (v: T) => (v: T) => NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType25.ts b/tests/cases/fourslash/refactorExtractType25.ts new file mode 100644 index 0000000000000..df5d02f50d4b7 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType25.ts @@ -0,0 +1,13 @@ +/// + +//// type A = () => (v: T) => /*a*/(v: T) => (v: T) => U/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = (v: T) => (v: T) => U; + +type A = () => (v: T) => NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType26.ts b/tests/cases/fourslash/refactorExtractType26.ts new file mode 100644 index 0000000000000..dd9b8816cee66 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType26.ts @@ -0,0 +1,13 @@ +/// + +//// type A = () => /*a*/(v: T) => (v: T) => (v: T) => U/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = (v: T) => (v: T) => (v: T) => U; + +type A = () => NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType27.ts b/tests/cases/fourslash/refactorExtractType27.ts new file mode 100644 index 0000000000000..4bddeef36b3e7 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType27.ts @@ -0,0 +1,13 @@ +/// + +//// type A = /*a*/() => (v: T) => (v: T) => (v: T) => U/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = () => (v: T) => (v: T) => (v: T) => U; + +type A = NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType28.ts b/tests/cases/fourslash/refactorExtractType28.ts new file mode 100644 index 0000000000000..19e99c5e9b4b2 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType28.ts @@ -0,0 +1,13 @@ +/// + +//// type Item = /*a*/T/*b*/ extends (infer P)[] ? P : never + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = T; + +type Item = NewType extends (infer P)[] ? P : never`, +}); diff --git a/tests/cases/fourslash/refactorExtractType29.ts b/tests/cases/fourslash/refactorExtractType29.ts new file mode 100644 index 0000000000000..bb55f3ae9932b --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType29.ts @@ -0,0 +1,13 @@ +/// + +//// type Item = T extends (infer P)[] ? /*a*/P/*b*/ : never + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType

= P; + +type Item = T extends (infer P)[] ? NewType

: never`, +}); diff --git a/tests/cases/fourslash/refactorExtractType30.ts b/tests/cases/fourslash/refactorExtractType30.ts new file mode 100644 index 0000000000000..84abfc71244d1 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType30.ts @@ -0,0 +1,13 @@ +/// + +//// type Item = T extends (infer P)[] ? P : /*a*/never/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = never; + +type Item = T extends (infer P)[] ? P : NewType`, +}); From 379de4b7d0026901a57bdc25c7804a2159466e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 25 Mar 2019 16:13:07 +0800 Subject: [PATCH 04/19] disallow infer type --- src/services/refactors/extractType.ts | 23 +++++++++++++++---- .../cases/fourslash/refactorExtractType31.ts | 6 +++++ .../cases/fourslash/refactorExtractType32.ts | 6 +++++ .../cases/fourslash/refactorExtractType33.ts | 6 +++++ .../cases/fourslash/refactorExtractType34.ts | 13 +++++++++++ 5 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType31.ts create mode 100644 tests/cases/fourslash/refactorExtractType32.ts create mode 100644 tests/cases/fourslash/refactorExtractType33.ts create mode 100644 tests/cases/fourslash/refactorExtractType34.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index a47698c9fb884..0f440392503bf 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -33,7 +33,9 @@ namespace ts.refactor { const selection = findAncestor(current, (node => node.parent && rangeContainsSkipTrivia(range, node, file) && !rangeContainsSkipTrivia(range, node.parent, file))); if (!selection || isInJSFile(selection) || !isTypeNode(selection)) return undefined; const firstStatement = Debug.assertDefined((findAncestor(selection, n => isStatement(n) && !isBlock(n)))) as Statement; - const typeParameters = collectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); + const typeParameters = checkAndCollectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); + if (!typeParameters) return undefined; + return { selection, firstStatement, typeParameters }; } @@ -41,20 +43,31 @@ namespace ts.refactor { return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); } - function collectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): string[] { + function checkAndCollectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): string[] | undefined { + let hasError = false; const result: string[] = []; visitor(selection); - return result; + return hasError ? undefined : result; function visitor(node: Node) { if (isTypeReferenceNode(node)) { if (isIdentifier(node.typeName)) { const symbol = checker.resolveName(node.typeName.text, node.typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); - if (symbol && rangeContainsSkipTrivia(statement, first(symbol.declarations), file) && !rangeContainsSkipTrivia(selection, first(symbol.declarations), file)) { - result.push(node.typeName.text); + if (symbol) { + const declaration = first(symbol.declarations); + if (rangeContainsSkipTrivia(statement, declaration, file) && !rangeContainsSkipTrivia(selection, declaration, file)) { + result.push(node.typeName.text); + } } } } + else if (isInferTypeNode(node)) { + const conditionalTypeNode = findAncestor(node, isConditionalTypeNode); + if (!conditionalTypeNode || !rangeContainsSkipTrivia(selection, conditionalTypeNode, file)) { + hasError = true; + return; + } + } forEachChild(node, visitor); } } diff --git a/tests/cases/fourslash/refactorExtractType31.ts b/tests/cases/fourslash/refactorExtractType31.ts new file mode 100644 index 0000000000000..84ef679de5d78 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType31.ts @@ -0,0 +1,6 @@ +/// + +//// type Item = T extends /*a*/(infer P)[]/*b*/ ? P : never + +goTo.select("a", "b"); +verify.not.refactorAvailable('Extract type') diff --git a/tests/cases/fourslash/refactorExtractType32.ts b/tests/cases/fourslash/refactorExtractType32.ts new file mode 100644 index 0000000000000..6bfe0fd26344c --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType32.ts @@ -0,0 +1,6 @@ +/// + +//// type Item = T extends (/*a*/infer P/*b*/)[] ? P : never + +goTo.select("a", "b"); +verify.not.refactorAvailable('Extract type') diff --git a/tests/cases/fourslash/refactorExtractType33.ts b/tests/cases/fourslash/refactorExtractType33.ts new file mode 100644 index 0000000000000..0124698325f8e --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType33.ts @@ -0,0 +1,6 @@ +/// + +//// type Item = T extends (infer /*a*/P/*b*/)[] ? P : never + +goTo.select("a", "b"); +verify.not.refactorAvailable('Extract type') diff --git a/tests/cases/fourslash/refactorExtractType34.ts b/tests/cases/fourslash/refactorExtractType34.ts new file mode 100644 index 0000000000000..046895be24554 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType34.ts @@ -0,0 +1,13 @@ +/// + +//// type Item = /*a*/T extends (infer P)[] ? P : never/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract type", + actionDescription: "Extract type", + newContent: `type /*RENAME*/NewType = T extends (infer P)[] ? P : never; + +type Item = NewType`, +}); From 0f414b4fbfc6c795a68e36ca9ceb236a43643d1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 25 Mar 2019 18:11:56 +0800 Subject: [PATCH 05/19] add support for typedef convert --- src/compiler/diagnosticMessages.json | 8 +++ src/services/refactors/extractType.ts | 69 ++++++++++++++----- tests/cases/fourslash/refactorExtractType1.ts | 4 +- .../cases/fourslash/refactorExtractType10.ts | 4 +- .../cases/fourslash/refactorExtractType11.ts | 4 +- .../cases/fourslash/refactorExtractType12.ts | 4 +- .../cases/fourslash/refactorExtractType13.ts | 4 +- .../cases/fourslash/refactorExtractType14.ts | 4 +- .../cases/fourslash/refactorExtractType15.ts | 4 +- .../cases/fourslash/refactorExtractType16.ts | 4 +- .../cases/fourslash/refactorExtractType17.ts | 4 +- .../cases/fourslash/refactorExtractType18.ts | 4 +- .../cases/fourslash/refactorExtractType19.ts | 4 +- tests/cases/fourslash/refactorExtractType2.ts | 4 +- .../cases/fourslash/refactorExtractType20.ts | 4 +- .../cases/fourslash/refactorExtractType21.ts | 4 +- .../cases/fourslash/refactorExtractType22.ts | 4 +- .../cases/fourslash/refactorExtractType23.ts | 4 +- .../cases/fourslash/refactorExtractType24.ts | 4 +- .../cases/fourslash/refactorExtractType25.ts | 4 +- .../cases/fourslash/refactorExtractType26.ts | 4 +- .../cases/fourslash/refactorExtractType27.ts | 4 +- .../cases/fourslash/refactorExtractType28.ts | 4 +- .../cases/fourslash/refactorExtractType29.ts | 4 +- tests/cases/fourslash/refactorExtractType3.ts | 4 +- .../cases/fourslash/refactorExtractType30.ts | 4 +- .../cases/fourslash/refactorExtractType34.ts | 4 +- .../cases/fourslash/refactorExtractType35.ts | 13 ++++ tests/cases/fourslash/refactorExtractType4.ts | 4 +- tests/cases/fourslash/refactorExtractType5.ts | 4 +- tests/cases/fourslash/refactorExtractType6.ts | 4 +- tests/cases/fourslash/refactorExtractType7.ts | 4 +- tests/cases/fourslash/refactorExtractType8.ts | 4 +- tests/cases/fourslash/refactorExtractType9.ts | 4 +- .../fourslash/refactorExtractType_js1.ts | 20 ++++++ .../fourslash/refactorExtractType_js2.ts | 20 ++++++ .../fourslash/refactorExtractType_js3.ts | 20 ++++++ 37 files changed, 196 insertions(+), 78 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType35.ts create mode 100644 tests/cases/fourslash/refactorExtractType_js1.ts create mode 100644 tests/cases/fourslash/refactorExtractType_js2.ts create mode 100644 tests/cases/fourslash/refactorExtractType_js3.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 69e1f2afafffc..dd581d7589414 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4934,5 +4934,13 @@ "Extract type": { "category": "Message", "code": 95076 + }, + "Extract to type alias": { + "category": "Message", + "code": 95077 + }, + "Extract to typedef": { + "category": "Message", + "code": 95078 } } diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 0f440392503bf..b3240bdab8d1b 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -1,23 +1,31 @@ /* @internal */ namespace ts.refactor { const refactorName = "Extract type"; + const extractToTypeAlias = "Extract to type alias"; + const extractToTypeDef = "Extract to typedef"; registerRefactor(refactorName, { getAvailableActions(context): ReadonlyArray { if (!getRangeToExtract(context)) return emptyArray; - const description = getLocaleSpecificMessage(Diagnostics.Extract_type); - return [{ name: refactorName, description, actions: [{ name: refactorName, description }] }]; + const isJs = isSourceFileJS(context.file); + + return [{ + name: refactorName, + description: getLocaleSpecificMessage(Diagnostics.Extract_type), + actions: [isJs ? { + name: extractToTypeDef, description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef) + } : { + name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias) + }] + }]; }, getEditsForAction(context, actionName): RefactorEditInfo { - Debug.assert(actionName === refactorName); + Debug.assert(actionName === extractToTypeAlias || actionName === extractToTypeDef); const { file } = context; - const { selection, firstStatement, typeParameters } = Debug.assertDefined(getRangeToExtract(context)); + const info = Debug.assertDefined(getRangeToExtract(context)); + const isJS = isSourceFileJS(file); const name = getUniqueName("NewType", file); - const newTypeNode = generateTypeAlias(name, selection, typeParameters); - const edits = textChanges.ChangeTracker.with(context, changes => { - changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true); - changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id, /* typeArguments */ undefined)))); - }); + const edits = textChanges.ChangeTracker.with(context, changes => isJS ? doTypedefChange(changes, file, name, info) : doTypeAliasChange(changes, file, name, info)); const renameFilename = file.fileName; const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false); @@ -25,20 +33,33 @@ namespace ts.refactor { } }); - interface Info { selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray; } + interface Info { selection: TypeNode; firstStatement: Statement | Statement & HasJSDoc; typeParameters: ReadonlyArray; } function getRangeToExtract(context: RefactorContext): Info | undefined { const { file, startPosition } = context; + const isJS = isSourceFileJS(file); const current = getTokenAtPosition(file, startPosition); const range = createTextRangeFromSpan(getRefactorContextSpan(context)); + const selection = findAncestor(current, (node => node.parent && rangeContainsSkipTrivia(range, node, file) && !rangeContainsSkipTrivia(range, node.parent, file))); - if (!selection || isInJSFile(selection) || !isTypeNode(selection)) return undefined; - const firstStatement = Debug.assertDefined((findAncestor(selection, n => isStatement(n) && !isBlock(n)))) as Statement; - const typeParameters = checkAndCollectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); + if (!selection || !isTypeNode(selection)) return undefined; + + const firstStatement = Debug.assertDefined((findAncestor(selection, isJS ? isStatementButNotBlockAndHasJSDoc : isStatementButNotBlock))); + + // typeparam tag is not supported yet + const typeParameters = isJS ? [] : checkAndCollectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); if (!typeParameters) return undefined; return { selection, firstStatement, typeParameters }; } + function isStatementButNotBlock(n: Node): n is Statement { + return n && isStatement(n) && !isBlock(n); + } + + function isStatementButNotBlockAndHasJSDoc(n: Node): n is (Statement & HasJSDoc) { + return isStatementButNotBlock && hasJSDocNodes(n); + } + function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); } @@ -72,13 +93,29 @@ namespace ts.refactor { } } - function generateTypeAlias(name: string, typeNode: TypeNode, typeParameters: ReadonlyArray) { - return createTypeAliasDeclaration( + function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: Info) { + const { firstStatement, selection, typeParameters } = info; + const newTypeNode = createTypeAliasDeclaration( /* decorators */ undefined, /* monifiers */ undefined, name, typeParameters.map(id => createTypeParameterDeclaration(id)), - typeNode + selection ); + changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true); + changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id, /* typeArguments */ undefined)))); + } + + function doTypedefChange (changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: Info) { + const { firstStatement, selection } = info; + + const node = createNode(SyntaxKind.JSDocTypedefTag); + node.tagName = createIdentifier("typedef"); // TODO: jsdoc factory https://github.com/Microsoft/TypeScript/pull/29539 + node.fullName = createIdentifier(name); + node.name = node.fullName; + node.typeExpression = createJSDocTypeExpression(selection); + + changes.insertNodeBefore(file, firstStatement, createJSDocComment(/* comment */ undefined, createNodeArray([node])), /* blankLineBetween */ true); + changes.replaceNode(file, selection, createTypeReferenceNode(name, /* typeArguments */ undefined)); } } diff --git a/tests/cases/fourslash/refactorExtractType1.ts b/tests/cases/fourslash/refactorExtractType1.ts index e6809459939f6..72a76280ca0b1 100644 --- a/tests/cases/fourslash/refactorExtractType1.ts +++ b/tests/cases/fourslash/refactorExtractType1.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = { a?: number; b?: string; diff --git a/tests/cases/fourslash/refactorExtractType10.ts b/tests/cases/fourslash/refactorExtractType10.ts index 1b5f085ab452d..870ba7e6207f3 100644 --- a/tests/cases/fourslash/refactorExtractType10.ts +++ b/tests/cases/fourslash/refactorExtractType10.ts @@ -7,8 +7,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = boolean; function foo(a: number, b?: number, ...c: number[]): NewType { diff --git a/tests/cases/fourslash/refactorExtractType11.ts b/tests/cases/fourslash/refactorExtractType11.ts index 051fff6f44b41..feb5e68c44093 100644 --- a/tests/cases/fourslash/refactorExtractType11.ts +++ b/tests/cases/fourslash/refactorExtractType11.ts @@ -7,8 +7,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `function foo(a: number, b?: number, ...c: number[]): boolean { type /*RENAME*/NewType = boolean; diff --git a/tests/cases/fourslash/refactorExtractType12.ts b/tests/cases/fourslash/refactorExtractType12.ts index 206f059294683..8c4a9e316e4aa 100644 --- a/tests/cases/fourslash/refactorExtractType12.ts +++ b/tests/cases/fourslash/refactorExtractType12.ts @@ -9,8 +9,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = string; interface A { diff --git a/tests/cases/fourslash/refactorExtractType13.ts b/tests/cases/fourslash/refactorExtractType13.ts index 2686ef39e236d..e72eadb2dd136 100644 --- a/tests/cases/fourslash/refactorExtractType13.ts +++ b/tests/cases/fourslash/refactorExtractType13.ts @@ -9,8 +9,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = boolean; interface A { diff --git a/tests/cases/fourslash/refactorExtractType14.ts b/tests/cases/fourslash/refactorExtractType14.ts index a5b100725bb52..4d5667bb54001 100644 --- a/tests/cases/fourslash/refactorExtractType14.ts +++ b/tests/cases/fourslash/refactorExtractType14.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = boolean; type A = string | number | T`, diff --git a/tests/cases/fourslash/refactorExtractType15.ts b/tests/cases/fourslash/refactorExtractType15.ts index 700b8b7750298..a716f42509ad8 100644 --- a/tests/cases/fourslash/refactorExtractType15.ts +++ b/tests/cases/fourslash/refactorExtractType15.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = string; type A = NewType | number | T`, diff --git a/tests/cases/fourslash/refactorExtractType16.ts b/tests/cases/fourslash/refactorExtractType16.ts index d6bc19bf50b5e..0ccc3000b56a8 100644 --- a/tests/cases/fourslash/refactorExtractType16.ts +++ b/tests/cases/fourslash/refactorExtractType16.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = number; var x: { a?: NewType, b?: string } = { };`, diff --git a/tests/cases/fourslash/refactorExtractType17.ts b/tests/cases/fourslash/refactorExtractType17.ts index b3c3774dd331d..ae62b402fe3c6 100644 --- a/tests/cases/fourslash/refactorExtractType17.ts +++ b/tests/cases/fourslash/refactorExtractType17.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = T; type A = string | number | NewType`, diff --git a/tests/cases/fourslash/refactorExtractType18.ts b/tests/cases/fourslash/refactorExtractType18.ts index 9956422a8f1db..1ebe8a2b35480 100644 --- a/tests/cases/fourslash/refactorExtractType18.ts +++ b/tests/cases/fourslash/refactorExtractType18.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = Partial; type A = NewType & D | C`, diff --git a/tests/cases/fourslash/refactorExtractType19.ts b/tests/cases/fourslash/refactorExtractType19.ts index 955aeeaeadd2a..4a982f6899db4 100644 --- a/tests/cases/fourslash/refactorExtractType19.ts +++ b/tests/cases/fourslash/refactorExtractType19.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = Partial; type A = NewType & D | C`, diff --git a/tests/cases/fourslash/refactorExtractType2.ts b/tests/cases/fourslash/refactorExtractType2.ts index c2f15bafe3a17..202de0f115ac0 100644 --- a/tests/cases/fourslash/refactorExtractType2.ts +++ b/tests/cases/fourslash/refactorExtractType2.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = string; var x: NewType = '';`, diff --git a/tests/cases/fourslash/refactorExtractType20.ts b/tests/cases/fourslash/refactorExtractType20.ts index 5fbd6f6aa29e1..b31f208704f08 100644 --- a/tests/cases/fourslash/refactorExtractType20.ts +++ b/tests/cases/fourslash/refactorExtractType20.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = T; type A = () => (v: NewType) => (v: T) => (v: T) => U`, diff --git a/tests/cases/fourslash/refactorExtractType21.ts b/tests/cases/fourslash/refactorExtractType21.ts index cffbb56a50ebd..e97485c82696e 100644 --- a/tests/cases/fourslash/refactorExtractType21.ts +++ b/tests/cases/fourslash/refactorExtractType21.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = T; type A = () => (v: T) => (v: NewType) => (v: T) => U`, diff --git a/tests/cases/fourslash/refactorExtractType22.ts b/tests/cases/fourslash/refactorExtractType22.ts index 1fc17e95a77a3..f7282df4ef707 100644 --- a/tests/cases/fourslash/refactorExtractType22.ts +++ b/tests/cases/fourslash/refactorExtractType22.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = T; type A = () => (v: T) => (v: T) => (v: NewType) => U`, diff --git a/tests/cases/fourslash/refactorExtractType23.ts b/tests/cases/fourslash/refactorExtractType23.ts index 53fd166e03c12..9e219dd3383dd 100644 --- a/tests/cases/fourslash/refactorExtractType23.ts +++ b/tests/cases/fourslash/refactorExtractType23.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = U; type A = () => (v: T) => (v: T) => (v: T) => NewType`, diff --git a/tests/cases/fourslash/refactorExtractType24.ts b/tests/cases/fourslash/refactorExtractType24.ts index c3ebce743d8b4..54b36acb252f7 100644 --- a/tests/cases/fourslash/refactorExtractType24.ts +++ b/tests/cases/fourslash/refactorExtractType24.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = (v: T) => U; type A = () => (v: T) => (v: T) => NewType`, diff --git a/tests/cases/fourslash/refactorExtractType25.ts b/tests/cases/fourslash/refactorExtractType25.ts index df5d02f50d4b7..b09d04912c1d9 100644 --- a/tests/cases/fourslash/refactorExtractType25.ts +++ b/tests/cases/fourslash/refactorExtractType25.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = (v: T) => (v: T) => U; type A = () => (v: T) => NewType`, diff --git a/tests/cases/fourslash/refactorExtractType26.ts b/tests/cases/fourslash/refactorExtractType26.ts index dd9b8816cee66..662ce7eb8dcab 100644 --- a/tests/cases/fourslash/refactorExtractType26.ts +++ b/tests/cases/fourslash/refactorExtractType26.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = (v: T) => (v: T) => (v: T) => U; type A = () => NewType`, diff --git a/tests/cases/fourslash/refactorExtractType27.ts b/tests/cases/fourslash/refactorExtractType27.ts index 4bddeef36b3e7..5163fd2d48055 100644 --- a/tests/cases/fourslash/refactorExtractType27.ts +++ b/tests/cases/fourslash/refactorExtractType27.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = () => (v: T) => (v: T) => (v: T) => U; type A = NewType`, diff --git a/tests/cases/fourslash/refactorExtractType28.ts b/tests/cases/fourslash/refactorExtractType28.ts index 19e99c5e9b4b2..81d1356ca2116 100644 --- a/tests/cases/fourslash/refactorExtractType28.ts +++ b/tests/cases/fourslash/refactorExtractType28.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = T; type Item = NewType extends (infer P)[] ? P : never`, diff --git a/tests/cases/fourslash/refactorExtractType29.ts b/tests/cases/fourslash/refactorExtractType29.ts index bb55f3ae9932b..a1fb2ac405c4f 100644 --- a/tests/cases/fourslash/refactorExtractType29.ts +++ b/tests/cases/fourslash/refactorExtractType29.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType

= P; type Item = T extends (infer P)[] ? NewType

: never`, diff --git a/tests/cases/fourslash/refactorExtractType3.ts b/tests/cases/fourslash/refactorExtractType3.ts index 341dbaf6a6d04..51185a43e006c 100644 --- a/tests/cases/fourslash/refactorExtractType3.ts +++ b/tests/cases/fourslash/refactorExtractType3.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = string | number | boolean; var x: NewType = '';`, diff --git a/tests/cases/fourslash/refactorExtractType30.ts b/tests/cases/fourslash/refactorExtractType30.ts index 84abfc71244d1..a0684cabb5300 100644 --- a/tests/cases/fourslash/refactorExtractType30.ts +++ b/tests/cases/fourslash/refactorExtractType30.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = never; type Item = T extends (infer P)[] ? P : NewType`, diff --git a/tests/cases/fourslash/refactorExtractType34.ts b/tests/cases/fourslash/refactorExtractType34.ts index 046895be24554..114d08a09a6cf 100644 --- a/tests/cases/fourslash/refactorExtractType34.ts +++ b/tests/cases/fourslash/refactorExtractType34.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = T extends (infer P)[] ? P : never; type Item = NewType`, diff --git a/tests/cases/fourslash/refactorExtractType35.ts b/tests/cases/fourslash/refactorExtractType35.ts new file mode 100644 index 0000000000000..8ace9f929b8b9 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType35.ts @@ -0,0 +1,13 @@ +/// + +//// type Union = /*a*/U | T/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = U | T; + +type Union = NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType4.ts b/tests/cases/fourslash/refactorExtractType4.ts index e1acc09733967..def60d0b643f5 100644 --- a/tests/cases/fourslash/refactorExtractType4.ts +++ b/tests/cases/fourslash/refactorExtractType4.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = 1; var x: NewType = 1;`, diff --git a/tests/cases/fourslash/refactorExtractType5.ts b/tests/cases/fourslash/refactorExtractType5.ts index fbc82d93f7e6c..72f6cd448e47d 100644 --- a/tests/cases/fourslash/refactorExtractType5.ts +++ b/tests/cases/fourslash/refactorExtractType5.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = 1 | 2; var x: NewType = 1;`, diff --git a/tests/cases/fourslash/refactorExtractType6.ts b/tests/cases/fourslash/refactorExtractType6.ts index 0f50b061541e6..437bc182af38d 100644 --- a/tests/cases/fourslash/refactorExtractType6.ts +++ b/tests/cases/fourslash/refactorExtractType6.ts @@ -5,8 +5,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = 2; var x: 1 | NewType = 1;`, diff --git a/tests/cases/fourslash/refactorExtractType7.ts b/tests/cases/fourslash/refactorExtractType7.ts index 7e2e11c37faf7..a5b1f4c7dd049 100644 --- a/tests/cases/fourslash/refactorExtractType7.ts +++ b/tests/cases/fourslash/refactorExtractType7.ts @@ -7,8 +7,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = number; function foo(a: NewType, b?: number, ...c: number[]): boolean { diff --git a/tests/cases/fourslash/refactorExtractType8.ts b/tests/cases/fourslash/refactorExtractType8.ts index 413125924f533..61121c6737457 100644 --- a/tests/cases/fourslash/refactorExtractType8.ts +++ b/tests/cases/fourslash/refactorExtractType8.ts @@ -7,8 +7,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = number; function foo(a: number, b?: NewType, ...c: number[]): boolean { diff --git a/tests/cases/fourslash/refactorExtractType9.ts b/tests/cases/fourslash/refactorExtractType9.ts index d13ede841395b..921f2a005a507 100644 --- a/tests/cases/fourslash/refactorExtractType9.ts +++ b/tests/cases/fourslash/refactorExtractType9.ts @@ -7,8 +7,8 @@ goTo.select("a", "b"); edit.applyRefactor({ refactorName: "Extract type", - actionName: "Extract type", - actionDescription: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", newContent: `type /*RENAME*/NewType = number[]; function foo(a: number, b?: number, ...c: NewType): boolean { diff --git a/tests/cases/fourslash/refactorExtractType_js1.ts b/tests/cases/fourslash/refactorExtractType_js1.ts new file mode 100644 index 0000000000000..e90c74d0116bd --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType_js1.ts @@ -0,0 +1,20 @@ +/// + +// @allowJs: true +// @Filename: a.js +//// /** @type { /*a*/string/*b*/ } */ +//// var x; + +goTo.file('a.js') +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to typedef", + actionDescription: "Extract to typedef", + newContent: `/** + * @typedef {string} /*RENAME*/NewType + */ + +/** @type { NewType } */ +var x;`, +}); diff --git a/tests/cases/fourslash/refactorExtractType_js2.ts b/tests/cases/fourslash/refactorExtractType_js2.ts new file mode 100644 index 0000000000000..592f0f6f16323 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType_js2.ts @@ -0,0 +1,20 @@ +/// + +// @allowJs: true +// @Filename: a.js +//// /** @type { /*a*/string/*b*/ | number } */ +//// var x; + +goTo.file('a.js') +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to typedef", + actionDescription: "Extract to typedef", + newContent: `/** + * @typedef {string} /*RENAME*/NewType + */ + +/** @type { NewType | number } */ +var x;`, +}); diff --git a/tests/cases/fourslash/refactorExtractType_js3.ts b/tests/cases/fourslash/refactorExtractType_js3.ts new file mode 100644 index 0000000000000..9d431b2fd0868 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType_js3.ts @@ -0,0 +1,20 @@ +/// + +// @allowJs: true +// @Filename: a.js +//// /** @type { /*a*/string | number/*b*/ } */ +//// var x; + +goTo.file('a.js') +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to typedef", + actionDescription: "Extract to typedef", + newContent: `/** + * @typedef {string | number} /*RENAME*/NewType + */ + +/** @type { NewType } */ +var x;`, +}); From 9e405fca58480391fd4ef20f3b81807b5c3b5a42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 25 Mar 2019 18:23:44 +0800 Subject: [PATCH 06/19] refactor info to make type safe --- src/services/refactors/extractType.ts | 46 ++++++++++++++++----------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index b3240bdab8d1b..9671dea8ae6bc 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -5,27 +5,29 @@ namespace ts.refactor { const extractToTypeDef = "Extract to typedef"; registerRefactor(refactorName, { getAvailableActions(context): ReadonlyArray { - if (!getRangeToExtract(context)) return emptyArray; - const isJs = isSourceFileJS(context.file); + const info = getRangeToExtract(context); + if (!info) return emptyArray; return [{ name: refactorName, description: getLocaleSpecificMessage(Diagnostics.Extract_type), - actions: [isJs ? { + actions: [info.isJS ? { name: extractToTypeDef, description: getLocaleSpecificMessage(Diagnostics.Extract_to_typedef) } : { - name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias) - }] + name: extractToTypeAlias, description: getLocaleSpecificMessage(Diagnostics.Extract_to_type_alias) + }] }]; }, getEditsForAction(context, actionName): RefactorEditInfo { Debug.assert(actionName === extractToTypeAlias || actionName === extractToTypeDef); const { file } = context; const info = Debug.assertDefined(getRangeToExtract(context)); - const isJS = isSourceFileJS(file); + Debug.assert(actionName === extractToTypeAlias && !info.isJS || actionName === extractToTypeDef && info.isJS); const name = getUniqueName("NewType", file); - const edits = textChanges.ChangeTracker.with(context, changes => isJS ? doTypedefChange(changes, file, name, info) : doTypeAliasChange(changes, file, name, info)); + const edits = textChanges.ChangeTracker.with(context, changes => info.isJS ? + doTypedefChange(changes, file, name, info.firstStatement, info.selection) : + doTypeAliasChange(changes, file, name, info.firstStatement, info.selection, info.typeParameters)); const renameFilename = file.fileName; const renameLocation = getRenameLocation(edits, renameFilename, name, /*preferLastLocation*/ false); @@ -33,7 +35,9 @@ namespace ts.refactor { } }); - interface Info { selection: TypeNode; firstStatement: Statement | Statement & HasJSDoc; typeParameters: ReadonlyArray; } + interface TypeAliasInfo { isJS: false; selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray; } + interface TypedefInfo { isJS: true; selection: TypeNode; firstStatement: Statement & HasJSDoc; } + type Info = TypeAliasInfo | TypedefInfo; function getRangeToExtract(context: RefactorContext): Info | undefined { const { file, startPosition } = context; const isJS = isSourceFileJS(file); @@ -43,13 +47,22 @@ namespace ts.refactor { const selection = findAncestor(current, (node => node.parent && rangeContainsSkipTrivia(range, node, file) && !rangeContainsSkipTrivia(range, node.parent, file))); if (!selection || !isTypeNode(selection)) return undefined; - const firstStatement = Debug.assertDefined((findAncestor(selection, isJS ? isStatementButNotBlockAndHasJSDoc : isStatementButNotBlock))); + if (isJS) { + return { + isJS, + selection, + firstStatement: Debug.assertDefined((findAncestor(selection, isStatementButNotBlockAndHasJSDoc))), + }; + } + else { + const firstStatement = Debug.assertDefined((findAncestor(selection, isStatementButNotBlock))); - // typeparam tag is not supported yet - const typeParameters = isJS ? [] : checkAndCollectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); - if (!typeParameters) return undefined; + // typeparam tag is not supported yet + const typeParameters = isJS ? [] : checkAndCollectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); + if (!typeParameters) return undefined; - return { selection, firstStatement, typeParameters }; + return { isJS, selection, firstStatement, typeParameters }; + } } function isStatementButNotBlock(n: Node): n is Statement { @@ -93,8 +106,7 @@ namespace ts.refactor { } } - function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: Info) { - const { firstStatement, selection, typeParameters } = info; + function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray) { const newTypeNode = createTypeAliasDeclaration( /* decorators */ undefined, /* monifiers */ undefined, @@ -106,9 +118,7 @@ namespace ts.refactor { changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id, /* typeArguments */ undefined)))); } - function doTypedefChange (changes: textChanges.ChangeTracker, file: SourceFile, name: string, info: Info) { - const { firstStatement, selection } = info; - + function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: HasJSDoc, selection: TypeNode) { const node = createNode(SyntaxKind.JSDocTypedefTag); node.tagName = createIdentifier("typedef"); // TODO: jsdoc factory https://github.com/Microsoft/TypeScript/pull/29539 node.fullName = createIdentifier(name); From ead7b05aef29eff4788dfbc0c7f9bda5225de35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 25 Mar 2019 19:25:04 +0800 Subject: [PATCH 07/19] disallow type pred --- src/services/refactors/extractType.ts | 4 ++++ tests/cases/fourslash/refactorExtractType36.ts | 14 ++++++++++++++ tests/cases/fourslash/refactorExtractType37.ts | 13 +++++++++++++ tests/cases/fourslash/refactorExtractType38.ts | 6 ++++++ tests/cases/fourslash/refactorExtractType39.ts | 13 +++++++++++++ 5 files changed, 50 insertions(+) create mode 100644 tests/cases/fourslash/refactorExtractType36.ts create mode 100644 tests/cases/fourslash/refactorExtractType37.ts create mode 100644 tests/cases/fourslash/refactorExtractType38.ts create mode 100644 tests/cases/fourslash/refactorExtractType39.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 9671dea8ae6bc..48c56931fa33f 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -102,6 +102,10 @@ namespace ts.refactor { return; } } + else if (isTypePredicateNode(node) && !rangeContainsSkipTrivia(selection, node.parent, file)) { + hasError = true; + return; + } forEachChild(node, visitor); } } diff --git a/tests/cases/fourslash/refactorExtractType36.ts b/tests/cases/fourslash/refactorExtractType36.ts new file mode 100644 index 0000000000000..5086bf130cecc --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType36.ts @@ -0,0 +1,14 @@ +/// + +//// type A = (v: /*a*/string | number/*b*/) => v is string + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = string | number; + +type A = (v: NewType) => v is string`, +}); + diff --git a/tests/cases/fourslash/refactorExtractType37.ts b/tests/cases/fourslash/refactorExtractType37.ts new file mode 100644 index 0000000000000..b24e219b3e4e3 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType37.ts @@ -0,0 +1,13 @@ +/// + +//// type A = (v: string | number) => v is /*a*/string/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = string; + +type A = (v: string | number) => v is NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType38.ts b/tests/cases/fourslash/refactorExtractType38.ts new file mode 100644 index 0000000000000..b74091f1aec42 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType38.ts @@ -0,0 +1,6 @@ +/// + +//// type A = (v: string | number) => /*a*/v is string/*b*/ + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type") diff --git a/tests/cases/fourslash/refactorExtractType39.ts b/tests/cases/fourslash/refactorExtractType39.ts new file mode 100644 index 0000000000000..1f55dcee39aaf --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType39.ts @@ -0,0 +1,13 @@ +/// + +//// type A = /*a*/(v: string | number) => v is string/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = (v: string | number) => v is string; + +type A = NewType`, +}); From 5749f7f732e2eef0165d200ed3386be69559e0e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 25 Mar 2019 19:34:12 +0800 Subject: [PATCH 08/19] avoid unnecessary branch --- src/services/refactors/extractType.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 48c56931fa33f..0bda2fc6e70f0 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -48,6 +48,7 @@ namespace ts.refactor { if (!selection || !isTypeNode(selection)) return undefined; if (isJS) { + // typeparam tag is not supported yet return { isJS, selection, @@ -56,9 +57,7 @@ namespace ts.refactor { } else { const firstStatement = Debug.assertDefined((findAncestor(selection, isStatementButNotBlock))); - - // typeparam tag is not supported yet - const typeParameters = isJS ? [] : checkAndCollectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); + const typeParameters = checkAndCollectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); if (!typeParameters) return undefined; return { isJS, selection, firstStatement, typeParameters }; From 0e376618b44d9c434a25acbb46e1001d183d0973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Thu, 28 Mar 2019 19:03:15 +0800 Subject: [PATCH 09/19] disallow type query --- src/services/refactors/extractType.ts | 7 +++++++ tests/cases/fourslash/refactorExtractType40.ts | 6 ++++++ tests/cases/fourslash/refactorExtractType41.ts | 14 ++++++++++++++ tests/cases/fourslash/refactorExtractType42.ts | 15 +++++++++++++++ tests/cases/fourslash/refactorExtractType43.ts | 6 ++++++ 5 files changed, 48 insertions(+) create mode 100644 tests/cases/fourslash/refactorExtractType40.ts create mode 100644 tests/cases/fourslash/refactorExtractType41.ts create mode 100644 tests/cases/fourslash/refactorExtractType42.ts create mode 100644 tests/cases/fourslash/refactorExtractType43.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 0bda2fc6e70f0..00b009cead389 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -105,6 +105,13 @@ namespace ts.refactor { hasError = true; return; } + else if (isTypeQueryNode(node) && isIdentifier(node.exprName)) { + const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false); + if (symbol && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) { + hasError = true; + return; + } + } forEachChild(node, visitor); } } diff --git a/tests/cases/fourslash/refactorExtractType40.ts b/tests/cases/fourslash/refactorExtractType40.ts new file mode 100644 index 0000000000000..cae32cec78618 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType40.ts @@ -0,0 +1,6 @@ +/// + +//// type A = (v: string | number) => /*a*/typeof v/*b*/ + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type") diff --git a/tests/cases/fourslash/refactorExtractType41.ts b/tests/cases/fourslash/refactorExtractType41.ts new file mode 100644 index 0000000000000..b0d3222a456e3 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType41.ts @@ -0,0 +1,14 @@ +/// + +//// type A = /*a*/(v: string | number) => typeof v/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = (v: string | number) => typeof v; + +type A = NewType`, +}); + diff --git a/tests/cases/fourslash/refactorExtractType42.ts b/tests/cases/fourslash/refactorExtractType42.ts new file mode 100644 index 0000000000000..29692ab06e6a3 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType42.ts @@ -0,0 +1,15 @@ +/// + +//// const a = 1 +//// type A = (v: string | number) => /*a*/typeof a/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `const a = 1 +type /*RENAME*/NewType = typeof a; + +type A = (v: string | number) => NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType43.ts b/tests/cases/fourslash/refactorExtractType43.ts new file mode 100644 index 0000000000000..c3c0900929516 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType43.ts @@ -0,0 +1,6 @@ +/// + +//// type A = (v: string | number) => /*a*/number | typeof v/*b*/ + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type") From 0c1866c8f4a1774f5ebccafd88b5b241943dbfba Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 28 Mar 2019 19:17:34 -0500 Subject: [PATCH 10/19] =?UTF-8?q?haha=F0=9F=98=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Kingwl --- src/services/refactors/extractType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 00b009cead389..60c5a897ec5ba 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -69,7 +69,7 @@ namespace ts.refactor { } function isStatementButNotBlockAndHasJSDoc(n: Node): n is (Statement & HasJSDoc) { - return isStatementButNotBlock && hasJSDocNodes(n); + return isStatementButNotBlock(n) && hasJSDocNodes(n); } function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { From 65ecee5920898231169265e2de8c21282915613b Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 28 Mar 2019 20:10:05 -0500 Subject: [PATCH 11/19] Update src/services/refactors/extractType.ts Co-Authored-By: Kingwl --- src/services/refactors/extractType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 60c5a897ec5ba..9e5693a2b4d97 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -76,7 +76,7 @@ namespace ts.refactor { return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); } - function checkAndCollectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): string[] | undefined { + function checkAndCollectTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): string[] | undefined { let hasError = false; const result: string[] = []; visitor(selection); From 26ff926c2a82f1cab858ccd9f081c9486c4cce02 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Thu, 28 Mar 2019 20:10:16 -0500 Subject: [PATCH 12/19] Update src/services/refactors/extractType.ts Co-Authored-By: Kingwl --- src/services/refactors/extractType.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 9e5693a2b4d97..b3c01fdd9ae9d 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -119,7 +119,7 @@ namespace ts.refactor { function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray) { const newTypeNode = createTypeAliasDeclaration( /* decorators */ undefined, - /* monifiers */ undefined, + /* modifiers */ undefined, name, typeParameters.map(id => createTypeParameterDeclaration(id)), selection From 566da684ea134b0488d97efa8cd9a36e07e217df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Fri, 29 Mar 2019 10:04:54 +0800 Subject: [PATCH 13/19] add more tests --- src/services/refactors/extractType.ts | 4 ++-- tests/cases/fourslash/refactorExtractType44.ts | 13 +++++++++++++ tests/cases/fourslash/refactorExtractType45.ts | 13 +++++++++++++ tests/cases/fourslash/refactorExtractType46.ts | 15 +++++++++++++++ tests/cases/fourslash/refactorExtractType47.ts | 5 +++++ tests/cases/fourslash/refactorExtractType48.ts | 13 +++++++++++++ 6 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType44.ts create mode 100644 tests/cases/fourslash/refactorExtractType45.ts create mode 100644 tests/cases/fourslash/refactorExtractType46.ts create mode 100644 tests/cases/fourslash/refactorExtractType47.ts create mode 100644 tests/cases/fourslash/refactorExtractType48.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index b3c01fdd9ae9d..c1c8511e910c2 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -76,7 +76,7 @@ namespace ts.refactor { return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); } - function checkAndCollectTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): string[] | undefined { + function checkAndCollectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): string[] | undefined { let hasError = false; const result: string[] = []; visitor(selection); @@ -95,7 +95,7 @@ namespace ts.refactor { } } else if (isInferTypeNode(node)) { - const conditionalTypeNode = findAncestor(node, isConditionalTypeNode); + const conditionalTypeNode = findAncestor(node, n => isConditionalTypeNode(n) && rangeContainsSkipTrivia(n.extendsType, node, file)); if (!conditionalTypeNode || !rangeContainsSkipTrivia(selection, conditionalTypeNode, file)) { hasError = true; return; diff --git a/tests/cases/fourslash/refactorExtractType44.ts b/tests/cases/fourslash/refactorExtractType44.ts new file mode 100644 index 0000000000000..22e1a9172dc82 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType44.ts @@ -0,0 +1,13 @@ +/// + +//// type A = /*a*/B.C.D/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = B.C.D; + +type A = NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType45.ts b/tests/cases/fourslash/refactorExtractType45.ts new file mode 100644 index 0000000000000..60d9d77bc29eb --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType45.ts @@ -0,0 +1,13 @@ +/// + +//// type A = /*a*/B.C.D/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = B.C.D; + +type A = NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType46.ts b/tests/cases/fourslash/refactorExtractType46.ts new file mode 100644 index 0000000000000..ddcfa4878c9c1 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType46.ts @@ -0,0 +1,15 @@ +/// + +//// namespace A { export const b = 1 } +//// function a(b: string): /*a*/typeof A.b/*b*/ { return 1 } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `namespace A { export const b = 1 } +type /*RENAME*/NewType = typeof A.b; + +function a(b: string): NewType { return 1 }`, +}); diff --git a/tests/cases/fourslash/refactorExtractType47.ts b/tests/cases/fourslash/refactorExtractType47.ts new file mode 100644 index 0000000000000..360c6eef69620 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType47.ts @@ -0,0 +1,5 @@ +/// + +//// type Crazy = T extends [infer P, (/*a*/infer R extends string ? string : never/*b*/)] ? P & R : string; + +verify.not.refactorAvailable("Extract type") \ No newline at end of file diff --git a/tests/cases/fourslash/refactorExtractType48.ts b/tests/cases/fourslash/refactorExtractType48.ts new file mode 100644 index 0000000000000..d10af6bb6379e --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType48.ts @@ -0,0 +1,13 @@ +/// + +//// type Crazy = /*a*/T extends [infer P, (infer R extends string ? string : never)] ? P & R : string/*b*/; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = T extends [infer P, (infer R extends string ? string : never)] ? P & R : string; + +type Crazy = NewType;`, +}); From 73e8297f54736fb965d1ae2f0b8130d1956f5b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Fri, 29 Mar 2019 17:55:52 +0800 Subject: [PATCH 14/19] add template tag support in jsdoc --- src/services/refactors/extractType.ts | 44 ++++++++++--------- .../fourslash/refactorExtractType_js4.ts | 31 +++++++++++++ 2 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType_js4.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index c1c8511e910c2..c3e3f6901c006 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -26,7 +26,7 @@ namespace ts.refactor { const name = getUniqueName("NewType", file); const edits = textChanges.ChangeTracker.with(context, changes => info.isJS ? - doTypedefChange(changes, file, name, info.firstStatement, info.selection) : + doTypedefChange(changes, file, name, info.firstStatement, info.selection, info.typeParameters) : doTypeAliasChange(changes, file, name, info.firstStatement, info.selection, info.typeParameters)); const renameFilename = file.fileName; @@ -35,9 +35,8 @@ namespace ts.refactor { } }); - interface TypeAliasInfo { isJS: false; selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray; } - interface TypedefInfo { isJS: true; selection: TypeNode; firstStatement: Statement & HasJSDoc; } - type Info = TypeAliasInfo | TypedefInfo; + interface Info { isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray; } + function getRangeToExtract(context: RefactorContext): Info | undefined { const { file, startPosition } = context; const isJS = isSourceFileJS(file); @@ -47,21 +46,12 @@ namespace ts.refactor { const selection = findAncestor(current, (node => node.parent && rangeContainsSkipTrivia(range, node, file) && !rangeContainsSkipTrivia(range, node.parent, file))); if (!selection || !isTypeNode(selection)) return undefined; - if (isJS) { - // typeparam tag is not supported yet - return { - isJS, - selection, - firstStatement: Debug.assertDefined((findAncestor(selection, isStatementButNotBlockAndHasJSDoc))), - }; - } - else { - const firstStatement = Debug.assertDefined((findAncestor(selection, isStatementButNotBlock))); - const typeParameters = checkAndCollectionTypeArguments(context.program.getTypeChecker(), selection, firstStatement, file); - if (!typeParameters) return undefined; + const checker = context.program.getTypeChecker(); + const firstStatement = Debug.assertDefined(isJS ? findAncestor(selection, isStatementButNotBlockAndHasJSDoc) : findAncestor(selection, isStatementButNotBlock)); + const typeParameters = checkAndCollectionTypeArguments(checker, selection, firstStatement, file); + if (!typeParameters) return undefined; - return { isJS, selection, firstStatement, typeParameters }; - } + return { isJS, selection, firstStatement, typeParameters }; } function isStatementButNotBlock(n: Node): n is Statement { @@ -128,14 +118,26 @@ namespace ts.refactor { changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id, /* typeArguments */ undefined)))); } - function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: HasJSDoc, selection: TypeNode) { + function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray) { const node = createNode(SyntaxKind.JSDocTypedefTag); node.tagName = createIdentifier("typedef"); // TODO: jsdoc factory https://github.com/Microsoft/TypeScript/pull/29539 node.fullName = createIdentifier(name); node.name = node.fullName; node.typeExpression = createJSDocTypeExpression(selection); - changes.insertNodeBefore(file, firstStatement, createJSDocComment(/* comment */ undefined, createNodeArray([node])), /* blankLineBetween */ true); - changes.replaceNode(file, selection, createTypeReferenceNode(name, /* typeArguments */ undefined)); + const tags: JSDocTag[] = [node]; + if (length(typeParameters)) { + const template = createNode(SyntaxKind.JSDocTemplateTag); + template.tagName = createIdentifier("template"); + template.typeParameters = createNodeArray(typeParameters.map(id => { + const typeParameter = createNode(SyntaxKind.TypeParameter); + typeParameter.name = createIdentifier(id); + return typeParameter; + })); + tags.unshift(template); + } + + changes.insertNodeBefore(file, firstStatement, createJSDocComment(/* comment */ undefined, createNodeArray(tags)), /* blankLineBetween */ true); + changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id, /* typeArguments */ undefined)))); } } diff --git a/tests/cases/fourslash/refactorExtractType_js4.ts b/tests/cases/fourslash/refactorExtractType_js4.ts new file mode 100644 index 0000000000000..5044f43308b8a --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType_js4.ts @@ -0,0 +1,31 @@ +/// + +// @allowJs: true +// @Filename: a.js +//// /** +//// * @template T, U +//// * @param {T} b +//// * @param {U} c +//// * @returns {/*a*/T | U/*b*/} +//// */ +//// function a(b, c) {} + +goTo.file('a.js') +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to typedef", + actionDescription: "Extract to typedef", + newContent: `/** + * @template T, U + * @typedef {T | U} /*RENAME*/NewType + */ + +/** + * @template T, U + * @param {T} b + * @param {U} c + * @returns {NewType} + */ +function a(b, c) {}`, +}); From 5c28b6067e0c53c1923106757bbf0b94baf20931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Fri, 29 Mar 2019 18:33:27 +0800 Subject: [PATCH 15/19] add support of type parameters constraint --- src/services/refactors/extractType.ts | 43 ++++++++++--------- .../cases/fourslash/refactorExtractType49.ts | 15 +++++++ .../fourslash/refactorExtractType_js4.ts | 9 ++-- .../fourslash/refactorExtractType_js5.ts | 34 +++++++++++++++ .../fourslash/refactorExtractType_js6.ts | 32 ++++++++++++++ 5 files changed, 110 insertions(+), 23 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType49.ts create mode 100644 tests/cases/fourslash/refactorExtractType_js5.ts create mode 100644 tests/cases/fourslash/refactorExtractType_js6.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index c3e3f6901c006..5a01d0fd3c094 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -35,7 +35,7 @@ namespace ts.refactor { } }); - interface Info { isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray; } + interface Info { isJS: boolean; selection: TypeNode; firstStatement: Statement; typeParameters: ReadonlyArray; } function getRangeToExtract(context: RefactorContext): Info | undefined { const { file, startPosition } = context; @@ -66,9 +66,9 @@ namespace ts.refactor { return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); } - function checkAndCollectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): string[] | undefined { + function checkAndCollectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { let hasError = false; - const result: string[] = []; + const result: TypeParameterDeclaration[] = []; visitor(selection); return hasError ? undefined : result; @@ -77,9 +77,9 @@ namespace ts.refactor { if (isIdentifier(node.typeName)) { const symbol = checker.resolveName(node.typeName.text, node.typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); if (symbol) { - const declaration = first(symbol.declarations); + const declaration = cast(first(symbol.declarations), isTypeParameterDeclaration); if (rangeContainsSkipTrivia(statement, declaration, file) && !rangeContainsSkipTrivia(selection, declaration, file)) { - result.push(node.typeName.text); + result.push(declaration); } } } @@ -106,38 +106,41 @@ namespace ts.refactor { } } - function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray) { + function doTypeAliasChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray) { const newTypeNode = createTypeAliasDeclaration( /* decorators */ undefined, /* modifiers */ undefined, name, - typeParameters.map(id => createTypeParameterDeclaration(id)), + typeParameters.map(id => updateTypeParameterDeclaration(id, id.name, id.constraint, /* defaultType */ undefined)), selection ); changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true); - changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id, /* typeArguments */ undefined)))); + changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id.name, /* typeArguments */ undefined)))); } - function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray) { + function doTypedefChange(changes: textChanges.ChangeTracker, file: SourceFile, name: string, firstStatement: Statement, selection: TypeNode, typeParameters: ReadonlyArray) { const node = createNode(SyntaxKind.JSDocTypedefTag); node.tagName = createIdentifier("typedef"); // TODO: jsdoc factory https://github.com/Microsoft/TypeScript/pull/29539 node.fullName = createIdentifier(name); node.name = node.fullName; node.typeExpression = createJSDocTypeExpression(selection); - const tags: JSDocTag[] = [node]; - if (length(typeParameters)) { + const templates: JSDocTemplateTag[] = []; + forEach(typeParameters, typeParameter => { + const constraint = getEffectiveConstraintOfTypeParameter(typeParameter); + const template = createNode(SyntaxKind.JSDocTemplateTag); template.tagName = createIdentifier("template"); - template.typeParameters = createNodeArray(typeParameters.map(id => { - const typeParameter = createNode(SyntaxKind.TypeParameter); - typeParameter.name = createIdentifier(id); - return typeParameter; - })); - tags.unshift(template); - } + template.constraint = constraint && cast(constraint, isJSDocTypeExpression); + + const parameter = createNode(SyntaxKind.TypeParameter); + parameter.name = typeParameter.name; + template.typeParameters = createNodeArray([parameter]); + + templates.push(template); + }); - changes.insertNodeBefore(file, firstStatement, createJSDocComment(/* comment */ undefined, createNodeArray(tags)), /* blankLineBetween */ true); - changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id, /* typeArguments */ undefined)))); + changes.insertNodeBefore(file, firstStatement, createJSDocComment(/* comment */ undefined, createNodeArray(concatenate(templates, [node]))), /* blankLineBetween */ true); + changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id.name, /* typeArguments */ undefined)))); } } diff --git a/tests/cases/fourslash/refactorExtractType49.ts b/tests/cases/fourslash/refactorExtractType49.ts new file mode 100644 index 0000000000000..7d965ee3d09e8 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType49.ts @@ -0,0 +1,15 @@ +/// + +//// type A = T +//// type B = /*a*/A/*b*/ + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type A = T +type /*RENAME*/NewType = A; + +type B = NewType`, +}); diff --git a/tests/cases/fourslash/refactorExtractType_js4.ts b/tests/cases/fourslash/refactorExtractType_js4.ts index 5044f43308b8a..1d4d4c3cdf7fd 100644 --- a/tests/cases/fourslash/refactorExtractType_js4.ts +++ b/tests/cases/fourslash/refactorExtractType_js4.ts @@ -3,7 +3,8 @@ // @allowJs: true // @Filename: a.js //// /** -//// * @template T, U +//// * @template T +//// * @template U //// * @param {T} b //// * @param {U} c //// * @returns {/*a*/T | U/*b*/} @@ -17,12 +18,14 @@ edit.applyRefactor({ actionName: "Extract to typedef", actionDescription: "Extract to typedef", newContent: `/** - * @template T, U + * @template T + * @template U * @typedef {T | U} /*RENAME*/NewType */ /** - * @template T, U + * @template T + * @template U * @param {T} b * @param {U} c * @returns {NewType} diff --git a/tests/cases/fourslash/refactorExtractType_js5.ts b/tests/cases/fourslash/refactorExtractType_js5.ts new file mode 100644 index 0000000000000..20df67d9c7682 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType_js5.ts @@ -0,0 +1,34 @@ +/// + +// @allowJs: true +// @Filename: a.js +//// /** +//// * @template {number} T +//// * @template {string} U +//// * @param {T} b +//// * @param {U} c +//// * @returns {/*a*/T | U/*b*/} +//// */ +//// function a(b, c) {} + +goTo.file('a.js') +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to typedef", + actionDescription: "Extract to typedef", + newContent: `/** + * @template {number} T + * @template {string} U + * @typedef {T | U} /*RENAME*/NewType + */ + +/** + * @template {number} T + * @template {string} U + * @param {T} b + * @param {U} c + * @returns {NewType} + */ +function a(b, c) {}`, +}); diff --git a/tests/cases/fourslash/refactorExtractType_js6.ts b/tests/cases/fourslash/refactorExtractType_js6.ts new file mode 100644 index 0000000000000..581e2cca1db24 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType_js6.ts @@ -0,0 +1,32 @@ +/// + +// @allowJs: true +// @Filename: a.js +//// /** +//// * @template {number} T, U +//// * @param {T} b +//// * @param {U} c +//// * @returns {/*a*/T | U/*b*/} +//// */ +//// function a(b, c) {} + +goTo.file('a.js') +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to typedef", + actionDescription: "Extract to typedef", + newContent: `/** + * @template {number} T + * @template U + * @typedef {T | U} /*RENAME*/NewType + */ + +/** + * @template {number} T, U + * @param {T} b + * @param {U} c + * @returns {NewType} + */ +function a(b, c) {}`, +}); From a861ec4fc373d072d617d1747ebabc105c742982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 1 Apr 2019 16:28:20 +0800 Subject: [PATCH 16/19] add more tests --- src/services/refactors/extractType.ts | 22 ++++++++++++++----- .../cases/fourslash/refactorExtractType47.ts | 1 + .../cases/fourslash/refactorExtractType50.ts | 6 +++++ .../cases/fourslash/refactorExtractType51.ts | 6 +++++ .../cases/fourslash/refactorExtractType52.ts | 6 +++++ .../cases/fourslash/refactorExtractType53.ts | 13 +++++++++++ .../cases/fourslash/refactorExtractType54.ts | 13 +++++++++++ .../cases/fourslash/refactorExtractType55.ts | 13 +++++++++++ 8 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType50.ts create mode 100644 tests/cases/fourslash/refactorExtractType51.ts create mode 100644 tests/cases/fourslash/refactorExtractType52.ts create mode 100644 tests/cases/fourslash/refactorExtractType53.ts create mode 100644 tests/cases/fourslash/refactorExtractType54.ts create mode 100644 tests/cases/fourslash/refactorExtractType55.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 5a01d0fd3c094..3eb70fcaf9744 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -95,11 +95,23 @@ namespace ts.refactor { hasError = true; return; } - else if (isTypeQueryNode(node) && isIdentifier(node.exprName)) { - const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false); - if (symbol && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) { - hasError = true; - return; + else if (isThisTypeNode(node) && !rangeContainsSkipTrivia(selection, node.parent, file)) { + hasError = true; + return; + } + else if (isTypeQueryNode(node)) { + if (isIdentifier(node.exprName)) { + const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false); + if (symbol && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) { + hasError = true; + return; + } + } + else { + if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selection, node.parent, file)) { + hasError = true; + return; + } } } forEachChild(node, visitor); diff --git a/tests/cases/fourslash/refactorExtractType47.ts b/tests/cases/fourslash/refactorExtractType47.ts index 360c6eef69620..e2062029fa327 100644 --- a/tests/cases/fourslash/refactorExtractType47.ts +++ b/tests/cases/fourslash/refactorExtractType47.ts @@ -2,4 +2,5 @@ //// type Crazy = T extends [infer P, (/*a*/infer R extends string ? string : never/*b*/)] ? P & R : string; +goTo.select("a", "b"); verify.not.refactorAvailable("Extract type") \ No newline at end of file diff --git a/tests/cases/fourslash/refactorExtractType50.ts b/tests/cases/fourslash/refactorExtractType50.ts new file mode 100644 index 0000000000000..8405c15c912ed --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType50.ts @@ -0,0 +1,6 @@ +/// + +//// interface I { f: (this: O, b: number) => /*a*/typeof this.a/*b*/ }; + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type") diff --git a/tests/cases/fourslash/refactorExtractType51.ts b/tests/cases/fourslash/refactorExtractType51.ts new file mode 100644 index 0000000000000..929898ad58e9f --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType51.ts @@ -0,0 +1,6 @@ +/// + +//// interface I { f: (this: O, b: number) => /*a*/this/*b*/ }; + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type") diff --git a/tests/cases/fourslash/refactorExtractType52.ts b/tests/cases/fourslash/refactorExtractType52.ts new file mode 100644 index 0000000000000..1ded541f88bfa --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType52.ts @@ -0,0 +1,6 @@ +/// + +//// interface I { f: (this: O, b: number) => /*a*/typeof this["a"]/*b*/ }; + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type") diff --git a/tests/cases/fourslash/refactorExtractType53.ts b/tests/cases/fourslash/refactorExtractType53.ts new file mode 100644 index 0000000000000..070d4b8c104d9 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType53.ts @@ -0,0 +1,13 @@ +/// + +//// interface I { f: /*a*/(this: O, b: number) => typeof this.a/*b*/ }; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = (this: O, b: number) => typeof this.a; + +interface I { f: NewType };`, +}); diff --git a/tests/cases/fourslash/refactorExtractType54.ts b/tests/cases/fourslash/refactorExtractType54.ts new file mode 100644 index 0000000000000..4b35634119849 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType54.ts @@ -0,0 +1,13 @@ +/// + +//// interface I { f: /*a*/(this: O, b: number) => this/*b*/ }; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = (this: O, b: number) => this; + +interface I { f: NewType };`, +}); diff --git a/tests/cases/fourslash/refactorExtractType55.ts b/tests/cases/fourslash/refactorExtractType55.ts new file mode 100644 index 0000000000000..61ddef7f44317 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType55.ts @@ -0,0 +1,13 @@ +/// + +//// interface I { f: /*a*/(this: O, b: number) => typeof this["a"]/*b*/ }; + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `type /*RENAME*/NewType = (this: O, b: number) => typeof this["a"]; + +interface I { f: NewType };`, +}); From 788d0d209047595b726cebb31dc39090b153c4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Mon, 1 Apr 2019 16:34:35 +0800 Subject: [PATCH 17/19] merge branch --- src/services/refactors/extractType.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 3eb70fcaf9744..942760d092bb0 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -91,11 +91,7 @@ namespace ts.refactor { return; } } - else if (isTypePredicateNode(node) && !rangeContainsSkipTrivia(selection, node.parent, file)) { - hasError = true; - return; - } - else if (isThisTypeNode(node) && !rangeContainsSkipTrivia(selection, node.parent, file)) { + else if ((isTypePredicateNode(node) || isThisTypeNode(node)) && !rangeContainsSkipTrivia(selection, node.parent, file)) { hasError = true; return; } From ee0915745ba00e6827a6f02ac38dabb61591d8ec Mon Sep 17 00:00:00 2001 From: kingwl Date: Sun, 7 Apr 2019 21:44:45 +0800 Subject: [PATCH 18/19] add more tests --- .../cases/fourslash/refactorExtractType56.ts | 7 +++++ .../cases/fourslash/refactorExtractType57.ts | 27 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 tests/cases/fourslash/refactorExtractType56.ts create mode 100644 tests/cases/fourslash/refactorExtractType57.ts diff --git a/tests/cases/fourslash/refactorExtractType56.ts b/tests/cases/fourslash/refactorExtractType56.ts new file mode 100644 index 0000000000000..6362a1a995a69 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType56.ts @@ -0,0 +1,7 @@ +/// + +// typeof other parameters within function signature? +//// function f(a: string, b: /*a*/typeof a/*b*/): typeof b { return ''; } + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type") diff --git a/tests/cases/fourslash/refactorExtractType57.ts b/tests/cases/fourslash/refactorExtractType57.ts new file mode 100644 index 0000000000000..7033e5e208613 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType57.ts @@ -0,0 +1,27 @@ +/// + +// Where do lines get inserted? +// The exact structure here doesn't matter, +// just want to see something within a block body +// to have the behavior defined in tests. +//// function id(x: T): T { +//// return (() => { +//// const s: /*a*/typeof x/*b*/ = x; +//// return s; +//// })(); +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Extract type", + actionName: "Extract to type alias", + actionDescription: "Extract to type alias", + newContent: `function id(x: T): T { + return (() => { + type /*RENAME*/NewType = typeof x; + + const s: NewType = x; + return s; + })(); +}`, +}); From bff56b725e26ef37cded48e27e31e222a14b307e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Tue, 7 May 2019 16:39:56 +0800 Subject: [PATCH 19/19] refactor and update function name --- src/services/refactors/extractType.ts | 39 ++++++++----------- .../cases/fourslash/refactorExtractType58.ts | 6 +++ .../cases/fourslash/refactorExtractType59.ts | 10 +++++ 3 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 tests/cases/fourslash/refactorExtractType58.ts create mode 100644 tests/cases/fourslash/refactorExtractType59.ts diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 942760d092bb0..02f6cd39a1885 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -47,32 +47,26 @@ namespace ts.refactor { if (!selection || !isTypeNode(selection)) return undefined; const checker = context.program.getTypeChecker(); - const firstStatement = Debug.assertDefined(isJS ? findAncestor(selection, isStatementButNotBlockAndHasJSDoc) : findAncestor(selection, isStatementButNotBlock)); - const typeParameters = checkAndCollectionTypeArguments(checker, selection, firstStatement, file); + const firstStatement = Debug.assertDefined(isJS ? findAncestor(selection, isStatementAndHasJSDoc) : findAncestor(selection, isStatement)); + const typeParameters = collectTypeParameters(checker, selection, firstStatement, file); if (!typeParameters) return undefined; return { isJS, selection, firstStatement, typeParameters }; } - function isStatementButNotBlock(n: Node): n is Statement { - return n && isStatement(n) && !isBlock(n); - } - - function isStatementButNotBlockAndHasJSDoc(n: Node): n is (Statement & HasJSDoc) { - return isStatementButNotBlock(n) && hasJSDocNodes(n); + function isStatementAndHasJSDoc(n: Node): n is (Statement & HasJSDoc) { + return isStatement(n) && hasJSDocNodes(n); } function rangeContainsSkipTrivia(r1: TextRange, node: Node, file: SourceFile): boolean { return rangeContainsStartEnd(r1, skipTrivia(file.text, node.pos), node.end); } - function checkAndCollectionTypeArguments(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { - let hasError = false; + function collectTypeParameters(checker: TypeChecker, selection: TypeNode, statement: Statement, file: SourceFile): TypeParameterDeclaration[] | undefined { const result: TypeParameterDeclaration[] = []; - visitor(selection); - return hasError ? undefined : result; + return visitor(selection) ? undefined : result; - function visitor(node: Node) { + function visitor(node: Node): true | undefined { if (isTypeReferenceNode(node)) { if (isIdentifier(node.typeName)) { const symbol = checker.resolveName(node.typeName.text, node.typeName, SymbolFlags.TypeParameter, /* excludeGlobals */ true); @@ -87,30 +81,29 @@ namespace ts.refactor { else if (isInferTypeNode(node)) { const conditionalTypeNode = findAncestor(node, n => isConditionalTypeNode(n) && rangeContainsSkipTrivia(n.extendsType, node, file)); if (!conditionalTypeNode || !rangeContainsSkipTrivia(selection, conditionalTypeNode, file)) { - hasError = true; - return; + return true; } } - else if ((isTypePredicateNode(node) || isThisTypeNode(node)) && !rangeContainsSkipTrivia(selection, node.parent, file)) { - hasError = true; - return; + else if ((isTypePredicateNode(node) || isThisTypeNode(node))) { + const functionLikeNode = findAncestor(node.parent, isFunctionLike); + if (functionLikeNode && functionLikeNode.type && rangeContainsSkipTrivia(functionLikeNode.type, node, file) && !rangeContainsSkipTrivia(selection, functionLikeNode, file)) { + return true; + } } else if (isTypeQueryNode(node)) { if (isIdentifier(node.exprName)) { const symbol = checker.resolveName(node.exprName.text, node.exprName, SymbolFlags.Value, /* excludeGlobals */ false); if (symbol && rangeContainsSkipTrivia(statement, symbol.valueDeclaration, file) && !rangeContainsSkipTrivia(selection, symbol.valueDeclaration, file)) { - hasError = true; - return; + return true; } } else { if (isThisIdentifier(node.exprName.left) && !rangeContainsSkipTrivia(selection, node.parent, file)) { - hasError = true; - return; + return true; } } } - forEachChild(node, visitor); + return forEachChild(node, visitor); } } diff --git a/tests/cases/fourslash/refactorExtractType58.ts b/tests/cases/fourslash/refactorExtractType58.ts new file mode 100644 index 0000000000000..54e58153f2b5f --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType58.ts @@ -0,0 +1,6 @@ +/// + +//// interface I { f: (this: O, b: number) => /*a*/ true | this | false /*b*/ }; + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type") \ No newline at end of file diff --git a/tests/cases/fourslash/refactorExtractType59.ts b/tests/cases/fourslash/refactorExtractType59.ts new file mode 100644 index 0000000000000..d5fced9611bf3 --- /dev/null +++ b/tests/cases/fourslash/refactorExtractType59.ts @@ -0,0 +1,10 @@ +/// + +//// class C { +//// m(): /*a*/T | this | number/*b*/ { +//// return {} as any +//// } +//// } + +goTo.select("a", "b"); +verify.not.refactorAvailable("Extract type")