@@ -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}
0 commit comments