Skip to content

Commit

Permalink
Test Helpers for Code Fixes & LSP Server (#911)
Browse files Browse the repository at this point in the history
  • Loading branch information
Booksbaum authored Apr 7, 2022
1 parent 28c4b5c commit d2cf362
Show file tree
Hide file tree
Showing 46 changed files with 5,694 additions and 1,047 deletions.
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@
*.sh text eol=lf
Makefile.orig text eol=lf
configure.sh text eol=lf

*.fs text eol=lf
*.fsi text eol=lf
*.fsx text eol=lf
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/AddExplicitTypeToParameter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ open FSharp.Compiler.Symbols
open FsAutoComplete.FCSPatches
open FSharp.Compiler.Syntax

let title = "Add explicit type annotation"
let fix (getParseResultsForFile: GetParseResultsForFile): CodeFix =
fun codeActionParams ->
asyncResult {
Expand Down Expand Up @@ -44,7 +45,7 @@ let fix (getParseResultsForFile: GetParseResultsForFile): CodeFix =
match symbolUse.Symbol with
| :? FSharpMemberOrFunctionOrValue as v when isValidParameterWithoutTypeAnnotation v symbolUse ->
let typeString = v.FullType.Format symbolUse.DisplayContext
let title = "Add explicit type annotation"
let title = title
let fcsSymbolRange = symbolUse.Range
let protocolSymbolRange = fcsRangeToLsp fcsSymbolRange
let! symbolText = sourceText.GetText fcsSymbolRange
Expand Down
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/AddMissingFunKeyword.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open Ionide.LanguageServerProtocol.Types
open FsAutoComplete
open FsAutoComplete.LspHelpers

let title = "Add missing 'fun' keyword"
/// a codefix that adds a missing 'fun' keyword to a lambda
let fix (getFileLines: GetFileLines) (getLineText: GetLineText): CodeFix =
Run.ifDiagnosticByCode
Expand Down Expand Up @@ -51,7 +52,7 @@ let fix (getFileLines: GetFileLines) (getLineText: GetLineText): CodeFix =
let symbolStartRange = fcsPosToProtocolRange fcsStartPos

return
[ { Title = "Add missing 'fun' keyword"
[ { Title = title
File = codeActionParams.TextDocument
SourceDiagnostic = Some diagnostic
Edits =
Expand Down
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/AddMissingInstanceMember.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ open FsToolkit.ErrorHandling
open FsAutoComplete.CodeFix.Types
open Ionide.LanguageServerProtocol.Types

let title = "Add missing instance member parameter"
let fix =
Run.ifDiagnosticByCode (Set.ofList [ "673" ]) (fun diagnostic codeActionParams -> asyncResult {
return [{
Title = "Add missing instance member parameter"
Title = title
File = codeActionParams.TextDocument
Kind = FixKind.Fix
SourceDiagnostic = Some diagnostic
Expand Down
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/ChangeTypeOfNameToNameOf.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type FSharpParseFileResults with
| _ -> defaultTraverse expr
| _ -> defaultTraverse expr })

let title = "Use 'nameof'"
let fix (getParseResultsForFile: GetParseResultsForFile): CodeFix =
fun codeActionParams ->
asyncResult {
Expand All @@ -42,7 +43,7 @@ let fix (getParseResultsForFile: GetParseResultsForFile): CodeFix =
return [{
Edits = [| { Range = fcsRangeToLsp results.FullExpressionRange; NewText = replacement } |]
File = codeActionParams.TextDocument
Title = "Use 'nameof'"
Title = title
SourceDiagnostic = None
Kind = FixKind.Refactor }]
}
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/ConvertPositionalDUToNamed.fs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ let private toPosSeq (range: FSharp.Compiler.Text.Range, text: NamedText) =
if FSharp.Compiler.Text.Range.rangeContainsPos range nextPos then Some (currentPos, nextPos) else None
)

let title = "Convert to named patterns"
let fix (getParseResultsForFile: GetParseResultsForFile) (getRangeText: GetRangeText) : CodeFix =
fun codeActionParams ->
asyncResult {
Expand Down Expand Up @@ -192,7 +193,7 @@ let fix (getParseResultsForFile: GetParseResultsForFile) (getRangeText: GetRange
return
[ { Edits = allEdits
File = codeActionParams.TextDocument
Title = "Convert to named patterns"
Title = title
SourceDiagnostic = None
Kind = FixKind.Refactor } ]
}
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/GenerateAbstractClassStub.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ open FsAutoComplete
open FsAutoComplete.LspHelpers
open FSharp.UMX

let title = "Generate abstract class members"
/// a codefix that generates stubs for required override members in abstract types
let fix (getParseResultsForFile: GetParseResultsForFile)
(genAbstractClassStub: _ -> _ -> _ -> _ -> Async<CoreResponse<string * FcsPos>>)
Expand Down Expand Up @@ -46,7 +47,7 @@ let fix (getParseResultsForFile: GetParseResultsForFile)

return
[ { SourceDiagnostic = Some diagnostic
Title = "Generate abstract class members"
Title = title
File = codeActionParams.TextDocument
Edits =
[| { Range = fcsPosToProtocolRange position
Expand Down
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/GenerateRecordStub.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ open Ionide.LanguageServerProtocol.Types
open FsAutoComplete
open FsAutoComplete.LspHelpers

let title = "Generate record stub"
/// a codefix that generates member stubs for a record declaration
let fix (getParseResultsForFile: GetParseResultsForFile)
(genRecordStub: _ -> _ -> _ -> _ -> Async<CoreResponse<string * FcsPos>>)
Expand All @@ -31,7 +32,7 @@ let fix (getParseResultsForFile: GetParseResultsForFile)

return
[ { SourceDiagnostic = None
Title = "Generate record stub"
Title = title
File = codeActionParams.TextDocument
Edits =
[| { Range = fcsPosToProtocolRange position
Expand Down
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/GenerateUnionCases.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ open FsAutoComplete
open FsAutoComplete.LspHelpers
open FsAutoComplete.CodeFix.Navigation

let title = "Generate union pattern match cases"
/// a codefix that generates union cases for an incomplete match expression
let fix (getFileLines: GetFileLines)
(getParseResultsForFile: GetParseResultsForFile)
Expand Down Expand Up @@ -46,7 +47,7 @@ let fix (getFileLines: GetFileLines)
return
[ { SourceDiagnostic = Some diagnostic
File = codeActionParams.TextDocument
Title = "Generate union pattern match cases"
Title = title
Edits = [| { Range = range; NewText = replaced } |]
Kind = FixKind.Fix } ]

Expand Down
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/MakeOuterBindingRecursive.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open Ionide.LanguageServerProtocol.Types
open FsAutoComplete
open FsAutoComplete.LspHelpers

let title = "Make outer binding recursive"
let fix (getParseResultsForFile: GetParseResultsForFile) (getLineText: GetLineText) : CodeFix =
Run.ifDiagnosticByCode
(Set.ofList [ "39" ])
Expand All @@ -31,7 +32,7 @@ let fix (getParseResultsForFile: GetParseResultsForFile) (getLineText: GetLineTe
"member names didn't match, don't suggest fix"

return
[ { Title = "Make outer binding recursive"
[ { Title = title
File = codeActionParams.TextDocument
SourceDiagnostic = Some diagnostic
Kind = FixKind.Fix
Expand Down
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/NegationToSubtraction.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open Ionide.LanguageServerProtocol.Types
open FsAutoComplete
open FsAutoComplete.LspHelpers

let title = "Use subtraction instead of negation"
/// a codefix that corrects -<something> to - <something> when negation is not intended
let fix (getFileLines: GetFileLines) : CodeFix =
Run.ifDiagnosticByCode (Set.ofList [ "3" ]) (fun diagnostic codeActionParams ->
Expand All @@ -22,7 +23,7 @@ let fix (getFileLines: GetFileLines) : CodeFix =
let! oneBack = dec lines dash |> Result.ofOption (fun _ -> "No one back")
return
[ { SourceDiagnostic = Some diagnostic
Title = "Use subtraction instead of negation"
Title = title
File = codeActionParams.TextDocument
Edits =
[| { Range = { Start = oneBack; End = dash }
Expand Down
6 changes: 4 additions & 2 deletions src/FsAutoComplete/CodeFixes/RemoveUnusedBinding.fs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type FSharpParseFileResults with
| Some range -> Some range
| _ -> defaultTraverse binding })

let titleParameter = "Remove unused parameter"
let titleBinding = "Remove unused binding"
let fix (getParseResults: GetParseResultsForFile): CodeFix =
Run.ifDiagnosticByCode
(Set.ofList ["1182"])
Expand All @@ -82,7 +84,7 @@ let fix (getParseResults: GetParseResultsForFile): CodeFix =
|> Result.ofOption (fun _ -> "failed to walk")
// replace from there to the end of the pattern's range
let replacementRange = { Start = endOfPrecedingToken; End = protocolRange.End }
return [ { Title = "Remove unused parameter"
return [ { Title = titleParameter
Edits = [| { Range = replacementRange; NewText = "" } |]
File = codeActionParams.TextDocument
SourceDiagnostic = Some diagnostic
Expand All @@ -98,7 +100,7 @@ let fix (getParseResults: GetParseResultsForFile): CodeFix =
// walk back to the start of the keyword, which is always `let` or `use`
let! keywordStartColumn = decMany lines endOfPrecedingKeyword 3 |> Result.ofOption (fun _ -> "failed to walk")
let replacementRange = { Start = keywordStartColumn; End = protocolRange.End }
return [ { Title = "Remove unused binding"
return [ { Title = titleBinding
Edits = [| { Range = replacementRange; NewText = "" } |]
File = codeActionParams.TextDocument
SourceDiagnostic = Some diagnostic
Expand Down
8 changes: 5 additions & 3 deletions src/FsAutoComplete/CodeFixes/UnusedValue.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ open FsAutoComplete.CodeFix.Types
open FsAutoComplete
open FsAutoComplete.LspHelpers

let titleReplace = "Replace with _"
let titlePrefix = "Prefix with _"
/// a codefix that suggests prepending a _ to unused values
let fix (getRangeText: GetRangeText) =
Run.ifDiagnosticByMessage
Expand All @@ -19,7 +21,7 @@ let fix (getRangeText: GetRangeText) =
return
[ { SourceDiagnostic = Some diagnostic
File = codeActionParams.TextDocument
Title = "Replace with _"
Title = titleReplace
Edits =
[| { Range = diagnostic.Range
NewText = "_" } |]
Expand All @@ -31,14 +33,14 @@ let fix (getRangeText: GetRangeText) =
return
[ { SourceDiagnostic = Some diagnostic
File = codeActionParams.TextDocument
Title = "Replace with _"
Title = titleReplace
Edits =
[| { Range = diagnostic.Range
NewText = replaceSuggestion } |]
Kind = FixKind.Refactor }
{ SourceDiagnostic = Some diagnostic
File = codeActionParams.TextDocument
Title = "Prefix with _"
Title = titlePrefix
Edits =
[| { Range = diagnostic.Range
NewText = prefixSuggestion } |]
Expand Down
3 changes: 2 additions & 1 deletion src/FsAutoComplete/CodeFixes/UseTripleQuotedInterpolation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open FsAutoComplete
open FsAutoComplete.LspHelpers
open FsAutoComplete.FCSPatches

let title = "Use triple-quoted string interpolation"
/// 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 ->
Expand All @@ -28,7 +29,7 @@ let fix (getParseResultsForFile: GetParseResultsForFile) (getRangeText: GetRange
return
[ { File = codeActionParams.TextDocument
SourceDiagnostic = Some diagnostic
Title = "Use triple-quoted string interpolation"
Title = title
Edits =
[| { Range = fcsRangeToLsp range
NewText = newText } |]
Expand Down
76 changes: 45 additions & 31 deletions src/FsAutoComplete/FsAutoComplete.Lsp.fs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ type FSharpLspClient(sendServerNotification: ClientNotificationSender, sendServe
sendServerNotification "fsharp/fileParsed" (box p)
|> Async.Ignore

member __.NotifyDocumentAnalyzed(p: DocumentAnalyzedNotification) =
sendServerNotification "fsharp/documentAnalyzed" (box p)
|> Async.Ignore

member __.NotifyTestDetected (p: TestDetectedNotification) =
sendServerNotification "fsharp/testDetected" (box p) |> Async.Ignore

Expand Down Expand Up @@ -244,7 +248,40 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS
let mutable sigHelpKind = None
let mutable binaryLogConfig = Ionide.ProjInfo.BinaryLogGeneration.Off

let parseFile (p: DidChangeTextDocumentParams) =
let analyzeFile (filePath) =
let analyzers = [
// if config.Linter then
// commands.Lint filePath |> Async.Ignore
if config.UnusedOpensAnalyzer then
commands.CheckUnusedOpens filePath
if config.UnusedDeclarationsAnalyzer then
commands.CheckUnusedDeclarations filePath
if config.SimplifyNameAnalyzer then
commands.CheckSimplifiedNames filePath
]

analyzers
|> Async.Parallel
|> Async.Ignore

let parseFile
(filePath: string<LocalPath>)
(version: int)
(content: NamedText)
= async {
let tfmConfig = config.UseSdkScripts
do!
commands.Parse filePath content version (Some tfmConfig)
|> Async.Ignore

async {
do! analyzeFile filePath
do! lspClient.NotifyDocumentAnalyzed { TextDocument = { Uri = filePath |> Path.LocalPathToUri; Version = Some version } }
}
|> Async.Start
}

let parseChangedFile (p: DidChangeTextDocumentParams) =

async {
let doc = p.TextDocument
Expand All @@ -256,26 +293,14 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS
if contentChange.Range.IsNone
&& contentChange.RangeLength.IsNone then
let content = NamedText(filePath, contentChange.Text)
let tfmConfig = config.UseSdkScripts

logger.info (
Log.setMessage "ParseFile - Parsing {file}"
>> Log.addContextDestructured "file" filePath
)

do!
commands.Parse filePath content version (Some tfmConfig)
|> Async.Ignore

// if config.Linter then do! (commands.Lint filePath |> Async.Ignore)
if config.UnusedOpensAnalyzer then
Async.Start(commands.CheckUnusedOpens filePath)

if config.UnusedDeclarationsAnalyzer then
Async.Start(commands.CheckUnusedDeclarations filePath) //fire and forget this analyzer now that it's syncronous
do! parseFile filePath version content

if config.SimplifyNameAnalyzer then
Async.Start(commands.CheckSimplifiedNames filePath)
else
logger.warn (Log.setMessage "ParseFile - Parse not started, received partial change")
| _ ->
Expand All @@ -286,7 +311,7 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS
}
|> Async.Start

let parseFileDebuncer = Debounce(500, parseFile)
let parseFileDebuncer = Debounce(500, parseChangedFile)


let sendDiagnostics (uri: DocumentUri) (diags: Diagnostic []) =
Expand Down Expand Up @@ -605,9 +630,10 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS
commands.SetLinterConfigRelativePath config.LinterConfig
// TODO(CH): make the destination part of config, so that non-FSAC editors don't have the '.ionide' path
binaryLogConfig <-
match config.GenerateBinlog with
| false -> Ionide.ProjInfo.BinaryLogGeneration.Off
| true -> Ionide.ProjInfo.BinaryLogGeneration.Within(DirectoryInfo(Path.Combine(rootPath.Value, ".ionide")))
match config.GenerateBinlog, rootPath with
| _, None
| false, _ -> Ionide.ProjInfo.BinaryLogGeneration.Off
| true, Some rootPath -> Ionide.ProjInfo.BinaryLogGeneration.Within(DirectoryInfo(Path.Combine(rootPath, ".ionide")))
()

do
Expand Down Expand Up @@ -995,19 +1021,7 @@ type FSharpLspServer(backgroundServiceEnabled: bool, state: State, lspClient: FS

commands.SetFileContent(filePath, content, Some doc.Version, config.ScriptTFM)

do!
(commands.Parse filePath content doc.Version (Some tfmConfig)
|> Async.Ignore)

// if config.Linter then do! (commands.Lint filePath |> Async.Ignore)
if config.UnusedOpensAnalyzer then
Async.Start(commands.CheckUnusedOpens filePath)

if config.UnusedDeclarationsAnalyzer then
Async.Start(commands.CheckUnusedDeclarations filePath)

if config.SimplifyNameAnalyzer then
Async.Start(commands.CheckSimplifiedNames filePath)
do! parseFile filePath doc.Version content
}

override __.TextDocumentDidChange(p) =
Expand Down
9 changes: 9 additions & 0 deletions src/FsAutoComplete/LspHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,15 @@ module ClassificationUtils =

type PlainNotification= { Content: string }

/// Notification when a `TextDocument` is completely analyzed:
/// F# Compiler checked file & all Analyzers (like `UnusedOpensAnalyzer`) are done.
///
/// Used to signal all Diagnostics for this `TextDocument` are collected and sent.
/// -> For tests to get all Diagnostics of `TextDocument`
type DocumentAnalyzedNotification = {
TextDocument: VersionedTextDocumentIdentifier
}

type TestDetectedNotification =
{ File: string
Tests: TestAdapter.TestAdapterEntry<Range> array }
Expand Down
Loading

0 comments on commit d2cf362

Please sign in to comment.