diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c6abec..793db27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Changed * [Add missing TAST walkers](https://github.com/ionide/FSharp.Analyzers.SDK/pull/185) (thanks @dawedawe!) +* [Add support for --include-analyzers to ignore all others](https://github.com/ionide/FSharp.Analyzers.SDK/pull/194) (thanks @dawedawe!) ## [0.22.0] - 2023-12-19 diff --git a/Directory.Packages.props b/Directory.Packages.props index 5aa4b90..2bf8274 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,9 +19,9 @@ - - - + + + diff --git a/src/FSharp.Analyzers.Cli/Program.fs b/src/FSharp.Analyzers.Cli/Program.fs index 3af739d..8b31207 100644 --- a/src/FSharp.Analyzers.Cli/Program.fs +++ b/src/FSharp.Analyzers.Cli/Program.fs @@ -28,6 +28,7 @@ type Arguments = | [] Treat_As_Error of string list | [] Ignore_Files of string list | [] Exclude_Analyzer of string list + | [] Include_Analyzer of string list | [] Report of string | [] FSC_Args of string | [] Code_Root of string @@ -53,6 +54,8 @@ type Arguments = "List of analyzer codes that should be treated as severity Error by the tool. Regardless of the original severity." | Ignore_Files _ -> "Source files that shouldn't be processed." | Exclude_Analyzer _ -> "The names of analyzers that should not be executed." + | Include_Analyzer _ -> + "The names of analyzers that should exclusively be executed while all others are ignored. Takes precedence over --exclude-analyzer." | 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]." @@ -531,7 +534,19 @@ let main argv = logger.LogInformation("Loading analyzers from {0}", (String.concat ", " analyzersPaths)) - let excludeAnalyzers = results.GetResult(<@ Exclude_Analyzer @>, []) + let includeExclude = + let excludeAnalyzers = results.GetResult(<@ Exclude_Analyzer @>, []) + let includeAnalyzers = results.GetResult(<@ Include_Analyzer @>, []) + + match excludeAnalyzers, includeAnalyzers with + | e, [] -> Exclude(Set.ofList e) + | [], i -> Include(Set.ofList i) + | i, _e -> + logger.LogWarning( + "--exclude-analyzers and --include-analyzers are mutually exclusive, ignoring --exclude-analyzers" + ) + + Include(Set.ofList i) AssemblyLoadContext.Default.add_Resolving (fun _ctx assemblyName -> if assemblyName.Name <> "FSharp.Core" then @@ -548,13 +563,12 @@ let main argv = exit 1 ) - let client = - Client(logger, Set.ofList excludeAnalyzers) + let client = Client(logger) let dlls, analyzers = ((0, 0), analyzersPaths) ||> List.fold (fun (accDlls, accAnalyzers) analyzersPath -> - let dlls, analyzers = client.LoadAnalyzers analyzersPath + let dlls, analyzers = client.LoadAnalyzers(analyzersPath, includeExclude) (accDlls + dlls), (accAnalyzers + analyzers) ) diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs index 3bf32b9..fb04e73 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs @@ -127,17 +127,19 @@ module Client = |> Seq.choose (analyzerFromMember<'TAnalyzerAttribute, 'TContext> path) |> Seq.toList -type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TContext :> Context> - (logger: ILogger, excludedAnalyzers: string Set) - = +type ExcludeInclude = + | Exclude of string Set + | Include of string Set + +type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TContext :> Context>(logger: ILogger) = do TASTCollecting.logger <- logger let registeredAnalyzers = ConcurrentDictionary list>() - new() = Client(Abstractions.NullLogger.Instance, Set.empty) + new() = Client(Abstractions.NullLogger.Instance) - member x.LoadAnalyzers(dir: string) : int * int = + member x.LoadAnalyzers(dir: string, ?excludeInclude: ExcludeInclude) : int * int = if Directory.Exists dir then let analyzerAssemblies = let regex = Regex(@".*test.*\.dll$") @@ -184,7 +186,7 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC true else logger.LogError( - "Trying to load {0} which was built using SDK version {1}. Expect {2} instead. Assembly will be skipped.", + "Trying to load {Name} which was built using SDK version {Version}. Expect {SdkVersion} instead. Assembly will be skipped.", name, version, Utils.currentFSharpAnalyzersSDKVersion @@ -197,16 +199,30 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC assembly.GetExportedTypes() |> Seq.collect (Client.analyzersFromType<'TAttribute, 'TContext> path) |> Seq.filter (fun registeredAnalyzer -> - let shouldExclude = excludedAnalyzers.Contains(registeredAnalyzer.Name) - - if shouldExclude then - logger.LogInformation( - "Excluding {0} from {1}", - registeredAnalyzer.Name, - assembly.FullName - ) - - not shouldExclude + match excludeInclude with + | Some(Exclude excluded) -> + let shouldExclude = excluded.Contains(registeredAnalyzer.Name) + + if shouldExclude then + logger.LogInformation( + "Excluding {Name} from {FullName}", + registeredAnalyzer.Name, + assembly.FullName + ) + + not shouldExclude + | Some(Include included) -> + let shouldInclude = included.Contains(registeredAnalyzer.Name) + + if shouldInclude then + logger.LogInformation( + "Including {Name} from {FullName}", + registeredAnalyzer.Name, + assembly.FullName + ) + + shouldInclude + | None -> true ) |> Seq.toList diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi index 9a52a04..b5e5ad7 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi @@ -8,15 +8,22 @@ type AnalysisResult = Output: Result } +type ExcludeInclude = + /// Analyzers in this set should be ignored. + | Exclude of string Set + /// Analyzers in this set should be used exclusively, while all others are ignored. + | Include of string Set + type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TContext :> Context> = - new: logger: ILogger * excludedAnalyzers: string Set -> Client<'TAttribute, 'TContext> + new: logger: ILogger -> Client<'TAttribute, 'TContext> new: unit -> Client<'TAttribute, 'TContext> /// /// Loads into private state any analyzers defined in any assembly /// matching `*Analyzer*.dll` in given directory (and any subdirectories) + /// Analyzers are filtered according to the ExcludeInclude set, if provided. /// /// number of found dlls matching `*Analyzer*.dll` and number of registered analyzers - member LoadAnalyzers: dir: string -> int * int + member LoadAnalyzers: dir: string * ?excludeInclude: ExcludeInclude -> int * int /// Runs all registered analyzers for given context (file). /// list of messages. Ignores errors from the analyzers member RunAnalyzers: ctx: 'TContext -> Async