@@ -11,6 +11,7 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
11
11
interface Info {
12
12
container : ContainerDeclaration ;
13
13
isStatic : boolean ;
14
+ isReadonly : boolean ;
14
15
type : TypeNode | undefined ;
15
16
declaration : AcceptedDeclaration ;
16
17
fieldName : AcceptedNameType ;
@@ -41,21 +42,40 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
41
42
42
43
const isJS = isSourceFileJavaScript ( file ) ;
43
44
const changeTracker = textChanges . ChangeTracker . fromContext ( context ) ;
44
- const { isStatic, fieldName, accessorName, type, container, declaration } = fieldInfo ;
45
+ const { isStatic, isReadonly, fieldName, accessorName, type, container, declaration } = fieldInfo ;
46
+
47
+ suppressLeadingAndTrailingTrivia ( fieldName ) ;
48
+ suppressLeadingAndTrailingTrivia ( declaration ) ;
49
+ suppressLeadingAndTrailingTrivia ( container ) ;
45
50
46
51
const isInClassLike = isClassLike ( container ) ;
52
+ // avoid Readonly modifier because it will convert to get accessor
53
+ const modifierFlags = getModifierFlags ( declaration ) & ~ ModifierFlags . Readonly ;
47
54
const accessorModifiers = isInClassLike
48
- ? ! declaration . modifiers || getModifierFlags ( declaration ) & ModifierFlags . Private ? getModifiers ( isJS , isStatic , SyntaxKind . PublicKeyword ) : declaration . modifiers
55
+ ? ! modifierFlags || modifierFlags & ModifierFlags . Private
56
+ ? getModifiers ( isJS , isStatic , SyntaxKind . PublicKeyword )
57
+ : createNodeArray ( createModifiersFromModifierFlags ( modifierFlags ) )
49
58
: undefined ;
50
59
const fieldModifiers = isInClassLike ? getModifiers ( isJS , isStatic , SyntaxKind . PrivateKeyword ) : undefined ;
51
60
52
61
updateFieldDeclaration ( changeTracker , file , declaration , fieldName , fieldModifiers ) ;
53
62
54
63
const getAccessor = generateGetAccessor ( fieldName , accessorName , type , accessorModifiers , isStatic , container ) ;
55
- const setAccessor = generateSetAccessor ( fieldName , accessorName , type , accessorModifiers , isStatic , container ) ;
56
-
64
+ suppressLeadingAndTrailingTrivia ( getAccessor ) ;
57
65
insertAccessor ( changeTracker , file , getAccessor , declaration , container ) ;
58
- insertAccessor ( changeTracker , file , setAccessor , declaration , container ) ;
66
+
67
+ if ( isReadonly ) {
68
+ // readonly modifier only existed in classLikeDeclaration
69
+ const constructor = getFirstConstructorWithBody ( < ClassLikeDeclaration > container ) ;
70
+ if ( constructor ) {
71
+ updateReadonlyPropertyInitializerStatementConstructor ( changeTracker , file , constructor , accessorName , fieldName ) ;
72
+ }
73
+ }
74
+ else {
75
+ const setAccessor = generateSetAccessor ( fieldName , accessorName , type , accessorModifiers , isStatic , container ) ;
76
+ suppressLeadingAndTrailingTrivia ( setAccessor ) ;
77
+ insertAccessor ( changeTracker , file , setAccessor , declaration , container ) ;
78
+ }
59
79
60
80
const edits = changeTracker . getChanges ( ) ;
61
81
const renameFilename = file . fileName ;
@@ -92,16 +112,15 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
92
112
function getConvertibleFieldAtPosition ( file : SourceFile , startPosition : number ) : Info | undefined {
93
113
const node = getTokenAtPosition ( file , startPosition , /*includeJsDocComment*/ false ) ;
94
114
const declaration = findAncestor ( node . parent , isAcceptedDeclaration ) ;
95
- // make sure propertyDeclaration have AccessibilityModifier or Static Modifier
96
- const meaning = ModifierFlags . AccessibilityModifier | ModifierFlags . Static ;
115
+ // make sure declaration have AccessibilityModifier or Static Modifier or Readonly Modifier
116
+ const meaning = ModifierFlags . AccessibilityModifier | ModifierFlags . Static | ModifierFlags . Readonly ;
97
117
if ( ! declaration || ! isConvertableName ( declaration . name ) || ( getModifierFlags ( declaration ) | meaning ) !== meaning ) return undefined ;
98
118
99
119
const fieldName = createPropertyName ( getUniqueName ( `_${ declaration . name . text } ` , file . text ) , declaration . name ) ;
100
120
const accessorName = createPropertyName ( declaration . name . text , declaration . name ) ;
101
- suppressLeadingAndTrailingTrivia ( fieldName ) ;
102
- suppressLeadingAndTrailingTrivia ( declaration ) ;
103
121
return {
104
122
isStatic : hasStaticModifier ( declaration ) ,
123
+ isReadonly : hasReadonlyModifier ( declaration ) ,
105
124
type : getTypeAnnotationNode ( declaration ) ,
106
125
container : declaration . kind === SyntaxKind . Parameter ? declaration . parent . parent : declaration . parent ,
107
126
declaration,
@@ -159,7 +178,6 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
159
178
declaration . type ,
160
179
declaration . initializer
161
180
) ;
162
-
163
181
changeTracker . replaceNode ( file , declaration , property ) ;
164
182
}
165
183
@@ -186,4 +204,38 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
186
204
? changeTracker . insertNodeAtClassStart ( file , < ClassLikeDeclaration > container , accessor )
187
205
: changeTracker . insertNodeAfter ( file , declaration , accessor ) ;
188
206
}
207
+
208
+ function updateReadonlyPropertyInitializerStatementConstructor ( changeTracker : textChanges . ChangeTracker , file : SourceFile , constructor : ConstructorDeclaration , accessorName : AcceptedNameType , fieldName : AcceptedNameType ) {
209
+ if ( constructor . body ) {
210
+ const initializerStatement = find ( constructor . body . statements , ( stmt =>
211
+ isExpressionStatement ( stmt ) &&
212
+ isAssignmentExpression ( stmt . expression ) &&
213
+ stmt . expression . operatorToken . kind === SyntaxKind . EqualsToken &&
214
+ ( isPropertyAccessExpression ( stmt . expression . left ) || isElementAccessExpression ( stmt . expression . left ) ) &&
215
+ isThis ( stmt . expression . left . expression ) &&
216
+ ( isPropertyAccessExpression ( stmt . expression . left )
217
+ ? ( getNameFromPropertyName ( stmt . expression . left . name ) === accessorName . text )
218
+ : ( isPropertyName ( stmt . expression . left . argumentExpression ) && isConvertableName ( stmt . expression . left . argumentExpression ) && getNameFromPropertyName ( stmt . expression . left . argumentExpression ) === accessorName . text
219
+ ) )
220
+ ) ) ;
221
+ if ( initializerStatement ) {
222
+ const initializerLeftHead = ( < PropertyAccessExpression | ElementAccessExpression > ( < AssignmentExpression < Token < SyntaxKind . EqualsToken > > > ( < ExpressionStatement > initializerStatement ) . expression ) . left ) ;
223
+
224
+ if ( isPropertyAccessExpression ( initializerLeftHead ) ) {
225
+ changeTracker . replaceNode ( file , initializerLeftHead , updatePropertyAccess (
226
+ initializerLeftHead ,
227
+ initializerLeftHead . expression ,
228
+ createIdentifier ( fieldName . text )
229
+ ) ) ;
230
+ }
231
+ else {
232
+ changeTracker . replaceNode ( file , initializerLeftHead , updateElementAccess (
233
+ initializerLeftHead ,
234
+ initializerLeftHead . expression ,
235
+ createPropertyName ( fieldName . text , < AcceptedNameType > initializerLeftHead . argumentExpression )
236
+ ) ) ;
237
+ }
238
+ }
239
+ }
240
+ }
189
241
}
0 commit comments