Skip to content

Commit 9c0671d

Browse files
KingwlAndy
authored and
Andy
committed
add refactor of convert private field to getter and setter (#22143)
* add refactor of convert private field to getter and setter * fix refactor * stash * refactor accessor generate * revert merge union type * refeactor and accept baseline * add support of PropertyAssignment and StringLiteral * add support for js file * allow static modifier in js file
1 parent 556a801 commit 9c0671d

File tree

52 files changed

+1608
-459
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1608
-459
lines changed

package-lock.json

+368-415
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/compiler/core.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3122,8 +3122,8 @@ namespace ts {
31223122
return (arg: T) => f(arg) && g(arg);
31233123
}
31243124

3125-
export function or<T>(f: (arg: T) => boolean, g: (arg: T) => boolean) {
3126-
return (arg: T) => f(arg) || g(arg);
3125+
export function or<T>(f: (arg: T) => boolean, g: (arg: T) => boolean, ...others: ((arg: T) => boolean)[]) {
3126+
return (arg: T) => f(arg) || g(arg) || others.some(f => f(arg));
31273127
}
31283128

31293129
export function assertTypeIsNever(_: never): void { } // tslint:disable-line no-empty

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -4161,5 +4161,9 @@
41614161
"Convert all constructor functions to classes": {
41624162
"category": "Message",
41634163
"code": 95045
4164+
},
4165+
"Generate 'get' and 'set' accessors": {
4166+
"category": "Message",
4167+
"code": 95046
41644168
}
41654169
}

src/compiler/utilities.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4273,7 +4273,7 @@ namespace ts {
42734273
}
42744274
}
42754275

4276-
export function isParameterPropertyDeclaration(node: Node): boolean {
4276+
export function isParameterPropertyDeclaration(node: Node): node is ParameterDeclaration {
42774277
return hasModifier(node, ModifierFlags.ParameterPropertyModifier) && node.parent.kind === SyntaxKind.Constructor && isClassLike(node.parent.parent);
42784278
}
42794279

src/services/refactors/extractSymbol.ts

-39
Original file line numberDiff line numberDiff line change
@@ -701,14 +701,6 @@ namespace ts.refactor.extractSymbol {
701701
Global,
702702
}
703703

704-
function getUniqueName(baseName: string, fileText: string): string {
705-
let nameText = baseName;
706-
for (let i = 1; stringContains(fileText, nameText); i++) {
707-
nameText = `${baseName}_${i}`;
708-
}
709-
return nameText;
710-
}
711-
712704
/**
713705
* Result of 'extractRange' operation for a specific scope.
714706
* Stores either a list of changes that should be applied to extract a range or a list of errors
@@ -1129,37 +1121,6 @@ namespace ts.refactor.extractSymbol {
11291121
}
11301122
}
11311123

1132-
/**
1133-
* @return The index of the (only) reference to the extracted symbol. We want the cursor
1134-
* to be on the reference, rather than the declaration, because it's closer to where the
1135-
* user was before extracting it.
1136-
*/
1137-
function getRenameLocation(edits: ReadonlyArray<FileTextChanges>, renameFilename: string, functionNameText: string, isDeclaredBeforeUse: boolean): number {
1138-
let delta = 0;
1139-
let lastPos = -1;
1140-
for (const { fileName, textChanges } of edits) {
1141-
Debug.assert(fileName === renameFilename);
1142-
for (const change of textChanges) {
1143-
const { span, newText } = change;
1144-
const index = newText.indexOf(functionNameText);
1145-
if (index !== -1) {
1146-
lastPos = span.start + delta + index;
1147-
1148-
// If the reference comes first, return immediately.
1149-
if (!isDeclaredBeforeUse) {
1150-
return lastPos;
1151-
}
1152-
}
1153-
delta += newText.length - span.length;
1154-
}
1155-
}
1156-
1157-
// If the declaration comes first, return the position of the last occurrence.
1158-
Debug.assert(isDeclaredBeforeUse);
1159-
Debug.assert(lastPos >= 0);
1160-
return lastPos;
1161-
}
1162-
11631124
function getFirstDeclaration(type: Type): Declaration | undefined {
11641125
let firstDeclaration;
11651126

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
/* @internal */
2+
namespace ts.refactor.generateGetAccessorAndSetAccessor {
3+
const actionName = "Generate 'get' and 'set' accessors";
4+
const actionDescription = Diagnostics.Generate_get_and_set_accessors.message;
5+
registerRefactor(actionName, { getEditsForAction, getAvailableActions });
6+
7+
type AccepedDeclaration = ParameterDeclaration | PropertyDeclaration | PropertyAssignment;
8+
type AccepedNameType = Identifier | StringLiteral;
9+
type ContainerDeclation = ClassLikeDeclaration | ObjectLiteralExpression;
10+
11+
interface DeclarationInfo {
12+
container: ContainerDeclation;
13+
isStatic: boolean;
14+
type: TypeNode | undefined;
15+
}
16+
17+
interface Info extends DeclarationInfo {
18+
declaration: AccepedDeclaration;
19+
fieldName: AccepedNameType;
20+
accessorName: AccepedNameType;
21+
}
22+
23+
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
24+
const { file, startPosition } = context;
25+
if (!getConvertibleFieldAtPosition(file, startPosition)) return undefined;
26+
27+
return [{
28+
name: actionName,
29+
description: actionDescription,
30+
actions: [
31+
{
32+
name: actionName,
33+
description: actionDescription
34+
}
35+
]
36+
}];
37+
}
38+
39+
function getEditsForAction(context: RefactorContext, _actionName: string): RefactorEditInfo | undefined {
40+
const { file, startPosition } = context;
41+
42+
const fieldInfo = getConvertibleFieldAtPosition(file, startPosition);
43+
if (!fieldInfo) return undefined;
44+
45+
const isJS = isSourceFileJavaScript(file);
46+
const changeTracker = textChanges.ChangeTracker.fromContext(context);
47+
const { isStatic, fieldName, accessorName, type, container, declaration } = fieldInfo;
48+
49+
const isInClassLike = isClassLike(container);
50+
const accessorModifiers = getAccessorModifiers(isJS, declaration, isStatic, isInClassLike);
51+
const fieldModifiers = getFieldModifiers(isJS, isStatic, isInClassLike);
52+
53+
updateFieldDeclaration(changeTracker, file, declaration, fieldName, fieldModifiers, container);
54+
55+
const getAccessor = generateGetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
56+
const setAccessor = generateSetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
57+
58+
insertAccessor(changeTracker, file, getAccessor, declaration, container);
59+
insertAccessor(changeTracker, file, setAccessor, declaration, container);
60+
61+
const edits = changeTracker.getChanges();
62+
const renameFilename = file.fileName;
63+
const renameLocationOffset = isIdentifier(fieldName) ? 0 : -1;
64+
const renameLocation = renameLocationOffset + getRenameLocation(edits, renameFilename, fieldName.text, /*isDeclaredBeforeUse*/ false);
65+
return { renameFilename, renameLocation, edits };
66+
}
67+
68+
function isConvertableName (name: DeclarationName): name is AccepedNameType {
69+
return isIdentifier(name) || isStringLiteral(name);
70+
}
71+
72+
function createPropertyName (name: string, originalName: AccepedNameType) {
73+
return isIdentifier(originalName) ? createIdentifier(name) : createLiteral(name);
74+
}
75+
76+
function createAccessorAccessExpression (fieldName: AccepedNameType, isStatic: boolean, container: ContainerDeclation) {
77+
const leftHead = isStatic ? (<ClassLikeDeclaration>container).name : createThis();
78+
return isIdentifier(fieldName) ? createPropertyAccess(leftHead, fieldName) : createElementAccess(leftHead, createLiteral(fieldName));
79+
}
80+
81+
function getAccessorModifiers(isJS: boolean, declaration: AccepedDeclaration, isStatic: boolean, isClassLike: boolean): NodeArray<Modifier> | undefined {
82+
if (!isClassLike) return undefined;
83+
84+
if (!declaration.modifiers || getModifierFlags(declaration) & ModifierFlags.Private) {
85+
const modifiers = append<Modifier>(
86+
!isJS ? [createToken(SyntaxKind.PublicKeyword)] : undefined,
87+
isStatic ? createToken(SyntaxKind.StaticKeyword) : undefined
88+
);
89+
return modifiers && createNodeArray(modifiers);
90+
}
91+
return declaration.modifiers;
92+
}
93+
94+
function getFieldModifiers(isJS: boolean, isStatic: boolean, isClassLike: boolean): NodeArray<Modifier> | undefined {
95+
if (!isClassLike) return undefined;
96+
97+
const modifiers = append<Modifier>(
98+
!isJS ? [createToken(SyntaxKind.PrivateKeyword)] : undefined,
99+
isStatic ? createToken(SyntaxKind.StaticKeyword) : undefined
100+
);
101+
return modifiers && createNodeArray(modifiers);
102+
}
103+
104+
function getPropertyDeclarationInfo(propertyDeclaration: PropertyDeclaration): DeclarationInfo | undefined {
105+
if (!isClassLike(propertyDeclaration.parent) || !propertyDeclaration.parent.members) return undefined;
106+
107+
return {
108+
isStatic: hasStaticModifier(propertyDeclaration),
109+
type: propertyDeclaration.type,
110+
container: propertyDeclaration.parent
111+
};
112+
}
113+
114+
function getParameterPropertyDeclarationInfo(parameterDeclaration: ParameterDeclaration): DeclarationInfo | undefined {
115+
if (!isClassLike(parameterDeclaration.parent.parent) || !parameterDeclaration.parent.parent.members) return undefined;
116+
117+
return {
118+
isStatic: false,
119+
type: parameterDeclaration.type,
120+
container: parameterDeclaration.parent.parent
121+
};
122+
}
123+
124+
function getPropertyAssignmentDeclarationInfo(propertyAssignment: PropertyAssignment): DeclarationInfo | undefined {
125+
return {
126+
isStatic: false,
127+
type: undefined,
128+
container: propertyAssignment.parent
129+
};
130+
}
131+
132+
function getDeclarationInfo(declaration: AccepedDeclaration) {
133+
if (isPropertyDeclaration(declaration)) {
134+
return getPropertyDeclarationInfo(declaration);
135+
}
136+
else if (isPropertyAssignment(declaration)) {
137+
return getPropertyAssignmentDeclarationInfo(declaration);
138+
}
139+
else {
140+
return getParameterPropertyDeclarationInfo(declaration);
141+
}
142+
}
143+
144+
function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined {
145+
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
146+
const declaration = <AccepedDeclaration>findAncestor(node.parent, or(isParameterPropertyDeclaration, isPropertyDeclaration, isPropertyAssignment));
147+
// make sure propertyDeclaration have AccessibilityModifier or Static Modifier
148+
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static;
149+
if (!declaration || !isConvertableName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined;
150+
151+
const info = getDeclarationInfo(declaration);
152+
const fieldName = createPropertyName(getUniqueName(`_${declaration.name.text}`, file.text), declaration.name);
153+
suppressLeadingAndTrailingTrivia(fieldName);
154+
suppressLeadingAndTrailingTrivia(declaration);
155+
return {
156+
...info,
157+
declaration,
158+
fieldName,
159+
accessorName: createPropertyName(declaration.name.text, declaration.name)
160+
};
161+
}
162+
163+
function generateGetAccessor(fieldName: AccepedNameType, accessorName: AccepedNameType, type: TypeNode, modifiers: ModifiersArray | undefined, isStatic: boolean, container: ContainerDeclation) {
164+
return createGetAccessor(
165+
/*decorators*/ undefined,
166+
modifiers,
167+
accessorName,
168+
/*parameters*/ undefined,
169+
type,
170+
createBlock([
171+
createReturn(
172+
createAccessorAccessExpression(fieldName, isStatic, container)
173+
)
174+
], /*multiLine*/ true)
175+
);
176+
}
177+
178+
function generateSetAccessor(fieldName: AccepedNameType, accessorName: AccepedNameType, type: TypeNode, modifiers: ModifiersArray | undefined, isStatic: boolean, container: ContainerDeclation) {
179+
return createSetAccessor(
180+
/*decorators*/ undefined,
181+
modifiers,
182+
accessorName,
183+
[createParameter(
184+
/*decorators*/ undefined,
185+
/*modifiers*/ undefined,
186+
/*dotDotDotToken*/ undefined,
187+
createIdentifier("value"),
188+
/*questionToken*/ undefined,
189+
type
190+
)],
191+
createBlock([
192+
createStatement(
193+
createAssignment(
194+
createAccessorAccessExpression(fieldName, isStatic, container),
195+
createIdentifier("value")
196+
)
197+
)
198+
], /*multiLine*/ true)
199+
);
200+
}
201+
202+
function updatePropertyDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyDeclaration, fieldName: AccepedNameType, modifiers: ModifiersArray | undefined) {
203+
const property = updateProperty(
204+
declaration,
205+
declaration.decorators,
206+
modifiers,
207+
fieldName,
208+
declaration.questionToken || declaration.exclamationToken,
209+
declaration.type,
210+
declaration.initializer
211+
);
212+
213+
changeTracker.replaceNode(file, declaration, property);
214+
}
215+
216+
function updateParameterPropertyDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: ParameterDeclaration, fieldName: AccepedNameType, modifiers: ModifiersArray | undefined, classLikeContainer: ClassLikeDeclaration) {
217+
const property = createProperty(
218+
declaration.decorators,
219+
modifiers,
220+
fieldName,
221+
declaration.questionToken,
222+
declaration.type,
223+
declaration.initializer
224+
);
225+
226+
changeTracker.insertNodeAtClassStart(file, classLikeContainer, property);
227+
changeTracker.deleteNodeInList(file, declaration);
228+
}
229+
230+
function updatePropertyAssignmentDeclaration (changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: PropertyAssignment, fieldName: AccepedNameType) {
231+
const assignment = updatePropertyAssignment(declaration, fieldName, declaration.initializer);
232+
changeTracker.replacePropertyAssignment(file, declaration, assignment);
233+
}
234+
235+
function updateFieldDeclaration(changeTracker: textChanges.ChangeTracker, file: SourceFile, declaration: AccepedDeclaration, fieldName: AccepedNameType, modifiers: ModifiersArray | undefined, container: ContainerDeclation) {
236+
if (isPropertyDeclaration(declaration)) {
237+
updatePropertyDeclaration(changeTracker, file, declaration, fieldName, modifiers);
238+
}
239+
else if (isPropertyAssignment(declaration)) {
240+
updatePropertyAssignmentDeclaration(changeTracker, file, declaration, fieldName);
241+
}
242+
else {
243+
updateParameterPropertyDeclaration(changeTracker, file, declaration, fieldName, modifiers, <ClassLikeDeclaration>container);
244+
}
245+
}
246+
247+
function insertAccessor(changeTracker: textChanges.ChangeTracker, file: SourceFile, accessor: AccessorDeclaration, declaration: AccepedDeclaration, container: ContainerDeclation) {
248+
isParameterPropertyDeclaration(declaration)
249+
? changeTracker.insertNodeAtClassStart(file, <ClassLikeDeclaration>container, accessor)
250+
: changeTracker.insertNodeAfter(file, declaration, accessor);
251+
}
252+
}

src/services/refactors/refactors.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/// <reference path="extractSymbol.ts" />
2+
/// <reference path="generateGetAccessorAndSetAccessor.ts" />

src/services/textChanges.ts

+9
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,12 @@ namespace ts.textChanges {
316316
return this.replaceRangeWithNodes(sourceFile, getAdjustedRange(sourceFile, startNode, endNode, options), newNodes, options);
317317
}
318318

319+
public replacePropertyAssignment(sourceFile: SourceFile, oldNode: PropertyAssignment, newNode: PropertyAssignment) {
320+
return this.replaceNode(sourceFile, oldNode, newNode, {
321+
suffix: "," + this.newLineCharacter
322+
});
323+
}
324+
319325
private insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) {
320326
this.replaceRange(sourceFile, createTextRange(pos), newNode, options);
321327
}
@@ -468,6 +474,9 @@ namespace ts.textChanges {
468474
else if (isVariableDeclaration(node)) {
469475
return { prefix: ", " };
470476
}
477+
else if (isPropertyAssignment(node)) {
478+
return { suffix: "," + this.newLineCharacter };
479+
}
471480
else if (isParameter(node)) {
472481
return {};
473482
}

0 commit comments

Comments
 (0)