diff --git a/src/fsharp/service/ServiceUntypedParse.fs b/src/fsharp/service/ServiceUntypedParse.fs index bd24fef3fad..0513f0a2fa4 100755 --- a/src/fsharp/service/ServiceUntypedParse.fs +++ b/src/fsharp/service/ServiceUntypedParse.fs @@ -100,6 +100,24 @@ type FSharpParseFileResults(errors: FSharpErrorInfo[], input: ParsedInput option member scope.ParseTree = input + 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 + | None -> + None + member scope.FindNoteworthyParamInfoLocations pos = match input with | Some input -> FSharpNoteworthyParamInfoLocations.Find(pos, input) diff --git a/src/fsharp/service/ServiceUntypedParse.fsi b/src/fsharp/service/ServiceUntypedParse.fsi index b88bc0efec3..96b783ecf3f 100755 --- a/src/fsharp/service/ServiceUntypedParse.fsi +++ b/src/fsharp/service/ServiceUntypedParse.fsi @@ -19,6 +19,13 @@ type public FSharpParseFileResults = /// The syntax tree resulting from the parse member ParseTree : ParsedInput option + /// + /// Given the position of an expression, attempts to find the range of the + /// '!' in a derefence operation of that expression, like: + /// '!expr', '!(expr)', etc. + /// + member TryRangeOfRefCellDereferenceContainingPos: expressionPos: pos -> Option + /// Notable parse info for ParameterInfo at a given location member FindNoteworthyParamInfoLocations : pos:pos -> FSharpNoteworthyParamInfoLocations option diff --git a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs index 42f826582fe..c409ba5c82a 100644 --- a/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs +++ b/tests/FSharp.Compiler.Service.Tests/SurfaceArea.netstandard.fs @@ -22749,6 +22749,7 @@ FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: Boolean get_ParseHadE FSharp.Compiler.SourceCodeServices.FSharpParseFileResults: FSharp.Compiler.SourceCodeServices.FSharpErrorInfo[] Errors 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] 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 diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index 18c7f7318d1..07d31f9a38e 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -305,3 +305,56 @@ type T = new (x:int) = () """ getTypeMemberRange source |> shouldEqual [ (3, 4), (3, 20) ] + + +[] +let ``TryRangeOfRefCellDereferenceContainingPos - simple``() = + let source = """ +let x = false +let y = !x +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfRefCellDereferenceContainingPos (mkPos 3 9) + match res with + | Some res -> + res + |> tups + |> fst + |> shouldEqual (3, 8) + | None -> + Assert.Fail("No deref operator found in source.") + +[] +let ``TryRangeOfRefCellDereferenceContainingPos - parens``() = + let source = """ +let x = false +let y = !(x) +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfRefCellDereferenceContainingPos (mkPos 3 10) + match res with + | Some res -> + res + |> tups + |> fst + |> shouldEqual (3, 8) + | None -> + Assert.Fail("No deref operator found in source.") + + +[] +let ``TryRangeOfRefCellDereferenceContainingPos - binary expr``() = + let source = """ +let x = false +let y = !(x = false) +""" + let parseFileResults, _ = getParseAndCheckResults source + let res = parseFileResults.TryRangeOfRefCellDereferenceContainingPos (mkPos 3 10) + match res with + | Some res -> + res + |> tups + |> fst + |> shouldEqual (3, 8) + | None -> + Assert.Fail("No deref operator found in source.") \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs new file mode 100644 index 00000000000..0d7b80e9d96 --- /dev/null +++ b/vsintegration/src/FSharp.Editor/CodeFix/ChangeRefCellDerefToNotExpression.fs @@ -0,0 +1,52 @@ +// 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.Tasks + +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.CodeFixes + +[] +type internal FSharpChangeRefCellDerefToNotExpressionCodeFixProvider + [] + ( + checkerProvider: FSharpCheckerProvider, + projectInfoManager: FSharpProjectOptionsManager + ) = + inherit CodeFixProvider() + + static let userOpName = "FSharpChangeRefCellDerefToNotExpressionCodeFix" + let fixableDiagnosticIds = set ["FS0001"] + + override __.FixableDiagnosticIds = Seq.toImmutableArray fixableDiagnosticIds + + override this.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! derefRange = parseResults.TryRangeOfRefCellDereferenceContainingPos errorRange.Start + let! derefSpan = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, derefRange) + + let title = SR.UseNotForNegation() + + let diagnostics = + context.Diagnostics + |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) + |> Seq.toImmutableArray + + let codeFix = + CodeFixHelpers.createTextChangeCodeFix( + title, + context, + (fun () -> asyncMaybe.Return [| TextChange(derefSpan, "not ") |])) + + context.RegisterCodeFix(codeFix, diagnostics) + } + |> Async.Ignore + |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken) \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 1091a3d4baf..4bbed460c4e 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 802675e062e..94f2d6c4c58 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.resx +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.resx @@ -1,4 +1,4 @@ - +