Skip to content

Commit

Permalink
Merge pull request #114 from nojaf/fix-112
Browse files Browse the repository at this point in the history
Exclude analyzers
  • Loading branch information
nojaf authored Oct 10, 2023
2 parents 40f3fdd + 580e31c commit a3654c8
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 16 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
* [Exclude analyzers](https://github.com/ionide/FSharp.Analyzers.SDK/issues/112) (thanks @nojaf)

## [0.14.1] - 2023-09-26

### Changed
Expand Down
2 changes: 1 addition & 1 deletion docs/content/Programmatic access.fsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The `Client` needs to know what type of analyzer you intend to load: *console* o
open FSharp.Analyzers.SDK

let client = Client<CliAnalyzerAttribute, CliContext>()
let countLoaded = client.LoadAnalyzers ignore @"C:\MyAnalyzers"
let countLoaded = client.LoadAnalyzers @"C:\MyAnalyzers"
let ctx = Unchecked.defaultof<CliContext> // Construct your context...
client.RunAnalyzers(ctx)

Expand Down
27 changes: 24 additions & 3 deletions src/FSharp.Analyzers.Cli/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@ type Arguments =
| Analyzers_Path of string
| Fail_On_Warnings of string list
| Ignore_Files of string list
| Exclude_Analyzer of string list
| Verbose

interface IArgParserTemplate with
member s.Usage = ""
member s.Usage =
match s with
| Project _ -> "Path to your .fsproj file."
| Analyzers_Path _ -> "Path to a folder where your analyzers are located."
| Fail_On_Warnings _ ->
"List of analyzer codes that should trigger tool failures in the presence of warnings."
| Ignore_Files _ -> "Source files that shouldn't be processed."
| Exclude_Analyzer _ -> "The names of analyzers that should not be executed."
| Verbose -> "Verbose logging."

let mutable verbose = false

Expand Down Expand Up @@ -169,9 +178,21 @@ let main argv =

printInfo "Loading analyzers from %s" analyzersPath

let client = Client<CliAnalyzerAttribute, CliContext>()
let excludeAnalyzers = results.GetResult(<@ Exclude_Analyzer @>, [])

let dlls, analyzers = client.LoadAnalyzers (printError "%s") analyzersPath
let logger =
{ new Logger with
member _.Error msg = printError "%s" msg

member _.Verbose msg =
if verbose then
printInfo "%s" msg
}

let client =
Client<CliAnalyzerAttribute, CliContext>(logger, Set.ofList excludeAnalyzers)

let dlls, analyzers = client.LoadAnalyzers analyzersPath

printInfo "Registered %d analyzers from %d dlls" analyzers dlls

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,11 @@ let getContext (opts: FSharpProjectOptions) source =
Map.tryFind fileName files |> async.Return

let fcs = Utils.createFCS (Some documentSource)
let printError (s: string) = Console.WriteLine(s)
let pathToAnalyzerDlls = Path.GetFullPath(".")

let foundDlls, registeredAnalyzers =
let client = Client<CliAnalyzerAttribute, CliContext>()
client.LoadAnalyzers printError pathToAnalyzerDlls
client.LoadAnalyzers pathToAnalyzerDlls

if foundDlls = 0 then
failwith $"no Dlls found in {pathToAnalyzerDlls}"
Expand Down
47 changes: 38 additions & 9 deletions src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ module Client =
let hasExpectReturnType (t: Type) =
// t might be a System.RunTimeType as could have no FullName
if not (isNull t.FullName) then
t.FullName.StartsWith
"Microsoft.FSharp.Control.FSharpAsync`1[[Microsoft.FSharp.Collections.FSharpList`1[[FSharp.Analyzers.SDK.Message"
t.FullName.StartsWith(
"Microsoft.FSharp.Control.FSharpAsync`1[[Microsoft.FSharp.Collections.FSharpList`1[[FSharp.Analyzers.SDK.Message",
StringComparison.InvariantCulture
)
elif t.Name = "FSharpAsync`1" && t.GenericTypeArguments.Length = 1 then
let listType = t.GenericTypeArguments.[0]

Expand Down Expand Up @@ -96,19 +98,39 @@ module Client =
|> Seq.choose analyzerFromMember<'TAnalyzerAttribute, 'TContext>
|> Seq.toList

type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TContext :> Context>() =
[<Interface>]
type Logger =
abstract member Error: string -> unit
abstract member Verbose: string -> unit

type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TContext :> Context>
(logger: Logger, excludedAnalyzers: string Set)
=
let registeredAnalyzers =
ConcurrentDictionary<string, (string * Analyzer<'TContext>) list>()

member x.LoadAnalyzers (printError: string -> unit) (dir: string) : int * int =
new() =
Client(
{ new Logger with
member this.Error _ = ()
member this.Verbose _ = ()
},
Set.empty
)

member x.LoadAnalyzers(dir: string) : int * int =
if Directory.Exists dir then
let analyzerAssemblies =
let regex = Regex(@".*test.*\.dll$")

Directory.GetFiles(dir, "*Analyzer*.dll", SearchOption.AllDirectories)
|> Array.filter (fun a ->
let s = Path.GetFileName(a).ToLowerInvariant()
not (s.EndsWith("fsharp.analyzers.sdk.dll") || regex.IsMatch(s))
let s = Path.GetFileName(a)

not (
s.EndsWith("fsharp.analyzers.sdk.dll", StringComparison.InvariantCultureIgnoreCase)
|| regex.IsMatch(s)
)
)
|> Array.choose (fun analyzerDll ->
try
Expand Down Expand Up @@ -139,7 +161,7 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC
if version = Utils.currentFSharpAnalyzersSDKVersion then
true
else
printError
logger.Error
$"Trying to load %s{name} which was built using SDK version %A{version}. Expect %A{Utils.currentFSharpAnalyzersSDKVersion} instead. Assembly will be skipped."

false
Expand All @@ -148,13 +170,20 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC
let analyzers =
assembly.GetExportedTypes()
|> Seq.collect Client.analyzersFromType<'TAttribute, 'TContext>
|> Seq.filter (fun (analyzerName, _) ->
let shouldExclude = excludedAnalyzers.Contains(analyzerName)

if shouldExclude then
logger.Verbose $"Excluding %s{analyzerName} from %s{assembly.FullName}"

not shouldExclude
)
|> Seq.toList

path, analyzers
)

for path, analyzers in analyzers do
let analyzers = Seq.toList analyzers

registeredAnalyzers.AddOrUpdate(path, analyzers, (fun _ _ -> analyzers))
|> ignore

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

[<Interface>]
type Logger =
abstract member Error: string -> unit
abstract member Verbose: string -> unit

type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TContext :> Context> =
new: logger: Logger * excludedAnalyzers: string Set -> 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: printError: (string -> unit) -> dir: string -> int * int
member LoadAnalyzers: dir: string -> 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<Message list>
Expand Down

0 comments on commit a3654c8

Please sign in to comment.