TypeSyntaxSimplifierWalker.vb 6.8 KB
Newer Older
1 2
' Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

3
Imports System.Collections.Immutable
4 5 6 7 8 9 10 11
Imports System.Threading
Imports Microsoft.CodeAnalysis.Options
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.SimplifyTypeNames
    Friend Class TypeSyntaxSimplifierWalker
        Inherits VisualBasicSyntaxWalker

12 13
        Private Shared ReadOnly s_emptyAliasedNames As ImmutableHashSet(Of String) = ImmutableHashSet.Create(Of String)(CaseInsensitiveComparison.Comparer)

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
        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]))

33 34 35 36 37
        Private ReadOnly _analyzer As VisualBasicSimplifyTypeNamesDiagnosticAnalyzer
        Private ReadOnly _semanticModel As SemanticModel
        Private ReadOnly _optionSet As OptionSet
        Private ReadOnly _cancellationToken As CancellationToken

38 39 40 41 42 43
        ''' <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>
44
        Private ReadOnly _aliasedNames As ImmutableHashSet(Of String)
45

46 47 48 49 50 51 52 53 54
        Public ReadOnly Property Diagnostics As List(Of Diagnostic) = New List(Of Diagnostic)()

        Public Sub New(analyzer As VisualBasicSimplifyTypeNamesDiagnosticAnalyzer, semanticModel As SemanticModel, optionSet As OptionSet, cancellationToken As CancellationToken)
            MyBase.New(SyntaxWalkerDepth.StructuredTrivia)

            _analyzer = analyzer
            _semanticModel = semanticModel
            _optionSet = optionSet
            _cancellationToken = cancellationToken
55

56 57 58
            Dim root = semanticModel.SyntaxTree.GetRoot(cancellationToken)
            _aliasedNames = GetAliasedNames(TryCast(root, CompilationUnitSyntax))

59 60 61 62 63
            For Each aliasSymbol In semanticModel.Compilation.AliasImports()
                _aliasedNames = _aliasedNames.Add(aliasSymbol.Target.Name)
            Next
        End Sub

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
        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)
87 88
                If identifierName IsNot Nothing Then
                    If Not String.IsNullOrEmpty(identifierName.Identifier.ValueText) Then
89
                        aliasedNames = aliasedNames.Add(identifierName.Identifier.ValueText)
90 91 92
                    End If
                End If
            End If
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
        End Sub

        Public Overrides Sub VisitQualifiedName(node As QualifiedNameSyntax)
            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 node.IsKind(SyntaxKind.SimpleMemberAccessExpression) AndAlso TrySimplify(node) Then
                Return
            End If

            MyBase.VisitMemberAccessExpression(node)
        End Sub

        Public Overrides Sub VisitIdentifierName(node As IdentifierNameSyntax)
112 113 114 115
            ' 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).
116 117 118 119 120 121 122
            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
123 124

            If canTrySimplify AndAlso TrySimplify(node) Then
125 126 127 128 129 130
                Return
            End If

            MyBase.VisitIdentifierName(node)
        End Sub

131 132 133 134 135 136 137 138
        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

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
        Public Overrides Sub VisitGenericName(node As GenericNameSyntax)
            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, _optionSet, _cancellationToken) Then
                Return False
            End If

            Diagnostics.Add(diagnostic)
            Return True
        End Function
    End Class
End Namespace