44using System . Collections . Generic ;
55using System . Collections . Immutable ;
66using System . Diagnostics ;
7+ using System . Linq ;
78using Microsoft . CodeAnalysis ;
89using Microsoft . CodeAnalysis . CSharp . Syntax ;
910using Microsoft . CodeAnalysis . Diagnostics ;
@@ -100,6 +101,49 @@ private void CodeBlockAction(OperationBlockAnalysisContext context)
100101 return ;
101102 }
102103
104+ Dictionary < string , List < Location > > containerProperties = new ( ) ;
105+
106+ // Check that 'container.Controls.Add(...)' statements are consequitive.
107+ // If not - the code has been manually modified, we won't be able to provide an auto-fix.
108+ foreach ( string containerName in calculatedContext . ContainerAddLocations . Keys )
109+ {
110+ containerProperties [ containerName ] = new ( ) ;
111+
112+ Location startLine = Location . None ;
113+ Location endLine = Location . None ;
114+ List < int > lines = new ( ) ;
115+ foreach ( Location location in calculatedContext . ContainerAddLocations [ containerName ] )
116+ {
117+ if ( startLine == Location . None || startLine . GetLineSpan ( ) . StartLinePosition . Line > location . GetLineSpan ( ) . StartLinePosition . Line )
118+ {
119+ startLine = location ;
120+ }
121+
122+ if ( endLine . GetLineSpan ( ) . StartLinePosition . Line < location . GetLineSpan ( ) . StartLinePosition . Line )
123+ {
124+ endLine = location ;
125+ }
126+
127+ lines . Add ( location . GetLineSpan ( ) . StartLinePosition . Line ) ;
128+ }
129+
130+ Debug . Assert ( startLine != Location . None ) ;
131+ Debug . Assert ( endLine != Location . None ) ;
132+
133+ if ( startLine == endLine )
134+ {
135+ // A single control with an invalid TabIndex
136+ }
137+ else if ( Enumerable . Range ( startLine . GetLineSpan ( ) . StartLinePosition . Line , endLine . GetLineSpan ( ) . StartLinePosition . Line - startLine . GetLineSpan ( ) . StartLinePosition . Line ) . Except ( lines ) . Any ( ) )
138+ {
139+ // 'container.Controls.Add(...)' statements aren't consequitive.
140+ }
141+ else
142+ {
143+ containerProperties [ containerName ] = calculatedContext . ContainerAddLocations [ containerName ] ;
144+ }
145+ }
146+
103147 // _controlsAddIndex dictionary, which looks something like this:
104148 //
105149 // [this.Controls.Add] : new List { button3, this.button1 }
@@ -111,37 +155,46 @@ private void CodeBlockAction(OperationBlockAnalysisContext context)
111155 // [this.button1:1]
112156 // [label2:0]
113157 Dictionary < string , int > flatControlsAddIndex = new ( ) ;
114- foreach ( string key in calculatedContext . ControlsAddIndex . Keys )
158+ Dictionary < string , string > containersByControl = new ( ) ;
159+ foreach ( string containerName in calculatedContext . ControlsAddIndex . Keys )
115160 {
116- for ( int i = 0 ; i < calculatedContext . ControlsAddIndex [ key ] . Count ; i ++ )
161+ for ( int i = 0 ; i < calculatedContext . ControlsAddIndex [ containerName ] . Count ; i ++ )
117162 {
118- string controlName = calculatedContext . ControlsAddIndex [ key ] [ i ] ;
163+ string controlName = calculatedContext . ControlsAddIndex [ containerName ] [ i ] ;
119164 flatControlsAddIndex [ controlName ] = i ;
165+
166+ containersByControl [ controlName ] = containerName ;
120167 }
121168 }
122169
123170 // Verify explicit TabIndex is the same as the "add order"
124- foreach ( string key in calculatedContext . ControlsTabIndex . Keys )
171+ foreach ( string controlName in calculatedContext . ControlsTabIndex . Keys )
125172 {
126- if ( ! flatControlsAddIndex . ContainsKey ( key ) )
173+ if ( ! flatControlsAddIndex . ContainsKey ( controlName ) )
127174 {
128175 // TODO: assert, diagnostics, etc.
129176 continue ;
130177 }
131178
132- int tabIndex = calculatedContext . ControlsTabIndex [ key ] ;
133- int addIndex = flatControlsAddIndex [ key ] ;
179+ int tabIndex = calculatedContext . ControlsTabIndex [ controlName ] ;
180+ int addIndex = flatControlsAddIndex [ controlName ] ;
134181
135182 if ( tabIndex == addIndex )
136183 {
137184 continue ;
138185 }
139186
187+ string containerName = containersByControl [ controlName ] ;
188+ Dictionary < string , string ? > properties = new ( ) ;
189+ properties [ "ZOrder" ] = addIndex . ToString ( ) ;
190+ properties [ "TabIndex" ] = tabIndex . ToString ( ) ;
191+
140192 var diagnostic = Diagnostic . Create (
141193 descriptor : InconsistentTabIndexRuleIdDescriptor ,
142- location : calculatedContext . ControlsAddIndexLocations [ key ] ,
143- properties : new Dictionary < string , string ? > { { "ZOrder" , addIndex . ToString ( ) } , { "TabIndex" , tabIndex . ToString ( ) } } . ToImmutableDictionary ( ) ,
144- key , addIndex , tabIndex ) ;
194+ location : calculatedContext . ControlsAddIndexLocations [ controlName ] ,
195+ additionalLocations : containerProperties [ containerName ] ,
196+ properties . ToImmutableDictionary ( ) ,
197+ controlName , addIndex , tabIndex ) ;
145198 context . ReportDiagnostic ( diagnostic ) ;
146199 }
147200 }
@@ -169,7 +222,7 @@ private void ParseControlAddStatements(InvocationExpressionSyntax expressionSynt
169222
170223 // this is something like "this.Controls.Add" or "panel1.Controls.Add", but good enough for our intents and purposes
171224 ExpressionSyntax ? syntax = expressionSyntax . Expression ;
172- string container = syntax . ToString ( ) ;
225+ string containerName = syntax . ToString ( ) ;
173226
174227 // Transform "Controls.Add" statements into a map. E.g.:
175228 //
@@ -182,13 +235,20 @@ private void ParseControlAddStatements(InvocationExpressionSyntax expressionSynt
182235 // [this.Controls.Add] : new List { button3, this.button1 }
183236 // [panel1.Controls.Add] : new List { label2 }
184237
185- if ( ! calculatedContext . ControlsAddIndex . ContainsKey ( container ) )
238+ if ( ! calculatedContext . ControlsAddIndex . ContainsKey ( containerName ) )
239+ {
240+ calculatedContext . ControlsAddIndex [ containerName ] = new List < string > ( ) ;
241+ }
242+
243+ calculatedContext . ControlsAddIndex [ containerName ] . Add ( controlName ) ;
244+ calculatedContext . ControlsAddIndexLocations [ controlName ] = syntax . Parent ! . Parent ! . GetLocation ( ) ; // e.g.: 'this.Controls.Add(button3);'
245+
246+ if ( ! calculatedContext . ContainerAddLocations . ContainsKey ( containerName ) )
186247 {
187- calculatedContext . ControlsAddIndex [ container ] = new List < string > ( ) ;
248+ calculatedContext . ContainerAddLocations [ containerName ] = new ( ) ;
188249 }
189250
190- calculatedContext . ControlsAddIndex [ container ] . Add ( controlName ) ;
191- calculatedContext . ControlsAddIndexLocations [ controlName ] = syntax . Parent ! . Parent ! . GetLocation ( ) ;
251+ calculatedContext . ContainerAddLocations [ containerName ] . Add ( calculatedContext . ControlsAddIndexLocations [ controlName ] ) ;
192252 }
193253
194254 private void ParseTabIndexAssignments ( AssignmentExpressionSyntax expressionSyntax , OperationBlockAnalysisContext context , CalculatedAnalysisContext calculatedContext )
@@ -246,6 +306,8 @@ private sealed class CalculatedAnalysisContext
246306 // Contains the list of fields and local controls in order those are added to parent controls.
247307 public Dictionary < string , List < string > > ControlsAddIndex { get ; } = new ( ) ;
248308 public Dictionary < string , Location > ControlsAddIndexLocations { get ; } = new ( ) ;
309+
310+ public Dictionary < string , List < Location > > ContainerAddLocations { get ; } = new ( ) ;
249311 }
250312 }
251313}
0 commit comments