From c5335436f7c3fe5e71031f9b3bce36acf880b99e Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Tue, 7 Feb 2023 23:02:16 -0500 Subject: [PATCH] fixes test discovery and analyzers for adaptive --- src/FsAutoComplete.Core/Commands.fs | 52 ++--- .../LspServers/AdaptiveFSharpLspServer.fs | 181 +++++++++++++++--- .../CompletionTests.fs | 3 +- test/FsAutoComplete.Tests.Lsp/CoreTests.fs | 6 +- test/FsAutoComplete.Tests.Lsp/Program.fs | 19 +- test/FsAutoComplete.Tests.Lsp/Utils/Server.fs | 2 +- 6 files changed, 201 insertions(+), 62 deletions(-) diff --git a/src/FsAutoComplete.Core/Commands.fs b/src/FsAutoComplete.Core/Commands.fs index 9da272ec2..97c3a87e0 100644 --- a/src/FsAutoComplete.Core/Commands.fs +++ b/src/FsAutoComplete.Core/Commands.fs @@ -896,30 +896,6 @@ module Commands = highlights |> Array.collect expandParents) |> Array.sortBy startToken -type Commands(checker: FSharpCompilerServiceChecker, state: State, hasAnalyzers: bool, rootPath: string option) = - let fileParsed = Event() - - let fileChecked = Event * int>() - - let scriptFileProjectOptions = Event() - - let disposables = ResizeArray() - - let mutable workspaceRoot: string option = rootPath - let mutable linterConfigFileRelativePath: string option = None - // let mutable linterConfiguration: FSharpLint.Application.Lint.ConfigurationParam = FSharpLint.Application.Lint.ConfigurationParam.Default - let mutable lastCheckResult: ParseAndCheckResults option = None - - let notify = Event() - - let fileStateSet = Event() - let commandsLogger = LogProvider.getLoggerByName "Commands" - - let checkerLogger = LogProvider.getLoggerByName "CheckerEvents" - - let fantomasLogger = LogProvider.getLoggerByName "Fantomas" - - let analyzerHandler (file: string, content, pt, tast, symbols, getAllEnts) = @@ -967,6 +943,32 @@ type Commands(checker: FSharpCompilerServiceChecker, state: State, hasAnalyzers: [||] + +type Commands(checker: FSharpCompilerServiceChecker, state: State, hasAnalyzers: bool, rootPath: string option) = + let fileParsed = Event() + + let fileChecked = Event * int>() + + let scriptFileProjectOptions = Event() + + let disposables = ResizeArray() + + let mutable workspaceRoot: string option = rootPath + let mutable linterConfigFileRelativePath: string option = None + // let mutable linterConfiguration: FSharpLint.Application.Lint.ConfigurationParam = FSharpLint.Application.Lint.ConfigurationParam.Default + let mutable lastCheckResult: ParseAndCheckResults option = None + + let notify = Event() + + let fileStateSet = Event() + let commandsLogger = LogProvider.getLoggerByName "Commands" + + let checkerLogger = LogProvider.getLoggerByName "CheckerEvents" + + let fantomasLogger = LogProvider.getLoggerByName "Fantomas" + + + do disposables.Add <| state.ProjectController.Notifications.Subscribe(NotificationEvent.Workspace >> notify.Trigger) @@ -1034,7 +1036,7 @@ type Commands(checker: FSharpCompilerServiceChecker, state: State, hasAnalyzers: | true, fileData -> let res = - analyzerHandler ( + Commands.analyzerHandler ( file, fileData.Lines.ToString().Split("\n"), parseAndCheck.GetParseResults.ParseTree, diff --git a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs index edd1f1c5e..650e637c6 100644 --- a/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs +++ b/src/FsAutoComplete/LspServers/AdaptiveFSharpLspServer.fs @@ -183,10 +183,11 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let config = cval FSharpConfig.Default - let checker = - config - |> AVal.map (fun c -> c.EnableAnalyzers) // Maps will cache values and we don't want to recreate FSharpCompilerServiceChecker unless only EnableAnalyzers changed - |> AVal.map (FSharpCompilerServiceChecker) + let analyzersEnabled = config |> AVal.map (fun c -> c.EnableAnalyzers) + + let checker = analyzersEnabled |> AVal.map (FSharpCompilerServiceChecker) + + let rootPath = cval None let mutableConfigChanges = let toCompilerToolArgument (path: string) = sprintf "--compilertool:%s" path @@ -194,12 +195,52 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar aval { let! config = config and! checker = checker + and! rootPath = rootPath checker.SetFSIAdditionalArguments [| yield! config.FSICompilerToolLocations |> Array.map toCompilerToolArgument yield! config.FSIExtraParameters |] - return () + if config.EnableAnalyzers then + Loggers.analyzers.info ( + Log.setMessage "Using analyzer roots of {roots}" + >> Log.addContextDestructured "roots" config.AnalyzersPath + ) + + config.AnalyzersPath + |> Array.iter (fun analyzerPath -> + match rootPath with + | None -> () + | Some workspacePath -> + let dir = + if + System.IO.Path.IsPathRooted analyzerPath + // if analyzer is using absolute path, use it as is + then + analyzerPath + // otherwise, it is a relative path and should be combined with the workspace path + else + System.IO.Path.Combine(workspacePath, analyzerPath) + + Loggers.analyzers.info ( + Log.setMessage "Loading analyzers from {dir}" + >> Log.addContextDestructured "dir" dir + ) + + let (n, m) = dir |> FSharp.Analyzers.SDK.Client.loadAnalyzers + + Loggers.analyzers.info ( + Log.setMessage "From {name}: {dllNo} dlls including {analyzersNo} analyzers" + >> Log.addContextDestructured "name" analyzerPath + >> Log.addContextDestructured "dllNo" n + >> Log.addContextDestructured "analyzersNo" m + )) + + return () + // otherwise, it is a relative path and should be combined with the workspace path + else + Loggers.analyzers.info (Log.setMessage "Analyzers disabled") + return () } let updateConfig c = @@ -233,6 +274,90 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let scriptFileProjectOptions = Event() + let fileParsed = + Event() + + let fileChecked = Event() + + + do + disposables.Add + <| fileParsed.Publish.Subscribe(fun (parseResults, proj, ct) -> + logger.info ( + Log.setMessage "Test Detection of {file} started" + >> Log.addContextDestructured "file" parseResults.FileName + ) + + let fn = UMX.tag parseResults.FileName + + let res = + if proj.OtherOptions |> Seq.exists (fun o -> o.Contains "Expecto.dll") then + TestAdapter.getExpectoTests parseResults.ParseTree + elif proj.OtherOptions |> Seq.exists (fun o -> o.Contains "nunit.framework.dll") then + TestAdapter.getNUnitTest parseResults.ParseTree + elif proj.OtherOptions |> Seq.exists (fun o -> o.Contains "xunit.assert.dll") then + TestAdapter.getXUnitTest parseResults.ParseTree + else + [] + + logger.info ( + Log.setMessage "Test Detection of {file} - {res}" + >> Log.addContextDestructured "file" parseResults.FileName + >> Log.addContextDestructured "res" res + ) + + notifications.Trigger(NotificationEvent.TestDetected(fn, res |> List.toArray), ct)) + + do + disposables.Add + <| fileChecked.Publish.Subscribe(fun (parseAndCheck, volatileFile, ct) -> + async { + if analyzersEnabled |> AVal.force then + let file = volatileFile.FileName + + try + Loggers.analyzers.info ( + Log.setMessage "begin analysis of {file}" + >> Log.addContextDestructured "file" file + ) + + match parseAndCheck.GetCheckResults.ImplementationFile with + | Some tast -> + + let res = + Commands.analyzerHandler ( + file, + volatileFile.Lines.ToString().Split("\n"), + parseAndCheck.GetParseResults.ParseTree, + tast, + parseAndCheck.GetCheckResults.PartialAssemblySignature.Entities |> Seq.toList, + parseAndCheck.GetAllEntities + ) + + notifications.Trigger(NotificationEvent.AnalyzerMessage(res, file), ct) + + Loggers.analyzers.info ( + Log.setMessage "end analysis of {file}" + >> Log.addContextDestructured "file" file + ) + + | _ -> + Loggers.analyzers.info ( + Log.setMessage "missing components of {file} to run analyzers, skipped them" + >> Log.addContextDestructured "file" file + ) + + () + with ex -> + Loggers.analyzers.error ( + Log.setMessage "Run failed for {file}" + >> Log.addContextDestructured "file" file + >> Log.addExn ex + ) + } + |> Async.StartWithCT ct) + + let handleCommandEvents (n: NotificationEvent, ct: CancellationToken) = try async { @@ -457,7 +582,6 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let loader = cval workspaceLoader - let rootPath = cval None let binlogConfig = @@ -583,7 +707,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar // Set some default values as FCS uses these for identification/caching purposes let fso = { fso with - SourceFiles = fso.SourceFiles |> Array.map(Utils.normalizePath >> UMX.untag) + SourceFiles = fso.SourceFiles |> Array.map (Utils.normalizePath >> UMX.untag) Stamp = fso.Stamp |> Option.orElse (Some DateTime.UtcNow.Ticks) ProjectId = fso.ProjectId |> Option.orElse (Some(Guid.NewGuid().ToString())) } @@ -875,7 +999,7 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar match! sourceFileToProjectOptions |> AMap.tryFind filePath with | None -> // openFilesToChangesAndProjectOptions contains script files that we may need to look through - match! openFilesToChangesAndProjectOptions |> AMap.tryFindA filePath with + match! openFilesToChangesAndProjectOptions |> AMap.tryFindA filePath with | None -> return [] | Some (_, projs) -> return projs | Some projs -> return projs @@ -1013,6 +1137,9 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar Async.Start( async { + + fileParsed.Trigger(parseAndCheck.GetParseResults, opts, ct) + fileChecked.Trigger(parseAndCheck, file, ct) let checkErrors = parseAndCheck.GetParseResults.Diagnostics let parseErrors = parseAndCheck.GetCheckResults.Diagnostics @@ -1068,13 +1195,14 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar let! opts = List.tryHead projectOptions and! cts = tryGetOpenFileToken file - return! - Debug.measure "parseFile" - <| fun () -> - let opts = Utils.projectOptionsToParseOptions opts + let parseOpts = Utils.projectOptionsToParseOptions opts - checker.ParseFile(file, info.Lines, opts) - |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) + let! result = + checker.ParseFile(file, info.Lines, parseOpts) + |> Async.RunSynchronouslyWithCTSafe(fun () -> cts.Token) + + fileParsed.Trigger(result, opts, cts.Token) + return result } }) @@ -1919,6 +2047,10 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar do! bypassAdaptiveAndCheckDepenenciesForFile filePath do! lspClient.CodeLensRefresh() + logger.info ( + Log.setMessage "TextDocumentDidSave Request Finished: {parms}" + >> Log.addContextDestructured "parms" p + ) return () with e -> @@ -2221,16 +2353,21 @@ type AdaptiveFSharpLspServer(workspaceLoader: IWorkspaceLoader, lspClient: FShar and! tyRes = forceGetTypeCheckResultsStale filePath |> Result.ofStringErr match tyRes.TryGetToolTipEnhanced pos lineStr with - | Ok (Some (tip, signature, footer, typeDoc)) -> + | Ok (Some (tip, signature, footer, typeDoc) as x) -> + logger.info ( + Log.setMessage "TryGetToolTipEnhanced : {parms}" + >> Log.addContextDestructured "parms" x + ) + let formatCommentStyle = let config = AVal.force config - - if config.TooltipMode = "full" then - TipFormatter.FormatCommentStyle.FullEnhanced - else if config.TooltipMode = "summary" then - TipFormatter.FormatCommentStyle.SummaryOnly - else - TipFormatter.FormatCommentStyle.Legacy + TipFormatter.FormatCommentStyle.FullEnhanced + // if config.TooltipMode = "full" then + // TipFormatter.FormatCommentStyle.FullEnhanced + // else if config.TooltipMode = "summary" then + // TipFormatter.FormatCommentStyle.SummaryOnly + // else + // TipFormatter.FormatCommentStyle.Legacy match TipFormatter.formatTipEnhanced tip signature footer typeDoc formatCommentStyle with | (sigCommentFooter :: _) :: _ -> diff --git a/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs b/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs index 929b66a0a..e3b415d23 100644 --- a/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CompletionTests.fs @@ -27,7 +27,8 @@ let tests state = } |> Async.Cache - testList + testSequenced + <| testList "Completion Tests" [ testCaseAsync "simple module member completion on dot" diff --git a/test/FsAutoComplete.Tests.Lsp/CoreTests.fs b/test/FsAutoComplete.Tests.Lsp/CoreTests.fs index f345ed320..d36427733 100644 --- a/test/FsAutoComplete.Tests.Lsp/CoreTests.fs +++ b/test/FsAutoComplete.Tests.Lsp/CoreTests.fs @@ -227,7 +227,7 @@ let tooltipTests state = let expectedDescription = concatLines expectedDescription testCaseAsync - (sprintf "description for line %d character %d should be '%s" line character expectedDescription) + (sprintf "description for line %d character %d" line character) (async { let! server, scriptPath = server @@ -242,8 +242,8 @@ let tooltipTests state = | Result.Error errors -> failtestf "Error while getting description: %A" errors }) - - testList + testSequenced + <| testList "tooltip evaluation" [ testList "tests" diff --git a/test/FsAutoComplete.Tests.Lsp/Program.fs b/test/FsAutoComplete.Tests.Lsp/Program.fs index e65e01dc1..e3eba40ab 100644 --- a/test/FsAutoComplete.Tests.Lsp/Program.fs +++ b/test/FsAutoComplete.Tests.Lsp/Program.fs @@ -50,8 +50,8 @@ let adaptiveLspServerFactory toolsPath workspaceLoaderFactory = let lspServers = [ - "FSharpLspServer", fsharpLspServerFactory - // "AdaptiveLspServer", adaptiveLspServerFactory + // "FSharpLspServer", fsharpLspServerFactory + "AdaptiveLspServer", adaptiveLspServerFactory ] let mutable toolsPath = @@ -88,13 +88,13 @@ let lspTests = dependencyManagerTests createServer interactiveDirectivesUnitTests - // // commented out because FSDN is down - // //fsdnTest createServer - uriTests + // commented out because FSDN is down + //fsdnTest createServer + //linterTests createServer + uriTests formattingTests createServer - if lspName <> "AdaptiveLspServer" then - analyzerTests createServer // stalling on adaptive + analyzerTests createServer // stalling on adaptive signatureTests createServer SignatureHelp.tests createServer CodeFixTests.Tests.tests createServer @@ -102,8 +102,7 @@ let lspTests = GoTo.tests createServer FindReferences.tests createServer InfoPanelTests.docFormattingTest createServer - if lspName <> "AdaptiveLspServer" then - DetectUnitTests.tests createServer //stalling on adaptive + DetectUnitTests.tests createServer XmlDocumentationGeneration.tests createServer InlayHintTests.tests createServer DependentFileChecking.tests createServer @@ -126,7 +125,7 @@ let tests = let main args = let outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] [{SourceContext}] {Message:lj}{NewLine}{Exception}" - + use _ = new FsAutoComplete.Debug.FSharpCompilerEventLogger.Listener() let parseLogLevel args = let debugMarker = "--debug" let logMarker = "--log=" diff --git a/test/FsAutoComplete.Tests.Lsp/Utils/Server.fs b/test/FsAutoComplete.Tests.Lsp/Utils/Server.fs index e311c0788..e36d6c7d3 100644 --- a/test/FsAutoComplete.Tests.Lsp/Utils/Server.fs +++ b/test/FsAutoComplete.Tests.Lsp/Utils/Server.fs @@ -379,7 +379,7 @@ module Document = TextDocument = doc.TextDocumentIdentifier } // Simulate the file being written to disk so we don't hit the typechecker cache - IO.File.SetLastWriteTimeUtc(doc.FilePath, DateTime.Now) + IO.File.SetLastWriteTimeUtc(doc.FilePath, DateTime.UtcNow) do! doc.Server.Server.TextDocumentDidSave p do! Async.Sleep(TimeSpan.FromMilliseconds 250.) return! doc |> waitForLatestDiagnostics Helpers.defaultTimeout