diff --git a/src/fsharp/service/FSharpParseFileResults.fs b/src/fsharp/service/FSharpParseFileResults.fs index a68c6359d2b..24f31915cbd 100644 --- a/src/fsharp/service/FSharpParseFileResults.fs +++ b/src/fsharp/service/FSharpParseFileResults.fs @@ -334,6 +334,17 @@ type FSharpParseFileResults(diagnostics: FSharpDiagnostic[], input: ParsedInput, None | _ -> defaultTraverse expr }) + member _.TryRangeOfExpressionBeingDereferencedContainingPos expressionPos = + SyntaxTraversal.Traverse(expressionPos, input, { new SyntaxVisitorBase<_>() 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 expr.Range + else + None + | _ -> defaultTraverse expr }) + member _.FindParameterLocations pos = ParameterLocations.Find(pos, input) diff --git a/src/fsharp/service/FSharpParseFileResults.fsi b/src/fsharp/service/FSharpParseFileResults.fsi index 3a530fc2050..9d3378a7b9a 100644 --- a/src/fsharp/service/FSharpParseFileResults.fsi +++ b/src/fsharp/service/FSharpParseFileResults.fsi @@ -46,6 +46,9 @@ type public FSharpParseFileResults = /// member TryRangeOfRefCellDereferenceContainingPos: expressionPos: pos -> range option + /// Gets the range of an expression being dereferenced. For `!expr`, gives the range of `expr` + member TryRangeOfExpressionBeingDereferencedContainingPos: expressionPos: pos -> range option + /// Notable parse info for ParameterInfo at a given location member FindParameterLocations: pos:pos -> ParameterLocations option diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index 51f59bb0a47..062bd21d080 100644 --- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs +++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs @@ -1496,6 +1496,7 @@ FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FShar FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfNameOfNearestOuterBindingContainingPos(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfRecordExpressionContainingPos(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfRefCellDereferenceContainingPos(FSharp.Compiler.Text.Position) +FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfExpressionBeingDereferencedContainingPos(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] ValidateBreakpointLocation(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.Range]] GetAllArgumentsForFunctionApplicationAtPostion(FSharp.Compiler.Text.Position) FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`2[FSharp.Compiler.Syntax.Ident,System.Int32]] TryIdentOfPipelineContainingPosAndNumArgsApplied(FSharp.Compiler.Text.Position) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 9130c7573c7..1344d7e180d 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -90,6 +90,7 @@ + diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx index 57bb282a098..0739ef50003 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -273,6 +273,9 @@ Use '<>' for inequality check + + Use '.Value' to dereference expression + Add type annotation diff --git a/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs new file mode 100644 index 00000000000..7958133727b --- /dev/null +++ b/vsintegration/src/FSharp.Editor/Refactor/ChangeDerefToValueRefactoring.fs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Composition +open System.Threading + +open FSharp.Compiler +open FSharp.Compiler.CodeAnalysis +open FSharp.Compiler.Symbols +open FSharp.Compiler.Text +open FSharp.Compiler.Syntax + +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.CodeRefactorings +open Microsoft.CodeAnalysis.CodeActions + +[] +type internal FSharpChangeDerefToValueRefactoring + [] + ( + checkerProvider: FSharpCheckerProvider, + projectInfoManager: FSharpProjectOptionsManager + ) = + inherit CodeRefactoringProvider() + + static let userOpName = "ChangeDerefToValue" + + override _.ComputeRefactoringsAsync context = + 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=userOpName) |> liftAsync + + let selectionRange = RoslynHelpers.TextSpanToFSharpRange(document.FilePath, context.Span, sourceText) + let! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos selectionRange.Start + let! exprRange = parseResults.TryRangeOfExpressionBeingDereferencedContainingPos selectionRange.Start + + let combinedRange = Range.unionRanges derefRange exprRange + let! combinedSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, combinedRange) + let replacementString = + // Trim off the `!` + sourceText.GetSubText(combinedSpan).ToString().[1..] + ".Value" + + let title = SR.UseValueInsteadOfDeref() + + let getChangedText (sourceText: SourceText) = + sourceText.WithChanges(TextChange(combinedSpan, replacementString)) + + let codeAction = + CodeAction.Create( + title, + (fun (cancellationToken: CancellationToken) -> + async { + let! sourceText = context.Document.GetTextAsync(cancellationToken) |> Async.AwaitTask + return context.Document.WithText(getChangedText sourceText) + } |> RoslynHelpers.StartAsyncAsTask(cancellationToken)), + title) + context.RegisterRefactoring(codeAction) + } + |> Async.Ignore + |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf index 251a1388662..629ec4c2dc2 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.cs.xlf @@ -262,6 +262,11 @@ Negovat výraz pomocí not + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses Uzavřít výraz do závorek diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf index 9249c295f7a..d1439f4fa4c 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.de.xlf @@ -262,6 +262,11 @@ "not" zum Negieren eines Ausdruck verwenden + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses Ausdruck in Klammern einschließen diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf index 4b002337fc9..8b9027c4da9 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.es.xlf @@ -262,6 +262,11 @@ Usar "not" para negar la expresión + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses Encapsular la expresión entre paréntesis diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf index 3f304fecda3..5d73604e920 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.fr.xlf @@ -262,6 +262,11 @@ Utiliser 'not' pour annuler l'expression + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses Mettre l'expression entre parenthèses diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf index 5f2e37301c0..e35409a8448 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.it.xlf @@ -262,6 +262,11 @@ Usare 'not' per negare l'espressione + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses Racchiudere l'espressione tra parentesi diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf index d8e55789efe..942e9424613 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ja.xlf @@ -262,6 +262,11 @@ 式を否定するには 'not' を使用する + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses 式をかっこで囲む diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf index 1b3c41a2268..41224386c06 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ko.xlf @@ -262,6 +262,11 @@ 식을 부정하려면 'not' 사용 + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses 식을 괄호로 래핑 diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf index d9ea6371acd..a51c64408cd 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pl.xlf @@ -262,6 +262,11 @@ Użyj operatora „not”, aby zanegować wyrażenie + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses Ujmij wyrażenie w nawiasy 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 b76c694378d..003d901383b 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.pt-BR.xlf @@ -262,6 +262,11 @@ Use 'not' para negar a expressão + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses Coloque a expressão entre parênteses diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf index 3f6596d7492..6ab6ffa5f2e 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.ru.xlf @@ -262,6 +262,11 @@ Используйте "not" для отрицания выражения. + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses Заключите выражение в круглые скобки. diff --git a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf index 0d433910885..e290098742b 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.tr.xlf @@ -262,6 +262,11 @@ İfadeyi negatif yapmak için 'not' kullanın + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses İfadeyi parantez içinde sarmalayın 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 020ff6e6b43..eec423b4781 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hans.xlf @@ -262,6 +262,11 @@ 使用 "not" 对表达式求反 + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses 将表达式用括号括起来 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 e6e56785150..b1d27931da5 100644 --- a/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf +++ b/vsintegration/src/FSharp.Editor/xlf/FSharp.Editor.zh-Hant.xlf @@ -262,6 +262,11 @@ 使用 'not' 來否定運算式 + + Use '.Value' to dereference expression + Use '.Value' to dereference expression + + Wrap expression in parentheses 使用括弧包裝運算式