Skip to content

Commit 2b5fabc

Browse files
committed
fix(33836): allow readonly modifier for a field with only get accessor
1 parent 140fee9 commit 2b5fabc

File tree

3 files changed

+50
-21
lines changed

3 files changed

+50
-21
lines changed

src/services/refactors/generateGetAccessorAndSetAccessor.ts

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,27 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
4141
const fieldInfo = getConvertibleFieldAtPosition(context);
4242
if (!fieldInfo) return undefined;
4343

44-
const isJS = isSourceFileJS(file);
4544
const changeTracker = textChanges.ChangeTracker.fromContext(context);
4645
const { isStatic, isReadonly, fieldName, accessorName, originalName, type, container, declaration, renameAccessor } = fieldInfo;
4746

4847
suppressLeadingAndTrailingTrivia(fieldName);
4948
suppressLeadingAndTrailingTrivia(declaration);
5049
suppressLeadingAndTrailingTrivia(container);
5150

52-
const isInClassLike = isClassLike(container);
53-
// avoid Readonly modifier because it will convert to get accessor
54-
const modifierFlags = getModifierFlags(declaration) & ~ModifierFlags.Readonly;
55-
const accessorModifiers = isInClassLike
56-
? !modifierFlags || modifierFlags & ModifierFlags.Private
57-
? getModifiers(isJS, isStatic, SyntaxKind.PublicKeyword)
58-
: createNodeArray(createModifiersFromModifierFlags(modifierFlags))
59-
: undefined;
60-
const fieldModifiers = isInClassLike ? getModifiers(isJS, isStatic, SyntaxKind.PrivateKeyword) : undefined;
51+
let accessorModifiers: ModifiersArray | undefined;
52+
let fieldModifiers: ModifiersArray | undefined;
53+
if (isClassLike(container)) {
54+
const modifierFlags = getModifierFlags(declaration);
55+
if (isSourceFileJS(file)) {
56+
const modifiers = createModifiers(modifierFlags);
57+
accessorModifiers = modifiers;
58+
fieldModifiers = modifiers;
59+
}
60+
else {
61+
accessorModifiers = createModifiers(prepareModifierFlagsForAccessor(modifierFlags));
62+
fieldModifiers = createModifiers(prepareModifierFlagsForField(modifierFlags));
63+
}
64+
}
6165

6266
updateFieldDeclaration(changeTracker, file, declaration, fieldName, fieldModifiers);
6367

@@ -95,21 +99,47 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
9599
return isParameterPropertyDeclaration(node, node.parent) || isPropertyDeclaration(node) || isPropertyAssignment(node);
96100
}
97101

98-
function createPropertyName (name: string, originalName: AcceptedNameType) {
102+
function createPropertyName(name: string, originalName: AcceptedNameType) {
99103
return isIdentifier(originalName) ? createIdentifier(name) : createLiteral(name);
100104
}
101105

102-
function createAccessorAccessExpression (fieldName: AcceptedNameType, isStatic: boolean, container: ContainerDeclaration) {
106+
function createAccessorAccessExpression(fieldName: AcceptedNameType, isStatic: boolean, container: ContainerDeclaration) {
103107
const leftHead = isStatic ? (<ClassLikeDeclaration>container).name! : createThis(); // TODO: GH#18217
104108
return isIdentifier(fieldName) ? createPropertyAccess(leftHead, fieldName) : createElementAccess(leftHead, createLiteral(fieldName));
105109
}
106110

107-
function getModifiers(isJS: boolean, isStatic: boolean, accessModifier: SyntaxKind.PublicKeyword | SyntaxKind.PrivateKeyword): NodeArray<Modifier> | undefined {
108-
const modifiers = append<Modifier>(
109-
!isJS ? [createToken(accessModifier) as Token<SyntaxKind.PublicKeyword> | Token<SyntaxKind.PrivateKeyword>] : undefined,
110-
isStatic ? createToken(SyntaxKind.StaticKeyword) : undefined
111-
);
112-
return modifiers && createNodeArray(modifiers);
111+
function createModifiers(modifierFlags: ModifierFlags): ModifiersArray | undefined {
112+
return modifierFlags ? createNodeArray(createModifiersFromModifierFlags(modifierFlags)) : undefined;
113+
}
114+
115+
function prepareModifierFlagsForAccessor(modifierFlags: ModifierFlags): ModifierFlags {
116+
// avoid Readonly modifier because it will convert to get accessor
117+
if (modifierFlags & ModifierFlags.Readonly) {
118+
modifierFlags &= ~ModifierFlags.Readonly;
119+
}
120+
121+
if (modifierFlags & ModifierFlags.Private) {
122+
modifierFlags &= ~ModifierFlags.Private;
123+
}
124+
125+
if (!(modifierFlags & ModifierFlags.Protected)) {
126+
modifierFlags |= ModifierFlags.Public;
127+
}
128+
129+
return modifierFlags;
130+
}
131+
132+
function prepareModifierFlagsForField(modifierFlags: ModifierFlags): ModifierFlags {
133+
if (modifierFlags & ModifierFlags.Public) {
134+
modifierFlags &= ~ModifierFlags.Public;
135+
}
136+
137+
if (modifierFlags & ModifierFlags.Protected) {
138+
modifierFlags &= ~ModifierFlags.Protected;
139+
}
140+
141+
modifierFlags |= ModifierFlags.Private;
142+
return modifierFlags;
113143
}
114144

115145
function startsWithUnderscore(name: string): boolean {

tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess14.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
//// /*a*/public readonly a: string = "foo";/*b*/
55
//// }
66

7-
goTo.select("a", "b");
87
goTo.select("a", "b");
98
edit.applyRefactor({
109
refactorName: "Generate 'get' and 'set' accessors",
1110
actionName: "Generate 'get' and 'set' accessors",
1211
actionDescription: "Generate 'get' and 'set' accessors",
1312
newContent: `class A {
14-
private /*RENAME*/_a: string = "foo";
13+
private readonly /*RENAME*/_a: string = "foo";
1514
public get a(): string {
1615
return this._a;
1716
}

tests/cases/fourslash/refactorConvertToGetAccessAndSetAccess33.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ edit.applyRefactor({
2222
actionName: "Generate 'get' and 'set' accessors",
2323
actionDescription: "Generate 'get' and 'set' accessors",
2424
newContent: `class A {
25-
private /*RENAME*/_a: number;
25+
private readonly /*RENAME*/_a: number;
2626
public get a(): number {
2727
return this._a;
2828
}

0 commit comments

Comments
 (0)