From 95c3ca5c967d4fd1868245e61d7c1020664814e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=96=87=E7=92=90?= Date: Fri, 23 Feb 2018 17:42:39 +0800 Subject: [PATCH] add refactor of convert private field to getter and setter --- src/compiler/diagnosticMessages.json | 4 + .../refactors/ConvertToGetterAndSetter.ts | 151 ++++++++++++++++++ src/services/refactors/refactors.ts | 1 + ...refactorConvertToGetAccessAndSetAccess1.ts | 21 +++ ...efactorConvertToGetAccessAndSetAccess10.ts | 23 +++ ...efactorConvertToGetAccessAndSetAccess11.ts | 21 +++ ...refactorConvertToGetAccessAndSetAccess2.ts | 21 +++ ...refactorConvertToGetAccessAndSetAccess3.ts | 21 +++ ...refactorConvertToGetAccessAndSetAccess4.ts | 21 +++ ...refactorConvertToGetAccessAndSetAccess5.ts | 21 +++ ...refactorConvertToGetAccessAndSetAccess6.ts | 21 +++ ...refactorConvertToGetAccessAndSetAccess7.ts | 21 +++ ...refactorConvertToGetAccessAndSetAccess8.ts | 21 +++ ...refactorConvertToGetAccessAndSetAccess9.ts | 23 +++ 14 files changed, 391 insertions(+) create mode 100644 src/services/refactors/ConvertToGetterAndSetter.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess1.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess10.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess11.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess2.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess3.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess4.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess5.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess6.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess7.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess8.ts create mode 100644 tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess9.ts diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index f120e81283a76..dcf0530241509 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -3965,5 +3965,9 @@ "Convert to ES6 module": { "category": "Message", "code": 95017 + }, + "Generate 'get' and 'set' accessors": { + "category": "Message", + "code": 95018 } } diff --git a/src/services/refactors/ConvertToGetterAndSetter.ts b/src/services/refactors/ConvertToGetterAndSetter.ts new file mode 100644 index 0000000000000..0c5cc6710931f --- /dev/null +++ b/src/services/refactors/ConvertToGetterAndSetter.ts @@ -0,0 +1,151 @@ +/* @internal */ +namespace ts.refactor.ConvertGetterAndSetter { + const actionName = "Generate 'get' and 'set' accessors"; + const actionDescription = Diagnostics.Generate_get_and_set_accessors.message; + + registerRefactor(actionName, { getEditsForAction, getAvailableActions }); + + function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined { + const { file, startPosition } = context; + + const fieldInfo = getConvertibleFieldAtPosition(file, startPosition); + if (!fieldInfo) return undefined; + + return [ + { + name: actionName, + description: actionDescription, + actions: [ + { + name: actionName, + description: actionDescription + } + ] + } + ]; + } + + function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined { + const { file, startPosition } = context; + + const fieldInfo = getConvertibleFieldAtPosition(file, startPosition); + if (!fieldInfo) return undefined; + + const changeTracker = textChanges.ChangeTracker.fromContext(context); + const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options); + + const { fieldName, accessorName, propertyDeclaration, needUpdateName, hasModifiers, needUpdateModifiers } = fieldInfo; + const accessorModifiers = hasModifiers ? createNodeArray([createToken(SyntaxKind.PublicKeyword)]) : undefined; + + const getAccessor = generateGetAccessor(propertyDeclaration, fieldName, accessorName, accessorModifiers); + const setAccessor = generateSetAccessor(propertyDeclaration, fieldName, accessorName, accessorModifiers); + + const modifiers = needUpdateModifiers ? createNodeArray([createToken(SyntaxKind.PrivateKeyword)]) : propertyDeclaration.modifiers; + if (needUpdateName || needUpdateModifiers) { + changeTracker.replaceNode(file, propertyDeclaration, updateOriginPropertyDeclaration(propertyDeclaration, fieldName, modifiers), { + suffix: newLineCharacter + }); + } + + changeTracker.insertNodeAfter(file, propertyDeclaration, getAccessor); + changeTracker.insertNodeAfter(file, propertyDeclaration, setAccessor); + + return { + edits: changeTracker.getChanges(), + renameFilename: undefined, + renameLocation: undefined, + }; + } + + interface Info { originName: string; fieldName: string; accessorName: string; propertyDeclaration: PropertyDeclaration; needUpdateName: boolean; hasModifiers: boolean; needUpdateModifiers: boolean; } + function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined { + const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false); + const propertyDeclaration = findAncestor(node.parent, isPropertyDeclaration); + + if (!(propertyDeclaration && propertyDeclaration.name.kind === SyntaxKind.Identifier && + (getModifierFlags(propertyDeclaration) | ModifierFlags.AccessibilityModifier) === ModifierFlags.AccessibilityModifier)) return undefined; + + const containerClass = getContainingClass(propertyDeclaration); + if (!containerClass) return undefined; + + const members = getMembersOfDeclaration(containerClass); + if (!members) return undefined; + + const needUpdateName = propertyDeclaration.name.text.charCodeAt(0) !== CharacterCodes._; + + const accessorName = needUpdateName ? propertyDeclaration.name.text : propertyDeclaration.name.text.substring(1); + const fieldName = `_${accessorName}`; + + if (find(members, member => needUpdateName ? member.name.getText() === fieldName : member.name.getText() === accessorName)) return undefined; + + const hasModifiers = !!find(members, member => !!member.modifiers); + const needUpdateModifiers = hasModifiers && (!propertyDeclaration.modifiers || hasModifier(propertyDeclaration, ModifierFlags.Public)); + + return { + originName: propertyDeclaration.name.text, + fieldName, + accessorName, + propertyDeclaration, + needUpdateName, + hasModifiers, + needUpdateModifiers + }; + } + + function generateGetAccessor (propertyDeclaration: PropertyDeclaration, fieldName: string, name: string, modifiers: ModifiersArray) { + return createGetAccessor( + /*decorators*/ undefined, + modifiers, + name, + /*parameters*/ undefined, + propertyDeclaration.type, + createBlock([ + createReturn( + createPropertyAccess( + createThis(), + fieldName + ) + ) + ], /*multiLine*/ true) + ); + } + + function generateSetAccessor (propertyDeclaration: PropertyDeclaration, fieldName: string, name: string, modifiers: ModifiersArray) { + return createSetAccessor( + /*decorators*/ undefined, + modifiers, + name, + [createParameter( + /*decorators*/ undefined, + /*modifies*/ undefined, + /*dotDotDotToken*/ undefined, + createIdentifier("value"), + /*questionToken*/ undefined, + propertyDeclaration.type + )], + createBlock([ + createStatement( + createAssignment( + createPropertyAccess( + createThis(), + fieldName + ), + createIdentifier("value") + ) + ) + ], /*multiLine*/ true) + ); + } + + function updateOriginPropertyDeclaration (propertyDeclaration: PropertyDeclaration, fieldName: string, modifiers: ModifiersArray) { + return updateProperty( + propertyDeclaration, + /*decorators*/ undefined, + modifiers, + fieldName, + /*questionOrExclamationToken*/ undefined, + propertyDeclaration.type, + propertyDeclaration.initializer, + ); + } +} diff --git a/src/services/refactors/refactors.ts b/src/services/refactors/refactors.ts index 8b4561700d554..48a3870b9a1ea 100644 --- a/src/services/refactors/refactors.ts +++ b/src/services/refactors/refactors.ts @@ -4,3 +4,4 @@ /// /// /// +/// diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess1.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess1.ts new file mode 100644 index 0000000000000..90b6dce8aa10a --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess1.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/public a: string;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + private _a: string; + public get a(): string { + return this._a; + } + public set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess10.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess10.ts new file mode 100644 index 0000000000000..8fa18e861b480 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess10.ts @@ -0,0 +1,23 @@ +/// + +//// class A { +//// public a: string; +//// /*a*/b: number;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + public a: string; + private _b: number; + public get b(): number { + return this._b; + } + public set b(value: number) { + this._b = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess11.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess11.ts new file mode 100644 index 0000000000000..91e209a5d263f --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess11.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/public a: string = "foo";/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + private _a: string = "foo"; + public get a(): string { + return this._a; + } + public set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess2.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess2.ts new file mode 100644 index 0000000000000..aa8c77313dd97 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess2.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/protected a: string;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + protected _a: string; + public get a(): string { + return this._a; + } + public set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess3.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess3.ts new file mode 100644 index 0000000000000..4f26ed33366cf --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess3.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/private a: string;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + private _a: string; + public get a(): string { + return this._a; + } + public set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess4.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess4.ts new file mode 100644 index 0000000000000..1597244e29f4a --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess4.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/private _a: string;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + private _a: string; + public get a(): string { + return this._a; + } + public set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess5.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess5.ts new file mode 100644 index 0000000000000..16edc3515d379 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess5.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/protected _a: string;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + protected _a: string; + public get a(): string { + return this._a; + } + public set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess6.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess6.ts new file mode 100644 index 0000000000000..476a1f851e05b --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess6.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/public _a: string;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + private _a: string; + public get a(): string { + return this._a; + } + public set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess7.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess7.ts new file mode 100644 index 0000000000000..e2f2022db8c63 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess7.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/_a: string;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + _a: string; + get a(): string { + return this._a; + } + set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess8.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess8.ts new file mode 100644 index 0000000000000..c6bfda108664c --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess8.ts @@ -0,0 +1,21 @@ +/// + +//// class A { +//// /*a*/a: string;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + _a: string; + get a(): string { + return this._a; + } + set a(value: string) { + this._a = value; + } +}`, +}); diff --git a/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess9.ts b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess9.ts new file mode 100644 index 0000000000000..be4c47967a570 --- /dev/null +++ b/tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess9.ts @@ -0,0 +1,23 @@ +/// + +//// class A { +//// a: string; +//// /*a*/b: number;/*b*/ +//// } + +goTo.select("a", "b"); +edit.applyRefactor({ + refactorName: "Generate 'get' and 'set' accessors", + actionName: "Generate 'get' and 'set' accessors", + actionDescription: "Generate 'get' and 'set' accessors", + newContent: `class A { + a: string; + _b: number; + get b(): number { + return this._b; + } + set b(value: number) { + this._b = value; + } +}`, +});