|
' 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 System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.CodeFixes.Iterator
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Iterator
<ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeFixProviderNames.ConvertToIterator), [Shared]>
Friend Class VisualBasicConvertToIteratorCodeFixProvider
Inherits AbstractIteratorCodeFixProvider
Friend Const BC30451 As String = "BC30451" ' error BC30451 : 'Yield' is not declared. It may be inaccessible due its protection level.
Friend Shared ReadOnly Ids As ImmutableArray(Of String) = ImmutableArray.Create(BC30451)
<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 Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String)
Get
Return Ids
End Get
End Property
Protected Overrides Async Function GetCodeFixAsync(root As SyntaxNode, node As SyntaxNode, document As Document, diagnostics As Diagnostic, cancellationToken As CancellationToken) As Task(Of CodeAction)
' Check node is identifier name.
Dim identifier = TryCast(node, IdentifierNameSyntax)
If identifier Is Nothing Then
Return Nothing
End If
' Check identifier text is 'Yield'
If String.Compare(identifier.Identifier.Text, "Yield", StringComparison.OrdinalIgnoreCase) <> 0 Then
Return Nothing
End If
' Check that parent is invocation expression syntax
If identifier.Parent.Kind <> SyntaxKind.InvocationExpression Then
Return Nothing
End If
' Check that containing type is method
Dim model = Await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(False)
Dim symbol = model.GetEnclosingSymbol(node.Span.Start, cancellationToken)
Dim method = TryCast(symbol, IMethodSymbol)
If method Is Nothing Then
Return Nothing
End If
' Check that return type of containing method is convertible to IEnumerable
Dim ienumerableSymbol = model.Compilation.GetTypeByMetadataName(GetType(IEnumerable(Of)).FullName)
If ienumerableSymbol Is Nothing Then
Return Nothing
End If
If method.ReturnType.GetArity() <> 1 Then
Return Nothing
End If
ienumerableSymbol = ienumerableSymbol.Construct(method.ReturnType.GetTypeArguments().First())
If Not method.ReturnType.Equals(ienumerableSymbol) Then
Return Nothing
End If
' Get the syntax node for the function
Dim syntaxReference = method.DeclaringSyntaxReferences.FirstOrDefault
If syntaxReference Is Nothing Then
Return Nothing
End If
' Get the method or lambda expression and add the iterator keyword
Dim methodNode = Await syntaxReference.GetSyntaxAsync(cancellationToken).ConfigureAwait(False)
Select Case methodNode.Kind
Case SyntaxKind.FunctionStatement
Dim methodStatementNode = TryCast(methodNode, MethodStatementSyntax)
If methodStatementNode IsNot Nothing AndAlso Not methodStatementNode.Modifiers.Any(SyntaxKind.IteratorKeyword) Then
root = AddIteratorKeywordToMethod(root, methodStatementNode)
Dim title = String.Format(VisualBasicCodeFixesResources.Convert_0_to_Iterator, methodStatementNode.Identifier)
Return CodeAction.Create(title, Function(c) Task.FromResult(document.WithSyntaxRoot(root)), title)
End If
Case SyntaxKind.MultiLineFunctionLambdaExpression
Dim lambdaNode = TryCast(methodNode, LambdaExpressionSyntax)
If lambdaNode IsNot Nothing AndAlso Not lambdaNode.SubOrFunctionHeader.Modifiers.Any(SyntaxKind.IteratorKeyword) Then
root = AddIteratorKeywordToLambda(root, lambdaNode)
Dim title = String.Format(VisualBasicCodeFixesResources.Convert_0_to_Iterator, lambdaNode.SubOrFunctionHeader.GetTypeDisplayName())
Return CodeAction.Create(title, Function(c) Task.FromResult(document.WithSyntaxRoot(root)), title)
End If
Case Else
End Select
Return Nothing
End Function
Private Shared Function AddIteratorKeywordToMethod(root As SyntaxNode, methodStatementNode As MethodStatementSyntax) As SyntaxNode
' Add iterator keyword
Dim iteratorToken As SyntaxToken = Token(SyntaxKind.IteratorKeyword).WithAdditionalAnnotations(Formatter.Annotation)
Dim newFunctionNode As MethodStatementSyntax
' If the iterator keyword is going to be added to the front of the method declaration,
' we will need to move the trivia from the method onto the keyword.
If methodStatementNode.Modifiers.IsEmpty() AndAlso methodStatementNode.HasLeadingTrivia Then
Dim leadingTrivia = methodStatementNode.GetLeadingTrivia
iteratorToken = iteratorToken.WithLeadingTrivia(leadingTrivia)
newFunctionNode = methodStatementNode.WithLeadingTrivia(CType(Nothing, SyntaxTriviaList))
newFunctionNode = newFunctionNode.WithModifiers(methodStatementNode.Modifiers.Add(iteratorToken))
Else
newFunctionNode = methodStatementNode.WithModifiers(methodStatementNode.Modifiers.Add(iteratorToken))
End If
Return root.ReplaceNode(methodStatementNode, newFunctionNode)
End Function
Private Shared Function AddIteratorKeywordToLambda(root As SyntaxNode, lambdaNode As LambdaExpressionSyntax) As SyntaxNode
' Add iterator keyword
Dim iteratorToken As SyntaxToken = Token(SyntaxKind.IteratorKeyword).WithAdditionalAnnotations(Formatter.Annotation)
Dim newHeader = lambdaNode.SubOrFunctionHeader.WithModifiers(lambdaNode.SubOrFunctionHeader.Modifiers.Add(iteratorToken))
Return root.ReplaceNode(lambdaNode.SubOrFunctionHeader, newHeader)
End Function
End Class
End Namespace
|