' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.

Imports System.Collections.Immutable
Imports System.IO
Imports System.Windows.Forms.Analyzers.Diagnostics
Imports System.Windows.Forms.VisualBasic.Analyzers.AvoidPassingTaskWithoutCancellationToken
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Testing
Imports Microsoft.CodeAnalysis.VisualBasic.Testing
Imports Xunit

<ForceGC()>
<SkipOnArchitecture(TestArchitectures.X86, "Analyzer tests hit OutOfMemoryException on x86 due to memory-mapped NuGet package extraction")>
Public Class AvoidPassingTaskWithoutCancellationTokenTests

    Private Const TestCode As String = "
Imports System
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Windows.Forms

Namespace VisualBasicControls

    Public Module Program

        Public Sub Main()

            Dim control As New Control()

            ' A sync Action delegate is always fine.
            Dim okAction As New Action(Sub() control.Text = ""Hello, World!"")

            ' A sync Func delegate is also fine.
            Dim okFunc As New Func(Of Integer)(Function() 42)

            ' Just a Task we will get in trouble since it's handled as a fire and forget.
            Dim notOkAsyncFunc As New Func(Of Task)(Function() 
                control.Text = ""Hello, World!""
                Return Task.CompletedTask
            End Function)

            ' A Task returning a value will also get us in trouble since it's handled as a fire and forget.
            Dim notOkAsyncFunc2 As New Func(Of Task(Of Integer))(Function() 
                control.Text = ""Hello, World!""
                Return Task.FromResult(42)
            End Function)

            ' OK.
            Dim task1 = control.InvokeAsync(okAction)

            ' Also OK.
            Dim task2 = control.InvokeAsync(okFunc)

            ' Concerning. - Most likely fire and forget by accident. We should warn about this.
            Dim task3 = control.InvokeAsync(notOkAsyncFunc, System.Threading.CancellationToken.None)

            ' Again: Concerning. - Most likely fire and forget by accident. We should warn about this.
            Dim task4 = control.InvokeAsync(notOkAsyncFunc, System.Threading.CancellationToken.None)

            ' And again concerning. - We should warn about this, too.
            Dim task5 = control.InvokeAsync(notOkAsyncFunc2, System.Threading.CancellationToken.None)

            ' This is OK, since we're passing a cancellation token.
            Dim okAsyncFunc = New Func(Of CancellationToken, ValueTask)(Function(cancellation) 
                control.Text = ""Hello, World!""
                Return ValueTask.CompletedTask
            End Function)

            ' This is also OK, again, because we're passing a cancellation token.
            Dim okAsyncFunc2 = New Func(Of CancellationToken, ValueTask(Of Integer))(Function(cancellation) 
                control.Text = ""Hello, World!""
                Return ValueTask.FromResult(42)
            End Function)

            ' And let's test that, too:
            Dim task6 = control.InvokeAsync(okAsyncFunc, System.Threading.CancellationToken.None)

            ' And that, too:
            Dim task7 = control.InvokeAsync(okAsyncFunc2, System.Threading.CancellationToken.None)

        End Sub

    End Module

End Namespace

"

    Public Shared Iterator Function GetReferenceAssemblies() As IEnumerable(Of Object())
        Yield {
            ReferenceAssemblies.Net.Net90.AddPackages(
                ImmutableArray.Create(New PackageIdentity("Microsoft.WindowsDesktop.App.Ref", "9.0.0"))
            ), ""
        }
        ' The latest public API surface area build from this repository.
        Yield {CurrentReferences.NetCoreAppReferences, CurrentReferences.WinFormsRefPath}
    End Function


    <Theory>
    <MemberData(NameOf(GetReferenceAssemblies))>
    Public Async Function VB_AvoidPassingFuncReturningTaskWithoutCancellationAnalyzer(referenceAssemblies As ReferenceAssemblies, pathToWinFormsAssembly As String) As Task
        Assert.NotNull(referenceAssemblies)
        Assert.NotNull(pathToWinFormsAssembly)
        Assert.True(pathToWinFormsAssembly = "" Or File.Exists(pathToWinFormsAssembly))

        Dim diagnosticId As String = DiagnosticIDs.AvoidPassingFuncReturningTaskWithoutCancellationToken

        Dim context As New VisualBasicAnalyzerTest(Of AvoidPassingTaskWithoutCancellationTokenAnalyzer, DefaultVerifier) With
            {
                .TestCode = TestCode,
                .ReferenceAssemblies = referenceAssemblies
            }

        context.TestState.OutputKind = OutputKind.WindowsApplication
        context.TestState.ExpectedDiagnostics.AddRange(
            {
                DiagnosticResult.CompilerWarning(diagnosticId).WithSpan(40, 25, 40, 101),
                DiagnosticResult.CompilerWarning(diagnosticId).WithSpan(43, 25, 43, 101),
                DiagnosticResult.CompilerWarning(diagnosticId).WithSpan(46, 25, 46, 102)
            })

        If pathToWinFormsAssembly <> "" Then
            context.TestState.AdditionalReferences.Add(pathToWinFormsAssembly)
        End If

        Await context.RunAsync().ConfigureAwait(continueOnCapturedContext:=True)
    End Function

End Class
