diff --git a/src/FsAutoComplete/CodeFixes/ToInterpolatedString.fsi b/src/FsAutoComplete/CodeFixes/ToInterpolatedString.fsi new file mode 100644 index 000000000..5adc16bfc --- /dev/null +++ b/src/FsAutoComplete/CodeFixes/ToInterpolatedString.fsi @@ -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> diff --git a/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fs b/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fs new file mode 100644 index 000000000..f3cddb442 --- /dev/null +++ b/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fs @@ -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 } ] + }) diff --git a/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fsi b/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fsi new file mode 100644 index 000000000..ede271327 --- /dev/null +++ b/src/FsAutoComplete/CodeFixes/UpdateValueInSignatureFile.fsi @@ -0,0 +1,6 @@ +module FsAutoComplete.CodeFix.UpdateValueInSignatureFile + +open FsAutoComplete.CodeFix.Types + +val title: string +val fix: getParseResultsForFile: GetParseResultsForFile -> CodeFix diff --git a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs index f5d829512..c97ce7f52 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveServerState.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveServerState.fs @@ -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 { diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/AdjustConstantTests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/AdjustConstantTests.fs index fd9ce4a8a..e0f8c6339 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/AdjustConstantTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/AdjustConstantTests.fs @@ -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 @@ -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 diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/RenameParamToMatchSignatureTests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/RenameParamToMatchSignatureTests.fs index 89e4c4e75..e4f01db14 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/RenameParamToMatchSignatureTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/RenameParamToMatchSignatureTests.fs @@ -40,6 +40,7 @@ let tests state = do! checkFixAt (fsDoc, diags) + fsDoc.VersionedTextDocumentIdentifier (fsSource, cursor) (Diagnostics.expectCode "3218") selectCodeFix diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs index f18f62585..dfd80ea5a 100644 --- a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs @@ -3351,4 +3351,5 @@ let tests textFactory state = useTripleQuotedInterpolationTests state wrapExpressionInParenthesesTests state removeRedundantAttributeSuffixTests state - removePatternArgumentTests state ] + removePatternArgumentTests state + UpdateValueInSignatureFileTests.tests state ] diff --git a/test/FsAutoComplete.Tests.Lsp/CodeFixTests/UpdateValueInSignatureFileTests.fs b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/UpdateValueInSignatureFileTests.fs new file mode 100644 index 000000000..4b3a19273 --- /dev/null +++ b/test/FsAutoComplete.Tests.Lsp/CodeFixTests/UpdateValueInSignatureFileTests.fs @@ -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 +""" + ]) diff --git a/test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fs b/test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fs index 1c99527f4..00fa8725f 100644 --- a/test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fs @@ -1,7 +1,6 @@ module Utils.CursorbasedTests open Expecto -open Expecto.Diff open Ionide.LanguageServerProtocol.Types open FsToolkit.ErrorHandling open Utils.Utils @@ -34,6 +33,7 @@ module CodeFix = let checkFixAt (doc: Document, diagnostics: Diagnostic[]) + (editsFrom: VersionedTextDocumentIdentifier) (beforeWithoutCursor: string, cursorRange: Range) (validateDiagnostics: Diagnostic[] -> unit) (chooseFix: ChooseFix) @@ -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 @@ -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. @@ -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. diff --git a/test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fsi b/test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fsi index 901e1e9ad..0812f801e 100644 --- a/test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fsi +++ b/test/FsAutoComplete.Tests.Lsp/Utils/CursorbasedTests.fsi @@ -1,13 +1,8 @@ module Utils.CursorbasedTests open Expecto -open Expecto.Diff open Ionide.LanguageServerProtocol.Types -open FsToolkit.ErrorHandling -open Utils.Utils open Utils.Server -open Utils.TextEdit -open Ionide.ProjInfo.Logging /// Checks for CodeFixes, CodeActions /// @@ -15,138 +10,139 @@ open Ionide.ProjInfo.Logging /// * `check`: Check to use inside a `testCaseAsync`. Not a Test itself! /// * `test`: Returns Expecto Test. Usually combines multiple tests (like: test all positions). module CodeFix = - /// Note: Return should be just ONE `CodeAction` (for Applicable) or ZERO `CodeAction` (for Not Applicable). - /// But actual return type is an array of `CodeAction`s: - /// * Easier to successive filter CodeActions down with simple pipe and `Array.filter` - /// * Returning `CodeAction option` would mean different filters for `check` (exactly one fix) and `checkNotApplicable` (exactly zero fix). - /// Both error with multiple matching fixes! - type ChooseFix = CodeAction[] -> CodeAction[] + /// Note: Return should be just ONE `CodeAction` (for Applicable) or ZERO `CodeAction` (for Not Applicable). + /// But actual return type is an array of `CodeAction`s: + /// * Easier to successive filter CodeActions down with simple pipe and `Array.filter` + /// * Returning `CodeAction option` would mean different filters for `check` (exactly one fix) and `checkNotApplicable` (exactly zero fix). + /// Both error with multiple matching fixes! + type ChooseFix = CodeAction[] -> CodeAction[] - type ExpectedResult = - | NotApplicable - | Applicable - | After of string + type ExpectedResult = + | NotApplicable + | Applicable + | After of string - val checkFixAt: - doc: Document * diagnostics: Diagnostic[] -> - beforeWithoutCursor: string * cursorRange: Range -> - validateDiagnostics: (Diagnostic[] -> unit) -> - chooseFix: ChooseFix -> - expected: ExpectedResult -> - Async - - /// Checks a CodeFix (CodeAction) for validity. - /// - /// * Extracts cursor position (`$0`) or range (between two `$0`) from `beforeWithCursor` - /// * Opens untitled Doc with source `beforeWithCursor` (with cursor removed) - /// * Note: untitled Document acts as Script file! - /// * Note: untitled Documents doesn't exist on disk! - /// * Waits for Diagnostics in that doc - /// * Filters Diags down to diags matching cursor position/range - /// * Then validates diags with `validateDiagnostics` - /// * Note: Validates filtered diags (-> only diags at cursor pos); not all diags in doc! - /// * Gets CodeFixes (CodeActions) from LSP server (`textDocument/codeAction`) for cursor range - /// * Request includes filtered diags - /// * Selects CodeFix from returned CodeFixes with `chooseFix` - /// * Note: `chooseFix` should return a single CodeFix. No CodeFix or multiple CodeFixes count as Failure! - /// * Use `checkNotApplicable` when there shouldn't be a CodeFix - /// * Note: Though `chooseFix` should return one CodeFix, the function actually returns an array of CodeFixes. - /// Reasons: - /// * Easier to filter down CodeFixes (`CodeFix.ofKind "..." >> CodeFix.withTitle "..."`) - /// * Better error messages: Can differentiate between no CodeFixes and too many CodeFixes - /// * Validates selected CodeFix: - /// * Applies selected CodeFix to source (`beforeWithCursor` with cursor removed) - /// * Compares result with `expected` - /// - /// Note: - /// `beforeWithCursor` as well as `expected` get trimmed with `Text.trimTripleQuotation`: Leading empty line and indentation gets removed. - /// - /// Note: - /// `beforeWithCursor` and `expected` MUST use `\n` for linebreaks -- using `\r` (either alone or as `\r\n`) results in test failure! - /// Linebreaks from edits in selected CodeFix are all transformed to just `\n` - /// -> CodeFix can use `\r` and `\r\n` - /// If you want to validate Line Endings of CodeFix, add a validation step to your `chooseFix` - val check: - server: CachedServer -> - beforeWithCursor: string -> - validateDiagnostics: (Diagnostic array -> unit) -> + val checkFixAt: + doc: Document * diagnostics: Diagnostic[] -> + editsFrom: VersionedTextDocumentIdentifier -> + beforeWithoutCursor: string * cursorRange: Range -> + validateDiagnostics: (Diagnostic[] -> unit) -> chooseFix: ChooseFix -> - expected: string -> - Async + expected: ExpectedResult -> + Async - /// Note: Doesn't apply Fix! Just checks its existence! - val checkApplicable: - server: CachedServer -> - beforeWithCursor: string -> - validateDiagnostics: (Diagnostic array -> unit) -> - chooseFix: ChooseFix -> - Async + /// Checks a CodeFix (CodeAction) for validity. + /// + /// * Extracts cursor position (`$0`) or range (between two `$0`) from `beforeWithCursor` + /// * Opens untitled Doc with source `beforeWithCursor` (with cursor removed) + /// * Note: untitled Document acts as Script file! + /// * Note: untitled Documents doesn't exist on disk! + /// * Waits for Diagnostics in that doc + /// * Filters Diags down to diags matching cursor position/range + /// * Then validates diags with `validateDiagnostics` + /// * Note: Validates filtered diags (-> only diags at cursor pos); not all diags in doc! + /// * Gets CodeFixes (CodeActions) from LSP server (`textDocument/codeAction`) for cursor range + /// * Request includes filtered diags + /// * Selects CodeFix from returned CodeFixes with `chooseFix` + /// * Note: `chooseFix` should return a single CodeFix. No CodeFix or multiple CodeFixes count as Failure! + /// * Use `checkNotApplicable` when there shouldn't be a CodeFix + /// * Note: Though `chooseFix` should return one CodeFix, the function actually returns an array of CodeFixes. + /// Reasons: + /// * Easier to filter down CodeFixes (`CodeFix.ofKind "..." >> CodeFix.withTitle "..."`) + /// * Better error messages: Can differentiate between no CodeFixes and too many CodeFixes + /// * Validates selected CodeFix: + /// * Applies selected CodeFix to source (`beforeWithCursor` with cursor removed) + /// * Compares result with `expected` + /// + /// Note: + /// `beforeWithCursor` as well as `expected` get trimmed with `Text.trimTripleQuotation`: Leading empty line and indentation gets removed. + /// + /// Note: + /// `beforeWithCursor` and `expected` MUST use `\n` for linebreaks -- using `\r` (either alone or as `\r\n`) results in test failure! + /// Linebreaks from edits in selected CodeFix are all transformed to just `\n` + /// -> CodeFix can use `\r` and `\r\n` + /// If you want to validate Line Endings of CodeFix, add a validation step to your `chooseFix` + val check: + server: CachedServer -> + beforeWithCursor: string -> + validateDiagnostics: (Diagnostic array -> unit) -> + chooseFix: ChooseFix -> + expected: string -> + Async - val checkNotApplicable: - server: CachedServer -> - beforeWithCursor: string -> - validateDiagnostics: (Diagnostic array -> unit) -> - chooseFix: ChooseFix -> - Async + /// Note: Doesn't apply Fix! Just checks its existence! + val checkApplicable: + server: CachedServer -> + beforeWithCursor: string -> + validateDiagnostics: (Diagnostic array -> unit) -> + chooseFix: ChooseFix -> + Async - val matching: cond: (CodeAction -> bool) -> fixes: CodeAction array -> CodeAction array - val withTitle: title: string -> (CodeAction array -> CodeAction array) - val ofKind: kind: string -> (CodeAction array -> CodeAction array) + val checkNotApplicable: + server: CachedServer -> + beforeWithCursor: string -> + validateDiagnostics: (Diagnostic array -> unit) -> + chooseFix: ChooseFix -> + Async - /// Bundled tests in Expecto test - module private Test = - /// One `testCaseAsync` for each cursorRange. - /// All test cases use same document (`ServerTests.documentTestList`) with source `beforeWithoutCursor`. - /// - /// Test names: - /// * `name` is name of outer test list. - /// * Each test case: `Cursor {i} at {pos or range}` - /// - /// Note: Sharing a common `Document` is just barely faster than using a new `Document` for each test (at least for simple source in `beforeWithoutCursor`). - val checkFixAll: - name: string -> - server: CachedServer -> - beforeWithoutCursor: string -> - cursorRanges: Range seq -> - validateDiagnostics: (Diagnostic[] -> unit) -> - chooseFix: ChooseFix -> - expected: ExpectedResult -> - Test + val matching: cond: (CodeAction -> bool) -> fixes: CodeAction array -> CodeAction array + val withTitle: title: string -> (CodeAction array -> CodeAction array) + val ofKind: kind: string -> (CodeAction array -> CodeAction array) - /// One test for each Cursor. - /// - /// Note: Tests single positions -> each `$0` gets checked. - /// -> Every test is for single-position range (`Start=End`)! - val checkAllPositions: - name: string -> - server: CachedServer -> - beforeWithCursors: string -> - validateDiagnostics: (Diagnostic[] -> unit) -> - chooseFix: ChooseFix -> - expected: (unit -> ExpectedResult) -> - Test + /// Bundled tests in Expecto test + module private Test = + /// One `testCaseAsync` for each cursorRange. + /// All test cases use same document (`ServerTests.documentTestList`) with source `beforeWithoutCursor`. + /// + /// Test names: + /// * `name` is name of outer test list. + /// * Each test case: `Cursor {i} at {pos or range}` + /// + /// Note: Sharing a common `Document` is just barely faster than using a new `Document` for each test (at least for simple source in `beforeWithoutCursor`). + val checkFixAll: + name: string -> + server: CachedServer -> + beforeWithoutCursor: string -> + cursorRanges: Range seq -> + validateDiagnostics: (Diagnostic[] -> unit) -> + chooseFix: ChooseFix -> + expected: ExpectedResult -> + Test - val testAllPositions: - name: string -> - server: CachedServer -> - beforeWithCursors: string -> - validateDiagnostics: (Diagnostic array -> unit) -> - chooseFix: ChooseFix -> - expected: string -> - Test + /// One test for each Cursor. + /// + /// Note: Tests single positions -> each `$0` gets checked. + /// -> Every test is for single-position range (`Start=End`)! + val checkAllPositions: + name: string -> + server: CachedServer -> + beforeWithCursors: string -> + validateDiagnostics: (Diagnostic[] -> unit) -> + chooseFix: ChooseFix -> + expected: (unit -> ExpectedResult) -> + Test - val testApplicableAllPositions: - name: string -> - server: CachedServer -> - beforeWithCursors: string -> - validateDiagnostics: (Diagnostic array -> unit) -> - chooseFix: ChooseFix -> - Test + val testAllPositions: + name: string -> + server: CachedServer -> + beforeWithCursors: string -> + validateDiagnostics: (Diagnostic array -> unit) -> + chooseFix: ChooseFix -> + expected: string -> + Test - val testNotApplicableAllPositions: - name: string -> - server: CachedServer -> - beforeWithCursors: string -> - validateDiagnostics: (Diagnostic array -> unit) -> - chooseFix: ChooseFix -> - Test + val testApplicableAllPositions: + name: string -> + server: CachedServer -> + beforeWithCursors: string -> + validateDiagnostics: (Diagnostic array -> unit) -> + chooseFix: ChooseFix -> + Test + + val testNotApplicableAllPositions: + name: string -> + server: CachedServer -> + beforeWithCursors: string -> + validateDiagnostics: (Diagnostic array -> unit) -> + chooseFix: ChooseFix -> + Test