File: CodeFixes\CorrectNextControlVariable\CorrectNextControlVariableCodeFixProvider.vb
Web Access
Project: src\src\Features\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Features.vbproj (Microsoft.CodeAnalysis.VisualBasic.Features)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
 
Imports System.Collections.Immutable
Imports System.Composition
Imports System.Diagnostics.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.CorrectNextControlVariable
    <ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeFixProviderNames.CorrectNextControlVariable), [Shared]>
    Partial Friend Class CorrectNextControlVariableCodeFixProvider
        Inherits CodeFixProvider
 
        Friend Const BC30070 As String = "BC30070" ' Next control variable does not match For loop control variable 'x'.
        Friend Const BC30451 As String = "BC30451" 'BC30451: 'y' is not declared. It may be inaccessible due to its protection level.
 
        <ImportingConstructor>
        <SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification:="Used in test code: https://github.com/dotnet/roslyn/issues/42814")>
        Public Sub New()
        End Sub
 
        Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String)
            Get
                Return ImmutableArray.Create(BC30070, BC30451)
            End Get
        End Property
 
        Public Overrides Function GetFixAllProvider() As FixAllProvider
            ' Fix All is not supported by this code fix
            ' https://github.com/dotnet/roslyn/issues/34470
            Return Nothing
        End Function
 
        Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task
            Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False)
 
            Dim node = root.FindNode(context.Span, getInnermostNodeForTie:=True)
            Dim nextStatement = node.FirstAncestorOrSelf(Of NextStatementSyntax)()
 
            If node Is Nothing OrElse nextStatement Is Nothing Then
                Return
            End If
 
            ' A Next statement could have multiple control variables. Find the index of the variable so that we 
            ' can find the correct nested ForBlock and it's control variable.
 
            ' The span of the diagnostic could be over just part of the controlvariable in case of unbound identifiers
            ' and so find the full expression for the control variable to be replaced.
            Dim indexOfControlVariable = nextStatement.ControlVariables.IndexOf(Function(n) n.Span.Contains(context.Span))
            If indexOfControlVariable = -1 Then
                Return
            End If
 
            Dim nodeToReplace = nextStatement.ControlVariables(indexOfControlVariable)
            Dim controlVariable = FindControlVariable(nextStatement, indexOfControlVariable)
            If controlVariable Is Nothing Then
                Return
            End If
 
            Dim newNode = SyntaxFactory.IdentifierName(controlVariable.Value).
                                    WithLeadingTrivia(nodeToReplace.GetLeadingTrivia()).
                                    WithTrailingTrivia(nodeToReplace.GetTrailingTrivia())
 
            context.RegisterCodeFix(New CorrectNextControlVariableCodeAction(context.Document, nodeToReplace, newNode), context.Diagnostics)
        End Function
 
        Private Shared Function FindControlVariable(nextStatement As NextStatementSyntax, nestingLevel As Integer) As SyntaxToken?
            Debug.Assert(nestingLevel >= 0)
 
            ' If we have code like this:
            ' For Each x In {1,2}
            '     For Each y in {1,3}
            '     Next y, x
            ' The Next statement is attached to the innermost for block. Starting from that block, the nesting level
            ' is the number of loops that we have to step up.
            Dim currentNode As SyntaxNode = nextStatement
            Dim forBlock As ForOrForEachBlockSyntax = Nothing
            For i = 0 To nestingLevel
                forBlock = currentNode.GetAncestor(Of ForOrForEachBlockSyntax)()
                If forBlock Is Nothing Then
                    Return Nothing
                End If
 
                currentNode = forBlock
            Next
 
            ' A ForBlockSyntax can either be a ForBlock or a ForEachBlock. Get the control variable
            ' from that.
            Dim controlVariable As SyntaxNode
            Select Case forBlock.Kind()
                Case SyntaxKind.ForBlock
                    Dim forStatement = DirectCast(forBlock.ForOrForEachStatement, ForStatementSyntax)
                    controlVariable = forStatement.ControlVariable
                    Exit Select
                Case SyntaxKind.ForEachBlock
                    Dim forEachStatement = DirectCast(forBlock.ForOrForEachStatement, ForEachStatementSyntax)
                    controlVariable = forEachStatement.ControlVariable
                    Exit Select
                Case Else
                    Debug.Assert(False, "Unknown next statement")
                    Return Nothing
            End Select
 
            ' The control variable can either be:
            ' For x = 1 to 10
            ' For x As Integer = 1 to 10
            Select Case controlVariable.Kind()
                Case SyntaxKind.IdentifierName
                    Return DirectCast(controlVariable, IdentifierNameSyntax).Identifier
                Case SyntaxKind.VariableDeclarator
                    Return DirectCast(controlVariable, VariableDeclaratorSyntax).Names.Single().Identifier
                Case Else
                    Debug.Assert(False, "Unknown control variable expression")
                    Return Nothing
            End Select
        End Function
    End Class
End Namespace