diff --git a/paket.dependencies b/paket.dependencies
index 55a3eab40..d60f779d7 100644
--- a/paket.dependencies
+++ b/paket.dependencies
@@ -14,6 +14,7 @@ nuget Ionide.ProjInfo == 0.61.3
nuget Ionide.ProjInfo.Sln == 0.61.3
nuget FSharp.Core ~> 6.0
nuget nunit ~> 3.0
+nuget Sarif.Sdk
nuget System.Reactive ~> 5
nuget NUnit3TestAdapter
nuget Microsoft.NET.Test.Sdk 17.7.2
@@ -60,4 +61,4 @@ group Build
nuget Fake.Core.UserInput
nuget Fake.IO.FileSystem
nuget Fake.DotNet.MsBuild
- nuget Fake.Api.GitHub
+ nuget Fake.Api.GitHub
\ No newline at end of file
diff --git a/paket.lock b/paket.lock
index 177bf2fb8..5baf32c4a 100644
--- a/paket.lock
+++ b/paket.lock
@@ -143,6 +143,7 @@ NUGET
Microsoft.Diagnostics.NETCore.Client (>= 0.2.410101)
System.Collections.Immutable (>= 6.0)
System.Runtime.CompilerServices.Unsafe (>= 6.0)
+ Microsoft.Diagnostics.Tracing.EventRegister (1.1.28)
Microsoft.Diagnostics.Tracing.TraceEvent (3.1.7)
Microsoft.Win32.Registry (>= 4.4)
System.Runtime.CompilerServices.Unsafe (>= 5.0)
@@ -250,6 +251,15 @@ NUGET
runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3)
runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3)
runtime.ubuntu.18.04-x64.runtime.native.System.Security.Cryptography.OpenSsl (4.3.3)
+ Sarif.Sdk (4.5.4)
+ Microsoft.Diagnostics.Tracing.EventRegister (>= 1.1.28)
+ Microsoft.Diagnostics.Tracing.TraceEvent (>= 3.1.3)
+ Newtonsoft.Json (>= 9.0.1)
+ System.Collections.Immutable (>= 5.0)
+ System.Diagnostics.Debug (>= 4.3)
+ System.IO.FileSystem.Primitives (>= 4.3)
+ System.Text.Encoding.CodePages (>= 4.3)
+ System.Text.Encoding.Extensions (>= 4.3)
SemanticVersioning (2.0.2) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= net6.0))
System.Buffers (4.5.1)
System.CodeDom (8.0) - copy_local: false
@@ -570,7 +580,6 @@ NUGET
System.Security.Cryptography.Primitives (>= 4.3)
System.Text.Encoding (>= 4.3)
System.Security.Cryptography.Cng (5.0) - copy_local: false
- System.Formats.Asn1 (>= 5.0) - restriction: || (== net6.0) (&& (== netstandard2.0) (>= netcoreapp3.0))
System.Security.Cryptography.Csp (4.3)
Microsoft.NETCore.Platforms (>= 1.1)
System.IO (>= 4.3)
@@ -663,9 +672,9 @@ NUGET
Microsoft.NETCore.Targets (>= 1.1)
System.Runtime (>= 4.3)
System.Text.Encoding (>= 4.3)
- System.Text.Encodings.Web (8.0) - copy_local: false, restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net5.0))
+ System.Text.Encodings.Web (8.0) - copy_local: false, restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
System.Runtime.CompilerServices.Unsafe (>= 6.0)
- System.Text.Json (8.0) - copy_local: false, restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net5.0))
+ System.Text.Json (8.0) - copy_local: false, restriction: || (== net6.0) (&& (== netstandard2.0) (>= net472)) (&& (== netstandard2.0) (>= net6.0))
System.Runtime.CompilerServices.Unsafe (>= 6.0)
System.Text.Encodings.Web (>= 8.0)
System.Threading (4.3)
diff --git a/src/FSharpLint.Console/FSharpLint.Console.fsproj b/src/FSharpLint.Console/FSharpLint.Console.fsproj
index 2a9d92b88..98ce1e188 100644
--- a/src/FSharpLint.Console/FSharpLint.Console.fsproj
+++ b/src/FSharpLint.Console/FSharpLint.Console.fsproj
@@ -17,6 +17,7 @@
+
diff --git a/src/FSharpLint.Console/Program.fs b/src/FSharpLint.Console/Program.fs
index 73d43cd43..c43cffdd4 100644
--- a/src/FSharpLint.Console/Program.fs
+++ b/src/FSharpLint.Console/Program.fs
@@ -24,6 +24,8 @@ type private FileType =
type private ToolArgs =
| [] Format of OutputFormat
| [] Lint of ParseResults
+ | [] Report of string
+ | [] Code_Root of string
| Version
with
interface IArgParserTemplate with
@@ -31,6 +33,8 @@ with
match this with
| Format _ -> "Output format of the linter."
| Lint _ -> "Runs FSharpLint against a file or a collection of files."
+ | Report _ -> "Write the result messages to a (sarif) report file."
+ | Code_Root _ -> "Root of the current code repository, used in the sarif report to construct the relative file path. The current working directory is used by default."
| Version -> "Prints current version."
// TODO: investigate erroneous warning on this type definition
@@ -94,6 +98,9 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo.
output.WriteError str
exitCode <- -1
+ let reportPath = arguments.TryGetResult Report
+ let codeRoot = arguments.TryGetResult Code_Root
+
match arguments.GetSubCommand() with
| Lint lintArgs ->
@@ -101,6 +108,10 @@ let private start (arguments:ParseResults) (toolsPath:Ionide.ProjInfo.
| LintResult.Success(warnings) ->
String.Format(Resources.GetString("ConsoleFinished"), List.length warnings)
|> output.WriteInfo
+
+ reportPath
+ |> Option.iter (fun report -> Sarif.writeReport warnings codeRoot report output)
+
if not (List.isEmpty warnings) then exitCode <- -1
| LintResult.Failure(failure) ->
handleError failure.Description
diff --git a/src/FSharpLint.Console/Sarif.fs b/src/FSharpLint.Console/Sarif.fs
new file mode 100644
index 000000000..4b1cd1734
--- /dev/null
+++ b/src/FSharpLint.Console/Sarif.fs
@@ -0,0 +1,103 @@
+module internal Sarif
+
+open FSharpLint.Framework
+open System.IO
+open System
+open Microsoft.CodeAnalysis.Sarif
+open Microsoft.CodeAnalysis.Sarif.Writers
+open FSharpLint.Console.Output
+
+let writeReport (results: Suggestion.LintWarning list) (codeRoot: string option) (report: string) (logger: IOutput) =
+ try
+ let codeRoot =
+ match codeRoot with
+ | None -> Directory.GetCurrentDirectory() |> Uri
+ | Some root -> Path.GetFullPath root |> Uri
+
+ // Construct full path to ensure path separators are normalized.
+ let report = Path.GetFullPath report
+ // Ensure the parent directory exists
+ let reportFile = FileInfo(report)
+ reportFile.Directory.Create()
+
+ let driver = ToolComponent()
+ driver.Name <- "FSharpLint.Console"
+ driver.InformationUri <- Uri("https://fsprojects.github.io/FSharpLint/")
+ driver.Version <- string (System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)
+ let tool = Tool()
+ tool.Driver <- driver
+ let run = Run()
+ run.Tool <- tool
+
+ use sarifLogger =
+ new SarifLogger(
+ report,
+ logFilePersistenceOptions =
+ (FilePersistenceOptions.PrettyPrint ||| FilePersistenceOptions.ForceOverwrite),
+ run = run,
+ levels = BaseLogger.ErrorWarningNote,
+ kinds = BaseLogger.Fail,
+ closeWriterOnDispose = true
+ )
+
+ sarifLogger.AnalysisStarted()
+
+ for analyzerResult in results do
+ let reportDescriptor = ReportingDescriptor()
+ reportDescriptor.Id <- analyzerResult.RuleIdentifier
+ reportDescriptor.Name <- analyzerResult.RuleName
+
+ (*
+ analyzerResult.ShortDescription
+ |> Option.iter (fun shortDescription ->
+ reportDescriptor.ShortDescription <-
+ MultiformatMessageString(shortDescription, shortDescription, dict [])
+ )
+ *)
+
+ let helpUri = $"https://fsprojects.github.io/FSharpLint/how-tos/rules/%s{analyzerResult.RuleIdentifier}.html"
+ reportDescriptor.HelpUri <- Uri(helpUri)
+
+ let result = Result()
+ result.RuleId <- reportDescriptor.Id
+
+ (*
+ result.Level <-
+ match analyzerResult.Message.Severity with
+ | Severity.Info -> FailureLevel.Note
+ | Severity.Hint -> FailureLevel.Note
+ | Severity.Warning -> FailureLevel.Warning
+ | Severity.Error -> FailureLevel.Error
+ *)
+ result.Level <- FailureLevel.Warning
+
+ let msg = Message()
+ msg.Text <- analyzerResult.Details.Message
+ result.Message <- msg
+
+ let physicalLocation = PhysicalLocation()
+
+ physicalLocation.ArtifactLocation <-
+ let al = ArtifactLocation()
+ al.Uri <- codeRoot.MakeRelativeUri(Uri(analyzerResult.Details.Range.FileName))
+ al
+
+ physicalLocation.Region <-
+ let r = Region()
+ r.StartLine <- analyzerResult.Details.Range.StartLine
+ r.StartColumn <- analyzerResult.Details.Range.StartColumn + 1
+ r.EndLine <- analyzerResult.Details.Range.EndLine
+ r.EndColumn <- analyzerResult.Details.Range.EndColumn + 1
+ r
+
+ let location: Location = Location()
+ location.PhysicalLocation <- physicalLocation
+ result.Locations <- [| location |]
+
+ sarifLogger.Log(reportDescriptor, result, System.Nullable())
+
+ sarifLogger.AnalysisStopped(RuntimeConditions.None)
+
+ sarifLogger.Dispose()
+ with ex ->
+ logger.WriteError($"Could not write sarif to %s{report}: %s{ex.Message}")
\ No newline at end of file
diff --git a/src/FSharpLint.Console/paket.references b/src/FSharpLint.Console/paket.references
index fc097e42e..46776b312 100644
--- a/src/FSharpLint.Console/paket.references
+++ b/src/FSharpLint.Console/paket.references
@@ -1,4 +1,5 @@
Argu
FSharp.Compiler.Service
FSharp.Core
-Microsoft.SourceLink.GitHub
\ No newline at end of file
+Microsoft.SourceLink.GitHub
+Sarif.Sdk import_targets: false
\ No newline at end of file