VisualBasicAddImportCodeFixProvider.vb 15.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
' Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

Imports System.Collections.Immutable
Imports System.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.CaseCorrection
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.CodeFixes.AddImport
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.LanguageServices
11
Imports Microsoft.CodeAnalysis.Simplification
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.AddImport
    <ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeFixProviderNames.AddUsingOrImport), [Shared]>
    Friend Class VisualBasicAddImportCodeFixProvider
        Inherits AbstractAddImportCodeFixProvider

        ''' <summary>
        ''' Type xxx is not defined
        ''' </summary>
        Friend Const BC30002 = "BC30002"

        ''' <summary>
        ''' Error 'x' is not declared
        ''' </summary>
        Friend Const BC30451 = "BC30451"

        ''' <summary>
        ''' xxx is not a member of yyy
        ''' </summary>
        Friend Const BC30456 = "BC30456"

34 35 36 37 38
        ''' <summary>
        ''' 'X' has no parameters and its return type cannot be indexed
        ''' </summary>
        Friend Const BC32016 = "BC32016"

39 40 41 42 43 44
        ''' <summary>
        ''' Too few type arguments
        ''' </summary>
        Friend Const BC32042 = "BC32042"

        ''' <summary>
C
Charles Stoner 已提交
45
        ''' Expression of type xxx is not queryable
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
        ''' </summary>
        Friend Const BC36593 = "BC36593"

        ''' <summary>
        ''' 'A' has no type parameters and so cannot have type arguments.
        ''' </summary>
        Friend Const BC32045 = "BC32045"

        ''' <summary>
        ''' 'A' is not accessible in this context because it is 'Friend'.
        ''' </summary>
        Friend Const BC30389 = "BC30389"

        ''' <summary>
        ''' 'A' cannot be used as an attribute because it does not inherit from 'System.Attribute'.
        ''' </summary>
        Friend Const BC31504 = "BC31504"

        ''' <summary>
        ''' Name 'A' is either not declared or not in the current scope.
        ''' </summary>
        Friend Const BC36610 = "BC36610"

69 70 71 72 73
        ''' <summary>
        ''' Cannot initialize the type 'A' with a collection initializer because it does not have an accessible 'Add' method
        ''' </summary>
        Friend Const BC36719 = "BC36719"

74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
        ''' <summary>
        ''' Option Strict On disallows implicit conversions from 'Integer' to 'String'.
        ''' </summary>
        Friend Const BC30512 = "BC30512"

        ''' <summary>
        ''' 'A' is not accessible in this context because it is 'Private'.
        ''' </summary>
        Friend Const BC30390 = "BC30390"

        ''' <summary>
        ''' XML comment has a tag With a 'cref' attribute that could not be resolved. XML comment will be ignored.
        ''' </summary>
        Friend Const BC42309 = "BC42309"

        ''' <summary>
        ''' Type expected.
        ''' </summary>
        Friend Const BC30182 = "BC30182"

        Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String)
            Get
96
                Return ImmutableArray.Create(BC30002, BC30451, BC30456, BC32042, BC36593, BC32045, BC30389, BC31504, BC32016, BC36610, BC36719, BC30512, BC30390, BC42309, BC30182)
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
            End Get
        End Property

        Protected Overrides ReadOnly Property IgnoreCase As Boolean
            Get
                Return True
            End Get
        End Property

        Protected Overrides Function CanAddImport(node As SyntaxNode, cancellationToken As CancellationToken) As Boolean
            If node.GetAncestor(Of ImportsStatementSyntax)() IsNot Nothing Then
                Return False
            End If

            Return node.CanAddImportsStatements(cancellationToken)
        End Function

        Protected Overrides Function CanAddImportForMethod(diagnostic As Diagnostic, syntaxFacts As ISyntaxFactsService, ByRef node As SyntaxNode) As Boolean
            Select Case diagnostic.Id
                Case BC30456, BC30390, BC42309, BC30451
                    Exit Select
                Case BC30512
                    ' look up its corresponding method name
                    Dim parent = node.GetAncestor(Of InvocationExpressionSyntax)()
                    If parent Is Nothing Then
                        Return False
                    End If
                    Dim method = TryCast(parent.Expression, MemberAccessExpressionSyntax)
                    If method IsNot Nothing Then
                        node = method.Name
                    Else
                        node = parent.Expression
                    End If
                    Exit Select
131 132 133 134 135 136
                Case BC36719
                    If node.IsKind(SyntaxKind.ObjectCollectionInitializer) Then
                        Return True
                    End If

                    Return False
137 138 139 140 141 142 143 144 145 146
                Case BC32016
                    Dim memberAccessName = TryCast(node, MemberAccessExpressionSyntax)?.Name
                    Dim conditionalAccessName = TryCast(TryCast(TryCast(node, ConditionalAccessExpressionSyntax)?.WhenNotNull, InvocationExpressionSyntax)?.Expression, MemberAccessExpressionSyntax)?.Name

                    If memberAccessName Is Nothing AndAlso conditionalAccessName Is Nothing Then
                        Return False
                    End If

                    node = If(memberAccessName Is Nothing, conditionalAccessName, memberAccessName)
                    Exit Select
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
                Case Else
                    Return False
            End Select

            Dim memberAccess = TryCast(node, MemberAccessExpressionSyntax)
            If memberAccess IsNot Nothing Then
                node = memberAccess.Name
            End If

            If memberAccess.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) Then
                Return False
            End If

            Dim simpleName = TryCast(node, SimpleNameSyntax)
            If simpleName Is Nothing Then
                Return False
            End If

            Return True
        End Function

        Protected Overrides Function CanAddImportForNamespace(diagnostic As Diagnostic, ByRef node As SyntaxNode) As Boolean
            Select Case diagnostic.Id
                Case BC30002, BC30451
                    Exit Select
                Case Else
                    Return False
            End Select

            Return CanAddImportForTypeOrNamespaceCore(node)
        End Function

        Protected Overrides Function CanAddImportForQuery(diagnostic As Diagnostic, ByRef node As SyntaxNode) As Boolean
            If diagnostic.Id <> BC36593 Then
                Return False
            End If

            Dim queryClause = node.GetAncestor(Of QueryExpressionSyntax)()
            Return queryClause IsNot Nothing
        End Function

        Private Function IsOutermostQueryExpression(node As SyntaxNode) As Boolean
            ' TODO(cyrusn): Figure out how to implement this.
            Return True
        End Function

        Protected Overrides Function CanAddImportForType(diagnostic As Diagnostic, ByRef node As SyntaxNode) As Boolean
            Select Case diagnostic.Id
                Case BC30002, BC30451, BC32042, BC32045, BC30389, BC31504, BC36610, BC30182
                    Exit Select
                Case BC42309
                    Select Case node.Kind
                        Case SyntaxKind.XmlCrefAttribute
                            node = CType(node, XmlCrefAttributeSyntax).Reference.DescendantNodes().OfType(Of IdentifierNameSyntax).FirstOrDefault()
                        Case SyntaxKind.CrefReference
                            node = CType(node, CrefReferenceSyntax).DescendantNodes().OfType(Of IdentifierNameSyntax).FirstOrDefault()
                    End Select
                Case Else
                    Return False
            End Select

            Return CanAddImportForTypeOrNamespaceCore(node)
        End Function

        Private Shared Function CanAddImportForTypeOrNamespaceCore(ByRef node As SyntaxNode) As Boolean
            Dim qn = TryCast(node, QualifiedNameSyntax)
            If qn IsNot Nothing Then
                node = GetLeftMostSimpleName(qn)
            End If

            Dim simpleName = TryCast(node, SimpleNameSyntax)
            Return simpleName.LooksLikeStandaloneTypeName()
        End Function

        Private Shared Function GetLeftMostSimpleName(qn As QualifiedNameSyntax) As SimpleNameSyntax
            While (qn IsNot Nothing)
                Dim left = qn.Left
                Dim simpleName = TryCast(left, SimpleNameSyntax)
                If simpleName IsNot Nothing Then
                    Return simpleName
                End If
                qn = TryCast(left, QualifiedNameSyntax)
            End While
            Return Nothing
        End Function

        Protected Overrides Function GetDescription(namespaceSymbol As INamespaceOrTypeSymbol, semanticModel As SemanticModel, root As SyntaxNode) As String
            Return String.Format(VBFeaturesResources.Import, namespaceSymbol.ToDisplayString())
        End Function

        Protected Overrides Function GetNamespacesInScope(semanticModel As SemanticModel, node As SyntaxNode, cancellationToken As CancellationToken) As ISet(Of INamespaceSymbol)
            Return semanticModel.GetImportNamespacesInScope(node)
        End Function

        Protected Overrides Function GetQueryClauseInfo(
                model As SemanticModel,
                node As SyntaxNode,
                cancellationToken As CancellationToken) As ITypeSymbol

            Dim query = TryCast(node, QueryExpressionSyntax)

            If query Is Nothing Then
                query = node.GetAncestor(Of QueryExpressionSyntax)()
            End If

            Dim semanticModel = DirectCast(model, SemanticModel)

            For Each clause In query.Clauses
                If TypeOf clause Is AggregateClauseSyntax Then
                    Dim aggregateClause = DirectCast(clause, AggregateClauseSyntax)
                    Dim aggregateInfo = semanticModel.GetAggregateClauseSymbolInfo(aggregateClause, cancellationToken)
                    If IsValid(aggregateInfo.Select1) OrElse IsValid(aggregateInfo.Select2) Then
                        Return Nothing
                    End If

                    For Each variable In aggregateClause.AggregationVariables
                        Dim info = semanticModel.GetSymbolInfo(variable.Aggregation, cancellationToken)
                        If IsValid(info) Then
                            Return Nothing
                        End If
                    Next
                Else
                    Dim symbolInfo = semanticModel.GetSymbolInfo(clause, cancellationToken)
                    If IsValid(symbolInfo) Then
                        Return Nothing
                    End If
                End If
            Next

            Dim type As ITypeSymbol
            Dim fromOrAggregateClause = query.Clauses.First()
            If TypeOf fromOrAggregateClause Is FromClauseSyntax Then
                Dim fromClause = DirectCast(fromOrAggregateClause, FromClauseSyntax)
                type = semanticModel.GetTypeInfo(fromClause.Variables.First().Expression, cancellationToken).Type
            Else
                Dim aggregateClause = DirectCast(fromOrAggregateClause, AggregateClauseSyntax)
                type = semanticModel.GetTypeInfo(aggregateClause.Variables.First().Expression, cancellationToken).Type
            End If

            Return type
        End Function

        Private Function IsValid(info As SymbolInfo) As Boolean
            Dim symbol = info.Symbol.GetOriginalUnreducedDefinition()
            Return symbol IsNot Nothing AndAlso symbol.Locations.Length > 0
        End Function

        Protected Overloads Overrides Function AddImportAsync(
                contextNode As SyntaxNode,
                symbol As INamespaceOrTypeSymbol,
                document As Document,
                placeSystemNamespaceFirst As Boolean,
                cancellationToken As CancellationToken) As Task(Of Document)
            Dim memberImportsClause =
301
                SyntaxFactory.SimpleImportsClause(name:=DirectCast(symbol.GenerateTypeSyntax(addGlobal:=False), NameSyntax).WithAdditionalAnnotations(Simplifier.Annotation))
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
            Dim newImport = SyntaxFactory.ImportsStatement(
                importsClauses:=SyntaxFactory.SingletonSeparatedList(Of ImportsClauseSyntax)(memberImportsClause))

            Dim syntaxTree = contextNode.SyntaxTree
            Dim root = DirectCast(syntaxTree.GetRoot(cancellationToken), CompilationUnitSyntax)
            Return Task.FromResult(
                document.WithSyntaxRoot(
                root.AddImportsStatement(newImport, placeSystemNamespaceFirst,
                                         CaseCorrector.Annotation, Formatter.Annotation)))
        End Function

        Protected Overrides Function IsViableExtensionMethod(method As IMethodSymbol,
                                                             expression As SyntaxNode,
                                                             semanticModel As SemanticModel,
                                                             syntaxFacts As ISyntaxFactsService,
                                                             cancellationToken As CancellationToken) As Boolean
            Dim leftExpressionType As ITypeSymbol = Nothing
            If syntaxFacts.IsInvocationExpression(expression) Then
                leftExpressionType = semanticModel.GetEnclosingNamedType(expression.SpanStart, cancellationToken)
            Else
322 323 324 325 326 327 328 329
                Dim leftExpression As SyntaxNode
                If TypeOf expression Is ObjectCreationExpressionSyntax Then
                    leftExpression = expression
                Else
                    leftExpression = syntaxFacts.GetExpressionOfMemberAccessExpression(expression)
                    If leftExpression Is Nothing Then
                        Return False
                    End If
330 331 332 333 334 335 336 337 338 339 340 341 342
                End If

                Dim semanticInfo = semanticModel.GetTypeInfo(leftExpression, cancellationToken)
                leftExpressionType = semanticInfo.Type
            End If

            Return leftExpressionType IsNot Nothing AndAlso method.ReduceExtensionMethod(leftExpressionType) IsNot Nothing
        End Function

        Protected Overrides Function GetProposedTypes(name As String, accessibleTypeSymbols As List(Of ITypeSymbol), semanticModel As SemanticModel, namespacesInScope As ISet(Of INamespaceSymbol)) As IEnumerable(Of ITypeSymbol)
            Return From typeSymbol In accessibleTypeSymbols
                   Select typeSymbol.ContainingType
        End Function
343 344 345 346 347 348 349 350

        Friend Overrides Function IsViableField(field As IFieldSymbol, expression As SyntaxNode, semanticModel As SemanticModel, syntaxFacts As ISyntaxFactsService, cancellationToken As CancellationToken) As Boolean
            Return False
        End Function

        Friend Overrides Function IsViableProperty([property] As IPropertySymbol, expression As SyntaxNode, semanticModel As SemanticModel, syntaxFacts As ISyntaxFactsService, cancellationToken As CancellationToken) As Boolean
            Return False
        End Function
351 352 353 354 355 356 357 358 359 360 361 362 363

        Friend Overrides Function IsAddMethodContext(node As SyntaxNode, semanticModel As SemanticModel) As Boolean
            If node.IsKind(SyntaxKind.ObjectCollectionInitializer) Then
                Dim objectCreateExpression = node.GetAncestor(Of ObjectCreationExpressionSyntax)
                If objectCreateExpression Is Nothing Then
                    Return False
                End If

                Return True
            End If

            Return False
        End Function
364 365
    End Class
End Namespace