Skip to content

Commit

Permalink
add refactor of convert private field to getter and setter
Browse files Browse the repository at this point in the history
  • Loading branch information
Kingwl committed Feb 24, 2018
1 parent e8fb587 commit 95c3ca5
Show file tree
Hide file tree
Showing 14 changed files with 391 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3965,5 +3965,9 @@
"Convert to ES6 module": {
"category": "Message",
"code": 95017
},
"Generate 'get' and 'set' accessors": {
"category": "Message",
"code": 95018
}
}
151 changes: 151 additions & 0 deletions src/services/refactors/ConvertToGetterAndSetter.ts
Original file line number Diff line number Diff line change
@@ -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,
);
}
}
1 change: 1 addition & 0 deletions src/services/refactors/refactors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/// <reference path="extractSymbol.ts" />
/// <reference path="installTypesForPackage.ts" />
/// <reference path="useDefaultImport.ts" />
/// <reference path="ConvertToGetterAndSetter.ts" />
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess1.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
23 changes: 23 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess10.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess11.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess5.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess6.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess7.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
21 changes: 21 additions & 0 deletions tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess8.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />

//// 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;
}
}`,
});
Loading

0 comments on commit 95c3ca5

Please sign in to comment.