@@ -15,8 +15,29 @@ open Ionide.ProjInfo
1515open FSharp.Analyzers .Cli
1616open FSharp.Analyzers .Cli .CustomLogging
1717
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+
1838type Arguments =
1939 | Project of string list
40+ | Script of string list
2041 | Analyzers_ Path of string list
2142 | [<EqualsAssignment; AltCommandLine( " -p:" ); AltCommandLine( " -p" ) >] Property of string * string
2243 | [<Unique; AltCommandLine( " -c" ) >] Configuration of string
@@ -40,7 +61,8 @@ type Arguments =
4061 interface IArgParserTemplate with
4162 member s.Usage =
4263 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`."
4466 | Analyzers_ Path _ ->
4567 " List of path to a folder where your analyzers are located. This will search recursively."
4668 | Property _ -> " A key=value pair of an MSBuild property."
@@ -153,7 +175,7 @@ let loadProjects toolsPath properties (projPaths: string list) =
153175
154176 if Seq.length failedLoads > 0 then
155177 logger.LogError( " Failed to load project '{0}'" , failedLoads)
156- exit 1
178+ exit ( int ExitErrorCodes.FailedToLoadProject )
157179
158180 let loaded =
159181 FCS.mapManyOptions projectOptions
@@ -235,7 +257,7 @@ let runFscArgs
235257 =
236258 if String.IsNullOrWhiteSpace fscArgs then
237259 logger.LogError( " Empty --fsc-args were passed!" )
238- exit 1
260+ exit ( int ExitErrorCodes.EmptyFscArgs )
239261 else
240262
241263 let fscArgs = fscArgs.Split( ';' , StringSplitOptions.RemoveEmptyEntries)
@@ -480,7 +502,7 @@ let expandMultiProperties (properties: (string * string) list) =
480502 match pair with
481503 | [| k; v |] when String.IsNullOrWhiteSpace( v) ->
482504 logger.LogError( " Missing property value for '{0}'" , k)
483- exit 1
505+ exit ( int ExitErrorCodes.MissingPropertyValue )
484506 | [| k; v |] -> yield ( k, v)
485507 | _ -> ()
486508
@@ -492,10 +514,10 @@ let validateRuntimeOsArchCombination (runtime, arch, os) =
492514 match runtime, os, arch with
493515 | Some _, Some _, _ ->
494516 logger.LogError( " Specifying both the `-r|--runtime` and `-os` options is not supported." )
495- exit 1
517+ exit ( int ExitErrorCodes.RuntimeAndOsOptions )
496518 | Some _, _, Some _ ->
497519 logger.LogError( " Specifying both the `-r|--runtime` and `-a|--arch` options is not supported." )
498- exit 1
520+ exit ( int ExitErrorCodes.RuntimeAndArchOptions )
499521 | _ -> ()
500522
501523let getProperties ( results : ParseResults < Arguments >) =
@@ -533,6 +555,7 @@ let getProperties (results: ParseResults<Arguments>) =
533555 | _ -> ()
534556 ]
535557
558+
536559[<EntryPoint>]
537560let main argv =
538561 let toolsPath = Init.init ( DirectoryInfo Environment.CurrentDirectory) None
@@ -554,7 +577,7 @@ let main argv =
554577 use factory = LoggerFactory.Create( fun b -> b.AddConsole() |> ignore)
555578 let logger = factory.CreateLogger( " " )
556579 logger.LogError( " unknown verbosity level given {0}" , x)
557- exit 1
580+ exit ( int ExitErrorCodes.UnknownLoggerVerbosity )
558581
559582 use factory =
560583 LoggerFactory.Create( fun builder ->
@@ -588,12 +611,35 @@ let main argv =
588611 if not ( severityMapping.IsValid()) then
589612 logger.LogError( " An analyzer code may only be listed once in the <treat-as-severity> arguments." )
590613
591- exit 1
614+ exit ( int ExitErrorCodes.AnalyzerListedMultipleTimesInTreatAsSeverity )
592615
593616 let projOpts = results.GetResults <@ Project @> |> List.concat
594617 let fscArgs = results.TryGetResult <@ FSC_ Args @>
595618 let report = results.TryGetResult <@ Report @>
596619 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+ )
597643
598644 let exclInclFiles =
599645 let excludeFiles = results.GetResult(<@ Exclude_ Files @>, [])
@@ -616,7 +662,7 @@ let main argv =
616662
617663 if Option.isSome fscArgs && not properties.IsEmpty then
618664 logger.LogError( " fsc-args can't be combined with MSBuild properties." )
619- exit 1
665+ exit ( int ExitErrorCodes.FscArgsCombinedWithMsBuildProperties )
620666
621667 properties
622668 |> List.iter ( fun ( k , v ) -> logger.LogInformation( " Property {0}={1}" , k, v))
@@ -675,7 +721,7 @@ let main argv =
675721 """
676722
677723 logger.LogError( msg)
678- exit 1
724+ exit ( int ExitErrorCodes.FSharpCoreAssemblyLoadFailed )
679725 )
680726
681727 let client = Client< CliAnalyzerAttribute, CliContext>( logger)
@@ -696,39 +742,76 @@ let main argv =
696742 if analyzers = 0 then
697743 None
698744 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 ->
704747 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 ->
707753 runFscArgs client fscArgs exclInclFiles severityMapping
708754 |> Async.RunSynchronously
709755 |> 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+ }
723787 )
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
729812
730813 match results with
731- | None -> - 1
814+ | None -> int ExitErrorCodes.NoAnalyzersFound
732815 | Some results ->
733816 let results , hasError =
734817 match Result.allOkOrError results with
@@ -762,8 +845,8 @@ let main argv =
762845 failedAssemblies
763846 )
764847
765- exit - 3
848+ exit ( int ExitErrorCodes.FailedAssemblyLoading )
766849
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