1818namespace Microsoft . DotNet . CodeFormatting . Rules
1919{
2020 [ RuleOrder ( RuleOrder . PrivateFieldNamingRule ) ]
21- // TODO Bug 1086632: Deactivated due to active bug in Roslyn.
22- // There is a hack to run this rule, but it's slow.
23- // If needed, enable the rule and enable the hack at the code below in RenameFields.
2421 internal sealed class PrivateFieldNamingRule : IFormattingRule
2522 {
23+ /// <summary>
24+ /// This will add an annotation to any private field that needs to be renamed.
25+ /// </summary>
26+ private sealed class PrivateFieldAnnotationsRewriter : CSharpSyntaxRewriter
27+ {
28+ internal readonly static SyntaxAnnotation Marker = new SyntaxAnnotation ( "PrivateFieldToRename" ) ;
29+
30+ // Used to avoid the array allocation on calls to WithAdditionalAnnotations
31+ private readonly static SyntaxAnnotation [ ] s_markerArray ;
32+
33+ static PrivateFieldAnnotationsRewriter ( )
34+ {
35+ s_markerArray = new SyntaxAnnotation [ ] { Marker } ;
36+ }
37+
38+ private int _count ;
39+
40+ internal static SyntaxNode AddAnnotations ( SyntaxNode node , out int count )
41+ {
42+ var rewriter = new PrivateFieldAnnotationsRewriter ( ) ;
43+ var newNode = rewriter . Visit ( node ) ;
44+ count = rewriter . _count ;
45+ return newNode ;
46+ }
47+
48+ public override SyntaxNode VisitFieldDeclaration ( FieldDeclarationSyntax node )
49+ {
50+ if ( NeedsRewrite ( node ) )
51+ {
52+ var list = new List < VariableDeclaratorSyntax > ( node . Declaration . Variables . Count ) ;
53+ foreach ( var v in node . Declaration . Variables )
54+ {
55+ if ( IsBadName ( v ) )
56+ {
57+ list . Add ( v . WithAdditionalAnnotations ( Marker ) ) ;
58+ _count ++ ;
59+ }
60+ else
61+ {
62+ list . Add ( v ) ;
63+ }
64+ }
65+
66+ var declaration = node . Declaration . WithVariables ( SyntaxFactory . SeparatedList ( list ) ) ;
67+ node = node . WithDeclaration ( declaration ) ;
68+
69+ return node ;
70+ }
71+
72+ return node ;
73+ }
74+
75+ private static bool NeedsRewrite ( FieldDeclarationSyntax fieldSyntax )
76+ {
77+ if ( ! IsPrivateField ( fieldSyntax ) )
78+ {
79+ return false ;
80+ }
81+
82+ foreach ( var v in fieldSyntax . Declaration . Variables )
83+ {
84+ if ( IsBadName ( v ) )
85+ {
86+ return true ;
87+ }
88+ }
89+
90+ return false ;
91+ }
92+
93+ private static bool IsBadName ( VariableDeclaratorSyntax node )
94+ {
95+ var name = node . Identifier . ValueText ;
96+ return name . Length > 0 && name [ 0 ] != '_' ;
97+ }
98+
99+ private static bool IsPrivateField ( FieldDeclarationSyntax fieldSyntax )
100+ {
101+ foreach ( var modifier in fieldSyntax . Modifiers )
102+ {
103+ switch ( modifier . CSharpKind ( ) )
104+ {
105+ case SyntaxKind . PublicKeyword :
106+ case SyntaxKind . ConstKeyword :
107+ case SyntaxKind . InternalKeyword :
108+ case SyntaxKind . ProtectedKeyword :
109+ return false ;
110+ }
111+ }
112+
113+ return true ;
114+ }
115+ }
116+
26117 /// <summary>
27118 /// This rewriter exists to work around DevDiv 1086632 in Roslyn. The Rename action is
28119 /// leaving a set of annotations in the tree. These annotations slow down further processing
@@ -31,126 +122,136 @@ internal sealed class PrivateFieldNamingRule : IFormattingRule
31122 /// </summary>
32123 private sealed class RemoveRenameAnnotationsRewriter : CSharpSyntaxRewriter
33124 {
125+ private const string RenameAnnotationName = "Rename" ;
34126
35- }
127+ public override SyntaxNode Visit ( SyntaxNode node )
128+ {
129+ node = base . Visit ( node ) ;
130+ if ( node != null && node . ContainsAnnotations && node . GetAnnotations ( RenameAnnotationName ) . Any ( ) )
131+ {
132+ node = node . WithoutAnnotations ( RenameAnnotationName ) ;
133+ }
134+
135+ return node ;
136+ }
137+
138+ public override SyntaxToken VisitToken ( SyntaxToken token )
139+ {
140+ if ( token . ContainsAnnotations && token . GetAnnotations ( RenameAnnotationName ) . Any ( ) )
141+ {
142+ token = token . WithoutAnnotations ( RenameAnnotationName ) ;
143+ }
36144
37- private static string [ ] s_keywordsToIgnore = { "public" , "internal" , "protected" , "const" } ;
38- private static readonly SyntaxAnnotation s_annotationMarker = new SyntaxAnnotation ( ) ;
39- private static readonly Regex s_regex = new Regex ( "^._" ) ;
145+ return token ;
146+ }
147+ }
40148
41149 public async Task < Document > ProcessAsync ( Document document , CancellationToken cancellationToken )
42150 {
43151 var syntaxRoot = await document . GetSyntaxRootAsync ( cancellationToken ) as CSharpSyntaxNode ;
44152 if ( syntaxRoot == null )
45- return document ;
46-
47- var typeNodes = syntaxRoot . DescendantNodes ( ) . Where ( type => type . CSharpKind ( ) == SyntaxKind . ClassDeclaration || type . CSharpKind ( ) == SyntaxKind . StructDeclaration ) ;
48- IEnumerable < SyntaxNode > privateFields = GetPrivateFields ( typeNodes ) ;
49- if ( privateFields . Any ( ) )
50153 {
51- var solutionWithAnnotation = GetSolutionWithRenameAnnotation ( document , syntaxRoot , privateFields , cancellationToken ) ;
52- var solution = await RenameFields ( solutionWithAnnotation , document . Id , privateFields , cancellationToken ) ;
53- return solution . GetDocument ( document . Id ) ;
154+ return document ;
54155 }
55156
56- return document ;
57- }
157+ int count ;
158+ var newSyntaxRoot = PrivateFieldAnnotationsRewriter . AddAnnotations ( syntaxRoot , out count ) ;
58159
59- private IEnumerable < SyntaxNode > GetPrivateFields ( IEnumerable < SyntaxNode > typeNodes )
60- {
61- IEnumerable < SyntaxNode > privateFields = Enumerable . Empty < SyntaxNode > ( ) ;
62- foreach ( var type in typeNodes )
160+ if ( count == 0 )
63161 {
64- privateFields = privateFields . Concat ( type . ChildNodes ( ) . OfType < FieldDeclarationSyntax > ( )
65- . Where ( f => ! s_keywordsToIgnore . Any ( f . Modifiers . ToString ( ) . Contains ) ) . SelectMany ( f => f . Declaration . Variables ) )
66- . Where ( v => ! ( v as VariableDeclaratorSyntax ) . Identifier . Text . StartsWith ( "_" ) ) ;
162+ return document ;
67163 }
68164
69- return privateFields ;
165+ var documentId = document . Id ;
166+ var solution = document . Project . Solution ;
167+ solution = solution . WithDocumentSyntaxRoot ( documentId , newSyntaxRoot ) ;
168+ solution = await RenameFields ( solution , documentId , count , cancellationToken ) ;
169+ return solution . GetDocument ( documentId ) ;
70170 }
71171
72- private Solution GetSolutionWithRenameAnnotation ( Document document , SyntaxNode syntaxRoot , IEnumerable < SyntaxNode > privateFields , CancellationToken cancellationToken )
172+ private static async Task < Solution > RenameFields ( Solution solution , DocumentId documentId , int count , CancellationToken cancellationToken )
73173 {
74- Func < SyntaxNode , SyntaxNode , SyntaxNode > addAnnotation = ( variable , dummy ) =>
174+ Solution oldSolution = null ;
175+ for ( int i = 0 ; i < count ; i ++ )
75176 {
76- return variable . WithAdditionalAnnotations ( s_annotationMarker ) ;
77- } ;
177+ // If this is not the first field then clean up the Rename annotations left
178+ // in the tree.
179+ if ( i > 0 )
180+ {
181+ solution = await CleanSolutionAsync ( solution , oldSolution , cancellationToken ) ;
182+ }
78183
79- return document . WithSyntaxRoot ( syntaxRoot . ReplaceNodes ( privateFields , addAnnotation ) ) . Project . Solution ;
80- }
184+ oldSolution = solution ;
81185
82- private async Task < Solution > RenameFields ( Solution solution , DocumentId documentId , IEnumerable < SyntaxNode > privateFields , CancellationToken cancellationToken )
83- {
84- int count = privateFields . Count ( ) ;
85- for ( int i = 0 ; i < count ; i ++ )
86- {
87- // This is a hack to till the roslyn bug is fixed. Very slow, enable this statement only if the rule is enabled.
88- solution = await CleanSolutionAsync ( solution , cancellationToken ) ;
89- var model = await solution . GetDocument ( documentId ) . GetSemanticModelAsync ( cancellationToken ) ;
90- var root = await model . SyntaxTree . GetRootAsync ( cancellationToken ) as CSharpSyntaxNode ;
91- var symbol = model . GetDeclaredSymbol ( root . GetAnnotatedNodes ( s_annotationMarker ) . ElementAt ( i ) , cancellationToken ) ;
92- var newName = GetNewSymbolName ( symbol ) ;
93- solution = await Renamer . RenameSymbolAsync ( solution , symbol , newName , solution . Workspace . Options , cancellationToken ) . ConfigureAwait ( false ) ;
186+ var semanticModel = await solution . GetDocument ( documentId ) . GetSemanticModelAsync ( cancellationToken ) ;
187+ var root = await semanticModel . SyntaxTree . GetRootAsync ( cancellationToken ) as CSharpSyntaxNode ;
188+ var declaration = root . GetAnnotatedNodes ( PrivateFieldAnnotationsRewriter . Marker ) . ElementAt ( i ) ;
189+ var fieldSymbol = ( IFieldSymbol ) semanticModel . GetDeclaredSymbol ( declaration , cancellationToken ) ;
190+ var newName = GetNewFieldName ( fieldSymbol ) ;
191+ solution = await Renamer . RenameSymbolAsync ( solution , fieldSymbol , newName , solution . Workspace . Options , cancellationToken ) . ConfigureAwait ( false ) ;
94192 }
95193
96194 return solution ;
97195 }
98196
99- private static string GetNewSymbolName ( ISymbol symbol )
197+ private static string GetNewFieldName ( IFieldSymbol fieldSymbol )
100198 {
101- var symbolName = symbol . Name . TrimStart ( '_' ) ;
102- if ( s_regex . IsMatch ( symbolName ) ) symbolName = symbolName . Remove ( 0 , 2 ) ;
103- if ( symbol . IsStatic )
199+ var name = fieldSymbol . Name ;
200+ if ( name . Length > 1 )
104201 {
105- // Check for ThreadStatic private fields.
106- if ( symbol . GetAttributes ( ) . Any ( a => a . AttributeClass . Name . Equals ( "ThreadStaticAttribute" ) ) )
202+ if ( name [ 0 ] == '_' )
203+ {
204+ name = name . TrimStart ( '_' ) ;
205+ }
206+ else if ( char . IsLetter ( name [ 0 ] ) && name [ 1 ] == '_' )
107207 {
108- if ( ! symbolName . StartsWith ( "t_" , StringComparison . OrdinalIgnoreCase ) )
109- return "t_" + symbolName ;
208+ name = name . Substring ( 2 ) ;
110209 }
111- else if ( ! symbolName . StartsWith ( "s_" , StringComparison . OrdinalIgnoreCase ) )
112- return "s_" + symbolName ;
210+ }
113211
114- return symbolName ;
212+ if ( fieldSymbol . IsStatic )
213+ {
214+ // Check for ThreadStatic private fields.
215+ if ( fieldSymbol . GetAttributes ( ) . Any ( a => a . AttributeClass . Name . Equals ( "ThreadStaticAttribute" , StringComparison . Ordinal ) ) )
216+ {
217+ return "t_" + name ;
218+ }
219+ else
220+ {
221+ return "s_" + name ;
222+ }
115223 }
116224
117- return "_" + symbolName ;
225+ return "_" + name ;
118226 }
119227
120- private async Task < Solution > CleanSolutionAsync ( Solution solution , CancellationToken cancellationToken )
228+ private static async Task < Solution > CleanSolutionAsync ( Solution newSolution , Solution oldSolution , CancellationToken cancellationToken )
121229 {
122- var documentIdsToProcess = solution . Projects . SelectMany ( p => p . DocumentIds ) . ToList ( ) ;
123- const string rename = "Rename" ;
124- foreach ( var documentId in documentIdsToProcess )
125- {
126- var root = await solution . GetDocument ( documentId ) . GetSyntaxRootAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
230+ var rewriter = new RemoveRenameAnnotationsRewriter ( ) ;
231+ var solution = newSolution ;
127232
128- while ( true )
233+ foreach ( var projectChange in newSolution . GetChanges ( oldSolution ) . GetProjectChanges ( ) )
234+ {
235+ foreach ( var documentId in projectChange . GetChangedDocuments ( ) )
129236 {
130- var renameNodes = root . DescendantNodes ( descendIntoTrivia : true ) . Where ( s => s . GetAnnotations ( rename ) . Any ( ) ) ;
131- if ( ! renameNodes . Any ( ) )
132- {
133- break ;
134- }
135-
136- root = root . ReplaceNode ( renameNodes . First ( ) , renameNodes . First ( ) . WithoutAnnotations ( rename ) ) ;
237+ solution = await CleanSolutionDocument ( rewriter , solution , documentId , cancellationToken ) ;
137238 }
239+ }
138240
139- while ( true )
140- {
141- var renameTokens = root . DescendantTokens ( descendIntoTrivia : true ) . Where ( s => s . GetAnnotations ( rename ) . Any ( ) ) ;
142- if ( ! renameTokens . Any ( ) )
143- {
144- break ;
145- }
146-
147- root = root . ReplaceToken ( renameTokens . First ( ) , renameTokens . First ( ) . WithoutAnnotations ( rename ) ) ;
148- }
241+ return solution ;
242+ }
149243
150- solution = solution . WithDocumentSyntaxRoot ( documentId , root ) ;
244+ private static async Task < Solution > CleanSolutionDocument ( RemoveRenameAnnotationsRewriter rewriter , Solution solution , DocumentId documentId , CancellationToken cancellationToken )
245+ {
246+ var document = solution . GetDocument ( documentId ) ;
247+ var syntaxNode = await document . GetSyntaxRootAsync ( cancellationToken ) as CSharpSyntaxNode ;
248+ if ( syntaxNode == null )
249+ {
250+ return solution ;
151251 }
152252
153- return solution ;
253+ var newNode = rewriter . Visit ( syntaxNode ) ;
254+ return solution . WithDocumentSyntaxRoot ( documentId , newNode ) ;
154255 }
155256 }
156257}
0 commit comments