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

Update analyzers support #1217

Merged
merged 10 commits into from
Jan 14, 2024
2 changes: 1 addition & 1 deletion paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ nuget Microsoft.Build.Utilities.Core >= 17.4 copy_local:false
nuget Microsoft.Build.Tasks.Core >= 17.4 copy_local: false
nuget Nuget.Frameworks >= 6.3 copy_local: false
nuget Microsoft.CodeAnalysis 4.5.0
nuget FSharp.Analyzers.SDK
nuget FSharp.Analyzers.SDK 0.23.0
nuget ICSharpCode.Decompiler
nuget Mono.Cecil >= 0.11.4
nuget FSharpLint.Core
Expand Down
15 changes: 7 additions & 8 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,11 @@ NUGET
FSharp.Core (>= 4.3.4)
FSharp.Analyzers.Build (0.2)
NETStandard.Library (>= 1.6.1)
FSharp.Analyzers.SDK (0.11)
FSharp.Compiler.Service (>= 41.0.1) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net5.0)) (&& (== netstandard2.1) (>= net5.0))
FSharp.Core (>= 6.0.1) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net5.0)) (&& (== netstandard2.1) (>= net5.0))
McMaster.NETCore.Plugins (>= 1.4) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net5.0)) (&& (== netstandard2.1) (>= net5.0))
FSharp.Analyzers.SDK (0.23)
FSharp.Compiler.Service (43.8.100) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0))
FSharp.Core (8.0.100) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0))
McMaster.NETCore.Plugins (>= 1.4) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0))
Microsoft.Extensions.Logging.Abstractions (>= 6.0) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0))
FSharp.Compiler.Service (43.8.100)
FSharp.Core (8.0.100)
System.Buffers (>= 4.5.1)
Expand Down Expand Up @@ -157,7 +158,7 @@ NUGET
Newtonsoft.Json (>= 13.0.1) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0))
Ionide.ProjInfo.Sln (0.62)
LinkDotNet.StringBuilder (1.18)
McMaster.NETCore.Plugins (1.4) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net5.0)) (&& (== netstandard2.1) (>= net5.0))
McMaster.NETCore.Plugins (1.4) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0))
Microsoft.DotNet.PlatformAbstractions (>= 3.1.6) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp2.1)) (&& (== netstandard2.1) (>= netcoreapp2.1))
Microsoft.Extensions.DependencyModel (>= 5.0) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= netcoreapp2.1)) (&& (== netstandard2.1) (>= netcoreapp2.1))
MessagePack (2.5.108)
Expand Down Expand Up @@ -277,7 +278,7 @@ NUGET
Microsoft.Extensions.DependencyInjection.Abstractions (6.0)
Microsoft.Bcl.AsyncInterfaces (>= 6.0) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< netstandard2.1)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net461))
System.Threading.Tasks.Extensions (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net6.0) (< netstandard2.1)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< netstandard2.1)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< netstandard2.1)) (== netstandard2.0) (&& (== netstandard2.1) (>= net461))
Microsoft.Extensions.DependencyModel (6.0) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net5.0)) (&& (== netstandard2.1) (>= net5.0))
Microsoft.Extensions.DependencyModel (6.0) - restriction: || (== net6.0) (== net7.0) (== net8.0) (&& (== netstandard2.0) (>= net6.0)) (&& (== netstandard2.1) (>= net6.0))
System.Buffers (>= 4.5.1)
System.Memory (>= 4.5.4)
System.Runtime.CompilerServices.Unsafe (>= 6.0)
Expand All @@ -291,8 +292,6 @@ NUGET
Microsoft.Extensions.Options (>= 6.0)
System.Diagnostics.DiagnosticSource (>= 6.0)
Microsoft.Extensions.Logging.Abstractions (6.0.2)
System.Buffers (>= 4.5.1) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< net6.0)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) (== netstandard2.1)
System.Memory (>= 4.5.4) - restriction: || (&& (== net6.0) (>= net461)) (&& (== net7.0) (>= net461)) (&& (== net7.0) (< net6.0)) (&& (== net8.0) (>= net461)) (&& (== net8.0) (< net6.0)) (== netstandard2.0) (== netstandard2.1)
Microsoft.Extensions.Logging.Configuration (6.0)
Microsoft.Extensions.Configuration (>= 6.0)
Microsoft.Extensions.Configuration.Abstractions (>= 6.0)
Expand Down
48 changes: 28 additions & 20 deletions src/FsAutoComplete.Core/Commands.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1137,14 +1137,22 @@ module Commands =



let analyzerHandler (file: string<LocalPath>, content, pt, tast, symbols, getAllEntities) =
let ctx: SDK.Context =
let analyzerHandler
(
client: SDK.Client<SDK.EditorAnalyzerAttribute, SDK.EditorContext>,
file: string<LocalPath>,
content: ISourceText,
pt,
tast,
checkFileResults: FSharpCheckFileResults
) =
let ctx: SDK.EditorContext =
{ FileName = UMX.untag file
Content = content
ParseTree = pt
TypedTree = tast
Symbols = symbols
GetAllEntities = getAllEntities }
SourceText = content
ParseFileResults = pt
CheckFileResults = Some checkFileResults
TypedTree = Some tast
CheckProjectResults = None }

let extractResultsFromAnalyzer (r: SDK.AnalysisResult) =
match r.Output with
Expand All @@ -1168,20 +1176,20 @@ module Commands =

[]

try
SDK.Client.runAnalyzersSafely ctx
|> List.collect extractResultsFromAnalyzer
|> List.toArray
with ex ->
Loggers.analyzers.error (
Log.setMessage "Error while processing analyzers for {file}: {message}"
>> Log.addContextDestructured "message" ex.Message
>> Log.addExn ex
>> Log.addContextDestructured "file" file
)

[||]
async {
try
let! r = client.RunAnalyzersSafely ctx
return r |> List.collect extractResultsFromAnalyzer |> List.toArray
with ex ->
Loggers.analyzers.error (
Log.setMessage "Error while processing analyzers for {file}: {message}"
>> Log.addContextDestructured "message" ex.Message
>> Log.addExn ex
>> Log.addContextDestructured "file" file
)

return [||]
}

type Commands() =

Expand Down
26 changes: 18 additions & 8 deletions src/FsAutoComplete/LspHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ type FSACDto =
type FSharpConfigDto =
{ AutomaticWorkspaceInit: bool option
WorkspaceModePeekDeepLevel: int option
ExcludeProjectDirectories: string[] option
ExcludeProjectDirectories: string array option
KeywordsAutocomplete: bool option
ExternalAutocomplete: bool option
FullNameExternalAutocomplete: bool option
Expand All @@ -660,13 +660,15 @@ type FSharpConfigDto =
ResolveNamespaces: bool option
EnableReferenceCodeLens: bool option
EnableAnalyzers: bool option
AnalyzersPath: string[] option
AnalyzersPath: string array option
ExcludeAnalyzers: string array option
IncludeAnalyzers: string array option
DisableInMemoryProjectReferences: bool option
LineLens: LineLensConfig option
UseSdkScripts: bool option
DotNetRoot: string option
FSIExtraParameters: string[] option
FSICompilerToolLocations: string[] option
FSIExtraParameters: string array option
FSICompilerToolLocations: string array option
TooltipMode: string option
GenerateBinlog: bool option
AbstractClassStubGeneration: bool option
Expand Down Expand Up @@ -772,7 +774,7 @@ let tryCreateRegex (pattern: string) =
type FSharpConfig =
{ AutomaticWorkspaceInit: bool
WorkspaceModePeekDeepLevel: int
ExcludeProjectDirectories: string[]
ExcludeProjectDirectories: string array
KeywordsAutocomplete: bool
ExternalAutocomplete: bool
FullNameExternalAutocomplete: bool
Expand All @@ -799,13 +801,15 @@ type FSharpConfig =
ResolveNamespaces: bool
EnableReferenceCodeLens: bool
EnableAnalyzers: bool
AnalyzersPath: string[]
AnalyzersPath: string array
ExcludeAnalyzers: string array
IncludeAnalyzers: string array
DisableInMemoryProjectReferences: bool
LineLens: LineLensConfig
UseSdkScripts: bool
DotNetRoot: string
FSIExtraParameters: string[]
FSICompilerToolLocations: string[]
FSIExtraParameters: string array
FSICompilerToolLocations: string array
TooltipMode: string
GenerateBinlog: bool
CodeLenses: CodeLensConfig
Expand Down Expand Up @@ -846,6 +850,8 @@ type FSharpConfig =
EnableReferenceCodeLens = false
EnableAnalyzers = false
AnalyzersPath = [||]
ExcludeAnalyzers = [||]
IncludeAnalyzers = [||]
DisableInMemoryProjectReferences = false
LineLens = { Enabled = "never"; Prefix = "" }
UseSdkScripts = true
Expand Down Expand Up @@ -894,6 +900,8 @@ type FSharpConfig =
EnableReferenceCodeLens = defaultArg dto.EnableReferenceCodeLens false
EnableAnalyzers = defaultArg dto.EnableAnalyzers false
AnalyzersPath = defaultArg dto.AnalyzersPath [||]
ExcludeAnalyzers = defaultArg dto.ExcludeAnalyzers [||]
IncludeAnalyzers = defaultArg dto.IncludeAnalyzers [||]
DisableInMemoryProjectReferences = defaultArg dto.DisableInMemoryProjectReferences false
LineLens =
{ Enabled = defaultArg (dto.LineLens |> Option.map (fun n -> n.Enabled)) "never"
Expand Down Expand Up @@ -999,6 +1007,8 @@ type FSharpConfig =
EnableReferenceCodeLens = defaultArg dto.EnableReferenceCodeLens x.EnableReferenceCodeLens
EnableAnalyzers = defaultArg dto.EnableAnalyzers x.EnableAnalyzers
AnalyzersPath = defaultArg dto.AnalyzersPath x.AnalyzersPath
ExcludeAnalyzers = defaultArg dto.ExcludeAnalyzers x.ExcludeAnalyzers
IncludeAnalyzers = defaultArg dto.IncludeAnalyzers x.IncludeAnalyzers
DisableInMemoryProjectReferences =
defaultArg dto.DisableInMemoryProjectReferences x.DisableInMemoryProjectReferences
LineLens =
Expand Down
20 changes: 12 additions & 8 deletions src/FsAutoComplete/LspHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ type FSACDto =
type FSharpConfigDto =
{ AutomaticWorkspaceInit: bool option
WorkspaceModePeekDeepLevel: int option
ExcludeProjectDirectories: string[] option
ExcludeProjectDirectories: string array option
KeywordsAutocomplete: bool option
ExternalAutocomplete: bool option
FullNameExternalAutocomplete: bool option
Expand All @@ -290,13 +290,15 @@ type FSharpConfigDto =
ResolveNamespaces: bool option
EnableReferenceCodeLens: bool option
EnableAnalyzers: bool option
AnalyzersPath: string[] option
AnalyzersPath: string array option
ExcludeAnalyzers: string array option
IncludeAnalyzers: string array option
DisableInMemoryProjectReferences: bool option
LineLens: LineLensConfig option
UseSdkScripts: bool option
DotNetRoot: string option
FSIExtraParameters: string[] option
FSICompilerToolLocations: string[] option
FSIExtraParameters: string array option
FSICompilerToolLocations: string array option
TooltipMode: string option
GenerateBinlog: bool option
AbstractClassStubGeneration: bool option
Expand Down Expand Up @@ -361,7 +363,7 @@ type DebugConfig =
type FSharpConfig =
{ AutomaticWorkspaceInit: bool
WorkspaceModePeekDeepLevel: int
ExcludeProjectDirectories: string[]
ExcludeProjectDirectories: string array
KeywordsAutocomplete: bool
ExternalAutocomplete: bool
FullNameExternalAutocomplete: bool
Expand All @@ -388,13 +390,15 @@ type FSharpConfig =
ResolveNamespaces: bool
EnableReferenceCodeLens: bool
EnableAnalyzers: bool
AnalyzersPath: string[]
AnalyzersPath: string array
ExcludeAnalyzers: string array
IncludeAnalyzers: string array
DisableInMemoryProjectReferences: bool
LineLens: LineLensConfig
UseSdkScripts: bool
DotNetRoot: string
FSIExtraParameters: string[]
FSICompilerToolLocations: string[]
FSIExtraParameters: string array
FSICompilerToolLocations: string array
TooltipMode: string
GenerateBinlog: bool
CodeLenses: CodeLensConfig
Expand Down
30 changes: 24 additions & 6 deletions src/FsAutoComplete/LspServers/AdaptiveServerState.fs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ type AdaptiveState(lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFac
[| yield! fsiCompilerToolLocations |> Array.map toCompilerToolArgument
yield! fsiExtraParameters |]

let analyzersClient =
FSharp.Analyzers.SDK.Client<FSharp.Analyzers.SDK.EditorAnalyzerAttribute, FSharp.Analyzers.SDK.EditorContext>(
Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance
)

/// <summary>Loads F# Analyzers from the configured directories</summary>
/// <param name="config">The FSharpConfig</param>
/// <param name="rootPath">The RootPath</param>
Expand All @@ -135,6 +140,18 @@ type AdaptiveState(lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFac
if config.EnableAnalyzers then
Loggers.analyzers.info (Log.setMessageI $"Using analyzer roots of {config.AnalyzersPath:roots}")

let excludeInclude =
match config.ExcludeAnalyzers, config.IncludeAnalyzers with
| e, [||] -> FSharp.Analyzers.SDK.ExcludeInclude.ExcludeFilter(fun (s: string) -> Array.contains s e)
| [||], i -> FSharp.Analyzers.SDK.ExcludeInclude.IncludeFilter(fun (s: string) -> Array.contains s i)
| _e, i ->
Loggers.analyzers.warn (
Log.setMessage
"--exclude-analyzers and --include-analyzers are mutually exclusive, ignoring --exclude-analyzers"
)

FSharp.Analyzers.SDK.ExcludeInclude.IncludeFilter(fun (s: string) -> Array.contains s i)

config.AnalyzersPath
|> Array.iter (fun analyzerPath ->
match rootPath with
Expand All @@ -152,7 +169,7 @@ type AdaptiveState(lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFac

Loggers.analyzers.info (Log.setMessageI $"Loading analyzers from {dir:dir}")

let (dllCount, analyzerCount) = dir |> FSharp.Analyzers.SDK.Client.loadAnalyzers
let (dllCount, analyzerCount) = analyzersClient.LoadAnalyzers(dir, excludeInclude)

Loggers.analyzers.info (
Log.setMessageI
Expand Down Expand Up @@ -369,14 +386,14 @@ type AdaptiveState(lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFac
// Since analyzers are not async, we need to switch to a new thread to not block threadpool
do! Async.SwitchToNewThread()

let res =
let! res =
Commands.analyzerHandler (
analyzersClient,
file,
volatileFile.Source.ToString().Split("\n"),
parseAndCheck.GetParseResults.ParseTree,
volatileFile.Source,
parseAndCheck.GetParseResults,
tast,
parseAndCheck.GetCheckResults.PartialAssemblySignature.Entities |> Seq.toList,
parseAndCheck.GetAllEntities
parseAndCheck.GetCheckResults
)

let! ct = Async.CancellationToken
Expand Down Expand Up @@ -552,6 +569,7 @@ type AdaptiveState(lspClient: FSharpLspClient, sourceTextFactory: ISourceTextFac

let severity =
match m.Severity with
| FSharp.Analyzers.SDK.Hint -> DiagnosticSeverity.Hint
| FSharp.Analyzers.SDK.Info -> DiagnosticSeverity.Information
| FSharp.Analyzers.SDK.Warning -> DiagnosticSeverity.Warning
| FSharp.Analyzers.SDK.Error -> DiagnosticSeverity.Error
Expand Down
2 changes: 2 additions & 0 deletions test/FsAutoComplete.Tests.Lsp/Helpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,8 @@ let defaultConfigDto: FSharpConfigDto =
EnableReferenceCodeLens = None
EnableAnalyzers = None
AnalyzersPath = None
ExcludeAnalyzers = None
IncludeAnalyzers = None
DisableInMemoryProjectReferences = None
AutomaticWorkspaceInit = Some true
InterfaceStubGeneration = None
Expand Down
59 changes: 32 additions & 27 deletions test/OptionAnalyzer/Analyzer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -117,31 +117,36 @@ let info message items =

let inline (==>) x y = x, box y

[<Analyzer "OptionAnalyzer">]
let optionValueAnalyzer: Analyzer =
[<EditorAnalyzer "OptionAnalyzer">]
let optionValueAnalyzer: Analyzer<EditorContext> =
fun ctx ->
info "analyzing {file} for uses of Option.Value" [ "file" ==> ctx.FileName ]
let state = ResizeArray<Range>()

let handler (range: Range) (m: FSharpMemberOrFunctionOrValue) =
let rangeString =
sprintf "(%d,%d)-(%d,%d)" range.Start.Line range.Start.Column range.End.Line range.End.Column

let name = String.Join(".", m.DeclaringEntity.Value.FullName, m.DisplayName)
info "checking value at {range} with name {name}" [ "range" ==> rangeString; "name" ==> name ]

if name = "Microsoft.FSharp.Core.FSharpOption`1.Value" then
info "matched at range {range}" [ "range" ==> rangeString ]
state.Add range

ctx.TypedTree.Declarations |> List.iter (visitDeclaration handler)

state
|> Seq.map (fun r ->
{ Type = "Option.Value analyzer"
Message = "Option.Value shouldn't be used"
Code = "OV001"
Severity = Warning
Range = r
Fixes = [] })
|> Seq.toList
async {
info "analyzing {file} for uses of Option.Value" [ "file" ==> ctx.FileName ]
let state = ResizeArray<Range>()

let handler (range: Range) (m: FSharpMemberOrFunctionOrValue) =
let rangeString =
sprintf "(%d,%d)-(%d,%d)" range.Start.Line range.Start.Column range.End.Line range.End.Column

let name = String.Join(".", m.DeclaringEntity.Value.FullName, m.DisplayName)
info "checking value at {range} with name {name}" [ "range" ==> rangeString; "name" ==> name ]

if name = "Microsoft.FSharp.Core.FSharpOption`1.Value" then
info "matched at range {range}" [ "range" ==> rangeString ]
state.Add range

match ctx.TypedTree with
| Some tt -> tt.Declarations |> List.iter (visitDeclaration handler)
| None -> ()

return
state
|> Seq.map (fun r ->
{ Type = "Option.Value analyzer"
Message = "Option.Value shouldn't be used"
Code = "OV001"
Severity = Warning
Range = r
Fixes = [] })
|> Seq.toList
}