@@ -11,10 +11,12 @@ 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 ;
17
18
accessorName : AcceptedNameType ;
19
+ originalName : AcceptedNameType ;
18
20
}
19
21
20
22
function getAvailableActions ( context : RefactorContext ) : ApplicableRefactorInfo [ ] | undefined {
@@ -41,21 +43,40 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
41
43
42
44
const isJS = isSourceFileJavaScript ( file ) ;
43
45
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 ) ;
45
51
46
52
const isInClassLike = isClassLike ( container ) ;
53
+ // avoid Readonly modifier because it will convert to get accessor
54
+ const modifierFlags = getModifierFlags ( declaration ) & ~ ModifierFlags . Readonly ;
47
55
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 ) )
49
59
: undefined ;
50
60
const fieldModifiers = isInClassLike ? getModifiers ( isJS , isStatic , SyntaxKind . PrivateKeyword ) : undefined ;
51
61
52
62
updateFieldDeclaration ( changeTracker , file , declaration , fieldName , fieldModifiers ) ;
53
63
54
64
const getAccessor = generateGetAccessor ( fieldName , accessorName , type , accessorModifiers , isStatic , container ) ;
55
- const setAccessor = generateSetAccessor ( fieldName , accessorName , type , accessorModifiers , isStatic , container ) ;
56
-
65
+ suppressLeadingAndTrailingTrivia ( getAccessor ) ;
57
66
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
+ }
59
80
60
81
const edits = changeTracker . getChanges ( ) ;
61
82
const renameFilename = file . fileName ;
@@ -92,18 +113,18 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
92
113
function getConvertibleFieldAtPosition ( file : SourceFile , startPosition : number ) : Info | undefined {
93
114
const node = getTokenAtPosition ( file , startPosition , /*includeJsDocComment*/ false ) ;
94
115
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 ;
97
118
if ( ! declaration || ! isConvertableName ( declaration . name ) || ( getModifierFlags ( declaration ) | meaning ) !== meaning ) return undefined ;
98
119
99
120
const fieldName = createPropertyName ( getUniqueName ( `_${ declaration . name . text } ` , file . text ) , declaration . name ) ;
100
121
const accessorName = createPropertyName ( declaration . name . text , declaration . name ) ;
101
- suppressLeadingAndTrailingTrivia ( fieldName ) ;
102
- suppressLeadingAndTrailingTrivia ( declaration ) ;
103
122
return {
104
123
isStatic : hasStaticModifier ( declaration ) ,
124
+ isReadonly : hasReadonlyModifier ( declaration ) ,
105
125
type : getTypeAnnotationNode ( declaration ) ,
106
126
container : declaration . kind === SyntaxKind . Parameter ? declaration . parent . parent : declaration . parent ,
127
+ originalName : < AcceptedNameType > declaration . name ,
107
128
declaration,
108
129
fieldName,
109
130
accessorName,
@@ -159,7 +180,6 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
159
180
declaration . type ,
160
181
declaration . initializer
161
182
) ;
162
-
163
183
changeTracker . replaceNode ( file , declaration , property ) ;
164
184
}
165
185
@@ -186,4 +206,24 @@ namespace ts.refactor.generateGetAccessorAndSetAccessor {
186
206
? changeTracker . insertNodeAtClassStart ( file , < ClassLikeDeclaration > container , accessor )
187
207
: changeTracker . insertNodeAfter ( file , declaration , accessor ) ;
188
208
}
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
+ }
189
229
}
0 commit comments