From 78ac6676cf948a15a119d8e41d42829460c0ad9c Mon Sep 17 00:00:00 2001 From: Petr Date: Tue, 15 Aug 2023 16:06:11 +0200 Subject: [PATCH] Fix the type annotation code fix --- ...peAnnotationToObjectOfIndeterminateType.fs | 179 +++++++++--------- ...otationToObjectOfIndeterminateTypeTests.fs | 90 +++++++++ .../FSharp.Editor.Tests.fsproj | 1 + 3 files changed, 185 insertions(+), 85 deletions(-) create mode 100644 vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateTypeTests.fs diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs b/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs index 6b914815a76..b14f080aea9 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateType.fs @@ -4,8 +4,6 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System open System.Composition -open System.Threading.Tasks -open System.Threading open System.Collections.Immutable open Microsoft.CodeAnalysis.Text @@ -14,6 +12,7 @@ open Microsoft.CodeAnalysis.CodeFixes open FSharp.Compiler.EditorServices open FSharp.Compiler.Text open FSharp.Compiler.Symbols + open CancellableTasks [] @@ -24,88 +23,98 @@ type internal AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider [ CancellableTask.start context.CancellationToken - |> Async.AwaitTask - |> liftAsync - - let decl = - checkFileResults.GetDeclarationLocation( - fcsTextLineNumber, - lexerSymbol.Ident.idRange.EndColumn, - textLine.ToString(), - lexerSymbol.FullIsland, - false - ) - - match decl with - | FindDeclResult.DeclFound declRange when declRange.FileName = document.FilePath -> - let! declSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, declRange) - let declTextLine = sourceText.Lines.GetLineFromPosition declSpan.Start - - let! symbolUse = - checkFileResults.GetSymbolUseAtLocation( - declRange.StartLine, - declRange.EndColumn, - declTextLine.ToString(), - lexerSymbol.FullIsland + override this.RegisterCodeFixesAsync context = context.RegisterFsharpFix this + + interface IFSharpCodeFixProvider with + member _.GetCodeFixIfAppliesAsync context = + cancellableTask { + let document = context.Document + let position = context.Span.Start + + let! lexerSymbolOpt = + document.TryFindFSharpLexerSymbolAsync( + position, + SymbolLookupKind.Greedy, + false, + false, + nameof AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider ) - match symbolUse.Symbol with - | :? FSharpMemberOrFunctionOrValue as mfv -> - let typeString = mfv.FullType.FormatWithConstraints symbolUse.DisplayContext - - let alreadyWrappedInParens = - let rec leftLoop ch pos = - if not (Char.IsWhiteSpace(ch)) then - ch = '(' - else - leftLoop sourceText.[pos - 1] (pos - 1) - - let rec rightLoop ch pos = - if not (Char.IsWhiteSpace(ch)) then - ch = ')' - else - rightLoop sourceText.[pos + 1] (pos + 1) - - let hasLeftParen = leftLoop sourceText.[declSpan.Start - 1] (declSpan.Start - 1) - let hasRightParen = rightLoop sourceText.[declSpan.End] declSpan.End - hasLeftParen && hasRightParen - - let changes = - [ - if alreadyWrappedInParens then - TextChange(TextSpan(declSpan.End, 0), ": " + typeString) - else - TextChange(TextSpan(declSpan.Start, 0), "(") - TextChange(TextSpan(declSpan.End + 1, 0), ": " + typeString + ")") - ] - - context.RegisterFsharpFix(CodeFix.AddTypeAnnotationToObjectOfIndeterminateType, title, changes) - - | _ -> () - | _ -> () - } - |> Async.Ignore - |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) + match lexerSymbolOpt with + | None -> return ValueNone + | Some lexerSymbol -> + let! sourceText = context.GetSourceTextAsync() + let textLine = sourceText.Lines.GetLineFromPosition position + let textLinePos = sourceText.Lines.GetLinePosition position + let fcsTextLineNumber = Line.fromZ textLinePos.Line + + let! _, checkFileResults = + document.GetFSharpParseAndCheckResultsAsync(nameof AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider) + + let decl = + checkFileResults.GetDeclarationLocation( + fcsTextLineNumber, + lexerSymbol.Ident.idRange.EndColumn, + textLine.ToString(), + lexerSymbol.FullIsland, + false + ) + + match decl with + | FindDeclResult.DeclFound declRange when declRange.FileName = document.FilePath -> + let declSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, declRange) + let declTextLine = sourceText.Lines.GetLineFromPosition declSpan.Start + + let symbolUseOpt = + checkFileResults.GetSymbolUseAtLocation( + declRange.StartLine, + declRange.EndColumn, + declTextLine.ToString(), + lexerSymbol.FullIsland + ) + + match symbolUseOpt with + | None -> return ValueNone + | Some symbolUse -> + match symbolUse.Symbol with + | :? FSharpMemberOrFunctionOrValue as mfv when not mfv.FullType.IsGenericParameter -> + let typeString = mfv.FullType.FormatWithConstraints symbolUse.DisplayContext + + let alreadyWrappedInParens = + let rec leftLoop ch pos = + if not (Char.IsWhiteSpace(ch)) then + ch = '(' + else + leftLoop sourceText[pos - 1] (pos - 1) + + let rec rightLoop ch pos = + if not (Char.IsWhiteSpace(ch)) then + ch = ')' + else + rightLoop sourceText[pos + 1] (pos + 1) + + let hasLeftParen = leftLoop sourceText[declSpan.Start - 1] (declSpan.Start - 1) + let hasRightParen = rightLoop sourceText[declSpan.End] declSpan.End + hasLeftParen && hasRightParen + + let changes = + [ + if alreadyWrappedInParens then + TextChange(TextSpan(declSpan.End, 0), ": " + typeString) + else + TextChange(TextSpan(declSpan.Start, 0), "(") + TextChange(TextSpan(declSpan.End, 0), ": " + typeString + ")") + ] + + return + ValueSome + { + Name = CodeFix.AddTypeAnnotationToObjectOfIndeterminateType + Message = title + Changes = changes + } + + | _ -> return ValueNone + + | _ -> return ValueNone + } diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateTypeTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateTypeTests.fs new file mode 100644 index 00000000000..fe32931d9c1 --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/AddTypeAnnotationToObjectOfIndeterminateTypeTests.fs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module FSharp.Editor.Tests.CodeFixes.AddTypeAnnotationToObjectOfIndeterminateTypeTests + +open Microsoft.VisualStudio.FSharp.Editor +open Xunit + +open CodeFixTestFramework + +let private codeFix = AddTypeAnnotationToObjectOfIndeterminateTypeFixProvider() + +[] +[] +[] +let ``Fixes FS0072`` leftParen rightParen = + let code = + $""" +let db = + [ + {{| Name = "Liam"; Id = 2 |}} + {{| Name = "Noel"; Id = 3 |}} + ] + +let f = List.filter (fun {leftParen}x{rightParen} -> x.Id = 7) db +""" + + let expected = + Some + { + Message = "Add type annotation" + FixedCode = + """ +let db = + [ + {| Name = "Liam"; Id = 2 |} + {| Name = "Noel"; Id = 3 |} + ] + +let f = List.filter (fun (x: {| Id: int; Name: string |}) -> x.Id = 7) db +""" + } + + let actual = codeFix |> tryFix code Auto + + Assert.Equal(expected, actual) + +[] +let ``Doesn't fix FS0072 for generic infered types`` () = + let code = + """ +let f x = + x.IsSpecial +""" + + let expected = None + + let actual = codeFix |> tryFix code Auto + + Assert.Equal(expected, actual) + +[] +[] +[] +let ``Fixes FS3245`` leftParen rightParen = + let code = + $""" +let count numbers = + numbers + |> List.fold + (fun {leftParen}s{rightParen} _ -> {{| s with Count = s.Count + 1 |}}) + {{| Count = 0 |}} +""" + + let expected = + Some + { + Message = "Add type annotation" + FixedCode = + """ +let count numbers = + numbers + |> List.fold + (fun (s: {| Count: int |}) _ -> {| s with Count = s.Count + 1 |}) + {| Count = 0 |} +""" + } + + let actual = codeFix |> tryFix code Auto + + 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 65851ab5b71..da144f399d7 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -47,6 +47,7 @@ +