File: Diagnostics\Analyzers\TypeSyntaxSimplifierWalker.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.Threading
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.Shared.Collections
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Simplification
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.SimplifyTypeNames
    Friend Class TypeSyntaxSimplifierWalker
        Inherits VisualBasicSyntaxWalker
 
        Private Shared ReadOnly s_emptyAliasedNames As ImmutableHashSet(Of String) = ImmutableHashSet.Create(Of String)(CaseInsensitiveComparison.Comparer)
 
        Private Shared ReadOnly s_predefinedTypeMetadataNames As ImmutableHashSet(Of String) = ImmutableHashSet.Create(
            CaseInsensitiveComparison.Comparer,
            NameOf([Boolean]),
            NameOf([SByte]),
            NameOf([Byte]),
            NameOf(Int16),
            NameOf(UInt16),
            NameOf(Int32),
            NameOf(UInt32),
            NameOf(Int64),
            NameOf(UInt64),
            NameOf([Single]),
            NameOf([Double]),
            NameOf([Decimal]),
            NameOf([String]),
            NameOf([Char]),
            NameOf(DateTime),
            NameOf([Object]))
 
        Private ReadOnly _analyzer As VisualBasicSimplifyTypeNamesDiagnosticAnalyzer
        Private ReadOnly _semanticModel As SemanticModel
        Private ReadOnly _options As VisualBasicSimplifierOptions
        Private ReadOnly _analyzerOptions As AnalyzerOptions
        Private ReadOnly _ignoredSpans As TextSpanMutableIntervalTree
        Private ReadOnly _cancellationToken As CancellationToken
 
        Private _diagnostics As ImmutableArray(Of Diagnostic).Builder
 
        ''' <summary>
        ''' Set of type and namespace names that have an alias associated with them.  i.e. if the
        ''' user has <c>Imports X = System.DateTime</c>, then <c>DateTime</c> will be in this set.
        ''' This is used so we can easily tell if we should try to simplify some identifier to an
        ''' alias when we encounter it.
        ''' </summary>
        Private ReadOnly _aliasedNames As ImmutableHashSet(Of String)
 
        Public ReadOnly Property Diagnostics As ImmutableArray(Of Diagnostic)
            Get
                Return If(_diagnostics?.ToImmutable(), ImmutableArray(Of Diagnostic).Empty)
            End Get
        End Property
 
        Public ReadOnly Property DiagnosticsBuilder As ImmutableArray(Of Diagnostic).Builder
            Get
                If _diagnostics Is Nothing Then
                    Interlocked.CompareExchange(_diagnostics, ImmutableArray.CreateBuilder(Of Diagnostic)(), Nothing)
                End If
 
                Return _diagnostics
            End Get
        End Property
 
        Public Sub New(analyzer As VisualBasicSimplifyTypeNamesDiagnosticAnalyzer, semanticModel As SemanticModel, options As VisualBasicSimplifierOptions, analyzerOptions As AnalyzerOptions, ignoredSpans As TextSpanMutableIntervalTree, cancellationToken As CancellationToken)
            MyBase.New(SyntaxWalkerDepth.StructuredTrivia)
 
            _analyzer = analyzer
            _semanticModel = semanticModel
            _options = options
            _analyzerOptions = analyzerOptions
            _ignoredSpans = ignoredSpans
            _cancellationToken = cancellationToken
 
            Dim root = semanticModel.SyntaxTree.GetRoot(cancellationToken)
            _aliasedNames = GetAliasedNames(TryCast(root, CompilationUnitSyntax))
 
            For Each aliasSymbol In semanticModel.Compilation.AliasImports()
                _aliasedNames = _aliasedNames.Add(aliasSymbol.Target.Name)
            Next
        End Sub
 
        Private Shared Function GetAliasedNames(compilationUnit As CompilationUnitSyntax) As ImmutableHashSet(Of String)
            Dim aliasedNames = s_emptyAliasedNames
            If compilationUnit Is Nothing Then
                Return aliasedNames
            End If
 
            For Each importsStatement In compilationUnit.Imports
                For Each importsClause In importsStatement.ImportsClauses
                    Dim simpleImportsClause = TryCast(importsClause, SimpleImportsClauseSyntax)
                    If simpleImportsClause Is Nothing Then
                        Continue For
                    End If
 
                    AddAliasedName(aliasedNames, simpleImportsClause)
                Next
            Next
 
            Return aliasedNames
        End Function
 
        Private Shared Sub AddAliasedName(ByRef aliasedNames As ImmutableHashSet(Of String), simpleImportsClause As SimpleImportsClauseSyntax)
            If simpleImportsClause.Alias IsNot Nothing Then
                Dim identifierName = TryCast(simpleImportsClause.Name.GetRightmostName(), IdentifierNameSyntax)
                If identifierName IsNot Nothing Then
                    If Not String.IsNullOrEmpty(identifierName.Identifier.ValueText) Then
                        aliasedNames = aliasedNames.Add(identifierName.Identifier.ValueText)
                    End If
                End If
            End If
        End Sub
 
        Public Overrides Sub VisitQualifiedName(node As QualifiedNameSyntax)
            If _ignoredSpans IsNot Nothing AndAlso _ignoredSpans.HasIntervalThatOverlapsWith(node.FullSpan.Start, node.FullSpan.Length) Then
                Return
            End If
 
            If node.IsKind(SyntaxKind.QualifiedName) AndAlso TrySimplify(node) Then
                Return
            End If
 
            MyBase.VisitQualifiedName(node)
        End Sub
 
        Public Overrides Sub VisitMemberAccessExpression(node As MemberAccessExpressionSyntax)
            If _ignoredSpans IsNot Nothing AndAlso _ignoredSpans.HasIntervalThatOverlapsWith(node.FullSpan.Start, node.FullSpan.Length) Then
                Return
            End If
 
            If node.IsKind(SyntaxKind.SimpleMemberAccessExpression) AndAlso TrySimplify(node) Then
                Return
            End If
 
            MyBase.VisitMemberAccessExpression(node)
        End Sub
 
        Public Overrides Sub VisitIdentifierName(node As IdentifierNameSyntax)
            If _ignoredSpans IsNot Nothing AndAlso _ignoredSpans.HasIntervalThatOverlapsWith(node.FullSpan.Start, node.FullSpan.Length) Then
                Return
            End If
 
            ' Always try to simplify identifiers with an 'Attribute' suffix.
            '
            ' In other cases, don't bother looking at the right side of A.B or A!B. We will process those in
            ' one of our other top level Visit methods (Like VisitQualifiedName).
            Dim canTrySimplify = CaseInsensitiveComparison.EndsWith(node.Identifier.ValueText, "Attribute")
            If Not canTrySimplify AndAlso Not node.IsRightSideOfDotOrBang() Then
                ' The only possible simplifications to an unqualified identifier are replacement with an alias or
                ' replacement with a predefined type.
                canTrySimplify = CanReplaceIdentifierWithAlias(node.Identifier.ValueText) _
                    OrElse CanReplaceIdentifierWithPredefinedType(node.Identifier.ValueText)
            End If
 
            If canTrySimplify AndAlso TrySimplify(node) Then
                Return
            End If
 
            MyBase.VisitIdentifierName(node)
        End Sub
 
        Private Function CanReplaceIdentifierWithAlias(identifier As String) As Boolean
            Return _aliasedNames.Contains(identifier)
        End Function
 
        Private Shared Function CanReplaceIdentifierWithPredefinedType(identifier As String) As Boolean
            Return s_predefinedTypeMetadataNames.Contains(identifier)
        End Function
 
        Public Overrides Sub VisitGenericName(node As GenericNameSyntax)
            If _ignoredSpans IsNot Nothing AndAlso _ignoredSpans.HasIntervalThatOverlapsWith(node.FullSpan.Start, node.FullSpan.Length) Then
                Return
            End If
 
            If node.IsKind(SyntaxKind.GenericName) AndAlso TrySimplify(node) Then
                Return
            End If
 
            MyBase.VisitGenericName(node)
        End Sub
 
        Private Function TrySimplify(node As SyntaxNode) As Boolean
            Dim diagnostic As Diagnostic = Nothing
            If Not _analyzer.TrySimplify(_semanticModel, node, diagnostic, _options, _analyzerOptions, _cancellationToken) Then
                Return False
            End If
 
            DiagnosticsBuilder.Add(diagnostic)
            Return True
        End Function
    End Class
End Namespace