Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use triple-quoted interpolations when single-quoted ones give errors #910

Merged
merged 1 commit into from
Apr 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/FsAutoComplete.Core/FCSPatches.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
37 changes: 37 additions & 0 deletions src/FsAutoComplete/CodeFixes/UseTripleQuotedInterpolation.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
module FsAutoComplete.CodeFix.UseTripleQuotedInterpolation

open FsToolkit.ErrorHandling
open FsAutoComplete.CodeFix.Types
open Ionide.LanguageServerProtocol.Types
open FsAutoComplete
open FsAutoComplete.LspHelpers
open FsAutoComplete.FCSPatches

/// a codefix that replaces erroring single-quoted interpolations with triple-quoted interpolations
let fix (getParseResultsForFile: GetParseResultsForFile) (getRangeText: GetRangeText) : CodeFix =
Run.ifDiagnosticByCode (Set.ofList [ "3373" ]) (fun diagnostic codeActionParams ->
asyncResult {
let pos = protocolPosToPos diagnostic.Range.Start

let filePath =
codeActionParams.TextDocument.GetFilePath()
|> Utils.normalizePath

let! tyRes, _, sourceText = getParseResultsForFile filePath pos

match tyRes.GetParseResults.TryRangeOfStringInterpolationContainingPos pos with
| Some range ->
let! interpolationText = sourceText.GetText range
// skip the leading '$' in the existing single-quoted interpolation
let newText = "$\"\"" + interpolationText.[1..] + "\"\""

return
[ { File = codeActionParams.TextDocument
SourceDiagnostic = Some diagnostic
Title = "Use triple-quoted string interpolation"
Edits =
[| { Range = fcsRangeToLsp range
NewText = newText } |]
Kind = FixKind.Fix } ]
| None -> return []
})
3 changes: 2 additions & 1 deletion src/FsAutoComplete/FsAutoComplete.Lsp.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
56 changes: 56 additions & 0 deletions test/FsAutoComplete.Tests.Lsp/CodeFixTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let a = $":^) {if true then "y" else "n"} d"