diff --git a/src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj b/src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj index eb2711a..9fd4150 100644 --- a/src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj +++ b/src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj @@ -14,6 +14,7 @@ + diff --git a/src/FSharp.Analyzers.Cli/Program.fs b/src/FSharp.Analyzers.Cli/Program.fs index f347dab..ecadd79 100644 --- a/src/FSharp.Analyzers.Cli/Program.fs +++ b/src/FSharp.Analyzers.Cli/Program.fs @@ -12,6 +12,7 @@ open Microsoft.CodeAnalysis.Sarif open Microsoft.CodeAnalysis.Sarif.Writers open Microsoft.Extensions.Logging open Ionide.ProjInfo +open FSharp.Analyzers.Cli open FSharp.Analyzers.Cli.CustomLogging type Arguments = @@ -136,6 +137,7 @@ let runProjectAux (fsharpOptions: FSharpProjectOptions) (excludeIncludeFiles: Choice) (mappings: SeverityMappings) + : Async list> = async { let! checkProjectResults = fcs.ParseAndCheckProject(fsharpOptions) @@ -157,26 +159,33 @@ let runProjectAux true | None -> false ) - |> Array.choose (fun fileName -> + |> Array.map (fun fileName -> let fileContent = File.ReadAllText fileName let sourceText = SourceText.ofString fileContent Utils.typeCheckFile fcs logger fsharpOptions fileName (Utils.SourceOfSource.SourceText sourceText) - |> Option.map (Utils.createContext checkProjectResults fileName sourceText) + |> Result.map (Utils.createContext checkProjectResults fileName sourceText) ) |> Array.map (fun ctx -> - logger.LogInformation("Running analyzers for {0}", ctx.FileName) - client.RunAnalyzers ctx + match ctx with + | Error e -> async.Return(Error e) + | Ok ctx -> + async { + logger.LogInformation("Running analyzers for {0}", ctx.FileName) + let! results = client.RunAnalyzers ctx + return Ok results + } ) |> Async.Parallel return - Some - [ - for messages in messagesPerAnalyzer do - let mappedMessages = messages |> List.map (mapMessageToSeverity mappings) - yield! mappedMessages - ] + messagesPerAnalyzer + |> Seq.map (fun messages -> + match messages with + | Error e -> Error e + | Ok messages -> messages |> List.map (mapMessageToSeverity mappings) |> Ok + ) + |> Seq.toList } let runProject @@ -267,7 +276,7 @@ let printMessages (msgs: AnalyzerMessage list) = let msgLogger = factory.CreateLogger("") msgs - |> Seq.iter (fun analyzerMessage -> + |> List.iter (fun analyzerMessage -> let m = analyzerMessage.Message msgLogger.Log( @@ -284,7 +293,7 @@ let printMessages (msgs: AnalyzerMessage list) = () -let writeReport (results: AnalyzerMessage list option) (codeRoot: string option) (report: string) = +let writeReport (results: AnalyzerMessage list) (codeRoot: string option) (report: string) = try let codeRoot = match codeRoot with @@ -319,7 +328,7 @@ let writeReport (results: AnalyzerMessage list option) (codeRoot: string option) sarifLogger.AnalysisStarted() - for analyzerResult in (Option.defaultValue List.empty results) do + for analyzerResult in results do let reportDescriptor = ReportingDescriptor() reportDescriptor.Id <- analyzerResult.Message.Code reportDescriptor.Name <- analyzerResult.Message.Message @@ -375,20 +384,6 @@ let writeReport (results: AnalyzerMessage list option) (codeRoot: string option) logger.LogError(ex, "Could not write sarif to {report}", report) logger.LogInformation("{0}", ex) -let calculateExitCode (msgs: AnalyzerMessage list option) : int = - match msgs with - | None -> -1 - | Some msgs -> - let check = - msgs - |> List.exists (fun analyzerMessage -> - let message = analyzerMessage.Message - - message.Severity = Severity.Error - ) - - if check then -2 else 0 - /// If multiple MSBuild properties are given in one -p flag like -p:prop1="val1a;val1b;val1c";prop2="1;2;3";prop3=val3 /// argu will think it means prop1 has the value: "val1a;val1b;val1c";prop2="1;2;3";prop3=val3 /// so this function expands the value into multiple key-value properties @@ -623,6 +618,7 @@ let main argv = | [], Some fscArgs -> runFscArgs client fscArgs exclInclFiles severityMapping |> Async.RunSynchronously + |> Some | projects, None -> for projPath in projects do if not (File.Exists(projPath)) then @@ -635,19 +631,39 @@ let main argv = ) |> Async.Sequential |> Async.RunSynchronously - |> Array.choose id |> List.concat |> Some - results |> Option.iter printMessages - report |> Option.iter (writeReport results codeRoot) + match results with + | None -> -1 + | Some results -> + let results, hasError = + match Result.allOkOrError results with + | Ok results -> results, false + | Error(results, _errors) -> results, true + + let results = results |> List.concat - if failedAssemblies > 0 then - logger.LogError( - "Because we failed to load some assemblies to obtain analyzers from them, exiting (failure count: {FailedAssemblyLoadCount})", - failedAssemblies - ) + printMessages results + + report |> Option.iter (writeReport results codeRoot) + + let check = + results + |> List.exists (fun analyzerMessage -> + let message = analyzerMessage.Message + + message.Severity = Severity.Error + ) + + if failedAssemblies > 0 then + logger.LogError( + "Because we failed to load some assemblies to obtain analyzers from them, exiting (failure count: {FailedAssemblyLoadCount})", + failedAssemblies + ) - exit -3 + exit -3 - calculateExitCode results + if check then -2 + elif hasError then -4 + else 0 diff --git a/src/FSharp.Analyzers.Cli/Result.fs b/src/FSharp.Analyzers.Cli/Result.fs new file mode 100644 index 0000000..f83daeb --- /dev/null +++ b/src/FSharp.Analyzers.Cli/Result.fs @@ -0,0 +1,15 @@ +module FSharp.Analyzers.Cli.Result + +let allOkOrError<'ok, 'err> (results: Result<'ok, 'err> list) : Result<'ok list, 'ok list * 'err list> = + let oks, errs = + (([], []), results) + ||> List.fold (fun (oks, errs) result -> + match result with + | Ok ok -> ok :: oks, errs + | Error err -> oks, err :: errs + ) + + let oks = List.rev oks + let errs = List.rev errs + + if List.isEmpty errs then Ok oks else Error(oks, errs) diff --git a/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fs b/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fs index 30b0762..064e949 100644 --- a/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fs +++ b/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fs @@ -257,7 +257,7 @@ let getContextFor (opts: FSharpProjectOptions) isSignature source = fileName (Utils.SourceOfSource.DiscreteSource source) with - | Some(parseFileResults, checkFileResults) -> + | Ok(parseFileResults, checkFileResults) -> let diagErrors = checkFileResults.Diagnostics |> Array.filter (fun d -> d.Severity = FSharpDiagnosticSeverity.Error) @@ -267,7 +267,7 @@ let getContextFor (opts: FSharpProjectOptions) isSignature source = let sourceText = SourceText.ofString source Utils.createContext checkProjectResults fileName sourceText (parseFileResults, checkFileResults) - | None -> failwith "typechecking file failed" + | Error e -> failwith $"typechecking file failed: %O{e}" let getContext (opts: FSharpProjectOptions) source = getContextFor opts false source let getContextForSignature (opts: FSharpProjectOptions) source = getContextFor opts true source diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs index c0b9414..ada105d 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs @@ -176,6 +176,8 @@ type AnalyzerMessage = HelpUri: string option } +type AnalysisFailure = | Aborted + module Utils = let currentFSharpAnalyzersSDKVersion = Assembly.GetExecutingAssembly().GetName().Version @@ -230,6 +232,7 @@ module Utils = (options: FSharpProjectOptions) (fileName: string) (source: SourceOfSource) + : Result = let sourceText = @@ -247,5 +250,5 @@ module Utils = match checkAnswer with | FSharpCheckFileAnswer.Aborted -> logger.LogError("Checking of file {0} aborted", fileName) - None - | FSharpCheckFileAnswer.Succeeded result -> Some(parseRes, result) + Error AnalysisFailure.Aborted + | FSharpCheckFileAnswer.Succeeded result -> Ok(parseRes, result) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi index 5748001..6afca2e 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi @@ -152,6 +152,12 @@ type AnalyzerMessage = HelpUri: string option } +/// Represents a failure to run FSC analysis. +[] +type AnalysisFailure = + /// The F# compiler service aborted during analysis. + | Aborted + module Utils = [] @@ -171,7 +177,7 @@ module Utils = options: FSharpProjectOptions -> fileName: string -> source: SourceOfSource -> - option + Result val createContext: checkProjectResults: FSharpCheckProjectResults ->