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

Update value in signature file. #1161

Merged
merged 6 commits into from
Jan 14, 2024
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
12 changes: 12 additions & 0 deletions src/FsAutoComplete/CodeFixes/ToInterpolatedString.fsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module FsAutoComplete.CodeFix.ToInterpolatedString

open FsAutoComplete.CodeFix.Types
open Ionide.LanguageServerProtocol.Types

val title: string

val fix:
getParseResultsForFile: GetParseResultsForFile ->
getLanguageVersion: GetLanguageVersion ->
codeActionParams: CodeActionParams ->
Async<Result<Fix list, string>>
102 changes: 102 additions & 0 deletions src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
module FsAutoComplete.CodeFix.UpdateValueInSignatureFile

open FSharp.Compiler.Diagnostics
open FSharp.Compiler.Diagnostics.ExtendedData
open FSharp.Compiler.Syntax
open FSharp.Compiler.Text
open FsToolkit.ErrorHandling
open Ionide.LanguageServerProtocol.Types
open FsAutoComplete.CodeFix.Types
open FsAutoComplete
open FsAutoComplete.LspHelpers

#nowarn "57"

let title = "Update val in signature file"

let fix (getParseResultsForFile: GetParseResultsForFile) : CodeFix =
Run.ifDiagnosticByCode (Set.ofList [ "34" ]) (fun diagnostic codeActionParams ->
asyncResult {
let implFilePath = codeActionParams.TextDocument.GetFilePath()
let sigFilePath = $"%s{implFilePath}i"

let implFileName = Utils.normalizePath implFilePath
let sigFileName = Utils.normalizePath sigFilePath

let sigTextDocumentIdentifier: TextDocumentIdentifier =
{ Uri = $"%s{codeActionParams.TextDocument.Uri}i" }

let! (implParseAndCheckResults: ParseAndCheckResults, implLine: string, _implSourceText: IFSACSourceText) =
getParseResultsForFile implFileName (protocolPosToPos diagnostic.Range.Start)

let mDiag =
protocolRangeToRange implParseAndCheckResults.GetParseResults.FileName diagnostic.Range

let! extendedDiagnosticData =
implParseAndCheckResults.GetCheckResults.Diagnostics
|> Array.tryPick (fun (diag: FSharpDiagnostic) ->
if diag.ErrorNumber <> 34 || not (Range.equals diag.Range mDiag) then
None
else
match diag.ExtendedData with
| Some(:? ValueNotContainedDiagnosticExtendedData as extendedData) -> Some extendedData
| _ -> None)
|> Result.ofOption (fun () -> "No extended data")

// Find the binding name in the implementation file.
let impVisitor =
{ new SyntaxVisitorBase<_>() with
override x.VisitBinding(path, defaultTraverse, SynBinding(headPat = pat)) =
match pat with
| SynPat.LongIdent(longDotId = SynLongIdent(id = [ ident ])) when Range.equals mDiag ident.idRange ->
Some ident
| _ -> None }

let! implBindingIdent =
SyntaxTraversal.Traverse(mDiag.Start, implParseAndCheckResults.GetParseResults.ParseTree, impVisitor)
|> Result.ofOption (fun () -> "No binding name found")

let endPos = implBindingIdent.idRange.End

let! symbolUse =
implParseAndCheckResults.GetCheckResults.GetSymbolUseAtLocation(
endPos.Line,
endPos.Column,
implLine,
[ implBindingIdent.idText ]
)
|> Result.ofOption (fun () -> "No symbolUse found")

let! valText =
extendedDiagnosticData.ImplementationValue.GetValSignatureText(symbolUse.DisplayContext, symbolUse.Range)
|> Result.ofOption (fun () -> "No val text found.")

// Find a matching val in the signature file.
let sigVisitor =
{ new SyntaxVisitorBase<_>() with
override x.VisitValSig(path, defaultTraverse, SynValSig(range = mValSig)) =
if Range.rangeContainsRange mValSig extendedDiagnosticData.SignatureValue.DeclarationLocation then
Some mValSig
else
None }

let! (sigParseAndCheckResults: ParseAndCheckResults, _sigLine: string, _sigSourceText: IFSACSourceText) =
getParseResultsForFile sigFileName extendedDiagnosticData.SignatureValue.DeclarationLocation.End

let! mVal =
SyntaxTraversal.Traverse(
extendedDiagnosticData.SignatureValue.DeclarationLocation.End,
sigParseAndCheckResults.GetParseResults.ParseTree,
sigVisitor
)
|> Result.ofOption (fun () -> "No val range found in signature file")

return
[ { SourceDiagnostic = None
Title = title
File = sigTextDocumentIdentifier
Edits =
[| { Range = fcsRangeToLsp mVal
NewText = valText } |]
Kind = FixKind.Fix } ]
})
6 changes: 6 additions & 0 deletions src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fsi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module FsAutoComplete.CodeFix.UpdateValueInSignatureFile

open FsAutoComplete.CodeFix.Types

val title: string
val fix: getParseResultsForFile: GetParseResultsForFile -> CodeFix
3 changes: 2 additions & 1 deletion src/FsAutoComplete/LspServers/AdaptiveServerState.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1768,7 +1768,8 @@ type AdaptiveState(lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFac
RenameParamToMatchSignature.fix tryGetParseResultsForFile
RemovePatternArgument.fix tryGetParseResultsForFile
ToInterpolatedString.fix tryGetParseResultsForFile getLanguageVersion
AdjustConstant.fix tryGetParseResultsForFile |])
AdjustConstant.fix tryGetParseResultsForFile
UpdateValueInSignatureFile.fix tryGetParseResultsForFile |])

let forgetDocument (uri: DocumentUri) =
async {
Expand Down
18 changes: 16 additions & 2 deletions test/FsAutoComplete.Tests.Lsp/CodeFixTests/AdjustConstantTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ module private ConvertIntToOtherBase =
else
ExpectedResult.After expected

do! checkFixAt (doc, diags) (source, cursor) Diagnostics.acceptAll (selectIntCodeFix base') expected
do!
checkFixAt
(doc, diags)
doc.VersionedTextDocumentIdentifier
(source, cursor)
Diagnostics.acceptAll
(selectIntCodeFix base')
expected
})

/// empty `expectedXXX`: there should be no corresponding Fix
Expand Down Expand Up @@ -941,7 +948,14 @@ module private ConvertCharToOtherForm =
else
ExpectedResult.After expected

do! checkFixAt (doc, diags) (source, cursor) Diagnostics.acceptAll (selectCharCodeFix (format)) expected
do!
checkFixAt
(doc, diags)
doc.VersionedTextDocumentIdentifier
(source, cursor)
Diagnostics.acceptAll
(selectCharCodeFix (format))
expected
})

let check
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ let tests state =
do!
checkFixAt
(fsDoc, diags)
fsDoc.VersionedTextDocumentIdentifier
(fsSource, cursor)
(Diagnostics.expectCode "3218")
selectCodeFix
Expand Down
3 changes: 2 additions & 1 deletion test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3351,4 +3351,5 @@ let tests textFactory state =
useTripleQuotedInterpolationTests state
wrapExpressionInParenthesesTests state
removeRedundantAttributeSuffixTests state
removePatternArgumentTests state ]
removePatternArgumentTests state
UpdateValueInSignatureFileTests.tests state ]
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
module private FsAutoComplete.Tests.CodeFixTests.UpdateValueInSignatureFileTests

open System.IO
open Expecto
open Helpers
open Utils.ServerTests
open Utils.CursorbasedTests
open FsAutoComplete.CodeFix
open Utils.Utils
open Utils.TextEdit
open Utils.Server
open Utils.CursorbasedTests.CodeFix

let path = Path.Combine(__SOURCE_DIRECTORY__, @"../TestCases/CodeFixTests/RenameParamToMatchSignature/")
let fsiFile, fsFile = ("Code.fsi", "Code.fs")

let checkWithFsi
server
fsiSource
fsSourceWithCursor
selectCodeFix
fsiSourceExpected
= async {
let fsiSource = fsiSource |> Text.trimTripleQuotation
let cursor, fsSource =
fsSourceWithCursor
|> Text.trimTripleQuotation
|> Cursor.assertExtractRange
let! fsiDoc, diags = server |> Server.openDocumentWithText fsiFile fsiSource
use fsiDoc = fsiDoc
Expect.isEmpty diags "There should be no diagnostics in fsi doc"
let! fsDoc, diags = server |> Server.openDocumentWithText fsFile fsSource
use fsDoc = fsDoc

do!
checkFixAt
(fsDoc, diags)
fsiDoc.VersionedTextDocumentIdentifier
(fsiSource, cursor)
(Diagnostics.expectCode "34")
selectCodeFix
(After (fsiSourceExpected |> Text.trimTripleQuotation))
}

let tests state =
serverTestList (nameof UpdateValueInSignatureFile) state defaultConfigDto (Some path) (fun server ->
[ let selectCodeFix = CodeFix.withTitle UpdateValueInSignatureFile.title

testCaseAsync "first unit test for UpdateValueInSignatureFile"
<| checkWithFsi
server
"""
module A

val a: b:int -> int
"""
"""
module A

let a$0 (b:int) (c: string) = 0
"""
selectCodeFix
"""
module A

val a: b: int -> c: string -> int
"""
])
23 changes: 19 additions & 4 deletions test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module Utils.CursorbasedTests

open Expecto
open Expecto.Diff
open Ionide.LanguageServerProtocol.Types
open FsToolkit.ErrorHandling
open Utils.Utils
Expand Down Expand Up @@ -34,6 +33,7 @@ module CodeFix =

let checkFixAt
(doc: Document, diagnostics: Diagnostic[])
(editsFrom: VersionedTextDocumentIdentifier)
(beforeWithoutCursor: string, cursorRange: Range)
(validateDiagnostics: Diagnostic[] -> unit)
(chooseFix: ChooseFix)
Expand Down Expand Up @@ -100,7 +100,7 @@ module CodeFix =
let edits =
codeAction.Edit
|> Option.defaultWith (fun _ -> failCodeFixTest "Code action doesn't contain any edits")
|> WorkspaceEdit.tryExtractTextEditsInSingleFile doc.VersionedTextDocumentIdentifier
|> WorkspaceEdit.tryExtractTextEditsInSingleFile editsFrom
|> Result.valueOr failCodeFixTest

// apply fix
Expand All @@ -124,7 +124,14 @@ module CodeFix =
let! (doc, diags) = server |> Server.createUntitledDocument text
use doc = doc // ensure doc gets closed (disposed) after test

do! checkFixAt (doc, diags) (text, range) validateDiagnostics chooseFix (expected ())
do!
checkFixAt
(doc, diags)
doc.VersionedTextDocumentIdentifier
(text, range)
validateDiagnostics
chooseFix
(expected ())
}

/// Checks a CodeFix (CodeAction) for validity.
Expand Down Expand Up @@ -206,7 +213,15 @@ module CodeFix =
$"Cursor {i} at {pos}"
(async {
let! (doc, diags) = doc
do! checkFixAt (doc, diags) (beforeWithoutCursor, range) validateDiagnostics chooseFix expected

do!
checkFixAt
(doc, diags)
doc.VersionedTextDocumentIdentifier
(beforeWithoutCursor, range)
validateDiagnostics
chooseFix
expected
}) ])

/// One test for each Cursor.
Expand Down
Loading
Loading