forked from ionide/FSharp.Analyzers.SDK
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.fs
669 lines (559 loc) · 24.7 KB
/
Program.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
open System
open System.IO
open System.Runtime.Loader
open System.Runtime.InteropServices
open System.Text.RegularExpressions
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.Text
open Argu
open FSharp.Analyzers.SDK
open GlobExpressions
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 =
| Project of string list
| Analyzers_Path of string list
| [<EqualsAssignment; AltCommandLine("-p:"); AltCommandLine("-p")>] Property of string * string
| [<Unique; AltCommandLine("-c")>] Configuration of string
| [<Unique; AltCommandLine("-r")>] Runtime of string
| [<Unique; AltCommandLine("-a")>] Arch of string
| [<Unique>] Os of string
| [<Unique>] Treat_As_Info of string list
| [<Unique>] Treat_As_Hint of string list
| [<Unique>] Treat_As_Warning of string list
| [<Unique>] Treat_As_Error of string list
| [<Unique>] Exclude_Files of string list
| [<Unique>] Include_Files of string list
| [<Unique>] Exclude_Analyzers of string list
| [<Unique>] Include_Analyzers of string list
| [<Unique>] Report of string
| [<Unique>] FSC_Args of string
| [<Unique>] Code_Root of string
| [<Unique; AltCommandLine("-v")>] Verbosity of string
interface IArgParserTemplate with
member s.Usage =
match s with
| Project _ -> "Path to your .fsproj file."
| Analyzers_Path _ -> "Path to a folder where your analyzers are located."
| Property _ -> "A key=value pair of an MSBuild property."
| Configuration _ -> "The configuration to use, e.g. Debug or Release."
| Runtime _ -> "The runtime identifier (RID)."
| Arch _ -> "The target architecture."
| Os _ -> "The target operating system."
| Treat_As_Info _ ->
"List of analyzer codes that should be treated as severity Info by the tool. Regardless of the original severity."
| Treat_As_Hint _ ->
"List of analyzer codes that should be treated as severity Hint by the tool. Regardless of the original severity."
| Treat_As_Warning _ ->
"List of analyzer codes that should be treated as severity Warning by the tool. Regardless of the original severity."
| Treat_As_Error _ ->
"List of analyzer codes that should be treated as severity Error by the tool. Regardless of the original severity."
| Exclude_Files _ -> "Source files that shouldn't be processed."
| Include_Files _ ->
"Source files that should be processed exclusively while all others are ignored. Takes precedence over --exclude-files."
| Exclude_Analyzers _ -> "The names of analyzers that should not be executed."
| Include_Analyzers _ ->
"The names of analyzers that should exclusively be executed while all others are ignored. Takes precedence over --exclude-analyzers."
| Report _ -> "Write the result messages to a (sarif) report file."
| Verbosity _ ->
"The verbosity level. The available verbosity levels are: n[ormal], d[etailed], diag[nostic]."
| FSC_Args _ -> "Pass in the raw fsc compiler arguments. Cannot be combined with the `--project` flag."
| 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."
type SeverityMappings =
{
TreatAsInfo: Set<string>
TreatAsHint: Set<string>
TreatAsWarning: Set<string>
TreatAsError: Set<string>
}
member x.IsValid() =
let allCodes = [ x.TreatAsInfo; x.TreatAsHint; x.TreatAsWarning; x.TreatAsError ]
let unionCount = allCodes |> Set.unionMany |> Set.count
let summedCount = allCodes |> List.sumBy Set.count
summedCount = unionCount
let mapMessageToSeverity (mappings: SeverityMappings) (msg: FSharp.Analyzers.SDK.AnalyzerMessage) =
let targetSeverity =
if mappings.TreatAsInfo |> Set.contains msg.Message.Code then
Severity.Info
else if mappings.TreatAsHint |> Set.contains msg.Message.Code then
Severity.Hint
else if mappings.TreatAsWarning |> Set.contains msg.Message.Code then
Severity.Warning
else if mappings.TreatAsError |> Set.contains msg.Message.Code then
Severity.Error
else
msg.Message.Severity
{ msg with
Message =
{ msg.Message with
Severity = targetSeverity
}
}
let mutable logLevel = LogLevel.Warning
let fcs = Utils.createFCS None
let parser = ArgumentParser.Create<Arguments>(errorHandler = ProcessExiter())
let rec mkKn (ty: Type) =
if Reflection.FSharpType.IsFunction(ty) then
let _, ran = Reflection.FSharpType.GetFunctionElements(ty)
let f = mkKn ran
Reflection.FSharpValue.MakeFunction(ty, (fun _ -> f))
else
box ()
let mutable logger: ILogger = Abstractions.NullLogger.Instance
let loadProject toolsPath properties projPath =
async {
let loader = WorkspaceLoader.Create(toolsPath, properties)
let parsed = loader.LoadProjects [ projPath ] |> Seq.toList
if parsed.IsEmpty then
logger.LogError("Failed to load project '{0}'", projPath)
exit 1
let fcsPo = FCS.mapToFSharpProjectOptions parsed.Head parsed
return fcsPo
}
let runProjectAux
(client: Client<CliAnalyzerAttribute, CliContext>)
(fsharpOptions: FSharpProjectOptions)
(excludeIncludeFiles: Choice<Glob list, Glob list>)
(mappings: SeverityMappings)
: Async<Result<AnalyzerMessage list, AnalysisFailure> list>
=
async {
let! checkProjectResults = fcs.ParseAndCheckProject(fsharpOptions)
let! messagesPerAnalyzer =
fsharpOptions.SourceFiles
|> Array.filter (fun file ->
match excludeIncludeFiles with
| Choice1Of2 excludeFiles ->
match excludeFiles |> List.tryFind (fun g -> g.IsMatch file) with
| Some g ->
logger.LogInformation("Ignoring file {0} for pattern {1}", file, g.Pattern)
false
| None -> true
| Choice2Of2 includeFiles ->
match includeFiles |> List.tryFind (fun g -> g.IsMatch file) with
| Some g ->
logger.LogInformation("Including file {0} for pattern {1}", file, g.Pattern)
true
| None -> false
)
|> Array.map (fun fileName ->
let fileContent = File.ReadAllText fileName
let sourceText = SourceText.ofString fileContent
Utils.typeCheckFile fcs logger fsharpOptions fileName (Utils.SourceOfSource.SourceText sourceText)
|> Result.map (Utils.createContext checkProjectResults fileName sourceText)
)
|> Array.map (fun 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
messagesPerAnalyzer
|> Seq.map (fun messages ->
match messages with
| Error e -> Error e
| Ok messages -> messages |> List.map (mapMessageToSeverity mappings) |> Ok
)
|> Seq.toList
}
let runProject
(client: Client<CliAnalyzerAttribute, CliContext>)
toolsPath
properties
proj
(excludeIncludeFiles: Choice<Glob list, Glob list>)
(mappings: SeverityMappings)
=
async {
let path = Path.Combine(Environment.CurrentDirectory, proj) |> Path.GetFullPath
let! option = loadProject toolsPath properties path
return! runProjectAux client option excludeIncludeFiles mappings
}
let fsharpFiles = set [| ".fs"; ".fsi"; ".fsx" |]
let isFSharpFile (file: string) =
Set.exists (fun (ext: string) -> file.EndsWith(ext, StringComparison.Ordinal)) fsharpFiles
let runFscArgs
(client: Client<CliAnalyzerAttribute, CliContext>)
(fscArgs: string)
(excludeIncludeFiles: Choice<Glob list, Glob list>)
(mappings: SeverityMappings)
=
if String.IsNullOrWhiteSpace fscArgs then
logger.LogError("Empty --fsc-args were passed!")
exit 1
else
let fscArgs = fscArgs.Split(';', StringSplitOptions.RemoveEmptyEntries)
let sourceFiles =
fscArgs
|> Array.choose (fun (argument: string) ->
// We make an absolute path because the sarif report cannot deal properly with relative path.
let path = Path.Combine(Directory.GetCurrentDirectory(), argument)
if not (isFSharpFile path) || not (File.Exists path) then
None
else
Some path
)
let otherOptions = fscArgs |> Array.filter (fun line -> not (isFSharpFile line))
let projectOptions =
{
ProjectFileName = "Project"
ProjectId = None
SourceFiles = sourceFiles
OtherOptions = otherOptions
ReferencedProjects = [||]
IsIncompleteTypeCheckEnvironment = false
UseScriptResolutionRules = false
LoadTime = DateTime.Now
UnresolvedReferences = None
OriginalLoadReferences = []
Stamp = None
}
runProjectAux client projectOptions excludeIncludeFiles mappings
let printMessages (msgs: AnalyzerMessage list) =
let severityToLogLevel =
Map.ofArray
[|
Severity.Error, LogLevel.Error
Severity.Warning, LogLevel.Warning
Severity.Info, LogLevel.Information
Severity.Hint, LogLevel.Trace
|]
if List.isEmpty msgs then
logger.LogInformation("No messages found from the analyzer(s)")
use factory =
LoggerFactory.Create(fun builder ->
builder
.AddCustomFormatter(fun options -> options.UseAnalyzersMsgStyle <- true)
.SetMinimumLevel(LogLevel.Trace)
|> ignore
)
let msgLogger = factory.CreateLogger("")
msgs
|> List.iter (fun analyzerMessage ->
let m = analyzerMessage.Message
msgLogger.Log(
severityToLogLevel[m.Severity],
"{0}({1},{2}): {3} {4} : {5}",
m.Range.FileName,
m.Range.StartLine,
m.Range.StartColumn,
m.Severity.ToString(),
m.Code,
m.Message
)
)
()
let writeReport (results: AnalyzerMessage list) (codeRoot: string option) (report: string) =
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 <- "Ionide.Analyzers.Cli"
driver.InformationUri <- Uri("https://ionide.io/FSharp.Analyzers.SDK/")
driver.Version <- string<Version> (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.Message.Code
reportDescriptor.Name <- analyzerResult.Message.Message
analyzerResult.ShortDescription
|> Option.iter (fun shortDescription ->
reportDescriptor.ShortDescription <-
MultiformatMessageString(shortDescription, shortDescription, dict [])
)
analyzerResult.HelpUri
|> Option.iter (fun helpUri -> 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
let msg = Message()
msg.Text <- analyzerResult.Message.Message
result.Message <- msg
let physicalLocation = PhysicalLocation()
physicalLocation.ArtifactLocation <-
let al = ArtifactLocation()
al.Uri <- codeRoot.MakeRelativeUri(Uri(analyzerResult.Message.Range.FileName))
al
physicalLocation.Region <-
let r = Region()
r.StartLine <- analyzerResult.Message.Range.StartLine
r.StartColumn <- analyzerResult.Message.Range.StartColumn + 1
r.EndLine <- analyzerResult.Message.Range.EndLine
r.EndColumn <- analyzerResult.Message.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.LogError(ex, "Could not write sarif to {report}", report)
logger.LogInformation("{0}", ex)
/// 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
let expandMultiProperties (properties: (string * string) list) =
properties
|> List.map (fun (k, v) ->
if not (v.Contains('=')) then // no multi properties given to expand
[ (k, v) ]
else
let regex = Regex(";([a-z,A-Z,0-9,_,-]*)=")
let splits = regex.Split(v)
[
yield (k, splits[0])
for pair in splits.[1..] |> Seq.chunkBySize 2 do
match pair with
| [| k; v |] when String.IsNullOrWhiteSpace(v) ->
logger.LogError("Missing property value for '{0}'", k)
exit 1
| [| k; v |] -> yield (k, v)
| _ -> ()
]
)
|> List.concat
let validateRuntimeOsArchCombination (runtime, arch, os) =
match runtime, os, arch with
| Some _, Some _, _ ->
logger.LogError("Specifying both the `-r|--runtime` and `-os` options is not supported.")
exit 1
| Some _, _, Some _ ->
logger.LogError("Specifying both the `-r|--runtime` and `-a|--arch` options is not supported.")
exit 1
| _ -> ()
let getProperties (results: ParseResults<Arguments>) =
let runtime = results.TryGetResult <@ Runtime @>
let arch = results.TryGetResult <@ Arch @>
let os = results.TryGetResult <@ Os @>
validateRuntimeOsArchCombination (runtime, os, arch)
let runtimeProp =
let rid = RuntimeInformation.RuntimeIdentifier // assuming we always get something like 'linux-x64'
match runtime, os, arch with
| Some r, _, _ -> Some r
| None, Some o, Some a -> Some $"{o}-{a}"
| None, Some o, None ->
let archOfRid = rid.Substring(rid.LastIndexOf('-') + 1)
Some $"{o}-{archOfRid}"
| None, None, Some a ->
let osOfRid = rid.Substring(0, rid.LastIndexOf('-'))
Some $"{osOfRid}-{a}"
| _ -> None
results.GetResults <@ Property @>
|> expandMultiProperties
|> fun props ->
[
yield! props
match results.TryGetResult <@ Configuration @> with
| (Some x) -> yield ("Configuration", x)
| _ -> ()
match runtimeProp with
| (Some x) -> yield ("RuntimeIdentifier", x)
| _ -> ()
]
[<EntryPoint>]
let main argv =
let toolsPath = Init.init (DirectoryInfo Environment.CurrentDirectory) None
let results = parser.ParseCommandLine argv
let logLevel =
let verbosity = results.TryGetResult <@ Verbosity @>
match verbosity with
| Some "d"
| Some "detailed" -> LogLevel.Information
| Some "diag"
| Some "diagnostic" -> LogLevel.Debug
| Some "n" -> LogLevel.Warning
| Some "normal" -> LogLevel.Warning
| None -> LogLevel.Warning
| Some x ->
use factory = LoggerFactory.Create(fun b -> b.AddConsole() |> ignore)
let logger = factory.CreateLogger("")
logger.LogError("unknown verbosity level given {0}", x)
exit 1
use factory =
LoggerFactory.Create(fun builder ->
builder
.AddCustomFormatter(fun options -> options.UseAnalyzersMsgStyle <- false)
.SetMinimumLevel(logLevel)
|> ignore
)
logger <- factory.CreateLogger("")
logger.LogInformation("Running in verbose mode")
let severityMapping =
{
TreatAsHint = results.GetResult(<@ Treat_As_Hint @>, []) |> Set.ofList
TreatAsInfo = results.GetResult(<@ Treat_As_Info @>, []) |> Set.ofList
TreatAsWarning = results.GetResult(<@ Treat_As_Warning @>, []) |> Set.ofList
TreatAsError = results.GetResult(<@ Treat_As_Error @>, []) |> Set.ofList
}
logger.LogInformation("Treat as Hints: [{0}]", (severityMapping.TreatAsHint |> String.concat ", "))
logger.LogInformation("Treat as Info: [{0}]", (severityMapping.TreatAsInfo |> String.concat ", "))
logger.LogInformation("Treat as Warning: [{0}]", (severityMapping.TreatAsWarning |> String.concat ", "))
logger.LogInformation("Treat as Error: [{0}]", (severityMapping.TreatAsError |> String.concat ", "))
if not (severityMapping.IsValid()) then
logger.LogError("An analyzer code may only be listed once in the <treat-as-severity> arguments.")
exit 1
let projOpts = results.GetResults <@ Project @> |> List.concat
let fscArgs = results.TryGetResult <@ FSC_Args @>
let report = results.TryGetResult <@ Report @>
let codeRoot = results.TryGetResult <@ Code_Root @>
let exclInclFiles =
let excludeFiles = results.GetResult(<@ Exclude_Files @>, [])
logger.LogInformation("Exclude Files: [{0}]", (excludeFiles |> String.concat ", "))
let excludeFiles = excludeFiles |> List.map Glob
let includeFiles = results.GetResult(<@ Include_Files @>, [])
logger.LogInformation("Include Files: [{0}]", (includeFiles |> String.concat ", "))
let includeFiles = includeFiles |> List.map Glob
match excludeFiles, includeFiles with
| e, [] -> Choice1Of2 e
| [], i -> Choice2Of2 i
| _e, i ->
logger.LogWarning("--exclude-files and --include-files are mutually exclusive, ignoring --exclude-files")
Choice2Of2 i
let properties = getProperties results
if Option.isSome fscArgs && not properties.IsEmpty then
logger.LogError("fsc-args can't be combined with MSBuild properties.")
exit 1
properties
|> List.iter (fun (k, v) -> logger.LogInformation("Property {0}={1}", k, v))
let analyzersPaths =
results.GetResults(<@ Analyzers_Path @>)
|> List.concat
|> function
| [] -> [ "packages/Analyzers" ]
| paths -> paths
|> List.map (fun path ->
if Path.IsPathRooted path then
path
else
Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, path))
)
logger.LogInformation("Loading analyzers from {0}", (String.concat ", " analyzersPaths))
let exclInclAnalyzers =
let excludeAnalyzers = results.GetResult(<@ Exclude_Analyzers @>, [])
let includeAnalyzers = results.GetResult(<@ Include_Analyzers @>, [])
match excludeAnalyzers, includeAnalyzers with
| e, [] ->
fun (s: string) -> e |> List.map Glob |> List.exists (fun g -> g.IsMatch s)
|> ExcludeFilter
| [], i ->
fun (s: string) -> i |> List.map Glob |> List.exists (fun g -> g.IsMatch s)
|> IncludeFilter
| _e, i ->
logger.LogWarning(
"--exclude-analyzers and --include-analyzers are mutually exclusive, ignoring --exclude-analyzers"
)
fun (s: string) -> i |> List.map Glob |> List.exists (fun g -> g.IsMatch s)
|> IncludeFilter
AssemblyLoadContext.Default.add_Resolving (fun _ctx assemblyName ->
if assemblyName.Name <> "FSharp.Core" then
null
else
let msg =
$"""Could not load FSharp.Core %A{assemblyName.Version}. The expected assembly version of FSharp.Core is %A{Utils.currentFSharpCoreVersion}.
Consider adding <PackageReference Update="FSharp.Core" Version="<CorrectVersion>" /> to your .fsproj.
The correct version can be found over at https://www.nuget.org/packages/FSharp.Analyzers.SDK#dependencies-body-tab.
"""
logger.LogError(msg)
exit 1
)
let client = Client<CliAnalyzerAttribute, CliContext>(logger)
let dlls, analyzers, failedAssemblies =
((0, 0, 0), analyzersPaths)
||> List.fold (fun (accDlls, accAnalyzers, accFailed) analyzersPath ->
let loadedDlls = client.LoadAnalyzers(analyzersPath, exclInclAnalyzers)
(accDlls + loadedDlls.AnalyzerAssemblies),
(accAnalyzers + loadedDlls.Analyzers),
(accFailed + loadedDlls.FailedAssemblies)
)
logger.LogInformation("Registered {0} analyzers from {1} dlls", analyzers, dlls)
let results =
if analyzers = 0 then
None
else
match projOpts, fscArgs with
| [], None ->
logger.LogError("No project given. Use `--project PATH_TO_FSPROJ`.")
None
| _ :: _, Some _ ->
logger.LogError("`--project` and `--fsc-args` cannot be combined.")
exit 1
| [], Some fscArgs ->
runFscArgs client fscArgs exclInclFiles severityMapping
|> Async.RunSynchronously
|> Some
| projects, None ->
for projPath in projects do
if not (File.Exists(projPath)) then
logger.LogError("Invalid `--project` argument. File does not exist: '{projPath}'", projPath)
exit 1
projects
|> List.map (fun projPath ->
runProject client toolsPath properties projPath exclInclFiles severityMapping
)
|> Async.Sequential
|> Async.RunSynchronously
|> List.concat
|> Some
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
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
if check then -2
elif hasError then -4
else 0