Skip to content

Commit cbb751d

Browse files
authored
Merge pull request #237 from ionide/234-support-script-files
234 support script files
2 parents 7bb647d + dd7f65e commit cbb751d

File tree

5 files changed

+141
-43
lines changed

5 files changed

+141
-43
lines changed

Directory.Packages.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<PackageVersion Include="FSharp.Compiler.Service" Version="[43.9.300]" />
1111
<PackageVersion Include="Ionide.KeepAChangelog.Tasks" Version="0.1.8" PrivateAssets="all" />
1212
<PackageVersion Include="McMaster.NETCore.Plugins" Version="1.4.0" />
13-
<PackageVersion Include="Argu" Version="6.1.1" />
13+
<PackageVersion Include="Argu" Version="6.2.5" />
1414
<PackageVersion Include="Glob" Version="1.1.9" />
1515
<PackageVersion Include="Ionide.ProjInfo.ProjectSystem" Version="0.71.0" />
1616
<PackageVersion Include="Microsoft.Build" Version="$(MsBuildPackageVersion)" ExcludeAssets="runtime" />

docs/index.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,19 @@ F# analyzers are live, real-time, project based plugins that enables to diagnose
1414
1. `dotnet build -c Release`
1515
2. Run the console application:
1616

17+
Against a project:
18+
19+
```shell
20+
dotnet run --project src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj -- --project ./samples/OptionAnalyzer/OptionAnalyzer.fsproj --analyzers-path ./artifacts/bin/OptionAnalyzer/release --verbosity d
21+
```
22+
23+
Against a script:
24+
1725
```shell
18-
dotnet run --project src\FSharp.Analyzers.Cli\FSharp.Analyzers.Cli.fsproj -- --project ./samples/OptionAnalyzer/OptionAnalyzer.fsproj --analyzers-path ./samples/OptionAnalyzer/bin/Release --verbosity d
26+
dotnet run --project src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj -- --script ./samples/BadOptionUsage.fsx --analyzers-path ./artifacts/bin/OptionAnalyzer/release --verbosity d
1927
```
2028

29+
2130
You can also set up a run configuration of FSharp.Analyzers.Cli in your favorite IDE using similar arguments. This also allows you to debug FSharp.Analyzers.Cli.
2231

2332
## Using Analyzers

global.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"sdk": {
3-
"version": "9.0.101"
3+
"version": "9.0.101",
4+
"rollForward": "latestMinor"
45
}
56
}

samples/BadOptionUsage.fsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
3+
let value = Some 42
4+
5+
printfn "The value is: %d" value.Value // This will cause a warning from the OptionAnalyzer

src/FSharp.Analyzers.Cli/Program.fs

Lines changed: 123 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,29 @@ open Ionide.ProjInfo
1515
open FSharp.Analyzers.Cli
1616
open 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+
1838
type 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

501523
let getProperties (results: ParseResults<Arguments>) =
@@ -533,6 +555,7 @@ let getProperties (results: ParseResults<Arguments>) =
533555
| _ -> ()
534556
]
535557

558+
536559
[<EntryPoint>]
537560
let 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

Comments
 (0)