|
' 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.Reflection
Imports System.Threading
Imports Microsoft.CodeAnalysis.CodeActions
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Copilot
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests
Imports Microsoft.CodeAnalysis.Editor.UnitTests
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.ErrorLogger
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.Host.Mef
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.UnitTests
Imports Roslyn.Utilities
Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests
<[UseExportProvider]>
<Trait(Traits.Feature, Traits.Features.Diagnostics)>
Public Class CodeFixServiceTests
Private Shared ReadOnly s_compositionWithMockDiagnosticUpdateSourceRegistrationService As TestComposition = EditorTestCompositions.EditorFeatures
Private ReadOnly _assemblyLoader As IAnalyzerAssemblyLoader = New InMemoryAssemblyLoader()
Public Function CreateAnalyzerFileReference(ByVal fullPath As String) As AnalyzerFileReference
Return New AnalyzerFileReference(fullPath, _assemblyLoader)
End Function
<Fact>
Public Async Function TestProjectCodeFix() As Task
Dim test = <Workspace>
<Project Language="C#" CommonReferences="true">
<Document FilePath="Test.cs">
class Goo { }
</Document>
</Project>
</Workspace>
Using workspace = EditorTestWorkspace.Create(test, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService)
Dim workspaceDiagnosticAnalyzer = New WorkspaceDiagnosticAnalyzer()
Dim workspaceCodeFixProvider = New WorkspaceCodeFixProvider()
Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(workspaceDiagnosticAnalyzer))
workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}))
Dim project = workspace.CurrentSolution.Projects(0)
Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)())
Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace)
Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService)))
Dim codefixService = New CodeFixService(
diagnosticService,
logger,
{New Lazy(Of CodeFixProvider, Mef.CodeChangeProviderMetadata)(
Function() workspaceCodeFixProvider,
New Mef.CodeChangeProviderMetadata(New Dictionary(Of String, Object)() From {{"Name", "C#"}, {"Languages", {LanguageNames.CSharp}}}))},
SpecializedCollections.EmptyEnumerable(Of Lazy(Of IConfigurationFixProvider, Mef.CodeChangeProviderMetadata)))
' Verify available diagnostics
Dim document = project.Documents.Single()
Dim diagnostics = Await diagnosticService.GetDiagnosticsForSpanAsync(document,
range:=(Await document.GetSyntaxRootAsync()).FullSpan, CancellationToken.None)
Assert.Equal(1, diagnostics.Count())
' Verify available codefix with a global fixer
Dim fixes = Await codefixService.GetFixesAsync(
document,
(Await document.GetSyntaxRootAsync()).FullSpan,
CancellationToken.None)
Assert.Equal(0, fixes.Count())
' Verify available codefix with a global fixer + a project fixer
' We will use this assembly as a project fixer provider.
Dim _assembly = Assembly.GetExecutingAssembly()
Dim projectAnalyzerReference = CreateAnalyzerFileReference(_assembly.Location)
Dim projectAnalyzerReferences = ImmutableArray.Create(Of AnalyzerReference)(projectAnalyzerReference)
project = project.WithAnalyzerReferences(projectAnalyzerReferences)
document = project.Documents.Single()
fixes = Await codefixService.GetFixesAsync(
document,
(Await document.GetSyntaxRootAsync()).FullSpan,
CancellationToken.None)
Assert.Equal(1, fixes.Count())
' Remove a project analyzer
project = project.RemoveAnalyzerReference(projectAnalyzerReference)
document = project.Documents.Single()
fixes = Await codefixService.GetFixesAsync(
document,
(Await document.GetSyntaxRootAsync()).FullSpan,
CancellationToken.None)
Assert.Equal(0, fixes.Count())
End Using
End Function
<Fact>
Public Async Function TestDifferentLanguageProjectCodeFix() As Task
Dim test = <Workspace>
<Project Language="Visual Basic" CommonReferences="true">
<Document FilePath="Test.vb">
Class Goo
End Class
</Document>
</Project>
</Workspace>
Using workspace = EditorTestWorkspace.Create(test, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService)
Dim workspaceDiagnosticAnalyzer = New WorkspaceDiagnosticAnalyzer()
Dim workspaceCodeFixProvider = New WorkspaceCodeFixProvider()
Dim analyzerReference = New AnalyzerImageReference(ImmutableArray.Create(Of DiagnosticAnalyzer)(workspaceDiagnosticAnalyzer))
workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference}))
Dim project = workspace.CurrentSolution.Projects(0)
Dim diagnosticService = Assert.IsType(Of DiagnosticAnalyzerService)(workspace.GetService(Of IDiagnosticAnalyzerService)())
Dim analyzer = diagnosticService.CreateIncrementalAnalyzer(workspace)
Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService)))
Dim codefixService = New CodeFixService(
diagnosticService,
logger,
{New Lazy(Of CodeFixProvider, Mef.CodeChangeProviderMetadata)(
Function() workspaceCodeFixProvider,
New Mef.CodeChangeProviderMetadata(New Dictionary(Of String, Object)() From {{"Name", "C#"}, {"Languages", {LanguageNames.CSharp}}}))},
SpecializedCollections.EmptyEnumerable(Of Lazy(Of IConfigurationFixProvider, Mef.CodeChangeProviderMetadata)))
' Verify available diagnostics
Dim document = project.Documents.Single()
Dim diagnostics = Await diagnosticService.GetDiagnosticsForSpanAsync(document,
range:=(Await document.GetSyntaxRootAsync()).FullSpan, CancellationToken.None)
Assert.Equal(1, diagnostics.Count())
' Verify no codefix with a global fixer
Dim fixes = Await codefixService.GetFixesAsync(
document,
(Await document.GetSyntaxRootAsync()).FullSpan,
CancellationToken.None)
Assert.Equal(0, fixes.Count())
' Verify no codefix with a global fixer + a project fixer
' We will use this assembly as a project fixer provider.
Dim _assembly = Assembly.GetExecutingAssembly()
Dim projectAnalyzerReference = CreateAnalyzerFileReference(_assembly.Location)
Dim projectAnalyzerReferences = ImmutableArray.Create(Of AnalyzerReference)(projectAnalyzerReference)
project = project.WithAnalyzerReferences(projectAnalyzerReferences)
document = project.Documents.Single()
fixes = Await codefixService.GetFixesAsync(
document,
(Await document.GetSyntaxRootAsync()).FullSpan,
CancellationToken.None)
Assert.Equal(0, fixes.Count())
End Using
End Function
Private Class WorkspaceDiagnosticAnalyzer
Inherits AbstractDiagnosticAnalyzer
Public ReadOnly Descriptor As DiagnosticDescriptor = New DiagnosticDescriptor("TEST1111",
"WorkspaceDiagnosticDescription",
"WorkspaceDiagnosticMessage",
"WorkspaceDiagnosticCategory",
DiagnosticSeverity.Warning,
isEnabledByDefault:=True)
Public Overrides ReadOnly Property DiagDescriptor As DiagnosticDescriptor
Get
Return Descriptor
End Get
End Property
End Class
Private MustInherit Class AbstractDiagnosticAnalyzer
Inherits DiagnosticAnalyzer
Public MustOverride ReadOnly Property DiagDescriptor As DiagnosticDescriptor
Public Overrides ReadOnly Property SupportedDiagnostics As ImmutableArray(Of DiagnosticDescriptor)
Get
Return ImmutableArray.Create(DiagDescriptor)
End Get
End Property
Public Overrides Sub Initialize(context As AnalysisContext)
context.RegisterSymbolAction(AddressOf AnalyzeSymbol, SymbolKind.NamedType)
End Sub
Public Sub AnalyzeSymbol(context As SymbolAnalysisContext)
context.ReportDiagnostic(Diagnostic.Create(DiagDescriptor, context.Symbol.Locations.First(), context.Symbol.Locations.Skip(1)))
End Sub
End Class
<ExportCodeFixProvider(LanguageNames.CSharp, Name:="WorkspaceCodeFixProvider"), [Shared]>
Private Class WorkspaceCodeFixProvider
Inherits CodeFixProvider
<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 NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String)
Get
Return ImmutableArray.Create("TEST0000")
End Get
End Property
Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task
Contract.ThrowIfFalse(context.Document.Project.Language = LanguageNames.CSharp)
Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False)
#Disable Warning RS0005
context.RegisterCodeFix(CodeAction.Create("FIX_TEST0000", Function(ct) Task.FromResult(context.Document.WithSyntaxRoot(root))), context.Diagnostics)
#Enable Warning RS0005
End Function
End Class
<ExportCodeFixProvider(LanguageNames.CSharp, Name:="ProjectCodeFixProvider"), [Shared]>
Public Class ProjectCodeFixProvider
Inherits CodeFixProvider
<ImportingConstructor>
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
Public Sub New()
End Sub
Public NotOverridable Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String)
Get
Return ImmutableArray.Create("TEST1111")
End Get
End Property
Public NotOverridable Overrides Async Function RegisterCodeFixesAsync(context As CodeFixContext) As Task
Contract.ThrowIfFalse(context.Document.Project.Language = LanguageNames.CSharp)
Dim root = Await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(False)
#Disable Warning RS0005
context.RegisterCodeFix(CodeAction.Create("FIX_TEST1111", Function(ct) Task.FromResult(context.Document.WithSyntaxRoot(root))), context.Diagnostics)
#Enable Warning RS0005
End Function
End Class
<Fact>
Public Async Function TestCopilotCodeAnalysisServiceWithoutSyntaxTree() As Task
Dim workspaceDefinition =
<Workspace>
<Project Language="NoCompilation" AssemblyName="TestAssembly" CommonReferencesPortable="true">
<Document>
var x = {}; // e.g., TypeScript code or anything else that doesn't support compilations
</Document>
</Project>
</Workspace>
Dim composition = EditorTestCompositions.EditorFeatures.AddParts(
GetType(NoCompilationContentTypeDefinitions),
GetType(NoCompilationContentTypeLanguageService),
GetType(NoCompilationCopilotCodeAnalysisService))
Using workspace = EditorTestWorkspace.Create(workspaceDefinition, composition:=composition)
Dim document = workspace.CurrentSolution.Projects.Single().Documents.Single()
Dim diagnosticsXml =
<Diagnostics>
<Error Id=<%= "TestId" %>
MappedFile=<%= document.Name %> MappedLine="0" MappedColumn="0"
OriginalFile=<%= document.Name %> OriginalLine="0" OriginalColumn="0"
Message=<%= "Test Message" %>/>
</Diagnostics>
Dim diagnostics = DiagnosticProviderTests.GetExpectedDiagnostics(workspace, diagnosticsXml)
Dim copilotCodeAnalysisService = document.Project.Services.GetService(Of ICopilotCodeAnalysisService)()
Dim noCompilationCopilotCodeAnalysisService = DirectCast(copilotCodeAnalysisService, NoCompilationCopilotCodeAnalysisService)
NoCompilationCopilotCodeAnalysisService.Diagnostics = diagnostics.SelectAsArray(Of Diagnostic)(
Function(d) d.ToDiagnosticAsync(document.Project, CancellationToken.None).Result)
Dim codefixService = workspace.ExportProvider.GetExportedValue(Of ICodeFixService)
' Make sure we don't crash
Dim unused = Await codefixService.GetMostSevereFixAsync(
document, Text.TextSpan.FromBounds(0, 0), New DefaultCodeActionRequestPriorityProvider(), CancellationToken.None)
End Using
End Function
<ExportLanguageService(GetType(ICopilotOptionsService), NoCompilationConstants.LanguageName, ServiceLayer.Test), [Shared], PartNotDiscoverable>
Private Class NoCompilationCopilotOptionsService
Implements ICopilotOptionsService
<ImportingConstructor>
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
Public Sub New()
End Sub
Public Function IsRefineOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotOptionsService.IsRefineOptionEnabledAsync
Return Task.FromResult(True)
End Function
Public Function IsCodeAnalysisOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotOptionsService.IsCodeAnalysisOptionEnabledAsync
Return Task.FromResult(True)
End Function
Public Function IsOnTheFlyDocsOptionEnabledAsync() As Task(Of Boolean) Implements ICopilotOptionsService.IsOnTheFlyDocsOptionEnabledASync
Return Task.FromResult(True)
End Function
End Class
<ExportLanguageService(GetType(ICopilotCodeAnalysisService), NoCompilationConstants.LanguageName, ServiceLayer.Test), [Shared], PartNotDiscoverable>
Private Class NoCompilationCopilotCodeAnalysisService
Implements ICopilotCodeAnalysisService
<ImportingConstructor>
<Obsolete(MefConstruction.ImportingConstructorMessage, True)>
Public Sub New()
End Sub
Public Shared Property Diagnostics As ImmutableArray(Of Diagnostic) = ImmutableArray(Of Diagnostic).Empty
Public Function IsAvailableAsync(cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsAvailableAsync
Return Task.FromResult(True)
End Function
Public Function GetAvailablePromptTitlesAsync(document As Document, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of String)) Implements ICopilotCodeAnalysisService.GetAvailablePromptTitlesAsync
Return Task.FromResult(ImmutableArray.Create("Title"))
End Function
Public Function AnalyzeDocumentAsync(document As Document, span As TextSpan?, promptTitle As String, cancellationToken As CancellationToken) As Task Implements ICopilotCodeAnalysisService.AnalyzeDocumentAsync
Return Task.CompletedTask
End Function
Public Function GetCachedDocumentDiagnosticsAsync(document As Document, span As TextSpan?, promptTitles As ImmutableArray(Of String), cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of Diagnostic)) Implements ICopilotCodeAnalysisService.GetCachedDocumentDiagnosticsAsync
Return Task.FromResult(Diagnostics)
End Function
Public Function StartRefinementSessionAsync(oldDocument As Document, newDocument As Document, primaryDiagnostic As Diagnostic, cancellationToken As CancellationToken) As Task Implements ICopilotCodeAnalysisService.StartRefinementSessionAsync
Return Task.CompletedTask
End Function
Public Function GetOnTheFlyDocsAsync(symbolSignature As String, declarationCode As ImmutableArray(Of String), language As String, cancellationToken As CancellationToken) As Task(Of String) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsAsync
Return Task.FromResult("")
End Function
Public Function IsFileExcludedAsync(filePath As String, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsFileExcludedAsync
Return Task.FromResult(False)
End Function
End Class
End Class
End Namespace
|