Skip to content

Commit 0da7635

Browse files
committed
Refactor error codes and improve script argument handling in CLI
1 parent 575cb83 commit 0da7635

File tree

1 file changed

+77
-45
lines changed

1 file changed

+77
-45
lines changed

src/FSharp.Analyzers.Cli/Program.fs

Lines changed: 77 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type ExitErrorCodes =
3131
| FscArgsCombinedWithMsBuildProperties = 17
3232
| FSharpCoreAssemblyLoadFailed = 18
3333
| ProjectAndFscArgs = 19
34-
| InvalidGlob = 20
34+
| InvalidScriptArguments = 20
3535
| InvalidProjectArguments = 21
3636

3737
type Arguments =
@@ -60,8 +60,8 @@ type Arguments =
6060
interface IArgParserTemplate with
6161
member s.Usage =
6262
match s with
63-
| Project _ -> "List of paths to your .fsproj file."
64-
| Script _ -> "List of paths to your .fsx file. Supports globs."
63+
| Project _ -> "List of paths to your .fsproj file. Cannot be combined with `--fsc-args`."
64+
| Script _ -> "List of paths to your .fsx file. Supports globs. Cannot be combined with `--fsc-args`."
6565
| Analyzers_Path _ ->
6666
"List of path to a folder where your analyzers are located. This will search recursively."
6767
| Property _ -> "A key=value pair of an MSBuild property."
@@ -618,19 +618,26 @@ let main argv =
618618
let codeRoot = results.TryGetResult <@ Code_Root @>
619619
let cwd = Directory.GetCurrentDirectory() |> DirectoryInfo
620620

621+
let beginsWithCurrentPath (path: string) =
622+
path.StartsWith("./") || path.StartsWith(".\\")
623+
621624
let scripts =
622625
results.GetResult(<@ Script @>, [])
623626
|> List.collect(fun scriptGlob ->
624-
if Path.IsPathRooted scriptGlob then
625-
[scriptGlob]
626-
else
627-
// TODO: need to discuss if this should fail fast or not
628-
// also other path arguments take a `./` while this oen does not and feels inconsistent
629-
if scriptGlob.StartsWith('.') then
630-
logger.LogCritical("Starting a glob with \".\" won't find files. You should remove this from the glob: {scriptGlob}", scriptGlob)
631-
exit (int ExitErrorCodes.InvalidGlob)
632-
cwd.GlobFiles(scriptGlob) |> Seq.toList |> List.map (fun file -> file.FullName)
633-
627+
let root, scriptGlob =
628+
if Path.IsPathRooted scriptGlob then
629+
// Glob can't handle absolute paths, so we need to make sure the scriptGlob is a relative path
630+
let root = Path.GetPathRoot scriptGlob
631+
let glob = scriptGlob.Substring(root.Length)
632+
DirectoryInfo root, glob
633+
else if beginsWithCurrentPath scriptGlob then
634+
// Glob can't handle relative paths starting with "./" or ".\", so we need trim it
635+
let relativeGlob = scriptGlob.Substring(2) // remove "./" or ".\"
636+
cwd, relativeGlob
637+
else
638+
cwd, scriptGlob
639+
640+
root.GlobFiles scriptGlob |> Seq.map (fun file -> file.FullName) |> Seq.toList
634641
)
635642

636643
let exclInclFiles =
@@ -735,47 +742,72 @@ let main argv =
735742
None
736743
else
737744
match fscArgs with
738-
// | None ->
739-
// logger.LogError("No project given. Use `--project PATH_TO_FSPROJ`.")
740-
// None
741745
| Some _ when projOpts |> List.isEmpty |> not ->
742746
logger.LogError("`--project` and `--fsc-args` cannot be combined.")
743747
exit (int ExitErrorCodes.ProjectAndFscArgs)
748+
| Some _ when scripts |> List.isEmpty |> not ->
749+
logger.LogError("`--script` and `--fsc-args` cannot be combined.")
750+
exit (int ExitErrorCodes.ProjectAndFscArgs)
744751
| Some fscArgs ->
745752
runFscArgs client fscArgs exclInclFiles severityMapping
746753
|> Async.RunSynchronously
747754
|> Some
748755
| None ->
749-
let scriptOptions =
750-
scripts
751-
|> List.map(fun script ->
752-
let fileContent = File.ReadAllText script
753-
let sourceText = SourceText.ofString fileContent
754-
let (options, _diagnostics) = fcs.GetProjectOptionsFromScript(script, sourceText) |> Async.RunSynchronously
755-
756-
options
757-
)
758-
759-
let projects = projOpts
760-
for projPath in projects do
761-
if not (File.Exists(projPath)) then
762-
logger.LogError("Invalid `--project` argument. File does not exist: '{projPath}'", projPath)
763-
exit (int ExitErrorCodes.InvalidProjectArguments)
764-
async {
765-
let! loadedProjects = loadProjects toolsPath properties projects
766-
767-
let loadedProjects = scriptOptions @ loadedProjects
768-
769-
return!
770-
loadedProjects
771-
|> List.map (fun (projPath: FSharpProjectOptions) ->
772-
runProject client projPath exclInclFiles severityMapping
756+
match projOpts, scripts with
757+
| [], [] ->
758+
logger.LogError("No projects or scripts were specified. Use `--project` or `--script` to specify them.")
759+
exit (int ExitErrorCodes.EmptyFscArgs)
760+
| projects, scripts ->
761+
762+
for script in scripts do
763+
if not (File.Exists(script)) then
764+
logger.LogError("Invalid `--script` argument. File does not exist: '{script}'", script)
765+
exit (int ExitErrorCodes.InvalidProjectArguments)
766+
767+
let scriptOptions =
768+
scripts
769+
|> List.map(fun script -> async {
770+
let fileContent = File.ReadAllText script
771+
let sourceText = SourceText.ofString fileContent
772+
// GetProjectOptionsFromScript cannot be run in parallel, it is not thread-safe.
773+
let! options, diagnostics = fcs.GetProjectOptionsFromScript(script, sourceText)
774+
if not (List.isEmpty diagnostics) then
775+
diagnostics
776+
|> List.iter (fun d ->
777+
logger.LogError(
778+
"Script {0} has a diagnostic: {1} at {2}",
779+
script,
780+
d.Message,
781+
d.Range
782+
)
783+
)
784+
return options
785+
}
773786
)
774-
|> Async.Parallel
775-
}
776-
|> Async.RunSynchronously
777-
|> List.concat
778-
|> Some
787+
|> Async.Sequential
788+
789+
for projPath in projects do
790+
if not (File.Exists(projPath)) then
791+
logger.LogError("Invalid `--project` argument. File does not exist: '{projPath}'", projPath)
792+
exit (int ExitErrorCodes.InvalidProjectArguments)
793+
async {
794+
let! scriptOptions = scriptOptions |> Async.StartChild
795+
let! loadedProjects = loadProjects toolsPath properties projects |> Async.StartChild
796+
let! loadedProjects = loadedProjects
797+
let! scriptOptions = scriptOptions
798+
799+
let loadedProjects = Array.toList scriptOptions @ loadedProjects
800+
801+
return!
802+
loadedProjects
803+
|> List.map (fun (projPath: FSharpProjectOptions) ->
804+
runProject client projPath exclInclFiles severityMapping
805+
)
806+
|> Async.Parallel
807+
}
808+
|> Async.RunSynchronously
809+
|> List.concat
810+
|> Some
779811

780812
match results with
781813
| None -> -1

0 commit comments

Comments
 (0)