VisualBasicAddImportCodeFixProvider.vb 18.3 KB
Newer Older
1 2 3 4 5 6
' 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
7
Imports Microsoft.CodeAnalysis.CodeActions
8 9 10 11
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.CodeFixes.AddImport
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.LanguageServices
12
Imports Microsoft.CodeAnalysis.Packaging
13
Imports Microsoft.CodeAnalysis.Simplification
14
Imports Microsoft.CodeAnalysis.SymbolSearch
15 16 17 18 19
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax

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

22 23 24 25 26 27
        Public Sub New()
        End Sub

        ''' <summary>
        ''' For testing purposes so that tests can pass in mocks for these values.
        ''' </summary>
28 29
        Friend Sub New(installerService As IPackageInstallerService,
                       searchService As ISymbolSearchService)
30 31 32
            MyBase.New(installerService, searchService)
        End Sub

33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
        ''' <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"

48 49 50 51 52
        ''' <summary>
        ''' 'X' has no parameters and its return type cannot be indexed
        ''' </summary>
        Friend Const BC32016 = "BC32016"

53 54 55 56 57 58
        ''' <summary>
        ''' Too few type arguments
        ''' </summary>
        Friend Const BC32042 = "BC32042"

        ''' <summary>
C
Charles Stoner 已提交
59
        ''' Expression of type xxx is not queryable
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
        ''' </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"

83 84 85 86 87
        ''' <summary>
        ''' Cannot initialize the type 'A' with a collection initializer because it does not have an accessible 'Add' method
        ''' </summary>
        Friend Const BC36719 = "BC36719"

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
        ''' <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
110
                Return ImmutableArray.Create(BC30002, BC30451, BC30456, BC32042, BC36593, BC32045, BC30389, BC31504, BC32016, BC36610, BC36719, BC30512, BC30390, BC42309, BC30182)
111 112 113 114 115 116 117 118 119 120 121
            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

122 123 124 125 126
        Protected Overrides Function CanAddImportForMethod(
                diagnostic As Diagnostic,
                syntaxFacts As ISyntaxFactsService,
                node As SyntaxNode,
                ByRef nameNode As SimpleNameSyntax) As Boolean
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
            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
143 144 145 146 147 148
                Case BC36719
                    If node.IsKind(SyntaxKind.ObjectCollectionInitializer) Then
                        Return True
                    End If

                    Return False
149 150 151 152 153 154 155 156 157 158
                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
159 160 161 162 163 164 165 166 167 168 169 170 171
                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

172 173
            nameNode = TryCast(node, SimpleNameSyntax)
            If nameNode Is Nothing Then
174 175 176 177 178 179
                Return False
            End If

            Return True
        End Function

180
        Protected Overrides Function CanAddImportForNamespace(diagnostic As Diagnostic, node As SyntaxNode, ByRef nameNode As SimpleNameSyntax) As Boolean
181 182 183 184 185 186 187
            Select Case diagnostic.Id
                Case BC30002, BC30451
                    Exit Select
                Case Else
                    Return False
            End Select

188
            Return CanAddImportForTypeOrNamespaceCore(node, nameNode)
189 190
        End Function

191
        Protected Overrides Function CanAddImportForQuery(diagnostic As Diagnostic, node As SyntaxNode) As Boolean
192 193 194 195 196 197 198 199 200 201 202 203 204
            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

205 206
        Protected Overrides Function CanAddImportForType(
                diagnostic As Diagnostic, node As SyntaxNode, ByRef nameNode As SimpleNameSyntax) As Boolean
207 208 209 210 211 212 213 214 215 216 217 218 219 220
            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

221
            Return CanAddImportForTypeOrNamespaceCore(node, nameNode)
222 223
        End Function

224
        Private Shared Function CanAddImportForTypeOrNamespaceCore(node As SyntaxNode, ByRef nameNode As SimpleNameSyntax) As Boolean
225 226 227 228 229
            Dim qn = TryCast(node, QualifiedNameSyntax)
            If qn IsNot Nothing Then
                node = GetLeftMostSimpleName(qn)
            End If

230 231
            nameNode = TryCast(node, SimpleNameSyntax)
            Return nameNode.LooksLikeStandaloneTypeName()
232 233 234 235 236 237 238 239 240 241 242 243 244 245
        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

246 247 248 249 250 251
        Protected Overrides Function GetDescription(nameParts As IReadOnlyList(Of String)) As String
            Return $"Imports { String.Join(".", nameParts) }"
        End Function

        Protected Overrides Function GetDescription(namespaceSymbol As INamespaceOrTypeSymbol, semanticModel As SemanticModel,
                                                    root As SyntaxNode, checkForExistingImport As Boolean) As String
252
            Return $"Imports {namespaceSymbol.ToDisplayString()}"
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 301 302 303 304 305 306 307 308 309 310 311
        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

312
        Protected Overloads Overrides Async Function AddImportAsync(
313 314 315 316 317
                contextNode As SyntaxNode,
                symbol As INamespaceOrTypeSymbol,
                document As Document,
                placeSystemNamespaceFirst As Boolean,
                cancellationToken As CancellationToken) As Task(Of Document)
318

319 320 321 322 323 324 325 326 327 328 329 330 331 332
            Dim nameSyntax = DirectCast(symbol.GenerateTypeSyntax(addGlobal:=False), NameSyntax)

            Return Await AddImportsAsync(
                contextNode, document, placeSystemNamespaceFirst, nameSyntax,
                additionalAnnotation:=Nothing, cancellationToken:=cancellationToken).ConfigureAwait(False)
        End Function

        Private Shared Async Function AddImportsAsync(
                contextNode As SyntaxNode,
                document As Document,
                placeSystemNamespaceFirst As Boolean,
                nameSyntax As NameSyntax,
                additionalAnnotation As SyntaxAnnotation,
                cancellationToken As CancellationToken) As Task(Of Document)
333
            Dim root = DirectCast(Await contextNode.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(False), CompilationUnitSyntax)
334

335
            Dim memberImportsClause =
336
                SyntaxFactory.SimpleImportsClause(name:=nameSyntax.WithAdditionalAnnotations(Simplifier.Annotation))
337 338 339
            Dim newImport = SyntaxFactory.ImportsStatement(
                importsClauses:=SyntaxFactory.SingletonSeparatedList(Of ImportsClauseSyntax)(memberImportsClause))

340 341 342 343 344 345 346 347 348
            If additionalAnnotation IsNot Nothing Then
                newImport = newImport.WithAdditionalAnnotations(additionalAnnotation)
            End If

            ' Don't add the import if an eqiuvalent one is already there.
            If root.Imports.Any(Function(i) i.IsEquivalentTo(newImport, topLevel:=False)) Then
                Return document
            End If

349
            Dim syntaxTree = contextNode.SyntaxTree
350
            Return document.WithSyntaxRoot(
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
                root.AddImportsStatement(newImport, placeSystemNamespaceFirst, CaseCorrector.Annotation, Formatter.Annotation))
        End Function

        Protected Overrides Function AddImportAsync(contextNode As SyntaxNode, nameSpaceParts As IReadOnlyList(Of String), document As Document, specialCaseSystem As Boolean, cancellationToken As CancellationToken) As Task(Of Document)
            Dim nameSyntax = CreateNameSyntax(nameSpaceParts, nameSpaceParts.Count - 1)

            ' Suppress diagnostics on the import we create.  Because we only get here when we are 
            ' adding a nuget package, it is certainly the case that in the preview this will not
            ' bind properly.  It will look silly to show such an error, so we just suppress things.
            Return AddImportsAsync(contextNode, document, specialCaseSystem, nameSyntax,
                                   SuppressDiagnosticsAnnotation.Create(), cancellationToken)
        End Function

        Private Function CreateNameSyntax(nameSpaceParts As IReadOnlyList(Of String), index As Integer) As NameSyntax
            Dim namePiece = SyntaxFactory.IdentifierName(nameSpaceParts(index))
            Return If(index = 0,
                DirectCast(namePiece, NameSyntax),
                SyntaxFactory.QualifiedName(CreateNameSyntax(nameSpaceParts, index - 1), namePiece))
369 370 371 372 373 374 375 376 377 378 379
        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
380 381 382 383 384 385 386 387
                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
388 389 390 391 392 393
                End If

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

394
            Return IsViableExtensionMethod(method, leftExpressionType)
395 396
        End Function

397 398 399 400 401 402 403
        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
404 405 406 407 408 409 410 411 412 413 414 415 416

        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
417 418
    End Class
End Namespace