Skip to content

Commit 1b796ed

Browse files
authored
Merge pull request #23954 from Kingwl/readonly-getter-support
add support for readonly modifier
2 parents 3e08c41 + 44d10dc commit 1b796ed

File tree

3 files changed

+104
-11
lines changed

3 files changed

+104
-11
lines changed

src/services/refactors/generateGetAccessorAndSetAccessor.ts

+50-10
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
1111
interface Info {
1212
container: ContainerDeclaration;
1313
isStatic: boolean;
14+
isReadonly: boolean;
1415
type: TypeNode | undefined;
1516
declaration: AcceptedDeclaration;
1617
fieldName: AcceptedNameType;
1718
accessorName: AcceptedNameType;
19+
originalName: AcceptedNameType;
1820
}
1921

2022
function getAvailableActions(context: RefactorContext): ApplicableRefactorInfo[] | undefined {
@@ -41,21 +43,40 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
4143

4244
const isJS = isSourceFileJavaScript(file);
4345
const changeTracker = textChanges.ChangeTracker.fromContext(context);
44-
const { isStatic, fieldName, accessorName, type, container, declaration } = fieldInfo;
46+
const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration } = fieldInfo;
47+
48+
suppressLeadingAndTrailingTrivia(fieldName);
49+
suppressLeadingAndTrailingTrivia(declaration);
50+
suppressLeadingAndTrailingTrivia(container);
4551

4652
const isInClassLike = isClassLike(container);
53+
// avoid Readonly modifier because it will convert to get accessor
54+
const modifierFlags = getModifierFlags(declaration) & ~ModifierFlags.Readonly;
4755
const accessorModifiers = isInClassLike
48-
? !declaration.modifiers || getModifierFlags(declaration) & ModifierFlags.Private ? getModifiers(isJS, isStatic, SyntaxKind.PublicKeyword) : declaration.modifiers
56+
? !modifierFlags || modifierFlags & ModifierFlags.Private
57+
? getModifiers(isJS, isStatic, SyntaxKind.PublicKeyword)
58+
: createNodeArray(createModifiersFromModifierFlags(modifierFlags))
4959
: undefined;
5060
const fieldModifiers = isInClassLike ? getModifiers(isJS, isStatic, SyntaxKind.PrivateKeyword) : undefined;
5161

5262
updateFieldDeclaration(changeTracker, file, declaration, fieldName, fieldModifiers);
5363

5464
const getAccessor = generateGetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
55-
const setAccessor = generateSetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
56-
65+
suppressLeadingAndTrailingTrivia(getAccessor);
5766
insertAccessor(changeTracker, file, getAccessor, declaration, container);
58-
insertAccessor(changeTracker, file, setAccessor, declaration, container);
67+
68+
if (isReadonly) {
69+
// readonly modifier only existed in classLikeDeclaration
70+
const constructor = getFirstConstructorWithBody(<ClassLikeDeclaration>container);
71+
if (constructor) {
72+
updateReadonlyPropertyInitializerStatementConstructor(changeTracker, context, constructor, fieldName, originalName);
73+
}
74+
}
75+
else {
76+
const setAccessor = generateSetAccessor(fieldName, accessorName, type, accessorModifiers, isStatic, container);
77+
suppressLeadingAndTrailingTrivia(setAccessor);
78+
insertAccessor(changeTracker, file, setAccessor, declaration, container);
79+
}
5980

6081
const edits = changeTracker.getChanges();
6182
const renameFilename = file.fileName;
@@ -92,18 +113,18 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
92113
function getConvertibleFieldAtPosition(file: SourceFile, startPosition: number): Info | undefined {
93114
const node = getTokenAtPosition(file, startPosition, /*includeJsDocComment*/ false);
94115
const declaration = findAncestor(node.parent, isAcceptedDeclaration);
95-
// make sure propertyDeclaration have AccessibilityModifier or Static Modifier
96-
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static;
116+
// make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier
117+
const meaning = ModifierFlags.AccessibilityModifier | ModifierFlags.Static | ModifierFlags.Readonly;
97118
if (!declaration || !isConvertableName(declaration.name) || (getModifierFlags(declaration) | meaning) !== meaning) return undefined;
98119

99120
const fieldName = createPropertyName(getUniqueName(`_${declaration.name.text}`, file.text), declaration.name);
100121
const accessorName = createPropertyName(declaration.name.text, declaration.name);
101-
suppressLeadingAndTrailingTrivia(fieldName);
102-
suppressLeadingAndTrailingTrivia(declaration);
103122
return {
104123
isStatic: hasStaticModifier(declaration),
124+
isReadonly: hasReadonlyModifier(declaration),
105125
type: getTypeAnnotationNode(declaration),
106126
container: declaration.kind === SyntaxKind.Parameter ? declaration.parent.parent : declaration.parent,
127+
originalName: <AcceptedNameType>declaration.name,
107128
declaration,
108129
fieldName,
109130
accessorName,
@@ -159,7 +180,6 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
159180
declaration.type,
160181
declaration.initializer
161182
);
162-
163183
changeTracker.replaceNode(file, declaration, property);
164184
}
165185

@@ -186,4 +206,24 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
186206
? changeTracker.insertNodeAtClassStart(file, <ClassLikeDeclaration>container, accessor)
187207
: changeTracker.insertNodeAfter(file, declaration, accessor);
188208
}
209+
210+
function updateReadonlyPropertyInitializerStatementConstructor(changeTracker: textChanges.ChangeTracker, context: RefactorContext, constructor: ConstructorDeclaration, fieldName: AcceptedNameType, originalName: AcceptedNameType) {
211+
if (!constructor.body) return;
212+
const { file, program, cancellationToken } = context;
213+
214+
const referenceEntries = mapDefined(FindAllReferences.getReferenceEntriesForNode(originalName.parent.pos, originalName, program, [file], cancellationToken), entry => (
215+
(entry.type === "node" && rangeContainsRange(constructor, entry.node) && isIdentifier(entry.node) && isWriteAccess(entry.node)) ? entry.node : undefined
216+
));
217+
218+
forEach(referenceEntries, entry => {
219+
const parent = entry.parent;
220+
const accessorName = createIdentifier(fieldName.text);
221+
const node = isBinaryExpression(parent)
222+
? updateBinary(parent, accessorName, parent.right, parent.operatorToken)
223+
: isPropertyAccessExpression(parent)
224+
? updatePropertyAccess(parent, parent.expression, accessorName)
225+
: Debug.fail("Unexpected write access token");
226+
changeTracker.replaceNode(file, parent, node);
227+
});
228+
}
189229
}

tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess14.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,15 @@
55
//// }
66

77
goTo.select("a", "b");
8-
verify.not.refactorAvailable("Generate 'get' and 'set' accessors");
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+
private /*RENAME*/_a: string = "foo";
15+
public get a(): string {
16+
return this._a;
17+
}
18+
}`,
19+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// class A {
4+
//// public readonly /*a*/a/*b*/: number;
5+
//// public b: number;
6+
//// constructor () {
7+
//// this.a = 1; // convert
8+
//// this.a++; // convert
9+
//// ++this.a; // convert
10+
//// if (Math.random()) {
11+
//// this.a = 2; // convert
12+
//// }
13+
//// console.log(this.a); // preserve
14+
//// this.b = this.a; // preserve
15+
//// }
16+
//// foo () { this.a = 2; }
17+
//// }
18+
19+
goTo.select("a", "b");
20+
edit.applyRefactor({
21+
refactorName: "Generate 'get' and 'set' accessors",
22+
actionName: "Generate 'get' and 'set' accessors",
23+
actionDescription: "Generate 'get' and 'set' accessors",
24+
newContent: `class A {
25+
private /*RENAME*/_a: number;
26+
public get a(): number {
27+
return this._a;
28+
}
29+
public b: number;
30+
constructor () {
31+
this._a = 1; // convert
32+
this._a++; // convert
33+
++this._a; // convert
34+
if (Math.random()) {
35+
this._a = 2; // convert
36+
}
37+
console.log(this.a); // preserve
38+
this.b = this.a; // preserve
39+
}
40+
foo () { this.a = 2; }
41+
}`,
42+
});

0 commit comments

Comments
 (0)