From 6527c2ab7bf8f9c123d28f552fed1f84d8a879d4 Mon Sep 17 00:00:00 2001 From: cartermp Date: Tue, 17 Nov 2020 20:34:21 -0800 Subject: [PATCH 1/3] Add ConvertToAnonymousRecord quick fixeroony --- src/fsharp/service/ServiceUntypedParse.fs | 33 ++++++---- src/fsharp/service/ServiceUntypedParse.fsi | 3 + tests/service/ServiceUntypedParseTests.fs | 26 +++++++- .../CodeFix/ConvertToAnonymousRecord.fs | 62 +++++++++++++++++++ .../src/FSharp.Editor/FSharp.Editor.fsproj | 1 + .../src/FSharp.Editor/FSharp.Editor.resx | 3 + .../FSharp.Editor/xlf/FSharp.Editor.cs.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.de.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.es.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.fr.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.it.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.ja.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.ko.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.pl.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.ru.xlf | 5 ++ .../FSharp.Editor/xlf/FSharp.Editor.tr.xlf | 5 ++ .../xlf/FSharp.Editor.zh-Hans.xlf | 5 ++ .../xlf/FSharp.Editor.zh-Hant.xlf | 5 ++ 19 files changed, 180 insertions(+), 13 deletions(-) create mode 100644 vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index 0513f0a2fa4..d302587c5ee 100755 --- a/src/fsharp/service/ServiceUntypedParse.fs +++ b/src/fsharp/service/ServiceUntypedParse.fs @@ -100,21 +100,30 @@ type FSharpParseFileResults(errors: FSharpErrorInfo[], input: ParsedInput option member scope.ParseTree = input + member scope.TryRangeOfRecordExpressionContainingPos pos = + match input with + | Some input -> + AstTraversal.Traverse(pos, input, { new AstTraversal.AstVisitorBase<_>() with + member _.VisitExpr(_, _, defaultTraverse, expr) = + match expr with + | SynExpr.Record(_, _, _, range) when rangeContainsPos range pos -> + Some range + | _ -> defaultTraverse expr }) + | None -> + None + member scope.TryRangeOfRefCellDereferenceContainingPos expressionPos = match input with | Some input -> - let res = - AstTraversal.Traverse(expressionPos, input, { new AstTraversal.AstVisitorBase<_>() with - member _.VisitExpr(_, _, defaultTraverse, expr) = - match expr with - | SynExpr.App(_, false, SynExpr.Ident funcIdent, expr, _) -> - if funcIdent.idText = "op_Dereference" && rangeContainsPos expr.Range expressionPos then - Some funcIdent.idRange - else - None - | _ -> defaultTraverse expr - }) - res + AstTraversal.Traverse(expressionPos, input, { new AstTraversal.AstVisitorBase<_>() with + member _.VisitExpr(_, _, defaultTraverse, expr) = + match expr with + | SynExpr.App(_, false, SynExpr.Ident funcIdent, expr, _) -> + if funcIdent.idText = "op_Dereference" && rangeContainsPos expr.Range expressionPos then + Some funcIdent.idRange + else + None + | _ -> defaultTraverse expr }) | None -> None diff --git a/src/fsharp/service/ServiceUntypedParse.fsi b/src/fsharp/service/ServiceUntypedParse.fsi index 96b783ecf3f..645ae7b74ff 100755 --- a/src/fsharp/service/ServiceUntypedParse.fsi +++ b/src/fsharp/service/ServiceUntypedParse.fsi @@ -19,6 +19,9 @@ type public FSharpParseFileResults = /// The syntax tree resulting from the parse member ParseTree : ParsedInput option + /// doot doot toot toot + member TryRangeOfRecordExpressionContainingPos: pos: pos -> Option + /// /// Given the position of an expression, attempts to find the range of the /// '!' in a derefence operation of that expression, like: diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index 07d31f9a38e..9ae95989f19 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -357,4 +357,28 @@ let y = !(x = false) |> fst |> shouldEqual (3, 8) | None -> - Assert.Fail("No deref operator found in source.") \ No newline at end of file + Assert.Fail("No deref operator found in source.") + +[] +let ``TryRangeOfRecordExpressionContainingPos - contained``() = + let source = """ +let x = { Name = "Hello" } +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfRecordExpressionContainingPos (mkPos 2 10) + match res with + | Some res -> + res + |> tups + |> shouldEqual ((2, 8), (2, 26)) + | None -> + Assert.Fail("No range of record found in source.") + +[] +let ``TryRangeOfRecordExpressionContainingPos - not contained``() = + let source = """ +let x = { Name = "Hello" } +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfRecordExpressionContainingPos (mkPos 2 7) + Assert.True(res.IsNone, "Expected not to find a range.") diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs new file mode 100644 index 00000000000..645ffee5ab8 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFix/ConvertToAnonymousRecord.fs @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System.Composition +open System.Threading +open System.Threading.Tasks + +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.CodeFixes +open Microsoft.CodeAnalysis.CodeActions + +[] +type internal FSharpConvertToAnonymousRecordCodeFixProvider + [] + ( + checkerProvider: FSharpCheckerProvider, + projectInfoManager: FSharpProjectOptionsManager + ) = + inherit CodeFixProvider() + + static let userOpName = "ConvertToAnonymousRecord" + + let fixableDiagnosticIds = set ["FS0039"] + + override _.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds + + override _.RegisterCodeFixesAsync context : Task = + asyncMaybe { + let document = context.Document + let! parsingOptions, _ = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, context.CancellationToken, userOpName) + let! sourceText = context.Document.GetTextAsync(context.CancellationToken) + let! parseResults = checkerProvider.Checker.ParseFile(document.FilePath, sourceText.ToFSharpSourceText(), parsingOptions, userOpName) |> liftAsync + + let errorRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) + let! recordRange = parseResults.TryRangeOfRecordExpressionContainingPos errorRange.Start + let! recordSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, recordRange) + + let getChangedText () = + sourceText.WithChanges(TextChange(TextSpan(recordSpan.Start + 1, 0), "|")) + .WithChanges(TextChange(TextSpan(recordSpan.End, 0), "|")) + + let diagnostics = + context.Diagnostics + |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) + |> Seq.toImmutableArray + + let title = SR.ConvertToAnonymousRecord() + + let codeFix = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + async { + return context.Document.WithText(getChangedText()) + } |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), + title) + + context.RegisterCodeFix(codeFix, diagnostics) + } + |> Async.Ignore + |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 761e97ccd5c..e9af1577d4f 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -89,6 +89,7 @@ + diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx index 62497078788..9b655cfe205 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -234,4 +234,7 @@ Use subtraction instead of negation + + Convert to Anonymous Record + \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf index 14b804cc247..44c66b5e40c 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf @@ -12,6 +12,11 @@ Přidejte klíčové slovo new. + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf index 4aa11aa5540..8cb325806c7 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf @@ -12,6 +12,11 @@ Schlüsselwort "new" hinzufügen + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf index d2adca7d01a..b36a430b8b9 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf @@ -12,6 +12,11 @@ Agregar "nueva" palabra clave + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf index ae54a946ed6..9be27b9b9a5 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf @@ -12,6 +12,11 @@ Ajouter le mot clé 'new' + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf index 9df3852f0cd..9a938cbdbd8 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf @@ -12,6 +12,11 @@ Aggiungi la parola chiave 'new' + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf index d141030be81..1b079c36f3d 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf @@ -12,6 +12,11 @@ 'new' キーワードを追加する + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf index b2e8082b7fc..20856c74bed 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf @@ -12,6 +12,11 @@ 'new' 키워드 추가 + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf index 4acc383f4bb..984bfa51d28 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf @@ -12,6 +12,11 @@ Dodaj słowo kluczowe „new” + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf index 8256c04823f..996dae5f340 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf @@ -12,6 +12,11 @@ Adicionar a palavra-chave 'new' + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf index 4df93cd671a..e9f99e6f197 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf @@ -12,6 +12,11 @@ Добавить ключевое слово "new" + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf index c42645b2e76..d257a725cfe 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf @@ -12,6 +12,11 @@ 'new' anahtar sözcüğünü ekleme + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf index ec5e69bca10..9dd3f0a6c8a 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf @@ -12,6 +12,11 @@ 添加“新”关键字 + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf index acccd1d14d4..1db5e4a9cd0 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf @@ -12,6 +12,11 @@ 新增 'new' 關鍵字 + + Convert to Anonymous Record + Convert to Anonymous Record + + Use '=' for equality check Use '=' for equality check From 06429feccaec2df66fc2e569fc4d16737f87f790 Mon Sep 17 00:00:00 2001 From: cartermp Date: Tue, 17 Nov 2020 21:05:30 -0800 Subject: [PATCH 2/3] area --- tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index d9590f3c95b..34589fe36eb 100644 --- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs +++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs @@ -22752,6 +22752,7 @@ FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.Sourc FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.SourceCodeServices.FSharpErrorInfo[] get_Errors() FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.SourceCodeServices.FSharpNavigationItems GetNavigationItems() FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfRefCellDereferenceContainingPos(pos) +FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] TryRangeOfRecordExpressionContainingPos(pos) FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Range+range] ValidateBreakpointLocation(pos) FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.SourceCodeServices.FSharpNoteworthyParamInfoLocations] FindNoteworthyParamInfoLocations(pos) FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.SyntaxTree+ParsedInput] ParseTree From 18705e3e95400c45737c1031befa7a249cbd4ae7 Mon Sep 17 00:00:00 2001 From: Phillip Carter Date: Wed, 18 Nov 2020 13:51:34 -0800 Subject: [PATCH 3/3] Update ServiceUntypedParse.fsi --- src/fsharp/service/ServiceUntypedParse.fsi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fsharp/service/ServiceUntypedParse.fsi b/src/fsharp/service/ServiceUntypedParse.fsi index 645ae7b74ff..c525f8523a5 100755 --- a/src/fsharp/service/ServiceUntypedParse.fsi +++ b/src/fsharp/service/ServiceUntypedParse.fsi @@ -19,7 +19,7 @@ type public FSharpParseFileResults = /// The syntax tree resulting from the parse member ParseTree : ParsedInput option - /// doot doot toot toot + /// Attempts to find the range of a record expression containing the given position. member TryRangeOfRecordExpressionContainingPos: pos: pos -> Option ///