diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs index e98976184a0..36c0ad6f593 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddInstanceMemberParameter.fs @@ -3,7 +3,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Threading.Tasks open System.Collections.Immutable open Microsoft.CodeAnalysis.Text @@ -17,16 +16,17 @@ type internal FSharpAddInstanceMemberParameterCodeFixProvider() = static let title = SR.AddMissingInstanceMemberParameter() - interface IFSharpCodeFix with - member _.GetChangesAsync _ span = - let changes = [ TextChange(TextSpan(span.Start, 0), "x.") ] - CancellableTask.singleton (title, changes) - override _.FixableDiagnosticIds = ImmutableArray.Create("FS0673") - override this.RegisterCodeFixesAsync context : Task = - cancellableTask { - let! title, changes = (this :> IFSharpCodeFix).GetChangesAsync context.Document context.Span - context.RegisterFsharpFix(CodeFix.AddInstanceMemberParameter, title, changes) - } - |> CancellableTask.startAsTask context.CancellationToken + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this) + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync _ span = + let codeFix = + { + Name = CodeFix.AddInstanceMemberParameter + Message = title + Changes = [ TextChange(TextSpan(span.Start, 0), "x.") ] + } + + CancellableTask.singleton (Some codeFix) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs index ec9c7dad660..73c7b9c7413 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs @@ -5,16 +5,11 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Collections.Immutable open System.Composition -open System.Threading open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes -open FSharp.Compiler -open FSharp.Compiler.CodeAnalysis - -open Microsoft.CodeAnalysis -open Microsoft.CodeAnalysis.CodeActions +open CancellableTasks [] type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [] () = @@ -24,48 +19,48 @@ type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [ liftAsync + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this) - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync document span = + cancellableTask { + let! cancellationToken = CancellableTask.getCurrentCancellationToken () - let funcStartPos = - let rec loop ch pos = - if not (Char.IsWhiteSpace(ch)) then - pos - else - loop sourceText.[pos + 1] (pos + 1) + let! defines, langVersion = + document.GetFSharpCompilationDefinesAndLangVersionAsync( + nameof (FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider) + ) - loop sourceText.[context.Span.End + 1] (context.Span.End + 1) + let! sourceText = document.GetTextAsync(cancellationToken) - let! funcLexerSymbol = - Tokenizer.getSymbolAtPosition ( - context.Document.Id, - sourceText, - funcStartPos, - context.Document.FilePath, - defines, - SymbolLookupKind.Greedy, - false, - false, - Some langVersion, - context.CancellationToken - ) + let funcStartPos = + let rec loop ch pos = + if not (Char.IsWhiteSpace(ch)) then + pos + else + loop sourceText.[pos + 1] (pos + 1) - let! funcNameSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, funcLexerSymbol.Range) - let funcName = sourceText.GetSubText(funcNameSpan).ToString() + loop sourceText.[span.End + 1] (span.End + 1) - do - context.RegisterFsharpFix( - CodeFix.AddMissingRecToMutuallyRecFunctions, - String.Format(titleFormat, funcName), - [| TextChange(TextSpan(context.Span.End, 0), " rec") |] - ) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + return + Tokenizer.getSymbolAtPosition ( + document.Id, + sourceText, + funcStartPos, + document.FilePath, + defines, + SymbolLookupKind.Greedy, + false, + false, + Some langVersion, + cancellationToken + ) + |> Option.bind (fun funcLexerSymbol -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, funcLexerSymbol.Range)) + |> Option.map (fun funcNameSpan -> sourceText.GetSubText(funcNameSpan).ToString()) + |> Option.map (fun funcName -> + { + Name = CodeFix.AddMissingRecToMutuallyRecFunctions + Message = String.Format(titleFormat, funcName) + Changes = [ TextChange(TextSpan(span.End, 0), " rec") ] + }) + } diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs b/vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs index 7136009a928..26d693a8751 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ChangeToUpcast.fs @@ -3,7 +3,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Threading.Tasks open System.Collections.Immutable open Microsoft.CodeAnalysis.Text @@ -17,8 +16,10 @@ type internal FSharpChangeToUpcastCodeFixProvider() = override _.FixableDiagnosticIds = ImmutableArray.Create("FS3198") - interface IFSharpCodeFix with - member _.GetChangesAsync document span = + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this) + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync document span = cancellableTask { let! cancellationToken = CancellableTask.getCurrentCancellationToken () @@ -29,33 +30,32 @@ type internal FSharpChangeToUpcastCodeFixProvider() = let isDowncastOperator = text.Contains(":?>") let isDowncastKeyword = text.Contains("downcast") - let changes = - [ - if - (isDowncastOperator || isDowncastKeyword) - && not (isDowncastOperator && isDowncastKeyword) - then - let replacement = - if isDowncastOperator then - text.Replace(":?>", ":>") - else - text.Replace("downcast", "upcast") - - TextChange(span, replacement) - ] - - let title = - if isDowncastOperator then - SR.UseUpcastOperator() - else - SR.UseUpcastKeyword() - - return title, changes + if + (isDowncastOperator || isDowncastKeyword) + && not (isDowncastOperator && isDowncastKeyword) + then + let replacement = + if isDowncastOperator then + text.Replace(":?>", ":>") + else + text.Replace("downcast", "upcast") + + let changes = [ TextChange(span, replacement) ] + + let title = + if isDowncastOperator then + SR.UseUpcastOperator() + else + SR.UseUpcastKeyword() + + let codeFix = + { + Name = CodeFix.ChangeToUpcast + Message = title + Changes = changes + } + + return Some codeFix + else + return None } - - override this.RegisterCodeFixesAsync context : Task = - cancellableTask { - let! title, changes = (this :> IFSharpCodeFix).GetChangesAsync context.Document context.Span - context.RegisterFsharpFix(CodeFix.ChangeToUpcast, title, changes) - } - |> CancellableTask.startAsTask context.CancellationToken diff --git a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs index 924bf6d38fb..13ce3fc3095 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/CodeFixHelpers.fs @@ -2,9 +2,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor -open System open System.Threading -open System.Threading.Tasks open System.Collections.Immutable open System.Diagnostics @@ -15,6 +13,8 @@ open Microsoft.CodeAnalysis.CodeFixes open Microsoft.CodeAnalysis.CodeActions open Microsoft.VisualStudio.FSharp.Editor.Telemetry +open CancellableTasks + [] module internal CodeFixHelpers = let private reportCodeFixTelemetry @@ -80,3 +80,11 @@ module internal CodeFixExtensions = let diag = diagnostics |> Option.defaultValue ctx.Diagnostics ctx.RegisterCodeFix(codeAction, diag) + + member ctx.RegisterFsharpFix(codeFix: IFSharpCodeFixProvider) = + cancellableTask { + match! codeFix.GetCodeFixIfAppliesAsync ctx.Document ctx.Span with + | Some codeFix -> ctx.RegisterFsharpFix(codeFix.Name, codeFix.Message, codeFix.Changes) + | None -> () + } + |> CancellableTask.startAsTask ctx.CancellationToken diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs index da39b93e9c3..1b2fbe49211 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs @@ -3,7 +3,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Threading.Tasks open System.Collections.Immutable open Microsoft.CodeAnalysis.Text @@ -19,8 +18,10 @@ type internal FSharpConvertToAnonymousRecordCodeFixProvider [ Option.bind (fun range -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, range)) |> Option.map (fun span -> - [ - TextChange(TextSpan(span.Start + 1, 0), "|") - TextChange(TextSpan(span.End - 1, 0), "|") - ]) - |> Option.defaultValue [] - - return title, changes + { + Name = CodeFix.ConvertToAnonymousRecord + Message = title + Changes = + [ + TextChange(TextSpan(span.Start + 1, 0), "|") + TextChange(TextSpan(span.End - 1, 0), "|") + ] + }) } - - override this.RegisterCodeFixesAsync context : Task = - cancellableTask { - let! title, changes = (this :> IFSharpCodeFix).GetChangesAsync context.Document context.Span - return context.RegisterFsharpFix(CodeFix.ConvertToAnonymousRecord, title, changes) - } - |> CancellableTask.startAsTask context.CancellationToken diff --git a/vsintegration/src/FSharp.Editor/CodeFix/IFSharpCodeFix.fs b/vsintegration/src/FSharp.Editor/CodeFix/IFSharpCodeFix.fs index e0a4fa23a6e..36a4ff2db56 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/IFSharpCodeFix.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/IFSharpCodeFix.fs @@ -7,5 +7,12 @@ open Microsoft.CodeAnalysis.Text open CancellableTasks -type IFSharpCodeFix = - abstract member GetChangesAsync: document: Document -> span: TextSpan -> CancellableTask +type FSharpCodeFix = + { + Name: string + Message: string + Changes: TextChange list + } + +type IFSharpCodeFixProvider = + abstract member GetCodeFixIfAppliesAsync: document: Document -> span: TextSpan -> CancellableTask diff --git a/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs b/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs index f69688fde13..16f33212ef7 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/RemoveReturnOrYield.fs @@ -8,40 +8,44 @@ open System.Collections.Immutable open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes +open CancellableTasks + [] type internal FSharpRemoveReturnOrYieldCodeFixProvider [] () = inherit CodeFixProvider() - override _.FixableDiagnosticIds = ImmutableArray.Create("FS0748", "FS0747") + override _.FixableDiagnosticIds = ImmutableArray.Create("FS0747", "FS0748") + + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this) - override _.RegisterCodeFixesAsync context = - asyncMaybe { - let! parseResults = - context.Document.GetFSharpParseResultsAsync(nameof (FSharpRemoveReturnOrYieldCodeFixProvider)) - |> liftAsync + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync document span = + cancellableTask { + let! cancellationToken = CancellableTask.getCurrentCancellationToken () - let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + let! parseResults = document.GetFSharpParseResultsAsync(nameof (FSharpRemoveReturnOrYieldCodeFixProvider)) - let errorRange = - RoslynHelpers.TextSpanToFSharpRange(context.Document.FilePath, context.Span, sourceText) + let! sourceText = document.GetTextAsync(cancellationToken) - let! exprRange = parseResults.TryRangeOfExprInYieldOrReturn errorRange.Start - let! exprSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, exprRange) + let errorRange = + RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText) - let title = - let text = sourceText.GetSubText(context.Span).ToString() + return + parseResults.TryRangeOfExprInYieldOrReturn errorRange.Start + |> Option.bind (fun exprRange -> RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, exprRange)) + |> Option.map (fun exprSpan -> [ TextChange(span, sourceText.GetSubText(exprSpan).ToString()) ]) + |> Option.map (fun changes -> + let title = + let text = sourceText.GetSubText(span).ToString() - if text.StartsWith("return!") then SR.RemoveReturnBang() - elif text.StartsWith("return") then SR.RemoveReturn() - elif text.StartsWith("yield!") then SR.RemoveYieldBang() - else SR.RemoveYield() + if text.StartsWith("return!") then SR.RemoveReturnBang() + elif text.StartsWith("return") then SR.RemoveReturn() + elif text.StartsWith("yield!") then SR.RemoveYieldBang() + else SR.RemoveYield() - do - context.RegisterFsharpFix( - CodeFix.RemoveReturnOrYield, - title, - [| TextChange(context.Span, sourceText.GetSubText(exprSpan).ToString()) |] - ) - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + { + Name = CodeFix.RemoveReturnOrYield + Message = title + Changes = changes + }) + } diff --git a/vsintegration/src/FSharp.Editor/CodeFix/WrapExpressionInParentheses.fs b/vsintegration/src/FSharp.Editor/CodeFix/WrapExpressionInParentheses.fs index 70ed64f5054..2c04415d2b2 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/WrapExpressionInParentheses.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/WrapExpressionInParentheses.fs @@ -3,13 +3,12 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition -open System.Threading -open System.Threading.Tasks open System.Collections.Immutable -open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes -open Microsoft.CodeAnalysis.CodeActions +open Microsoft.CodeAnalysis.Text + +open CancellableTasks [] type internal FSharpWrapExpressionInParenthesesFixProvider() = @@ -19,13 +18,19 @@ type internal FSharpWrapExpressionInParenthesesFixProvider() = override _.FixableDiagnosticIds = ImmutableArray.Create("FS0597") - override this.RegisterCodeFixesAsync context : Task = - backgroundTask { - let changes = - [ - TextChange(TextSpan(context.Span.Start, 0), "(") - TextChange(TextSpan(context.Span.End + 1, 0), ")") - ] - - context.RegisterFsharpFix(CodeFix.AddParentheses, title, changes) - } + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix(this) + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync _ span = + let codeFix = + { + Name = CodeFix.AddParentheses + Message = title + Changes = + [ + TextChange(TextSpan(span.Start, 0), "(") + TextChange(TextSpan(span.End, 0), ")") + ] + } + + CancellableTask.singleton (Some codeFix) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddInstanceMemberParameterTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddInstanceMemberParameterTests.fs index 1fbd6b4a39d..6737045f7f1 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddInstanceMemberParameterTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddInstanceMemberParameterTests.fs @@ -19,15 +19,16 @@ type UsefulTestHarness() = """ let expected = - { - Title = "Add missing instance member parameter" - FixedCode = - """ + Some + { + Message = "Add missing instance member parameter" + FixedCode = + """ type UsefulTestHarness() = member x.FortyTwo = 42 """ - } + } - let actual = codeFix |> fix code diagnostic + let actual = codeFix |> tryFix code diagnostic Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingRecToMutuallyRecFunctionsTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingRecToMutuallyRecFunctionsTests.fs new file mode 100644 index 00000000000..04e1bbd4929 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingRecToMutuallyRecFunctionsTests.fs @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.AddMissingRecToMutuallyRecFunctionsTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider() +let private diagnostic = 0576 // The declaration form 'let ... and ...' for non-recursive bindings is not used in F# code... + +// TODO: write some negative test cases here + +[] +let ``Fixes FS0576`` () = + let code = + """ +let isEven n = + match n with + | 0 -> true + | _ -> isOdd (n - 1) + +and isOdd n = + match n with + | 0 -> false + | _ -> isEven (n - 1) +""" + + let expected = + Some + { + Message = "Make 'isEven' recursive" + FixedCode = + """ +let rec isEven n = + match n with + | 0 -> true + | _ -> isOdd (n - 1) + +and isOdd n = + match n with + | 0 -> false + | _ -> isEven (n - 1) +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeToUpcastTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeToUpcastTests.fs index 9b0ead993d7..d47f320d284 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeToUpcastTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeToUpcastTests.fs @@ -24,18 +24,19 @@ let Thing : IFoo = Foo() :?> IFoo """ let expected = - { - Title = "Use ':>' operator" - FixedCode = - """ + Some + { + Message = "Use ':>' operator" + FixedCode = + """ type IFoo = abstract member Bar : unit -> unit type Foo() = interface IFoo with member __.Bar () = () let Thing : IFoo = Foo() :> IFoo """ - } + } - let actual = codeFix |> fix code diagnostic + let actual = codeFix |> tryFix code diagnostic Assert.Equal(expected, actual) @@ -50,17 +51,35 @@ let Thing : IFoo = downcast Foo() """ let expected = - { - Title = "Use 'upcast'" - FixedCode = - """ + Some + { + Message = "Use 'upcast'" + FixedCode = + """ type IFoo = abstract member Bar : unit -> unit type Foo() = interface IFoo with member __.Bar () = () let Thing : IFoo = upcast Foo() """ - } + } - let actual = codeFix |> fix code diagnostic + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +// TODO: that's a weird thing, we should rather rewrite the code of the code fix +let ``Doesn't fix FS3198 when both`` () = + let code = + """ +type IdowncastFoo = abstract member Bar : unit -> unit +type Foo() = interface IdowncastFoo with member __.Bar () = () + +let Thing : IdowncastFoo = Foo() :?> IdowncastFoo +""" + + let expected = None + + let actual = codeFix |> tryFix code diagnostic Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs index 04998935ea8..616bc907fe5 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs @@ -11,7 +11,7 @@ open Microsoft.VisualStudio.FSharp.Editor.CancellableTasks open FSharp.Editor.Tests.Helpers -type TestCodeFix = { Title: string; FixedCode: string } +type TestCodeFix = { Message: string; FixedCode: string } let getRelevantDiagnostic (document: Document) errorNumber = cancellableTask { @@ -23,7 +23,7 @@ let getRelevantDiagnostic (document: Document) errorNumber = |> Seq.exactlyOne } -let fix (code: string) diagnostic (fixProvider: IFSharpCodeFix) = +let tryFix (code: string) diagnostic (fixProvider: IFSharpCodeFixProvider) = cancellableTask { let sourceText = SourceText.From code let document = RoslynTestHelpers.GetFsDocument code @@ -33,14 +33,15 @@ let fix (code: string) diagnostic (fixProvider: IFSharpCodeFix) = let diagnosticSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, diagnostic.Range) - let! title, changes = fixProvider.GetChangesAsync document diagnosticSpan - let fixedSourceText = sourceText.WithChanges changes + let! result = fixProvider.GetCodeFixIfAppliesAsync document diagnosticSpan return - { - Title = title - FixedCode = fixedSourceText.ToString() - } + (result + |> Option.map (fun codeFix -> + { + Message = codeFix.Message + FixedCode = (sourceText.WithChanges codeFix.Changes).ToString() + })) } |> CancellableTask.start CancellationToken.None |> fun task -> task.Result diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToAnonymousRecordTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToAnonymousRecordTests.fs index 9f8008356bb..33207bddaab 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToAnonymousRecordTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ConvertToAnonymousRecordTests.fs @@ -8,24 +8,38 @@ open Xunit open CodeFixTestFramework let private codeFix = FSharpConvertToAnonymousRecordCodeFixProvider() -let private diagnostic = 0039 // The record label is not defined... +let private diagnostic = 0039 // ... is not defined... [] -let ``Fixes FS0039`` () = +let ``Fixes FS0039 for records`` () = let code = """ let band = { Name = "The Velvet Underground" } """ let expected = - { - Title = "Convert to Anonymous Record" - FixedCode = - """ + Some + { + Message = "Convert to Anonymous Record" + FixedCode = + """ let band = {| Name = "The Velvet Underground" |} """ - } + } - let actual = codeFix |> fix code diagnostic + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) + +[] +let ``Doesn't fix FS0039 for random undefined identifiers`` () = + let code = + """ +let x = someUndefinedFunction 42 +""" + + let expected = None + + let actual = codeFix |> tryFix code diagnostic Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/FSharpWrapExpressionInParenthesesFixProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/FSharpWrapExpressionInParenthesesFixProviderTests.fs new file mode 100644 index 00000000000..32009583e06 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/FSharpWrapExpressionInParenthesesFixProviderTests.fs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.FSharpWrapExpressionInParenthesesFixProviderTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = FSharpWrapExpressionInParenthesesFixProvider() +let private diagnostic = 0597 // ... arguments involving function or method applications should be parenthesized + +// Test case is taken from the original PR: +// https://github.com/dotnet/fsharp/pull/10460 + +[] +let ``Fixes FS0597`` () = + let code = + """ +let rng = System.Random() + +printfn "Hello %d" rng.Next(5) +""" + + let expected = + Some + { + Message = "Wrap expression in parentheses" + FixedCode = + """ +let rng = System.Random() + +printfn "Hello %d" (rng.Next(5)) +""" + } + + let actual = codeFix |> tryFix code diagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveReturnOrYieldTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveReturnOrYieldTests.fs new file mode 100644 index 00000000000..83c841f198e --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveReturnOrYieldTests.fs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.RemoveReturnOrYieldTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = FSharpRemoveReturnOrYieldCodeFixProvider() +let private yieldDiagnostic = 0747 // This construct may only be used within list, array and sequence expressions... +let private returnDiagnostic = 0748 // This construct may only be used with computation expressions... + +// TODO: write some negative tests here + +[] +let ``Fixes FS0747 - yield`` () = + let code = + """ +let answer question = + yield 42 +""" + + let expected = + Some + { + Message = "Remove 'yield'" + FixedCode = + """ +let answer question = + 42 +""" + } + + let actual = codeFix |> tryFix code yieldDiagnostic + + Assert.Equal(expected, actual) + +[] +let ``Fixes FS0747 - yield!`` () = + let code = + """ +let answer question = + yield! 42 +""" + + let expected = + Some + { + Message = "Remove 'yield!'" + FixedCode = + """ +let answer question = + 42 +""" + } + + let actual = codeFix |> tryFix code yieldDiagnostic + + Assert.Equal(expected, actual) + +[] +let ``Fixes FS0748 - return`` () = + let code = + """ +let answer question = + return 42 +""" + + let expected = + Some + { + Message = "Remove 'return'" + FixedCode = + """ +let answer question = + 42 +""" + } + + let actual = codeFix |> tryFix code returnDiagnostic + + Assert.Equal(expected, actual) + +[] +let ``Fixes FS0748 - return!`` () = + let code = + """ +let answer question = + return! 42 +""" + + let expected = + Some + { + Message = "Remove 'return!'" + FixedCode = + """ +let answer question = + 42 +""" + } + + let actual = codeFix |> tryFix code returnDiagnostic + + Assert.Equal(expected, actual) diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj index 232e394455c..f539937e07b 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -36,6 +36,9 @@ + + +