Skip to content

Commit 89cc536

Browse files
committed
WIP: Add Sarif output support to FSharpLint.Console
This is using the Microsoft Sarif.Sdk to write Sarif files.
1 parent d11d440 commit 89cc536

File tree

5 files changed

+117
-0
lines changed

5 files changed

+117
-0
lines changed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
2626
<PackageVersion Include="NUnit" Version="3.14.0" />
2727
<PackageVersion Include="NUnit3TestAdapter" Version="5.0.0" />
28+
<PackageVersion Include="Sarif.Sdk" Version="4.5.4" />
2829
<PackageVersion Include="System.Reactive" Version="6.0.1" />
2930
</ItemGroup>
3031
</Project>

src/FSharpLint.Console/FSharpLint.Console.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
<ItemGroup>
1919
<Compile Include="Output.fs" />
20+
<Compile Include="Sarif.fs" />
2021
<Compile Include="Program.fs" />
2122
</ItemGroup>
2223

src/FSharpLint.Console/Program.fs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ type private FileType =
2424
type private ToolArgs =
2525
| [<AltCommandLine("-f")>] Format of OutputFormat
2626
| [<CliPrefix(CliPrefix.None)>] Lint of ParseResults<LintArgs>
27+
| [<Unique>] Report of string
28+
| [<Unique>] Code_Root of string
2729
| Version
2830
with
2931
interface IArgParserTemplate with
3032
member this.Usage =
3133
match this with
3234
| Format _ -> "Output format of the linter."
3335
| Lint _ -> "Runs FSharpLint against a file or a collection of files."
36+
| Report _ -> "Write the result messages to a (sarif) report file."
37+
| 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."
3438
| Version -> "Prints current version."
3539

3640
// TODO: investigate erroneous warning on this type definition
@@ -94,13 +98,20 @@ let private start (arguments:ParseResults<ToolArgs>) (toolsPath:Ionide.ProjInfo.
9498
output.WriteError str
9599
exitCode <- -1
96100

101+
let reportPath = arguments.TryGetResult Report
102+
let codeRoot = arguments.TryGetResult Code_Root
103+
97104
match arguments.GetSubCommand() with
98105
| Lint lintArgs ->
99106

100107
let handleLintResult = function
101108
| LintResult.Success(warnings) ->
102109
String.Format(Resources.GetString("ConsoleFinished"), List.length warnings)
103110
|> output.WriteInfo
111+
112+
reportPath
113+
|> Option.iter (fun report -> Sarif.writeReport warnings codeRoot report output)
114+
104115
if not (List.isEmpty warnings) then exitCode <- -1
105116
| LintResult.Failure(failure) ->
106117
handleError failure.Description

src/FSharpLint.Console/Sarif.fs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
module internal Sarif
2+
3+
open FSharpLint.Framework
4+
open System.IO
5+
open System
6+
open Microsoft.CodeAnalysis.Sarif
7+
open Microsoft.CodeAnalysis.Sarif.Writers
8+
open FSharpLint.Console.Output
9+
10+
let writeReport (results: Suggestion.LintWarning list) (codeRoot: string option) (report: string) (logger: IOutput) =
11+
try
12+
let codeRoot =
13+
match codeRoot with
14+
| None -> Directory.GetCurrentDirectory() |> Uri
15+
| Some root -> Path.GetFullPath root |> Uri
16+
17+
// Construct full path to ensure path separators are normalized.
18+
let report = Path.GetFullPath report
19+
// Ensure the parent directory exists
20+
let reportFile = FileInfo(report)
21+
reportFile.Directory.Create()
22+
23+
let driver = ToolComponent()
24+
driver.Name <- "FSharpLint.Console"
25+
driver.InformationUri <- Uri("https://fsprojects.github.io/FSharpLint/")
26+
driver.Version <- string<Version> (System.Reflection.Assembly.GetExecutingAssembly().GetName().Version)
27+
let tool = Tool()
28+
tool.Driver <- driver
29+
let run = Run()
30+
run.Tool <- tool
31+
32+
use sarifLogger =
33+
new SarifLogger(
34+
report,
35+
logFilePersistenceOptions =
36+
(FilePersistenceOptions.PrettyPrint ||| FilePersistenceOptions.ForceOverwrite),
37+
run = run,
38+
levels = BaseLogger.ErrorWarningNote,
39+
kinds = BaseLogger.Fail,
40+
closeWriterOnDispose = true
41+
)
42+
43+
sarifLogger.AnalysisStarted()
44+
45+
for analyzerResult in results do
46+
let reportDescriptor = ReportingDescriptor()
47+
reportDescriptor.Id <- analyzerResult.RuleIdentifier
48+
reportDescriptor.Name <- analyzerResult.RuleName
49+
50+
(*
51+
analyzerResult.ShortDescription
52+
|> Option.iter (fun shortDescription ->
53+
reportDescriptor.ShortDescription <-
54+
MultiformatMessageString(shortDescription, shortDescription, dict [])
55+
)
56+
*)
57+
58+
let helpUri = $"https://fsprojects.github.io/FSharpLint/how-tos/rules/%s{analyzerResult.RuleIdentifier}.html"
59+
reportDescriptor.HelpUri <- Uri(helpUri)
60+
61+
let result = Result()
62+
result.RuleId <- reportDescriptor.Id
63+
64+
(*
65+
result.Level <-
66+
match analyzerResult.Message.Severity with
67+
| Severity.Info -> FailureLevel.Note
68+
| Severity.Hint -> FailureLevel.Note
69+
| Severity.Warning -> FailureLevel.Warning
70+
| Severity.Error -> FailureLevel.Error
71+
*)
72+
result.Level <- FailureLevel.Warning
73+
74+
let msg = Message()
75+
msg.Text <- analyzerResult.Details.Message
76+
result.Message <- msg
77+
78+
let physicalLocation = PhysicalLocation()
79+
80+
physicalLocation.ArtifactLocation <-
81+
let al = ArtifactLocation()
82+
al.Uri <- codeRoot.MakeRelativeUri(Uri(analyzerResult.Details.Range.FileName))
83+
al
84+
85+
physicalLocation.Region <-
86+
let r = Region()
87+
r.StartLine <- analyzerResult.Details.Range.StartLine
88+
r.StartColumn <- analyzerResult.Details.Range.StartColumn + 1
89+
r.EndLine <- analyzerResult.Details.Range.EndLine
90+
r.EndColumn <- analyzerResult.Details.Range.EndColumn + 1
91+
r
92+
93+
let location: Location = Location()
94+
location.PhysicalLocation <- physicalLocation
95+
result.Locations <- [| location |]
96+
97+
sarifLogger.Log(reportDescriptor, result, System.Nullable())
98+
99+
sarifLogger.AnalysisStopped(RuntimeConditions.None)
100+
101+
sarifLogger.Dispose()
102+
with ex ->
103+
logger.WriteError($"Could not write sarif to %s{report}: %s{ex.Message}")

src/FSharpLint.Core/FSharpLint.Core.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
<PackageReference Include="Microsoft.Build.Utilities.Core" ExcludeAssets="runtime" />
151151
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="all" />
152152
<PackageReference Include="Newtonsoft.Json" />
153+
<PackageReference Include="Sarif.Sdk" />
153154
</ItemGroup>
154155

155156
</Project>

0 commit comments

Comments
 (0)