Skip to content

Commit

Permalink
Include analyzer details in AnalyzerMessage.
Browse files Browse the repository at this point in the history
  • Loading branch information
nojaf committed Oct 12, 2023
1 parent 0990919 commit d7ed1de
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 28 deletions.
22 changes: 15 additions & 7 deletions src/FSharp.Analyzers.Cli/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,17 @@ let runProject (client: Client<CliAnalyzerAttribute, CliContext>) toolsPath proj
]
}

let printMessages failOnWarnings (msgs: Message list) =
let printMessages failOnWarnings (msgs: AnalyzerMessage list) =
if verbose then
printfn ""

if verbose && List.isEmpty msgs then
printfn "No messages found from the analyzer(s)"

msgs
|> Seq.iter (fun m ->
|> Seq.iter (fun analyzerMessage ->
let m = analyzerMessage.Message

let color =
match m.Severity with
| Error -> ConsoleColor.Red
Expand Down Expand Up @@ -159,7 +161,9 @@ let encodeSeverity =
| Severity.Warning -> Encode.string "warning"
| Severity.Error -> Encode.string "error"

let encodeMessage (message: Message) =
let encodeMessage (analyzerMessage: AnalyzerMessage) =
let message = analyzerMessage.Message

Encode.object
[
"type", Encode.string message.Type
Expand All @@ -168,6 +172,8 @@ let encodeMessage (message: Message) =
"severity", encodeSeverity message.Severity
"range", encodeRange message.Range
"fileName", Encode.string message.Range.FileName
"assembly", Encode.string analyzerMessage.AssemblyPath
"analyzerName", Encode.string analyzerMessage.Name
]

let writeReport results report =
Expand All @@ -189,15 +195,17 @@ let writeReport results report =
let details = if not verbose then "" else $" %s{ex.Message}"
printfn $"Could not write report json to %s{report}%s{details}"

let calculateExitCode failOnWarnings (msgs: Message list option) : int =
let calculateExitCode failOnWarnings (msgs: AnalyzerMessage list option) : int =
match msgs with
| None -> -1
| Some msgs ->
let check =
msgs
|> List.exists (fun n ->
n.Severity = Error
|| (n.Severity = Warning && failOnWarnings |> List.contains n.Code)
|> List.exists (fun analyzerMessage ->
let message = analyzerMessage.Message

message.Severity = Error
|| (message.Severity = Warning && failOnWarnings |> List.contains message.Code)
)

if check then -2 else 0
Expand Down
72 changes: 54 additions & 18 deletions src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@ type AnalysisResult =

module Client =

type RegisteredAnalyzer<'TContext when 'TContext :> Context> =
{
AssemblyPath: string
Name: string
Analyzer: Analyzer<'TContext>
}

let isAnalyzer<'TAttribute when 'TAttribute :> AnalyzerAttribute> (mi: MemberInfo) =
mi.GetCustomAttributes true
|> Seq.tryFind (fun n -> n.GetType().Name = typeof<'TAttribute>.Name)
|> Option.map unbox<'TAttribute>

let analyzerFromMember<'TAnalyzerAttribute, 'TContext when 'TAnalyzerAttribute :> AnalyzerAttribute>
let analyzerFromMember<'TAnalyzerAttribute, 'TContext
when 'TAnalyzerAttribute :> AnalyzerAttribute and 'TContext :> Context>
(path: string)
(mi: MemberInfo)
: (string * Analyzer<'TContext>) option
: RegisteredAnalyzer<'TContext> option
=
let inline unboxAnalyzer v =
if isNull v then failwith "Analyzer is null" else unbox v
Expand Down Expand Up @@ -75,13 +84,28 @@ module Client =
match isAnalyzer<'TAnalyzerAttribute> mi with
| Some analyzerAttribute ->
match getAnalyzerFromMemberInfo mi with
| Some analyzer -> Some(analyzerAttribute.Name, analyzer)
| Some analyzer ->
let name =
if String.IsNullOrWhiteSpace analyzerAttribute.Name then
mi.Name
else
analyzerAttribute.Name

Some
{
AssemblyPath = path
Name = name
Analyzer = analyzer
}

| None -> None
| None -> None

let analyzersFromType<'TAnalyzerAttribute, 'TContext when 'TAnalyzerAttribute :> AnalyzerAttribute>
let analyzersFromType<'TAnalyzerAttribute, 'TContext
when 'TAnalyzerAttribute :> AnalyzerAttribute and 'TContext :> Context>
(path: string)
(t: Type)
: (string * Analyzer<'TContext>) list
: RegisteredAnalyzer<'TContext> list
=
let asMembers x = Seq.map (fun m -> m :> MemberInfo) x
let bindingFlags = BindingFlags.Public ||| BindingFlags.Static
Expand All @@ -95,7 +119,7 @@ module Client =
|> Seq.collect id

members
|> Seq.choose analyzerFromMember<'TAnalyzerAttribute, 'TContext>
|> Seq.choose (analyzerFromMember<'TAnalyzerAttribute, 'TContext> path)
|> Seq.toList

[<Interface>]
Expand All @@ -107,7 +131,7 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC
(logger: Logger, excludedAnalyzers: string Set)
=
let registeredAnalyzers =
ConcurrentDictionary<string, (string * Analyzer<'TContext>) list>()
ConcurrentDictionary<string, Client.RegisteredAnalyzer<'TContext> list>()

new() =
Client(
Expand Down Expand Up @@ -169,12 +193,12 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC
|> Array.map (fun (path, assembly) ->
let analyzers =
assembly.GetExportedTypes()
|> Seq.collect Client.analyzersFromType<'TAttribute, 'TContext>
|> Seq.filter (fun (analyzerName, _) ->
let shouldExclude = excludedAnalyzers.Contains(analyzerName)
|> Seq.collect (Client.analyzersFromType<'TAttribute, 'TContext> path)
|> Seq.filter (fun registeredAnalyzer ->
let shouldExclude = excludedAnalyzers.Contains(registeredAnalyzer.Name)

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

not shouldExclude
)
Expand All @@ -191,15 +215,27 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC
else
0, 0

member x.RunAnalyzers(ctx: 'TContext) : Async<Message list> =
member x.RunAnalyzers(ctx: 'TContext) : Async<AnalyzerMessage list> =
async {
let analyzers = registeredAnalyzers.Values |> Seq.collect id

let! messagesPerAnalyzer =
analyzers
|> Seq.map (fun (_analyzerName, analyzer) ->
|> Seq.map (fun registeredAnalyzer ->
try
analyzer ctx
async {
let! messages = registeredAnalyzer.Analyzer ctx

return
messages
|> List.map (fun message ->
{
Message = message
Name = registeredAnalyzer.Name
AssemblyPath = registeredAnalyzer.AssemblyPath
}
)
}
with error ->
async.Return []
)
Expand All @@ -218,20 +254,20 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC

let! results =
analyzers
|> Seq.map (fun (analyzerName, analyzer) ->
|> Seq.map (fun registeredAnalyzer ->
async {
try
let! result = analyzer ctx
let! result = registeredAnalyzer.Analyzer ctx

return
{
AnalyzerName = analyzerName
AnalyzerName = registeredAnalyzer.Name
Output = Result.Ok result
}
with error ->
return
{
AnalyzerName = analyzerName
AnalyzerName = registeredAnalyzer.Name
Output = Result.Error error
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC
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>
member RunAnalyzers: ctx: 'TContext -> Async<AnalyzerMessage list>
/// <summary>Runs all registered analyzers for given context (file).</summary>
/// <returns>list of results per analyzer which can either be messages or an exception.</returns>
member RunAnalyzersSafely: ctx: 'TContext -> Async<AnalysisResult list>
13 changes: 11 additions & 2 deletions src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,17 @@ type Message =

type Analyzer<'TContext> = 'TContext -> Async<Message list>

module Utils =
type AnalyzerMessage =
{
/// A message produced by the analyzer.
Message: Message
/// Either the Name property used from the AnalyzerAttribute of the name of the function or member.
Name: string
/// Assembly the analyzer was found in.
AssemblyPath: string
}

module Utils =
let currentFSharpAnalyzersSDKVersion =
Assembly.GetExecutingAssembly().GetName().Version

Expand Down Expand Up @@ -185,7 +194,7 @@ module Utils =

let typeCheckFile
(fcs: FSharpChecker)
(printError: (string -> unit))
(printError: string -> unit)
(options: FSharpProjectOptions)
(fileName: string)
(source: SourceOfSource)
Expand Down
7 changes: 7 additions & 0 deletions src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ type Message =

type Analyzer<'TContext> = 'TContext -> Async<Message list>

type AnalyzerMessage =
{
Message: Message
Name: string
AssemblyPath: string
}

module Utils =

[<RequireQualifiedAccess>]
Expand Down

0 comments on commit d7ed1de

Please sign in to comment.