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,8 @@ private void CodeBlockAction(OperationBlockAnalysisContext context)
100101 return ;
101102 }
102103
104+ Dictionary < string , List < Location > > containerProperties = BuildContainerAddLocations ( calculatedContext . ContainerAddLocations ) ;
105+
103106 // _controlsAddIndex dictionary, which looks something like this:
104107 //
105108 // [this.Controls.Add] : new List { button3, this.button1 }
@@ -111,42 +114,99 @@ private void CodeBlockAction(OperationBlockAnalysisContext context)
111114 // [this.button1:1]
112115 // [label2:0]
113116 Dictionary < string , int > flatControlsAddIndex = new ( ) ;
114- foreach ( string key in calculatedContext . ControlsAddIndex . Keys )
117+ Dictionary < string , string > containersByControl = new ( ) ;
118+ foreach ( string containerName in calculatedContext . ControlsAddIndex . Keys )
115119 {
116- for ( int i = 0 ; i < calculatedContext . ControlsAddIndex [ key ] . Count ; i ++ )
120+ for ( int i = 0 ; i < calculatedContext . ControlsAddIndex [ containerName ] . Count ; i ++ )
117121 {
118- string controlName = calculatedContext . ControlsAddIndex [ key ] [ i ] ;
122+ string controlName = calculatedContext . ControlsAddIndex [ containerName ] [ i ] ;
119123 flatControlsAddIndex [ controlName ] = i ;
124+
125+ containersByControl [ controlName ] = containerName ;
120126 }
121127 }
122128
123129 // Verify explicit TabIndex is the same as the "add order"
124- foreach ( string key in calculatedContext . ControlsTabIndex . Keys )
130+ foreach ( string controlName in calculatedContext . ControlsTabIndex . Keys )
125131 {
126- if ( ! flatControlsAddIndex . ContainsKey ( key ) )
132+ if ( ! flatControlsAddIndex . ContainsKey ( controlName ) )
127133 {
128134 // TODO: assert, diagnostics, etc.
129135 continue ;
130136 }
131137
132- int tabIndex = calculatedContext . ControlsTabIndex [ key ] ;
133- int addIndex = flatControlsAddIndex [ key ] ;
138+ int tabIndex = calculatedContext . ControlsTabIndex [ controlName ] ;
139+ int addIndex = flatControlsAddIndex [ controlName ] ;
134140
135141 if ( tabIndex == addIndex )
136142 {
137143 continue ;
138144 }
139145
146+ string containerName = containersByControl [ controlName ] ;
147+ Dictionary < string , string ? > properties = new ( ) ;
148+ properties [ "ZOrder" ] = addIndex . ToString ( ) ;
149+ properties [ "TabIndex" ] = tabIndex . ToString ( ) ;
150+
140151 var diagnostic = Diagnostic . Create (
141152 descriptor : InconsistentTabIndexRuleIdDescriptor ,
142- location : calculatedContext . ControlsAddIndexLocations [ key ] ,
143- properties : new Dictionary < string , string ? > { { "ZOrder" , addIndex . ToString ( ) } , { "TabIndex" , tabIndex . ToString ( ) } } . ToImmutableDictionary ( ) ,
144- key , addIndex , tabIndex ) ;
153+ location : calculatedContext . ControlsAddIndexLocations [ controlName ] ,
154+ additionalLocations : containerProperties [ containerName ] ,
155+ properties . ToImmutableDictionary ( ) ,
156+ controlName , addIndex , tabIndex ) ;
145157 context . ReportDiagnostic ( diagnostic ) ;
146158 }
147159 }
148160 }
149161
162+ private static Dictionary < string , List < Location > > BuildContainerAddLocations ( in Dictionary < string , List < Location > > containerAddLocations )
163+ {
164+ Dictionary < string , List < Location > > containerProperties = new ( ) ;
165+
166+ // Check that 'container.Controls.Add(...)' statements are consequitive.
167+ // If not - the code has been manually modified, we won't be able to provide an auto-fix.
168+ foreach ( string containerName in containerAddLocations . Keys )
169+ {
170+ containerProperties [ containerName ] = new ( ) ;
171+
172+ Location startLine = Location . None ;
173+ Location endLine = Location . None ;
174+ List < int > lines = new ( ) ;
175+ foreach ( Location location in containerAddLocations [ containerName ] )
176+ {
177+ if ( startLine == Location . None || startLine . GetLineSpan ( ) . StartLinePosition . Line > location . GetLineSpan ( ) . StartLinePosition . Line )
178+ {
179+ startLine = location ;
180+ }
181+
182+ if ( endLine . GetLineSpan ( ) . StartLinePosition . Line < location . GetLineSpan ( ) . StartLinePosition . Line )
183+ {
184+ endLine = location ;
185+ }
186+
187+ lines . Add ( location . GetLineSpan ( ) . StartLinePosition . Line ) ;
188+ }
189+
190+ Debug . Assert ( startLine != Location . None ) ;
191+ Debug . Assert ( endLine != Location . None ) ;
192+
193+ if ( startLine == endLine )
194+ {
195+ // A single control with an invalid TabIndex
196+ }
197+ else if ( Enumerable . Range ( startLine . GetLineSpan ( ) . StartLinePosition . Line , endLine . GetLineSpan ( ) . StartLinePosition . Line - startLine . GetLineSpan ( ) . StartLinePosition . Line ) . Except ( lines ) . Any ( ) )
198+ {
199+ // 'container.Controls.Add(...)' statements aren't consequitive.
200+ }
201+ else
202+ {
203+ containerProperties [ containerName ] = containerAddLocations [ containerName ] ;
204+ }
205+ }
206+
207+ return containerProperties ;
208+ }
209+
150210 private void ParseControlAddStatements ( InvocationExpressionSyntax expressionSyntax , CalculatedAnalysisContext calculatedContext )
151211 {
152212 if ( ! expressionSyntax . Expression . ToString ( ) . EndsWith ( ".Controls.Add" ) )
@@ -169,7 +229,7 @@ private void ParseControlAddStatements(InvocationExpressionSyntax expressionSynt
169229
170230 // this is something like "this.Controls.Add" or "panel1.Controls.Add", but good enough for our intents and purposes
171231 ExpressionSyntax ? syntax = expressionSyntax . Expression ;
172- string container = syntax . ToString ( ) ;
232+ string containerName = syntax . ToString ( ) ;
173233
174234 // Transform "Controls.Add" statements into a map. E.g.:
175235 //
@@ -182,13 +242,20 @@ private void ParseControlAddStatements(InvocationExpressionSyntax expressionSynt
182242 // [this.Controls.Add] : new List { button3, this.button1 }
183243 // [panel1.Controls.Add] : new List { label2 }
184244
185- if ( ! calculatedContext . ControlsAddIndex . ContainsKey ( container ) )
245+ if ( ! calculatedContext . ControlsAddIndex . ContainsKey ( containerName ) )
246+ {
247+ calculatedContext . ControlsAddIndex [ containerName ] = new List < string > ( ) ;
248+ }
249+
250+ calculatedContext . ControlsAddIndex [ containerName ] . Add ( controlName ) ;
251+ calculatedContext . ControlsAddIndexLocations [ controlName ] = syntax . Parent ! . Parent ! . GetLocation ( ) ; // e.g.: 'this.Controls.Add(button3);'
252+
253+ if ( ! calculatedContext . ContainerAddLocations . ContainsKey ( containerName ) )
186254 {
187- calculatedContext . ControlsAddIndex [ container ] = new List < string > ( ) ;
255+ calculatedContext . ContainerAddLocations [ containerName ] = new ( ) ;
188256 }
189257
190- calculatedContext . ControlsAddIndex [ container ] . Add ( controlName ) ;
191- calculatedContext . ControlsAddIndexLocations [ controlName ] = syntax . Parent ! . Parent ! . GetLocation ( ) ;
258+ calculatedContext . ContainerAddLocations [ containerName ] . Add ( calculatedContext . ControlsAddIndexLocations [ controlName ] ) ;
192259 }
193260
194261 private void ParseTabIndexAssignments ( AssignmentExpressionSyntax expressionSyntax , OperationBlockAnalysisContext context , CalculatedAnalysisContext calculatedContext )
@@ -246,6 +313,8 @@ private sealed class CalculatedAnalysisContext
246313 // Contains the list of fields and local controls in order those are added to parent controls.
247314 public Dictionary < string , List < string > > ControlsAddIndex { get ; } = new ( ) ;
248315 public Dictionary < string , Location > ControlsAddIndexLocations { get ; } = new ( ) ;
316+
317+ public Dictionary < string , List < Location > > ContainerAddLocations { get ; } = new ( ) ;
249318 }
250319 }
251320}
0 commit comments