@@ -15,8 +15,29 @@ open Ionide.ProjInfo
15
15
open FSharp.Analyzers .Cli
16
16
open FSharp.Analyzers .Cli .CustomLogging
17
17
18
+
19
+ type ExitErrorCodes =
20
+ | Success = 0
21
+ | NoAnalyzersFound = - 1
22
+ | AnalyzerFoundError = - 2
23
+ | FailedAssemblyLoading = - 3
24
+ | AnalysisAborted = - 4
25
+ | FailedToLoadProject = 10
26
+ | EmptyFscArgs = 11
27
+ | MissingPropertyValue = 12
28
+ | RuntimeAndOsOptions = 13
29
+ | RuntimeAndArchOptions = 14
30
+ | UnknownLoggerVerbosity = 15
31
+ | AnalyzerListedMultipleTimesInTreatAsSeverity = 16
32
+ | FscArgsCombinedWithMsBuildProperties = 17
33
+ | FSharpCoreAssemblyLoadFailed = 18
34
+ | ProjectAndFscArgs = 19
35
+ | InvalidScriptArguments = 20
36
+ | InvalidProjectArguments = 21
37
+
18
38
type Arguments =
19
39
| Project of string list
40
+ | Script of string list
20
41
| Analyzers_ Path of string list
21
42
| [<EqualsAssignment; AltCommandLine( " -p:" ); AltCommandLine( " -p" ) >] Property of string * string
22
43
| [<Unique; AltCommandLine( " -c" ) >] Configuration of string
@@ -40,7 +61,8 @@ type Arguments =
40
61
interface IArgParserTemplate with
41
62
member s.Usage =
42
63
match s with
43
- | Project _ -> " List of paths to your .fsproj file."
64
+ | Project _ -> " List of paths to your .fsproj file. Cannot be combined with `--fsc-args`."
65
+ | Script _ -> " List of paths to your .fsx file. Supports globs. Cannot be combined with `--fsc-args`."
44
66
| Analyzers_ Path _ ->
45
67
" List of path to a folder where your analyzers are located. This will search recursively."
46
68
| Property _ -> " A key=value pair of an MSBuild property."
@@ -153,7 +175,7 @@ let loadProjects toolsPath properties (projPaths: string list) =
153
175
154
176
if Seq.length failedLoads > 0 then
155
177
logger.LogError( " Failed to load project '{0}'" , failedLoads)
156
- exit 1
178
+ exit ( int ExitErrorCodes.FailedToLoadProject )
157
179
158
180
let loaded =
159
181
FCS.mapManyOptions projectOptions
@@ -235,7 +257,7 @@ let runFscArgs
235
257
=
236
258
if String.IsNullOrWhiteSpace fscArgs then
237
259
logger.LogError( " Empty --fsc-args were passed!" )
238
- exit 1
260
+ exit ( int ExitErrorCodes.EmptyFscArgs )
239
261
else
240
262
241
263
let fscArgs = fscArgs.Split( ';' , StringSplitOptions.RemoveEmptyEntries)
@@ -480,7 +502,7 @@ let expandMultiProperties (properties: (string * string) list) =
480
502
match pair with
481
503
| [| k; v |] when String.IsNullOrWhiteSpace( v) ->
482
504
logger.LogError( " Missing property value for '{0}'" , k)
483
- exit 1
505
+ exit ( int ExitErrorCodes.MissingPropertyValue )
484
506
| [| k; v |] -> yield ( k, v)
485
507
| _ -> ()
486
508
@@ -492,10 +514,10 @@ let validateRuntimeOsArchCombination (runtime, arch, os) =
492
514
match runtime, os, arch with
493
515
| Some _, Some _, _ ->
494
516
logger.LogError( " Specifying both the `-r|--runtime` and `-os` options is not supported." )
495
- exit 1
517
+ exit ( int ExitErrorCodes.RuntimeAndOsOptions )
496
518
| Some _, _, Some _ ->
497
519
logger.LogError( " Specifying both the `-r|--runtime` and `-a|--arch` options is not supported." )
498
- exit 1
520
+ exit ( int ExitErrorCodes.RuntimeAndArchOptions )
499
521
| _ -> ()
500
522
501
523
let getProperties ( results : ParseResults < Arguments >) =
@@ -533,6 +555,7 @@ let getProperties (results: ParseResults<Arguments>) =
533
555
| _ -> ()
534
556
]
535
557
558
+
536
559
[<EntryPoint>]
537
560
let main argv =
538
561
let toolsPath = Init.init ( DirectoryInfo Environment.CurrentDirectory) None
@@ -554,7 +577,7 @@ let main argv =
554
577
use factory = LoggerFactory.Create( fun b -> b.AddConsole() |> ignore)
555
578
let logger = factory.CreateLogger( " " )
556
579
logger.LogError( " unknown verbosity level given {0}" , x)
557
- exit 1
580
+ exit ( int ExitErrorCodes.UnknownLoggerVerbosity )
558
581
559
582
use factory =
560
583
LoggerFactory.Create( fun builder ->
@@ -588,12 +611,35 @@ let main argv =
588
611
if not ( severityMapping.IsValid()) then
589
612
logger.LogError( " An analyzer code may only be listed once in the <treat-as-severity> arguments." )
590
613
591
- exit 1
614
+ exit ( int ExitErrorCodes.AnalyzerListedMultipleTimesInTreatAsSeverity )
592
615
593
616
let projOpts = results.GetResults <@ Project @> |> List.concat
594
617
let fscArgs = results.TryGetResult <@ FSC_ Args @>
595
618
let report = results.TryGetResult <@ Report @>
596
619
let codeRoot = results.TryGetResult <@ Code_ Root @>
620
+ let cwd = Directory.GetCurrentDirectory() |> DirectoryInfo
621
+
622
+ let beginsWithCurrentPath ( path : string ) =
623
+ path.StartsWith( " ./" ) || path.StartsWith( " .\\ " )
624
+
625
+ let scripts =
626
+ results.GetResult(<@ Script @>, [])
627
+ |> List.collect( fun scriptGlob ->
628
+ let root , scriptGlob =
629
+ if Path.IsPathRooted scriptGlob then
630
+ // Glob can't handle absolute paths, so we need to make sure the scriptGlob is a relative path
631
+ let root = Path.GetPathRoot scriptGlob
632
+ let glob = scriptGlob.Substring( root.Length)
633
+ DirectoryInfo root, glob
634
+ else if beginsWithCurrentPath scriptGlob then
635
+ // Glob can't handle relative paths starting with "./" or ".\", so we need trim it
636
+ let relativeGlob = scriptGlob.Substring( 2 ) // remove "./" or ".\"
637
+ cwd, relativeGlob
638
+ else
639
+ cwd, scriptGlob
640
+
641
+ root.GlobFiles scriptGlob |> Seq.map ( fun file -> file.FullName) |> Seq.toList
642
+ )
597
643
598
644
let exclInclFiles =
599
645
let excludeFiles = results.GetResult(<@ Exclude_ Files @>, [])
@@ -616,7 +662,7 @@ let main argv =
616
662
617
663
if Option.isSome fscArgs && not properties.IsEmpty then
618
664
logger.LogError( " fsc-args can't be combined with MSBuild properties." )
619
- exit 1
665
+ exit ( int ExitErrorCodes.FscArgsCombinedWithMsBuildProperties )
620
666
621
667
properties
622
668
|> List.iter ( fun ( k , v ) -> logger.LogInformation( " Property {0}={1}" , k, v))
@@ -675,7 +721,7 @@ let main argv =
675
721
"""
676
722
677
723
logger.LogError( msg)
678
- exit 1
724
+ exit ( int ExitErrorCodes.FSharpCoreAssemblyLoadFailed )
679
725
)
680
726
681
727
let client = Client< CliAnalyzerAttribute, CliContext>( logger)
@@ -696,39 +742,76 @@ let main argv =
696
742
if analyzers = 0 then
697
743
None
698
744
else
699
- match projOpts, fscArgs with
700
- | [], None ->
701
- logger.LogError( " No project given. Use `--project PATH_TO_FSPROJ`." )
702
- None
703
- | _ :: _, Some _ ->
745
+ match fscArgs with
746
+ | Some _ when projOpts |> List.isEmpty |> not ->
704
747
logger.LogError( " `--project` and `--fsc-args` cannot be combined." )
705
- exit 1
706
- | [], Some fscArgs ->
748
+ exit ( int ExitErrorCodes.ProjectAndFscArgs)
749
+ | Some _ when scripts |> List.isEmpty |> not ->
750
+ logger.LogError( " `--script` and `--fsc-args` cannot be combined." )
751
+ exit ( int ExitErrorCodes.ProjectAndFscArgs)
752
+ | Some fscArgs ->
707
753
runFscArgs client fscArgs exclInclFiles severityMapping
708
754
|> Async.RunSynchronously
709
755
|> Some
710
- | projects, None ->
711
- for projPath in projects do
712
- if not ( File.Exists( projPath)) then
713
- logger.LogError( " Invalid `--project` argument. File does not exist: '{projPath}'" , projPath)
714
- exit 1
715
-
716
- async {
717
- let! loadedProjects = loadProjects toolsPath properties projects
718
-
719
- return !
720
- loadedProjects
721
- |> List.map ( fun ( projPath : FSharpProjectOptions ) ->
722
- runProject client projPath exclInclFiles severityMapping
756
+ | None ->
757
+ match projOpts, scripts with
758
+ | [], [] ->
759
+ logger.LogError( " No projects or scripts were specified. Use `--project` or `--script` to specify them." )
760
+ exit ( int ExitErrorCodes.EmptyFscArgs)
761
+ | projects, scripts ->
762
+
763
+ for script in scripts do
764
+ if not ( File.Exists( script)) then
765
+ logger.LogError( " Invalid `--script` argument. File does not exist: '{script}'" , script)
766
+ exit ( int ExitErrorCodes.InvalidProjectArguments)
767
+
768
+ let scriptOptions =
769
+ scripts
770
+ |> List.map( fun script -> async {
771
+ let! fileContent = File.ReadAllTextAsync script |> Async.AwaitTask
772
+ let sourceText = SourceText.ofString fileContent
773
+ // GetProjectOptionsFromScript cannot be run in parallel, it is not thread-safe.
774
+ let! options , diagnostics = fcs.GetProjectOptionsFromScript( script, sourceText)
775
+ if not ( List.isEmpty diagnostics) then
776
+ diagnostics
777
+ |> List.iter ( fun d ->
778
+ logger.LogError(
779
+ " Script {0} has a diagnostic: {1} at {2}" ,
780
+ script,
781
+ d.Message,
782
+ d.Range
783
+ )
784
+ )
785
+ return options
786
+ }
723
787
)
724
- |> Async.Parallel
725
- }
726
- |> Async.RunSynchronously
727
- |> List.concat
728
- |> Some
788
+ |> Async.Sequential
789
+
790
+ for projPath in projects do
791
+ if not ( File.Exists( projPath)) then
792
+ logger.LogError( " Invalid `--project` argument. File does not exist: '{projPath}'" , projPath)
793
+ exit ( int ExitErrorCodes.InvalidProjectArguments)
794
+ async {
795
+ let! scriptOptions = scriptOptions |> Async.StartChild
796
+ let! loadedProjects = loadProjects toolsPath properties projects |> Async.StartChild
797
+ let! loadedProjects = loadedProjects
798
+ let! scriptOptions = scriptOptions
799
+
800
+ let loadedProjects = Array.toList scriptOptions @ loadedProjects
801
+
802
+ return !
803
+ loadedProjects
804
+ |> List.map ( fun ( projPath : FSharpProjectOptions ) ->
805
+ runProject client projPath exclInclFiles severityMapping
806
+ )
807
+ |> Async.Parallel
808
+ }
809
+ |> Async.RunSynchronously
810
+ |> List.concat
811
+ |> Some
729
812
730
813
match results with
731
- | None -> - 1
814
+ | None -> int ExitErrorCodes.NoAnalyzersFound
732
815
| Some results ->
733
816
let results , hasError =
734
817
match Result.allOkOrError results with
@@ -762,8 +845,8 @@ let main argv =
762
845
failedAssemblies
763
846
)
764
847
765
- exit - 3
848
+ exit ( int ExitErrorCodes.FailedAssemblyLoading )
766
849
767
- if check then - 2
768
- elif hasError then - 4
769
- else 0
850
+ if check then ( int ExitErrorCodes.AnalyzerFoundError )
851
+ elif hasError then ( int ExitErrorCodes.AnalysisAborted )
852
+ else ( int ExitErrorCodes.Success )
0 commit comments