diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index ac013a11aac..63cdeea4221 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 c3d85bd19ca..8fc31d8cd2f 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 + /// Attempts to find the range of a record expression containing the given position. + 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/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index 7d426b327a2..ae080ea92cc 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: Boolean IsPositionContainedInACurriedParameter(pos) 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 d1ad750f1f8..bb94fee1f86 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 cc79f2dd7f9..1b048c017fd 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -243,6 +243,9 @@ Use subtraction instead of negation + + Convert to Anonymous Record + Use '<-' to mutate value diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf index 8f4b72116da..696632d8431 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 398d1dc6222..6211ac66433 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 20d5f653715..3b52e44f6ef 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 2c95c0d1292..f2a49f771d1 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 1bb527a3361..5716858b0d0 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 0a18d050f7f..d9d1f37f2e0 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 d95bde0d93e..3ff829d0c18 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 579d328911a..88891e9f75d 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 ce039317f06..989e14866ae 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 72b8228c262..a5bab017c76 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 a8bd45e7577..04be92de984 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 0ebd31d4d09..c094963ad32 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 da6ba8d19c3..1f1b7c1f65a 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