Skip to content

Commit 5133b68

Browse files
committed
add refactor of convert private field to getter and setter
1 parent b31aa4e commit 5133b68

14 files changed

+391
-0
lines changed

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -3977,5 +3977,9 @@
39773977
"Add definite assignment assertion to property '{0}'": {
39783978
"category": "Message",
39793979
"code": 95020
3980+
},
3981+
"Generate 'get' and 'set' accessors": {
3982+
"category": "Message",
3983+
"code": 95021
39803984
}
39813985
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/* @internal */
2+
namespace ts.refactor.GenerateGetterAndSetter {
3+
const actionName = "Generate 'get' and 'set' accessors";
4+
const actionDescription = Diagnostics.Generate_get_and_set_accessors.message;
5+
6+
registerRefactor(actionName, { getEditsForAction, getAvailableActions });
7+
8+
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
9+
const { file, startPosition } = context;
10+
11+
const fieldInfo = getConvertibleFieldAtPosition(file, startPosition);
12+
if (!fieldInfo) return undefined;
13+
14+
return [
15+
{
16+
name: actionName,
17+
description: actionDescription,
18+
actions: [
19+
{
20+
name: actionName,
21+
description: actionDescription
22+
}
23+
]
24+
}
25+
];
26+
}
27+
28+
function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined {
29+
const { file, startPosition } = context;
30+
31+
const fieldInfo = getConvertibleFieldAtPosition(file, startPosition);
32+
if (!fieldInfo) return undefined;
33+
34+
const changeTracker = textChanges.ChangeTracker.fromContext(context);
35+
const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options);
36+
37+
const { fieldName, accessorName, propertyDeclaration, needUpdateName, hasModifiers, needUpdateModifiers } = fieldInfo;
38+
const accessorModifiers = hasModifiers ? createNodeArray([createToken(SyntaxKind.PublicKeyword)]) : undefined;
39+
40+
const getAccessor = generateGetAccessor(propertyDeclaration, fieldName, accessorName, accessorModifiers);
41+
const setAccessor = generateSetAccessor(propertyDeclaration, fieldName, accessorName, accessorModifiers);
42+
43+
const modifiers = needUpdateModifiers ? createNodeArray([createToken(SyntaxKind.PrivateKeyword)]) : propertyDeclaration.modifiers;
44+
if (needUpdateName || needUpdateModifiers) {
45+
changeTracker.replaceNode(file, propertyDeclaration, updateOriginPropertyDeclaration(propertyDeclaration, fieldName, modifiers), {
46+
suffix: newLineCharacter
47+
});
48+
}
49+
50+
changeTracker.insertNodeAfter(file, propertyDeclaration, getAccessor);
51+
changeTracker.insertNodeAfter(file, propertyDeclaration, setAccessor);
52+
53+
return {
54+
edits: changeTracker.getChanges(),
55+
renameFilename: undefined,
56+
renameLocation: undefined,
57+
};
58+
}
59+
60+
interface Info { originName: string; fieldName: string; accessorName: string; propertyDeclaration: PropertyDeclaration; needUpdateName: boolean; hasModifiers: boolean; needUpdateModifiers: boolean; }
61+
function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined {
62+
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
63+
const propertyDeclaration = findAncestor(node.parent, isPropertyDeclaration);
64+
65+
if (!(propertyDeclaration && propertyDeclaration.name.kind === SyntaxKind.Identifier &&
66+
(getModifierFlags(propertyDeclaration) | ModifierFlags.AccessibilityModifier) === ModifierFlags.AccessibilityModifier)) return undefined;
67+
68+
const containerClass = getContainingClass(propertyDeclaration);
69+
if (!containerClass) return undefined;
70+
71+
const members = getMembersOfDeclaration(containerClass);
72+
if (!members) return undefined;
73+
74+
const needUpdateName = propertyDeclaration.name.text.charCodeAt(0) !== CharacterCodes._;
75+
76+
const accessorName = needUpdateName ? propertyDeclaration.name.text : propertyDeclaration.name.text.substring(1);
77+
const fieldName = `_${accessorName}`;
78+
79+
if (find(members, member => needUpdateName ? member.name.getText() === fieldName : member.name.getText() === accessorName)) return undefined;
80+
81+
const hasModifiers = !!find(members, member => !!member.modifiers);
82+
const needUpdateModifiers = hasModifiers && (!propertyDeclaration.modifiers || hasModifier(propertyDeclaration, ModifierFlags.Public));
83+
84+
return {
85+
originName: propertyDeclaration.name.text,
86+
fieldName,
87+
accessorName,
88+
propertyDeclaration,
89+
needUpdateName,
90+
hasModifiers,
91+
needUpdateModifiers
92+
};
93+
}
94+
95+
function generateGetAccessor (propertyDeclaration: PropertyDeclaration, fieldName: string, name: string, modifiers: ModifiersArray) {
96+
return createGetAccessor(
97+
/*decorators*/ undefined,
98+
modifiers,
99+
name,
100+
/*parameters*/ undefined,
101+
propertyDeclaration.type,
102+
createBlock([
103+
createReturn(
104+
createPropertyAccess(
105+
createThis(),
106+
fieldName
107+
)
108+
)
109+
], /*multiLine*/ true)
110+
);
111+
}
112+
113+
function generateSetAccessor (propertyDeclaration: PropertyDeclaration, fieldName: string, name: string, modifiers: ModifiersArray) {
114+
return createSetAccessor(
115+
/*decorators*/ undefined,
116+
modifiers,
117+
name,
118+
[createParameter(
119+
/*decorators*/ undefined,
120+
/*modifies*/ undefined,
121+
/*dotDotDotToken*/ undefined,
122+
createIdentifier("value"),
123+
/*questionToken*/ undefined,
124+
propertyDeclaration.type
125+
)],
126+
createBlock([
127+
createStatement(
128+
createAssignment(
129+
createPropertyAccess(
130+
createThis(),
131+
fieldName
132+
),
133+
createIdentifier("value")
134+
)
135+
)
136+
], /*multiLine*/ true)
137+
);
138+
}
139+
140+
function updateOriginPropertyDeclaration (propertyDeclaration: PropertyDeclaration, fieldName: string, modifiers: ModifiersArray) {
141+
return updateProperty(
142+
propertyDeclaration,
143+
/*decorators*/ undefined,
144+
modifiers,
145+
fieldName,
146+
/*questionOrExclamationToken*/ undefined,
147+
propertyDeclaration.type,
148+
propertyDeclaration.initializer,
149+
);
150+
}
151+
}

src/services/refactors/refactors.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/// <reference path="extractSymbol.ts" />
55
/// <reference path="installTypesForPackage.ts" />
66
/// <reference path="useDefaultImport.ts" />
7+
/// <reference path="GenerateGetterAndSetter.ts" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/public a: string;/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
private _a: string;
14+
public get a(): string {
15+
return this._a;
16+
}
17+
public set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// public a: string;
5+
//// /*a*/b: number;/*b*/
6+
//// }
7+
8+
goTo.select("a", "b");
9+
edit.applyRefactor({
10+
refactorName: "Generate 'get' and 'set' accessors",
11+
actionName: "Generate 'get' and 'set' accessors",
12+
actionDescription: "Generate 'get' and 'set' accessors",
13+
newContent: `class A {
14+
public a: string;
15+
private _b: number;
16+
public get b(): number {
17+
return this._b;
18+
}
19+
public set b(value: number) {
20+
this._b = value;
21+
}
22+
}`,
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/public a: string = "foo";/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
private _a: string = "foo";
14+
public get a(): string {
15+
return this._a;
16+
}
17+
public set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/protected a: string;/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
protected _a: string;
14+
public get a(): string {
15+
return this._a;
16+
}
17+
public set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/private a: string;/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
private _a: string;
14+
public get a(): string {
15+
return this._a;
16+
}
17+
public set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/private _a: string;/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
private _a: string;
14+
public get a(): string {
15+
return this._a;
16+
}
17+
public set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/protected _a: string;/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
protected _a: string;
14+
public get a(): string {
15+
return this._a;
16+
}
17+
public set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/public _a: string;/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
private _a: string;
14+
public get a(): string {
15+
return this._a;
16+
}
17+
public set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/_a: string;/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
_a: string;
14+
get a(): string {
15+
return this._a;
16+
}
17+
set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// /*a*/a: string;/*b*/
5+
//// }
6+
7+
goTo.select("a", "b");
8+
edit.applyRefactor({
9+
refactorName: "Generate 'get' and 'set' accessors",
10+
actionName: "Generate 'get' and 'set' accessors",
11+
actionDescription: "Generate 'get' and 'set' accessors",
12+
newContent: `class A {
13+
_a: string;
14+
get a(): string {
15+
return this._a;
16+
}
17+
set a(value: string) {
18+
this._a = value;
19+
}
20+
}`,
21+
});

0 commit comments

Comments
 (0)