@@ -5,6 +5,8 @@ open FSharp.Compiler.Text
5
5
open Argu
6
6
open FSharp.Analyzers .SDK
7
7
open GlobExpressions
8
+ open Microsoft.CodeAnalysis .Sarif
9
+ open Microsoft.CodeAnalysis .Sarif .Writers
8
10
open Ionide.ProjInfo
9
11
10
12
type Arguments =
@@ -13,6 +15,7 @@ type Arguments =
13
15
| Fail_ On_ Warnings of string list
14
16
| Ignore_ Files of string list
15
17
| Exclude_ Analyzer of string list
18
+ | Report of string
16
19
| Verbose
17
20
18
21
interface IArgParserTemplate with
@@ -24,6 +27,7 @@ type Arguments =
24
27
" List of analyzer codes that should trigger tool failures in the presence of warnings."
25
28
| Ignore_ Files _ -> " Source files that shouldn't be processed."
26
29
| Exclude_ Analyzer _ -> " The names of analyzers that should not be executed."
30
+ | Report _ -> " Write the result messages to a (sarif) report file."
27
31
| Verbose -> " Verbose logging."
28
32
29
33
let mutable verbose = false
@@ -107,15 +111,17 @@ let runProject (client: Client<CliAnalyzerAttribute, CliContext>) toolsPath proj
107
111
]
108
112
}
109
113
110
- let printMessages failOnWarnings ( msgs : Message list) =
114
+ let printMessages failOnWarnings ( msgs : AnalyzerMessage list) =
111
115
if verbose then
112
116
printfn " "
113
117
114
118
if verbose && List.isEmpty msgs then
115
119
printfn " No messages found from the analyzer(s)"
116
120
117
121
msgs
118
- |> Seq.iter ( fun m ->
122
+ |> Seq.iter ( fun analyzerMessage ->
123
+ let m = analyzerMessage.Message
124
+
119
125
let color =
120
126
match m.Severity with
121
127
| Error -> ConsoleColor.Red
@@ -140,15 +146,97 @@ let printMessages failOnWarnings (msgs: Message list) =
140
146
141
147
msgs
142
148
143
- let calculateExitCode failOnWarnings ( msgs : Message list option ) : int =
149
+ let writeReport ( results : AnalyzerMessage list option ) ( report : string ) =
150
+ try
151
+ let driver = ToolComponent()
152
+ driver.Name <- " Ionide.Analyzers.Cli"
153
+ driver.InformationUri <- Uri( " https://ionide.io/FSharp.Analyzers.SDK/" )
154
+ driver.Version <- string ( System.Reflection.Assembly.GetExecutingAssembly() .GetName() .Version)
155
+ let tool = Tool()
156
+ tool.Driver <- driver
157
+ let run = Run()
158
+ run.Tool <- tool
159
+
160
+ use sarifLogger =
161
+ new SarifLogger(
162
+ report,
163
+ logFilePersistenceOptions =
164
+ ( FilePersistenceOptions.PrettyPrint ||| FilePersistenceOptions.ForceOverwrite),
165
+ run = run,
166
+ levels = BaseLogger.ErrorWarningNote,
167
+ kinds = BaseLogger.Fail,
168
+ closeWriterOnDispose = true
169
+ )
170
+
171
+ sarifLogger.AnalysisStarted()
172
+
173
+ for analyzerResult in ( Option.defaultValue List.empty results) do
174
+ let reportDescriptor = ReportingDescriptor()
175
+ reportDescriptor.Id <- analyzerResult.Message.Code
176
+ reportDescriptor.Name <- analyzerResult.Message.Message
177
+
178
+ analyzerResult.ShortDescription
179
+ |> Option.iter ( fun shortDescription ->
180
+ reportDescriptor.ShortDescription <-
181
+ MultiformatMessageString( shortDescription, shortDescription, dict [])
182
+ )
183
+
184
+ analyzerResult.HelpUri
185
+ |> Option.iter ( fun helpUri -> reportDescriptor.HelpUri <- Uri( helpUri))
186
+
187
+ let result = Result()
188
+ result.RuleId <- reportDescriptor.Id
189
+
190
+ result.Level <-
191
+ match analyzerResult.Message.Severity with
192
+ | Info -> FailureLevel.Note
193
+ | Hint -> FailureLevel.None
194
+ | Warning -> FailureLevel.Warning
195
+ | Error -> FailureLevel.Error
196
+
197
+ let msg = Message()
198
+ msg.Text <- analyzerResult.Message.Message
199
+ result.Message <- msg
200
+
201
+ let physicalLocation = PhysicalLocation()
202
+
203
+ physicalLocation.ArtifactLocation <-
204
+ let al = ArtifactLocation()
205
+ al.Uri <- Uri( analyzerResult.Message.Range.FileName)
206
+ al
207
+
208
+ physicalLocation.Region <-
209
+ let r = Region()
210
+ r.StartLine <- analyzerResult.Message.Range.StartLine
211
+ r.StartColumn <- analyzerResult.Message.Range.StartColumn
212
+ r.EndLine <- analyzerResult.Message.Range.EndLine
213
+ r.EndColumn <- analyzerResult.Message.Range.EndColumn
214
+ r
215
+
216
+ let location : Location = Location()
217
+ location.PhysicalLocation <- physicalLocation
218
+ result.Locations <- [| location |]
219
+
220
+ sarifLogger.Log( reportDescriptor, result, System.Nullable())
221
+
222
+ sarifLogger.AnalysisStopped( RuntimeConditions.None)
223
+
224
+ sarifLogger.Dispose()
225
+ with ex ->
226
+ let details = if not verbose then " " else $" %s {ex.Message}"
227
+ printfn $" Could not write sarif to %s {report}%s {details}"
228
+
229
+ let calculateExitCode failOnWarnings ( msgs : AnalyzerMessage list option ) : int =
144
230
match msgs with
145
231
| None -> - 1
146
232
| Some msgs ->
147
233
let check =
148
234
msgs
149
- |> List.exists ( fun n ->
150
- n.Severity = Error
151
- || ( n.Severity = Warning && failOnWarnings |> List.contains n.Code)
235
+ |> List.exists ( fun analyzerMessage ->
236
+ let message = analyzerMessage.Message
237
+
238
+ message.Severity = Error
239
+ || ( message.Severity = Warning && failOnWarnings |> List.contains message.Code)
152
240
)
153
241
154
242
if check then - 2 else 0
@@ -197,6 +285,7 @@ let main argv =
197
285
printInfo " Registered %d analyzers from %d dlls" analyzers dlls
198
286
199
287
let projOpts = results.TryGetResult <@ Project @>
288
+ let report = results.TryGetResult <@ Report @>
200
289
201
290
let results =
202
291
if analyzers = 0 then
@@ -231,4 +320,6 @@ let main argv =
231
320
|> List.concat
232
321
|> Some
233
322
323
+ report |> Option.iter ( writeReport results)
324
+
234
325
calculateExitCode failOnWarnings results
0 commit comments