Skip to content

Commit

Permalink
add codefix for converting interpolation issues
Browse files Browse the repository at this point in the history
  • Loading branch information
baronfel committed Apr 5, 2022
1 parent 3e2d6ef commit d718700
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 1 deletion.
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"

0 comments on commit d718700

Please sign in to comment.