diff --git a/src/FsAutoComplete.Core/FCSPatches.fs b/src/FsAutoComplete.Core/FCSPatches.fs index 961aac170..b8cd0a499 100644 --- a/src/FsAutoComplete.Core/FCSPatches.fs +++ b/src/FsAutoComplete.Core/FCSPatches.fs @@ -304,6 +304,15 @@ type FSharpParseFileResults with None | _ -> defaultTraverse expr }) + /// Attempts to find the range of the string interpolation that contains a given position. + member scope.TryRangeOfStringInterpolationContainingPos pos = + SyntaxTraversal.Traverse(pos, scope.ParseTree, { new SyntaxVisitorBase<_>() with + member _.VisitExpr(_, _, defaultTraverse, expr) = + match expr with + | SynExpr.InterpolatedString(range = range) when Range.rangeContainsPos range pos -> + Some range + | _ -> defaultTraverse expr }) + module SyntaxTreeOps = open FSharp.Compiler.Syntax let rec synExprContainsError inpExpr = diff --git a/src/FsAutoComplete/FsAutoComplete.Lsp.fs b/src/FsAutoComplete/FsAutoComplete.Lsp.fs index ba6cf8979..ebe3605e9 100644 --- a/src/FsAutoComplete/FsAutoComplete.Lsp.fs +++ b/src/FsAutoComplete/FsAutoComplete.Lsp.fs @@ -868,7 +868,8 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS ChangeTypeOfNameToNameOf.fix tryGetParseResultsForFile AddMissingInstanceMember.fix AddExplicitTypeToParameter.fix tryGetParseResultsForFile - ConvertPositionalDUToNamed.fix tryGetParseResultsForFile getRangeText |] + ConvertPositionalDUToNamed.fix tryGetParseResultsForFile getRangeText + UseTripleQuotedInterpolation.fix tryGetParseResultsForFile getRangeText |] match p.RootPath, c.AutomaticWorkspaceInit with diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests.fs index a1188cb89..483e44d97 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests.fs @@ -852,6 +852,62 @@ let positionalToNamedDUTests state = expectEdits patternPos edits) ] +let tripleQuotedInterpolationTests state = + let server = + async { + let path = + Path.Combine(__SOURCE_DIRECTORY__, "TestCases", "TripleQuotedInterpolation") + + let cfg = defaultConfigDto + let! (server, events) = serverInitialize path cfg state + do! waitForWorkspaceFinishedParsing events + let path = Path.Combine(path, "Script.fsx") + let tdop: DidOpenTextDocumentParams = { TextDocument = loadDocument path } + do! server.TextDocumentDidOpen tdop + + let! diagnostics = + events + |> waitForParseResultsForFile "Script.fsx" + |> AsyncResult.bimap (fun _ -> failtest "Should have had errors") id + + return (server, path, diagnostics) + } + |> Async.Cache + + testList + "interpolation fixes" + [ testCaseAsync + "converts erroring single-quoted interpolation to triple-quoted" + (async { + let! (server, filePath, diagnostics) = server + + let diagnostic = + diagnostics + |> Array.tryFind (fun d -> d.Code = Some "3373") + |> Option.defaultWith (fun _ -> failtest "Should have gotten an error of type 3373") + + let context: CodeActionParams = + { Context = { Diagnostics = [| diagnostic |] } + Range = + { Start = diagnostic.Range.Start + End = diagnostic.Range.Start } + TextDocument = { Uri = Path.FilePathToUri filePath } } + + match! server.TextDocumentCodeAction context with + | Ok (Some (TextDocumentCodeActionResult.CodeActions [| { Title = "Use triple-quoted string interpolation" + Kind = Some "quickfix" + Edit = Some { DocumentChanges = Some [| { Edits = [| { Range = { Start = { Line = 0 + Character = 8 } + End = { Line = 0 + Character = 44 } } + NewText = "$\"\"\":^) {if true then \"y\" else \"n\"} d\"\"\"" } |] } |] } } |])) -> + () + | Ok other -> + failtestf + $"Should have converted single quoted interpolations to triple quotes, but instead generated %A{other}" + | Error reason -> failtestf $"Should have succeeded, but failed with %A{reason}" + }) ] + let tests state = testList "codefix tests"