Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support include analyzers and make life simpler for editor clients #194

Merged
merged 5 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
<PackageVersion Include="Microsoft.Build.Locator" Version="1.4.1" />
<!-- Need to update Directory.Build.props DotNet.ReproducibleBuilds.Isolated version when updating this-->
<PackageVersion Include="DotNet.ReproducibleBuilds" Version="1.1.1" PrivateAssets="All" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="6.0.0" />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mea culpa, should have used that version from the start.

<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="MSBuild.StructuredLogger" Version="2.1.815" />
<PackageVersion Include="NUnit" Version="3.13.3" />
Expand Down
23 changes: 19 additions & 4 deletions src/FSharp.Analyzers.Cli/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type Arguments =
| [<Unique>] Treat_As_Error of string list
| [<Unique>] Ignore_Files of string list
| [<Unique>] Exclude_Analyzer of string list
| [<Unique>] Include_Analyzer of string list
| [<Unique>] Report of string
| [<Unique>] FSC_Args of string
| [<Unique>] Code_Root of string
Expand All @@ -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]."
Expand Down Expand Up @@ -531,7 +534,20 @@ 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
| [], [] -> None
| e, [] -> Some(Exclude(Set.ofList e))
| [], i -> Some(Include(Set.ofList i))
| i, _e ->
logger.LogWarning(
"--exclude-analyzers and --include-analyzers are mutually exclusive, ignoring --exclude-analyzers"
)

Some(Include(Set.ofList i))

AssemblyLoadContext.Default.add_Resolving (fun _ctx assemblyName ->
if assemblyName.Name <> "FSharp.Core" then
Expand All @@ -548,13 +564,12 @@ let main argv =
exit 1
)

let client =
Client<CliAnalyzerAttribute, CliContext>(logger, Set.ofList excludeAnalyzers)
let client = Client<CliAnalyzerAttribute, CliContext>(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)
)

Expand Down
48 changes: 33 additions & 15 deletions src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs
Original file line number Diff line number Diff line change
Expand Up @@ -127,17 +127,21 @@ 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<string, Client.RegisteredAnalyzer<'TContext> list>()

new() = Client(Abstractions.NullLogger.Instance, Set.empty)
new() = Client(Abstractions.NullLogger.Instance)

member x.LoadAnalyzers(dir: string) : int * int = x.LoadAnalyzers(dir, None)

member x.LoadAnalyzers(dir: string) : int * int =
member x.LoadAnalyzers(dir: string, excludeInclude: ExcludeInclude option) : int * int =
dawedawe marked this conversation as resolved.
Show resolved Hide resolved
if Directory.Exists dir then
let analyzerAssemblies =
let regex = Regex(@".*test.*\.dll$")
Expand Down Expand Up @@ -197,16 +201,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 {0} from {1}",
dawedawe marked this conversation as resolved.
Show resolved Hide resolved
registeredAnalyzer.Name,
assembly.FullName
)

not shouldExclude
| Some(Include included) ->
let shouldInclude = included.Contains(registeredAnalyzer.Name)

if shouldInclude then
logger.LogInformation(
"Including {0} from {1}",
registeredAnalyzer.Name,
assembly.FullName
)

shouldInclude
| None -> true
)
|> Seq.toList

Expand Down
15 changes: 14 additions & 1 deletion src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,28 @@ type AnalysisResult =
Output: Result<Message list, exn>
}

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>
/// <summary>
/// Loads into private state any analyzers defined in any assembly
/// matching `*Analyzer*.dll` in given directory (and any subdirectories)
/// </summary>
/// <returns>number of found dlls matching `*Analyzer*.dll` and number of registered analyzers</returns>
member LoadAnalyzers: dir: string -> int * int
/// <summary>
/// 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 given ExcludeInclude set.
/// </summary>
/// <returns>number of found dlls matching `*Analyzer*.dll` and number of registered analyzers</returns>
member LoadAnalyzers: dir: string * excludeInclude: ExcludeInclude option -> int * int
/// <summary>Runs all registered analyzers for given context (file).</summary>
/// <returns>list of messages. Ignores errors from the analyzers</returns>
member RunAnalyzers: ctx: 'TContext -> Async<AnalyzerMessage list>
Expand Down