diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs index ecf54be37f..a5cd9ec4e9 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingEqualsToTypeDefinition.fs @@ -4,6 +4,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition open System.Collections.Immutable +open System.Threading.Tasks open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes @@ -18,33 +19,33 @@ type internal AddMissingEqualsToTypeDefinitionCodeFixProvider() = override _.FixableDiagnosticIds = ImmutableArray.Create "FS0010" - override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this + override this.RegisterCodeFixesAsync context = + // This is a performance shortcut. + // Since FS0010 fires all too often, we're just stopping any processing if it's a different error message. + // The code fix logic itself still has this logic and implements it more reliably. + if + context.Diagnostics + |> Seq.exists (fun d -> d.Descriptor.MessageFormat.ToString().Contains "=") + then + context.RegisterFsharpFix this + else + Task.CompletedTask interface IFSharpCodeFixProvider with member _.GetCodeFixIfAppliesAsync context = cancellableTask { - let message = - context.Diagnostics - |> Seq.exactlyOne - |> fun d -> d.Descriptor.MessageFormat.ToString() + let! range = context.GetErrorRangeAsync() + let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof AddMissingEqualsToTypeDefinitionCodeFixProvider) - // this should eliminate 99.9% of germs - if not <| message.Contains "=" then + if not <| parseResults.IsPositionWithinTypeDefinition range.Start then return ValueNone - else - - let! range = context.GetErrorRangeAsync() - let! parseResults = context.Document.GetFSharpParseResultsAsync(nameof AddMissingEqualsToTypeDefinitionCodeFixProvider) - if not <| parseResults.IsPositionWithinTypeDefinition range.Start then - return ValueNone - - else - return - ValueSome - { - Name = CodeFix.AddMissingEqualsToTypeDefinition - Message = title - Changes = [ TextChange(TextSpan(context.Span.Start, 0), "= ") ] - } + else + return + ValueSome + { + Name = CodeFix.AddMissingEqualsToTypeDefinition + Message = title + Changes = [ TextChange(TextSpan(context.Span.Start, 0), "= ") ] + } } diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs index 136116deef..adc0cc8db0 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddMissingFunKeyword.fs @@ -5,6 +5,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Composition open System.Collections.Immutable +open System.Threading.Tasks open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.CodeFixes @@ -28,7 +29,17 @@ type internal AddMissingFunKeywordCodeFixProvider [] () = override _.FixableDiagnosticIds = ImmutableArray.Create "FS0010" - override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this + override this.RegisterCodeFixesAsync context = + // This is a performance shortcut. + // Since FS0010 fires all too often, we're just stopping any processing if it's a different error message. + // The code fix logic itself still has this logic and implements it more reliably. + if + context.Diagnostics + |> Seq.exists (fun d -> d.Descriptor.MessageFormat.ToString().Contains "->") + then + context.RegisterFsharpFix this + else + Task.CompletedTask interface IFSharpCodeFixProvider with member _.GetCodeFixIfAppliesAsync context = diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/ChangeEqualsInFieldTypeToColon.fs b/vsintegration/src/FSharp.Editor/CodeFixes/ChangeEqualsInFieldTypeToColon.fs index 9dc3feed11..1ebeb6c452 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/ChangeEqualsInFieldTypeToColon.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/ChangeEqualsInFieldTypeToColon.fs @@ -32,7 +32,7 @@ type internal ChangeEqualsInFieldTypeToColonCodeFixProvider() = // This is a performance shortcut. // Since FS0010 fires all too often, we're just stopping any processing if it's a different error message. // The code fix logic itself still has this logic and implements it more reliably. - if context.Diagnostics[ 0 ].GetMessage() = errorMessage then + if context.Diagnostics |> Seq.exists (fun d -> d.GetMessage() = errorMessage) then context.RegisterFsharpFix this else Task.CompletedTask diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingEqualsToTypeDefinitionTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingEqualsToTypeDefinitionTests.fs index a2ead170a1..c81dbe9961 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingEqualsToTypeDefinitionTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingEqualsToTypeDefinitionTests.fs @@ -54,6 +54,13 @@ type Name = Name of string [] [] [ + let _ = [ + x with + ] +">] let ``Doesn't fix FS0010 for random unexpected symbols`` code = let expected = None diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingFunKeywordTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingFunKeywordTests.fs index a52daf0f54..4987e9b10a 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingFunKeywordTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddMissingFunKeywordTests.fs @@ -34,13 +34,18 @@ let gettingEven numbers = Assert.Equal(expected, actual) -[] -let ``Doesn't fix FS0010 for random unexpected symbols`` () = - let code = - """ +[] +[] +[ + let _ = [ + x with + ] +">] +let ``Doesn't fix FS0010 for random unexpected symbols`` code = let expected = None let actual = codeFix |> tryFix code Auto diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeEqualsInFieldTypeToColonTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeEqualsInFieldTypeToColonTests.fs index 942fa866d2..3b6781a435 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeEqualsInFieldTypeToColonTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/ChangeEqualsInFieldTypeToColonTests.fs @@ -58,6 +58,13 @@ type Band = { Name open string } [] +[ + let _ = [ + x with + ] +">] let ``Doesn't fix FS0010 for random unexpected symbols`` code = let expected = None diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs index 72ea92e53a..9faf37538b 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs @@ -36,43 +36,54 @@ let getDocument code mode = | WithOption option -> RoslynTestHelpers.GetFsDocument(code, option) | Manual _ -> RoslynTestHelpers.GetFsDocument code -let getRelevantDiagnostic (document: Document) = +let getRelevantDiagnostics (document: Document) = cancellableTask { let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync "test" - return checkFileResults.Diagnostics |> Seq.head + return checkFileResults.Diagnostics } + |> CancellableTask.startWithoutCancellation + |> fun task -> task.Result -let createTestCodeFixContext (code: string) document (mode: Mode) = +let createTestCodeFixContext (code: string) document (mode: Mode) diagnosticIds = cancellableTask { let! cancellationToken = CancellableTask.getCancellationToken () let sourceText = SourceText.From code - let! diagnostic = + let diagnostics = match mode with - | Auto -> getRelevantDiagnostic document - | WithOption _ -> getRelevantDiagnostic document + | Auto -> + getRelevantDiagnostics document + |> Array.filter (fun d -> diagnosticIds |> Seq.contains d.ErrorNumberText) + | WithOption _ -> getRelevantDiagnostics document | Manual (squiggly, number) -> let spanStart = code.IndexOf squiggly let span = TextSpan(spanStart, squiggly.Length) let range = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, span, sourceText) - CancellableTask.singleton (FSharpDiagnostic.Create(FSharpDiagnosticSeverity.Warning, "test", number, range)) - let location = - RoslynHelpers.RangeToLocation(diagnostic.Range, sourceText, document.FilePath) + [| + FSharpDiagnostic.Create(FSharpDiagnosticSeverity.Warning, "test", number, range) + |] + + let range = diagnostics[0].Range + let location = RoslynHelpers.RangeToLocation(range, sourceText, document.FilePath) + let textSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range) - let roslynDiagnostic = RoslynHelpers.ConvertError(diagnostic, location) + let roslynDiagnostics = + diagnostics + |> Array.map (fun diagnostic -> RoslynHelpers.ConvertError(diagnostic, location)) + |> ImmutableArray.ToImmutableArray - return CodeFixContext(document, roslynDiagnostic, mockAction, cancellationToken) + return CodeFixContext(document, textSpan, roslynDiagnostics, mockAction, cancellationToken) } -let tryFix (code: string) mode (fixProvider: IFSharpCodeFixProvider) = +let tryFix (code: string) mode (fixProvider: 'T when 'T :> IFSharpCodeFixProvider and 'T :> CodeFixProvider) = cancellableTask { let sourceText = SourceText.From code let document = getDocument code mode - let! context = createTestCodeFixContext code document mode + let! context = createTestCodeFixContext code document mode fixProvider.FixableDiagnosticIds let! result = fixProvider.GetCodeFixIfAppliesAsync context @@ -85,5 +96,5 @@ let tryFix (code: string) mode (fixProvider: IFSharpCodeFixProvider) = FixedCode = (sourceText.WithChanges codeFix.Changes).ToString() })) } - |> CancellableTask.start CancellationToken.None + |> CancellableTask.startWithoutCancellation |> fun task -> task.Result