From 0734600d21783ceb34d4a4de7e57f48d73b052a2 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 18 Oct 2025 22:45:32 -0400 Subject: [PATCH 1/5] Add back fantomas --- .config/dotnet-tools.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 8de306d..0663f50 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -6,7 +6,15 @@ "version": "20.0.0", "commands": [ "fsdocs" - ] + ], + "rollForward": false + }, + "fantomas": { + "version": "7.0.3", + "commands": [ + "fantomas" + ], + "rollForward": false } } } \ No newline at end of file From bba95a3cc91a2ce12aed579871b5ec1baa35e0f8 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 18 Oct 2025 22:45:47 -0400 Subject: [PATCH 2/5] Enhance EditorConfig and .gitattributes for improved formatting and ignore rules; add .git-blame-ignore-revs file --- .editorconfig | 70 +++++++++++++++++++++++++++++++++++++++++- .fantomasignore | 3 ++ .git-blame-ignore-revs | 6 ++++ .gitattributes | 30 +++++++++++++++--- 4 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 .git-blame-ignore-revs diff --git a/.editorconfig b/.editorconfig index 4ca9cea..6a1d1e8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,14 +1,82 @@ +# EditorConfig is awesome: +http://EditorConfig.org + +# top-most EditorConfig file root = true -[*.{fs,fsi,fsx}] +# Default settings: +# A newline ending every file +# Use 4 spaces as indentation +[*] +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true end_of_line = lf + +[*.{fs,fsi,fsx,config}] +# https://fsprojects.github.io/fantomas/docs/end-users/Configuration.html +charset = utf-8 +trim_trailing_whitespace = true +max_line_length=100 fsharp_keep_max_number_of_blank_lines = 1 fsharp_multi_line_lambda_closing_newline = true +fsharp_bar_before_discriminated_union_declaration = true fsharp_alternative_long_member_definitions = true fsharp_align_function_signature_to_indentation = true fsharp_experimental_keep_indent_in_branch = true fsharp_bar_before_discriminated_union_declaration = true fsharp_multiline_bracket_style = aligned +fsharp_max_array_or_list_number_of_items = 1 +fsharp_array_or_list_multiline_formatter = number_of_items +fsharp_max_infix_operator_expression = 10 +fsharp_multi_line_lambda_closing_newline = true + + + + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj,sfproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# Markdown Files +[*.{md,mdx}] +trim_trailing_whitespace = false + +# Bash Files +[*.{sh}] +end_of_line = lf + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Powershell Files +[*.{ps1, psm1}] +end_of_line = crlf + +# Paket files +[paket.*] +trim_trailing_whitespace = true +indent_size = 2 + +[*.paket.references] +trim_trailing_whitespace = true +indent_size = 2 + + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 +indent_style = space [build.fsx] fsharp_blank_lines_around_nested_multiline_expressions = false diff --git a/.fantomasignore b/.fantomasignore index 01309d9..2a69821 100644 --- a/.fantomasignore +++ b/.fantomasignore @@ -1 +1,4 @@ **/bin/**/*.fs +**/obj/**/*.fs +# Ignore AssemblyInfo files +AssemblyInfo.fs \ No newline at end of file diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..9c8817e --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,6 @@ +# This file contains a list of git hashes of revisions to be ignored by git +# These revisions are considered "unimportant" in +# that they are unlikely to be what you are interested in when blaming. +# Like formatting with Fantomas +# https://docs.github.com/en/repositories/working-with-files/using-files/viewing-a-file#ignore-commits-in-the-blame-view +# Add formatting commits here diff --git a/.gitattributes b/.gitattributes index e3ca87a..f92b14a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,27 @@ -# Automatically normalize line endings +# Auto detect text files * text=auto -# Always use lf for F# files -*.fs text eol=lf -*.fsx text eol=lf -*.fsi text eol=lf \ No newline at end of file +# Custom for Visual Studio +*.cs diff=csharp text=auto eol=lf +*.vb diff=csharp text=auto eol=lf +*.fs diff=csharp text=auto eol=lf +*.fsi diff=csharp text=auto eol=lf +*.fsx diff=csharp text=auto eol=lf +*.sln text eol=crlf merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union +*.sh text eol=lf + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain \ No newline at end of file From e3f43199df986104289f52013950f6859ebd74f3 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 18 Oct 2025 22:46:38 -0400 Subject: [PATCH 3/5] Add formatting during build --- .editorconfig | 1 - Directory.Build.targets | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 Directory.Build.targets diff --git a/.editorconfig b/.editorconfig index 6a1d1e8..21811a9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -30,7 +30,6 @@ fsharp_multiline_bracket_style = aligned fsharp_max_array_or_list_number_of_items = 1 fsharp_array_or_list_multiline_formatter = number_of_items fsharp_max_infix_operator_expression = 10 -fsharp_multi_line_lambda_closing_newline = true diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000..e6958bc --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,46 @@ + + + + + + + + + <_BuildProjBaseIntermediateOutputPath>$(MSBuildThisFileDirectory)build/obj/ + <_DotnetToolManifestFile>$(MSBuildThisFileDirectory).config/dotnet-tools.json + <_DotnetToolRestoreOutputFile>$(_BuildProjBaseIntermediateOutputPath)/dotnet-tool-restore-$(NETCoreSdkVersion)-$(OS) + <_DotnetFantomasOutputFile>$(BaseIntermediateOutputPath)dotnet-fantomas-msbuild-$(NETCoreSdkVersion)-$(OS) + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 55548184ea7fcec78fad4f8a8f491efda841a58d Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sat, 18 Oct 2025 22:50:36 -0400 Subject: [PATCH 4/5] Uncomment lint stage to enable code formatting checks with Fantomas --- build.fsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/build.fsx b/build.fsx index 6518e41..c0a477e 100644 --- a/build.fsx +++ b/build.fsx @@ -20,8 +20,7 @@ let buildStage = pipeline "Build" { restoreStage - // TODO: can uncomment this after .NET SDK 9.0.101 releases - // stage "lint" { run "dotnet fantomas . --check" } + stage "lint" { run "dotnet fantomas . --check" } stage "build" { run "dotnet build -c Release --no-restore -maxCpuCount" } stage "test" { purgeBinLogCache () @@ -31,7 +30,9 @@ pipeline "Build" { run "dotnet run --project src/FSharp.Analyzers.Cli/FSharp.Analyzers.Cli.fsproj -- --project ./samples/OptionAnalyzer/OptionAnalyzer.fsproj --analyzers-path ./artifacts/bin/OptionAnalyzer/release --verbosity d --binlog-path temp/binlogs" } - stage "docs" { run "dotnet fsdocs build --properties Configuration=Release --eval --clean --strict" } + stage "docs" { + run "dotnet fsdocs build --properties Configuration=Release --eval --clean --strict" + } runIfOnlySpecified false } From 277184a0dc6f20f442a8afc3767bea571ec7fe65 Mon Sep 17 00:00:00 2001 From: Jimmy Byrd Date: Sun, 19 Oct 2025 18:23:41 -0400 Subject: [PATCH 5/5] Formatting --- docs/content/Dual Analyzer.fsx | 53 +- docs/content/Getting Started Writing.fsx | 66 ++- docs/content/Unit Testing.fsx | 11 +- samples/BadOptionUsage.fsx | 4 +- samples/MsBuildExample/Program.fs | 2 +- samples/OptionAnalyzer.Test/UnitTests.fs | 118 +++-- samples/OptionAnalyzer/Library.fs | 8 +- src/FSharp.Analyzers.Cli/CustomLogging.fs | 11 +- src/FSharp.Analyzers.Cli/Program.fs | 452 +++++++++++++----- src/FSharp.Analyzers.Cli/Result.fs | 10 +- .../FSharp.Analyzers.SDK.Testing.fs | 82 +++- .../FSharp.Analyzers.SDK.Testing.fsi | 3 +- src/FSharp.Analyzers.SDK/ASTCollecting.fs | 276 ++++++++--- src/FSharp.Analyzers.SDK/ASTCollecting.fsi | 51 +- .../FSharp.Analyzers.SDK.Client.fs | 135 ++++-- .../FSharp.Analyzers.SDK.Client.fsi | 8 +- .../FSharp.Analyzers.SDK.fs | 143 ++++-- .../FSharp.Analyzers.SDK.fsi | 12 +- src/FSharp.Analyzers.SDK/TASTCollecting.fs | 149 ++++-- src/FSharp.Analyzers.SDK/TASTCollecting.fsi | 144 ++++-- 20 files changed, 1308 insertions(+), 430 deletions(-) diff --git a/docs/content/Dual Analyzer.fsx b/docs/content/Dual Analyzer.fsx index c6bfbf7..27dfff8 100644 --- a/docs/content/Dual Analyzer.fsx +++ b/docs/content/Dual Analyzer.fsx @@ -37,34 +37,49 @@ let private topologicallySortedOpenStatementsAnalyzer let allOpenStatements = ResizeArray() let (|LongIdentAsString|) (lid: SynLongIdent) = - lid.LongIdent |> List.map (fun ident -> ident.idText) + lid.LongIdent + |> List.map (fun ident -> ident.idText) let walker = { new SyntaxCollectorBase() with override _.WalkSynModuleSigDecl(_, decl: SynModuleSigDecl) = match decl with | SynModuleSigDecl.Open( - target = SynOpenDeclTarget.ModuleOrNamespace(longId = LongIdentAsString value; range = mOpen)) -> + target = SynOpenDeclTarget.ModuleOrNamespace( + longId = LongIdentAsString value; range = mOpen)) -> allOpenStatements.Add(value, mOpen) | _ -> () override _.WalkSynModuleDecl(_, decl: SynModuleDecl) = match decl with | SynModuleDecl.Open( - target = SynOpenDeclTarget.ModuleOrNamespace(longId = LongIdentAsString value; range = mOpen)) -> + target = SynOpenDeclTarget.ModuleOrNamespace( + longId = LongIdentAsString value; range = mOpen)) -> allOpenStatements.Add(value, mOpen) | _ -> () } ASTCollecting.walkAst walker untypedTree - allOpenStatements |> Seq.toList + allOpenStatements + |> Seq.toList let isSystemOpenStatement (openStatement: string list, mOpen: range) = let isFromBCL () = - let line = sourceText.GetLineString(mOpen.EndLine - 1) - - match checkResults.GetSymbolUseAtLocation(mOpen.EndLine, mOpen.EndColumn, line, openStatement) with + let line = + sourceText.GetLineString( + mOpen.EndLine + - 1 + ) + + match + checkResults.GetSymbolUseAtLocation( + mOpen.EndLine, + mOpen.EndColumn, + line, + openStatement + ) + with | Some symbolUse -> match symbolUse.Symbol.Assembly.FileName with | None -> false @@ -73,19 +88,25 @@ let private topologicallySortedOpenStatementsAnalyzer assemblyPath.ToLower().Contains "microsoft.netcore.app.ref" | _ -> false - openStatement.[0].StartsWith("System") && isFromBCL () + openStatement.[0].StartsWith("System") + && isFromBCL () - let nonSystemOpens = allOpenStatements |> List.skipWhile isSystemOpenStatement + let nonSystemOpens = + allOpenStatements + |> List.skipWhile isSystemOpenStatement return nonSystemOpens |> List.filter isSystemOpenStatement |> List.map (fun (openStatement, mOpen) -> - let openStatementText = openStatement |> String.concat "." + let openStatementText = + openStatement + |> String.concat "." { Type = "Unsorted System open statement" - Message = $"%s{openStatementText} was found after non System namespaces where opened!" + Message = + $"%s{openStatementText} was found after non System namespaces where opened!" Code = "SOT001" Severity = Severity.Warning Range = mOpen @@ -96,7 +117,10 @@ let private topologicallySortedOpenStatementsAnalyzer [] let cliAnalyzer (ctx: CliContext) : Async = - topologicallySortedOpenStatementsAnalyzer ctx.SourceText ctx.ParseFileResults.ParseTree ctx.CheckFileResults + topologicallySortedOpenStatementsAnalyzer + ctx.SourceText + ctx.ParseFileResults.ParseTree + ctx.CheckFileResults [] let editorAnalyzer (ctx: EditorContext) : Async = @@ -104,7 +128,10 @@ let editorAnalyzer (ctx: EditorContext) : Async = // The editor might not have any check results for a given file. So we don't return any messages. | None -> async.Return [] | Some checkResults -> - topologicallySortedOpenStatementsAnalyzer ctx.SourceText ctx.ParseFileResults.ParseTree checkResults + topologicallySortedOpenStatementsAnalyzer + ctx.SourceText + ctx.ParseFileResults.ParseTree + checkResults (** Both analyzers will follow the same code path: the console application will always have the required data, while the editor needs to be more careful. diff --git a/docs/content/Getting Started Writing.fsx b/docs/content/Getting Started Writing.fsx index 679b8c3..2510aaa 100644 --- a/docs/content/Getting Started Writing.fsx +++ b/docs/content/Getting Started Writing.fsx @@ -146,7 +146,9 @@ let releaseNotes = ReleaseNotes.load "RELEASE_NOTES.md" Target.create "PackAnalyzer" (fun _ -> - let analyzerProject = "src" "BadCodeAnalyzer" + let analyzerProject = + "src" + "BadCodeAnalyzer" let args = [ @@ -154,38 +156,80 @@ Target.create "--configuration Release" sprintf "/p:PackageVersion=%s" releaseNotes.NugetVersion sprintf "/p:PackageReleaseNotes=\"%s\"" (String.concat "\n" releaseNotes.Notes) - sprintf "--output %s" (__SOURCE_DIRECTORY__ "dist") + sprintf + "--output %s" + (__SOURCE_DIRECTORY__ + "dist") ] // create initial nuget package let exitCode = Shell.Exec("dotnet", String.concat " " args, analyzerProject) - if exitCode <> 0 then + if + exitCode + <> 0 + then failwith "dotnet pack failed" else - match Shell.Exec("dotnet", "publish --configuration Release --framework net8.0", analyzerProject) with + match + Shell.Exec( + "dotnet", + "publish --configuration Release --framework net8.0", + analyzerProject + ) + with | 0 -> let nupkg = - System.IO.Directory.GetFiles(__SOURCE_DIRECTORY__ "dist") + System.IO.Directory.GetFiles( + __SOURCE_DIRECTORY__ + "dist" + ) |> Seq.head |> Path.GetFullPath let nugetParent = DirectoryInfo(nupkg).Parent.FullName let nugetFileName = Path.GetFileNameWithoutExtension(nupkg) - let publishPath = analyzerProject "bin" "Release" "net8.0" "publish" + let publishPath = + analyzerProject + "bin" + "Release" + "net8.0" + "publish" // Unzip the nuget - ZipFile.ExtractToDirectory(nupkg, nugetParent nugetFileName) + ZipFile.ExtractToDirectory( + nupkg, + nugetParent + nugetFileName + ) // delete the initial nuget package File.Delete nupkg // remove stuff from ./lib/net8.0 - Shell.deleteDir (nugetParent nugetFileName "lib" "net8.0") + Shell.deleteDir ( + nugetParent + nugetFileName + "lib" + "net8.0" + ) // move the output of publish folder into the ./lib/net8.0 directory - Shell.copyDir (nugetParent nugetFileName "lib" "net8.0") publishPath (fun _ -> true) + Shell.copyDir + (nugetParent + nugetFileName + "lib" + "net8.0") + publishPath + (fun _ -> true) // re-create the nuget package - ZipFile.CreateFromDirectory(nugetParent nugetFileName, nupkg) + ZipFile.CreateFromDirectory( + nugetParent + nugetFileName, + nupkg + ) // delete intermediate directory - Shell.deleteDir (nugetParent nugetFileName) + Shell.deleteDir ( + nugetParent + nugetFileName + ) | _ -> failwith "dotnet publish failed" ) diff --git a/docs/content/Unit Testing.fsx b/docs/content/Unit Testing.fsx index c0e0ef0..25a3759 100644 --- a/docs/content/Unit Testing.fsx +++ b/docs/content/Unit Testing.fsx @@ -38,10 +38,13 @@ let Setup () = mkOptionsFromProject "net7.0" [ - // The SDK uses this in a "dotnet add package x --version y" command - // to generate the needed FSharpProjectOptions - { Name = "Newtonsoft.Json" - Version = "13.0.3" } ] + // The SDK uses this in a "dotnet add package x --version y" command + // to generate the needed FSharpProjectOptions + { + Name = "Newtonsoft.Json" + Version = "13.0.3" + } + ] projectOptions <- opts } diff --git a/samples/BadOptionUsage.fsx b/samples/BadOptionUsage.fsx index df4ed70..36201b5 100644 --- a/samples/BadOptionUsage.fsx +++ b/samples/BadOptionUsage.fsx @@ -1,5 +1,3 @@ - - let value = Some 42 -printfn "The value is: %d" value.Value // This will cause a warning from the OptionAnalyzer \ No newline at end of file +printfn "The value is: %d" value.Value // This will cause a warning from the OptionAnalyzer diff --git a/samples/MsBuildExample/Program.fs b/samples/MsBuildExample/Program.fs index 4f045b6..bd60b7e 100644 --- a/samples/MsBuildExample/Program.fs +++ b/samples/MsBuildExample/Program.fs @@ -2,4 +2,4 @@ let value = Some 42 -printfn "The value is: %d" value.Value // This will cause a warning from the OptionAnalyzer \ No newline at end of file +printfn "The value is: %d" value.Value // This will cause a warning from the OptionAnalyzer diff --git a/samples/OptionAnalyzer.Test/UnitTests.fs b/samples/OptionAnalyzer.Test/UnitTests.fs index 1dd3736..aea586c 100644 --- a/samples/OptionAnalyzer.Test/UnitTests.fs +++ b/samples/OptionAnalyzer.Test/UnitTests.fs @@ -79,7 +79,11 @@ let notUsed() = let ctx = getContext projectOptions source let! msgs = optionValueAnalyzer ctx - Assert.IsTrue(msgs |> List.contains expectedMsg) + + Assert.IsTrue( + msgs + |> List.contains expectedMsg + ) } module IgnoreRangeTests = @@ -100,7 +104,9 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ NextLine 3 ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ NextLine 3 ] } [] @@ -114,8 +120,12 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ NextLine 3 ] - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-002" [ NextLine 3 ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ NextLine 3 ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-002" [ NextLine 3 ] } [] @@ -128,7 +138,9 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ CurrentLine 3 ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ CurrentLine 3 ] } [] @@ -141,8 +153,12 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ CurrentLine 3 ] - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-002" [ CurrentLine 3 ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ CurrentLine 3 ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-002" [ CurrentLine 3 ] } [] @@ -156,7 +172,9 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ File ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ File ] } [] @@ -170,9 +188,15 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ File ] - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-002" [ File ] - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-003" [ File ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ File ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-002" [ File ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-003" [ File ] } [] @@ -187,7 +211,9 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ Range(3, 5) ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ Range(3, 5) ] } [] @@ -202,8 +228,12 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ Range(3, 5) ] - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-002" [ Range(3, 5) ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ Range(3, 5) ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-002" [ Range(3, 5) ] } [] @@ -220,8 +250,12 @@ module IgnoreRangeTests = """ let ctx = getContext projectOptions source - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-001" [ Range(3, 7) ] - ctx.AnalyzerIgnoreRanges |> tryCompareRanges "IONIDE-002" [ Range(4, 6) ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-001" [ Range(3, 7) ] + + ctx.AnalyzerIgnoreRanges + |> tryCompareRanges "IONIDE-002" [ Range(4, 6) ] } [] @@ -268,7 +302,13 @@ module IgnoreRangeTests = let ctx = getContext projectOptions source ctx.AnalyzerIgnoreRanges - |> tryCompareRanges "IONIDE-001" [ File; NextLine 5; Range(4, 7) ] + |> tryCompareRanges + "IONIDE-001" + [ + File + NextLine 5 + Range(4, 7) + ] } [] @@ -428,7 +468,7 @@ module ClientTests = let path = System.IO.Path.GetFullPath(".") let stats = client.LoadAnalyzers(path) let! messages = client.RunAnalyzersSafely(ctx) - + Assert.That(stats.Analyzers, Is.Not.EqualTo 0) match List.tryHead messages with @@ -457,14 +497,15 @@ module ClientTests = let path = System.IO.Path.GetFullPath(".") let stats = client.LoadAnalyzers(path) let! messages = client.RunAnalyzersSafely(ctx) - + Assert.That(stats.Analyzers, Is.Not.EqualTo 0) match List.tryHead messages with | Some message -> match message.Output with | Ok msgs -> Assert.That(msgs, Is.Empty) - | Error ex -> Assert.Fail(sprintf "Expected no messages but got exception: %A" ex) + | Error ex -> + Assert.Fail(sprintf "Expected no messages but got exception: %A" ex) | None -> Assert.Fail("Expected at least one analyzer result") } @@ -487,15 +528,16 @@ module ClientTests = let! messages = client.RunAnalyzersSafely(ctx) Assert.That(stats.Analyzers, Is.Not.EqualTo 0) - + match List.tryHead messages with | Some message -> match message.Output with | Ok msgs -> Assert.That(msgs, Is.Empty) - | Error ex -> Assert.Fail(sprintf "Expected no messages but got exception: %A" ex) + | Error ex -> + Assert.Fail(sprintf "Expected no messages but got exception: %A" ex) | None -> Assert.Fail("Expected at least one analyzer result") } - + [] let ``run analyzer safely ignores file comment properly`` () = async { @@ -514,17 +556,18 @@ module ClientTests = let path = System.IO.Path.GetFullPath(".") let stats = client.LoadAnalyzers(path) let! messages = client.RunAnalyzersSafely(ctx) - + Assert.That(stats.Analyzers, Is.Not.EqualTo 0) - + match List.tryHead messages with | Some message -> match message.Output with | Ok msgs -> Assert.That(msgs, Is.Empty) - | Error ex -> Assert.Fail(sprintf "Expected no messages but got exception: %A" ex) + | Error ex -> + Assert.Fail(sprintf "Expected no messages but got exception: %A" ex) | None -> Assert.Fail("Expected at least one analyzer result") } - + [] let ``run analyzer safely ignores range comment properly`` () = async { @@ -544,14 +587,15 @@ module ClientTests = let path = System.IO.Path.GetFullPath(".") let stats = client.LoadAnalyzers(path) let! messages = client.RunAnalyzersSafely(ctx) - + Assert.That(stats.Analyzers, Is.Not.EqualTo 0) - + match List.tryHead messages with | Some message -> match message.Output with | Ok msgs -> Assert.That(msgs, Is.Empty) - | Error ex -> Assert.Fail(sprintf "Expected no messages but got exception: %A" ex) + | Error ex -> + Assert.Fail(sprintf "Expected no messages but got exception: %A" ex) | None -> Assert.Fail("Expected at least one analyzer result") } @@ -596,7 +640,7 @@ module ClientTests = let path = System.IO.Path.GetFullPath(".") let stats = client.LoadAnalyzers(path) let! messages = client.RunAnalyzers(ctx) - + Assert.That(stats.Analyzers, Is.Not.EqualTo 0) Assert.That(messages, Is.Not.Empty) } @@ -619,7 +663,7 @@ module ClientTests = let path = System.IO.Path.GetFullPath(".") let stats = client.LoadAnalyzers(path) let! messages = client.RunAnalyzers(ctx) - + Assert.That(stats.Analyzers, Is.Not.EqualTo 0) Assert.That(messages, Is.Empty) } @@ -645,7 +689,7 @@ module ClientTests = Assert.That(stats.Analyzers, Is.Not.EqualTo 0) Assert.That(messages, Is.Empty) } - + [] let ``run analyzer ignores file comment properly`` () = async { @@ -664,11 +708,11 @@ module ClientTests = let path = System.IO.Path.GetFullPath(".") let stats = client.LoadAnalyzers(path) let! messages = client.RunAnalyzers(ctx) - + Assert.That(stats.Analyzers, Is.Not.EqualTo 0) Assert.That(messages, Is.Empty) } - + [] let ``run analyzer ignores range comment properly`` () = async { @@ -688,7 +732,7 @@ module ClientTests = let path = System.IO.Path.GetFullPath(".") let stats = client.LoadAnalyzers(path) let! messages = client.RunAnalyzers(ctx) - + Assert.That(stats.Analyzers, Is.Not.EqualTo 0) Assert.That(messages, Is.Empty) - } \ No newline at end of file + } diff --git a/samples/OptionAnalyzer/Library.fs b/samples/OptionAnalyzer/Library.fs index 936b9f0..8e0ff5a 100644 --- a/samples/OptionAnalyzer/Library.fs +++ b/samples/OptionAnalyzer/Library.fs @@ -45,11 +45,9 @@ let handler typeTree = |> Seq.toList } - [] -let analyzerEditorContext : Analyzer = - fun ctx -> handler ctx.TypedTree +let analyzerEditorContext: Analyzer = + fun ctx -> handler ctx.TypedTree [] -let optionValueAnalyzer: Analyzer = - fun ctx -> handler ctx.TypedTree +let optionValueAnalyzer: Analyzer = fun ctx -> handler ctx.TypedTree diff --git a/src/FSharp.Analyzers.Cli/CustomLogging.fs b/src/FSharp.Analyzers.Cli/CustomLogging.fs index 96aa31e..3ee639b 100644 --- a/src/FSharp.Analyzers.Cli/CustomLogging.fs +++ b/src/FSharp.Analyzers.Cli/CustomLogging.fs @@ -27,7 +27,11 @@ type CustomFormatter(options: IOptionsMonitor) as this = member private _.ReloadLoggerOptions(opts: CustomOptions) = formatterOptions <- opts override this.Write<'TState> - (logEntry: inref>, _scopeProvider: IExternalScopeProvider, textWriter: TextWriter) + ( + logEntry: inref>, + _scopeProvider: IExternalScopeProvider, + textWriter: TextWriter + ) = let message = logEntry.Formatter.Invoke(logEntry.State, logEntry.Exception) @@ -89,7 +93,10 @@ type CustomFormatter(options: IOptionsMonitor) as this = type ConsoleLoggerExtensions = [] - static member AddCustomFormatter(builder: ILoggingBuilder, configure: Action) : ILoggingBuilder = + static member AddCustomFormatter + (builder: ILoggingBuilder, configure: Action) + : ILoggingBuilder + = builder .AddConsole(fun options -> options.FormatterName <- "customName") .AddConsoleFormatter(configure) diff --git a/src/FSharp.Analyzers.Cli/Program.fs b/src/FSharp.Analyzers.Cli/Program.fs index ea08a65..43336c5 100644 --- a/src/FSharp.Analyzers.Cli/Program.fs +++ b/src/FSharp.Analyzers.Cli/Program.fs @@ -15,7 +15,6 @@ open Ionide.ProjInfo open FSharp.Analyzers.Cli open FSharp.Analyzers.Cli.CustomLogging - type ExitErrorCodes = | Success = 0 | NoAnalyzersFound = -1 @@ -63,8 +62,10 @@ type Arguments = interface IArgParserTemplate with member s.Usage = match s with - | Project _ -> "List of paths to your .fsproj file. Cannot be combined with `--fsc-args`." - | Script _ -> "List of paths to your .fsx file. Supports globs. Cannot be combined with `--fsc-args`." + | Project _ -> + "List of paths to your .fsproj file. Cannot be combined with `--fsc-args`." + | Script _ -> + "List of paths to your .fsx file. Supports globs. Cannot be combined with `--fsc-args`." | Analyzers_Path _ -> "List of path to a folder where your analyzers are located. This will search recursively." | Property _ -> "A key=value pair of an MSBuild property." @@ -89,12 +90,14 @@ type Arguments = | 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]." - | FSC_Args _ -> "Pass in the raw fsc compiler arguments. Cannot be combined with the `--project` flag." + | FSC_Args _ -> + "Pass in the raw fsc compiler arguments. Cannot be combined with the `--project` flag." | Code_Root _ -> "Root of the current code repository, used in the sarif report to construct the relative file path. The current working directory is used by default." | Output_Format _ -> - "Format in which to write analyzer results to stdout. The available options are: default, github." - | BinLog_Path(_) -> "Path to a directory where MSBuild binary logs (binlog) will be written. You can use https://msbuildlog.com/ to view them." + "Format in which to write analyzer results to stdout. The available options are: default, github." + | BinLog_Path(_) -> + "Path to a directory where MSBuild binary logs (binlog) will be written. You can use https://msbuildlog.com/ to view them." type SeverityMappings = { @@ -105,21 +108,46 @@ type SeverityMappings = } member x.IsValid() = - let allCodes = [ x.TreatAsInfo; x.TreatAsHint; x.TreatAsWarning; x.TreatAsError ] + let allCodes = + [ + x.TreatAsInfo + x.TreatAsHint + x.TreatAsWarning + x.TreatAsError + ] + + let unionCount = + allCodes + |> Set.unionMany + |> Set.count + + let summedCount = + allCodes + |> List.sumBy Set.count - let unionCount = allCodes |> Set.unionMany |> Set.count - let summedCount = allCodes |> List.sumBy Set.count summedCount = unionCount let mapMessageToSeverity (mappings: SeverityMappings) (msg: FSharp.Analyzers.SDK.AnalyzerMessage) = let targetSeverity = - if mappings.TreatAsInfo |> Set.contains msg.Message.Code then + if + mappings.TreatAsInfo + |> Set.contains msg.Message.Code + then Severity.Info - else if mappings.TreatAsHint |> Set.contains msg.Message.Code then + else if + mappings.TreatAsHint + |> Set.contains msg.Message.Code + then Severity.Hint - else if mappings.TreatAsWarning |> Set.contains msg.Message.Code then + else if + mappings.TreatAsWarning + |> Set.contains msg.Message.Code + then Severity.Warning - else if mappings.TreatAsError |> Set.contains msg.Message.Code then + else if + mappings.TreatAsError + |> Set.contains msg.Message.Code + then Severity.Error else msg.Message.Severity @@ -133,13 +161,14 @@ let mapMessageToSeverity (mappings: SeverityMappings) (msg: FSharp.Analyzers.SDK [] type OutputFormat = -| Default -| GitHub + | Default + | GitHub -let parseOutputFormat = function -| "github" -> Ok OutputFormat.GitHub -| "default" -> Ok OutputFormat.Default -| other -> Error $"Unknown output format: %s{other}." +let parseOutputFormat = + function + | "github" -> Ok OutputFormat.GitHub + | "default" -> Ok OutputFormat.Default + | other -> Error $"Unknown output format: %s{other}." let mutable logLevel = LogLevel.Warning @@ -159,30 +188,40 @@ let mutable logger: ILogger = Abstractions.NullLogger.Instance /// Runs MSBuild to create FSharpProjectOptions based on the projPaths. /// Returns only the FSharpProjectOptions based on the projPaths and not any referenced projects. -let loadProjects toolsPath properties (projPaths: string list) (binLogPath : DirectoryInfo option) = +let loadProjects toolsPath properties (projPaths: string list) (binLogPath: DirectoryInfo option) = async { let projPaths = projPaths - |> List.map (fun proj -> Path.Combine(Environment.CurrentDirectory, proj) |> Path.GetFullPath) + |> List.map (fun proj -> + Path.Combine(Environment.CurrentDirectory, proj) + |> Path.GetFullPath + ) for proj in projPaths do logger.LogInformation("Loading project {0}", proj) let loader = WorkspaceLoader.Create(toolsPath, properties) + binLogPath |> Option.iter (fun path -> logger.LogInformation("Using binary log path: {0}", path.FullName) ) + let binLogConfig = binLogPath |> Option.map (fun path -> BinaryLogGeneration.Within path) |> Option.defaultValue BinaryLogGeneration.Off - - let projectOptions = loader.LoadProjects(projPaths, [], binaryLog = binLogConfig) + + let projectOptions = loader.LoadProjects(projPaths, [], binaryLog = binLogConfig) let failedLoads = projPaths - |> Seq.filter (fun path -> not (projectOptions |> Seq.exists (fun p -> p.ProjectFileName = path))) + |> Seq.filter (fun path -> + not ( + projectOptions + |> Seq.exists (fun p -> p.ProjectFileName = path) + ) + ) |> Seq.toList if Seq.length failedLoads > 0 then @@ -191,7 +230,10 @@ let loadProjects toolsPath properties (projPaths: string list) (binLogPath : Dir let loaded = FCS.mapManyOptions projectOptions - |> Seq.filter (fun p -> projPaths |> List.exists (fun x -> x = p.ProjectFileName)) // We only want to analyze what was passed in + |> Seq.filter (fun p -> + projPaths + |> List.exists (fun x -> x = p.ProjectFileName) + ) // We only want to analyze what was passed in |> Seq.toList return loaded @@ -214,30 +256,50 @@ let runProject |> Array.filter (fun file -> match excludeIncludeFiles with | Choice1Of2 excludeFiles -> - match excludeFiles |> List.tryFind (fun g -> g.IsMatch file) with + match + excludeFiles + |> List.tryFind (fun g -> g.IsMatch file) + with | Some g -> logger.LogInformation("Ignoring file {0} for pattern {1}", file, g.Pattern) false | None -> true | Choice2Of2 includeFiles -> - match includeFiles |> List.tryFind (fun g -> g.IsMatch file) with + match + includeFiles + |> List.tryFind (fun g -> g.IsMatch file) + with | Some g -> - logger.LogInformation("Including file {0} for pattern {1}", file, g.Pattern) + logger.LogInformation( + "Including file {0} for pattern {1}", + file, + g.Pattern + ) + true | None -> false ) |> Array.map (fun fileName -> async { - let! fileContent = File.ReadAllTextAsync fileName |> Async.AwaitTask + let! fileContent = + File.ReadAllTextAsync fileName + |> Async.AwaitTask + let sourceText = SourceText.ofString fileContent logger.LogDebug("Checking file {0}", fileName) // Since we did ParseAndCheckProject, we can be sure that the file is in the project. // See https://fsharp.github.io/fsharp-compiler-docs/fcs/project.html for more information. - let! parseAndCheckResults = fcs.GetBackgroundCheckResultsForFileInProject(fileName, fsharpOptions) + let! parseAndCheckResults = + fcs.GetBackgroundCheckResultsForFileInProject(fileName, fsharpOptions) let ctx = - Utils.createContext checkProjectResults fileName sourceText parseAndCheckResults analyzerOptions + Utils.createContext + checkProjectResults + fileName + sourceText + parseAndCheckResults + analyzerOptions logger.LogInformation("Running analyzers for {0}", ctx.FileName) let! results = client.RunAnalyzers ctx @@ -252,12 +314,21 @@ let runProject |> Seq.map (fun messages -> match messages with | Error e -> Error e - | Ok messages -> messages |> List.map (mapMessageToSeverity mappings) |> Ok + | Ok messages -> + messages + |> List.map (mapMessageToSeverity mappings) + |> Ok ) |> Seq.toList } -let fsharpFiles = set [| ".fs"; ".fsi"; ".fsx" |] +let fsharpFiles = + set + [| + ".fs" + ".fsi" + ".fsx" + |] let isFSharpFile (file: string) = Set.exists (fun (ext: string) -> file.EndsWith(ext, StringComparison.Ordinal)) fsharpFiles @@ -281,13 +352,18 @@ let runFscArgs // We make an absolute path because the sarif report cannot deal properly with relative path. let path = Path.Combine(Directory.GetCurrentDirectory(), argument) - if not (isFSharpFile path) || not (File.Exists path) then + if + not (isFSharpFile path) + || not (File.Exists path) + then None else Some path ) - let otherOptions = fscArgs |> Array.filter (fun line -> not (isFSharpFile line)) + let otherOptions = + fscArgs + |> Array.filter (fun line -> not (isFSharpFile line)) let projectOptions = { @@ -348,7 +424,7 @@ let printMessagesInDefaultFormat (msgs: AnalyzerMessage list) = () -let printMessagesInGitHubFormat (codeRoot : Uri) (msgs: AnalyzerMessage list) = +let printMessagesInGitHubFormat (codeRoot: Uri) (msgs: AnalyzerMessage list) = let severityToLogLevel = Map.ofArray [| @@ -421,7 +497,10 @@ let writeReport (results: AnalyzerMessage list) (codeRoot: Uri) (report: string) let driver = ToolComponent() driver.Name <- "Ionide.Analyzers.Cli" driver.InformationUri <- Uri("https://ionide.io/FSharp.Analyzers.SDK/") - driver.Version <- string (System.Reflection.Assembly.GetExecutingAssembly().GetName().Version) + + driver.Version <- + string (System.Reflection.Assembly.GetExecutingAssembly().GetName().Version) + let tool = Tool() tool.Driver <- driver let run = Run() @@ -431,7 +510,8 @@ let writeReport (results: AnalyzerMessage list) (codeRoot: Uri) (report: string) new SarifLogger( report, logFilePersistenceOptions = - (FilePersistenceOptions.PrettyPrint ||| FilePersistenceOptions.ForceOverwrite), + (FilePersistenceOptions.PrettyPrint + ||| FilePersistenceOptions.ForceOverwrite), run = run, levels = BaseLogger.ErrorWarningNote, kinds = BaseLogger.Fail, @@ -478,9 +558,17 @@ let writeReport (results: AnalyzerMessage list) (codeRoot: Uri) (report: string) physicalLocation.Region <- let r = Region() r.StartLine <- analyzerResult.Message.Range.StartLine - r.StartColumn <- analyzerResult.Message.Range.StartColumn + 1 + + r.StartColumn <- + analyzerResult.Message.Range.StartColumn + + 1 + r.EndLine <- analyzerResult.Message.Range.EndLine - r.EndColumn <- analyzerResult.Message.Range.EndColumn + 1 + + r.EndColumn <- + analyzerResult.Message.Range.EndColumn + + 1 + r let location: Location = Location() @@ -511,7 +599,9 @@ let expandMultiProperties (properties: (string * string) list) = [ yield (k, splits[0]) - for pair in splits.[1..] |> Seq.chunkBySize 2 do + for pair in + splits.[1..] + |> Seq.chunkBySize 2 do match pair with | [| k; v |] when String.IsNullOrWhiteSpace(v) -> logger.LogError("Missing property value for '{0}'", k) @@ -529,7 +619,10 @@ let validateRuntimeOsArchCombination (runtime, arch, os) = logger.LogError("Specifying both the `-r|--runtime` and `-os` options is not supported.") exit (int ExitErrorCodes.RuntimeAndOsOptions) | Some _, _, Some _ -> - logger.LogError("Specifying both the `-r|--runtime` and `-a|--arch` options is not supported.") + logger.LogError( + "Specifying both the `-r|--runtime` and `-a|--arch` options is not supported." + ) + exit (int ExitErrorCodes.RuntimeAndArchOptions) | _ -> () @@ -546,7 +639,12 @@ let getProperties (results: ParseResults) = | Some r, _, _ -> Some r | None, Some o, Some a -> Some $"{o}-{a}" | None, Some o, None -> - let archOfRid = rid.Substring(rid.LastIndexOf('-') + 1) + let archOfRid = + rid.Substring( + rid.LastIndexOf('-') + + 1 + ) + Some $"{o}-{archOfRid}" | None, None, Some a -> let osOfRid = rid.Substring(0, rid.LastIndexOf('-')) @@ -568,7 +666,6 @@ let getProperties (results: ParseResults) = | _ -> () ] - [] let main argv = let toolsPath = Init.init (DirectoryInfo Environment.CurrentDirectory) None @@ -587,7 +684,12 @@ let main argv = | Some "normal" -> LogLevel.Warning | None -> LogLevel.Warning | Some x -> - use factory = LoggerFactory.Create(fun b -> b.AddConsole() |> ignore) + use factory = + LoggerFactory.Create(fun b -> + b.AddConsole() + |> ignore + ) + let logger = factory.CreateLogger("") logger.LogError("unknown verbosity level given {0}", x) exit (int ExitErrorCodes.UnknownLoggerVerbosity) @@ -607,59 +709,106 @@ let main argv = logger <- factory.CreateLogger("FSharp.Analyzers.Cli") // Set the Ionide.ProjInfo logger to use the same Microsoft.Extensions.Logging logger - if logLevel <= LogLevel.Information then - Ionide.ProjInfo.Logging.Providers.MicrosoftExtensionsLoggingProvider.setMicrosoftLoggerFactory factory + if + logLevel + <= LogLevel.Information + then + Ionide.ProjInfo.Logging.Providers.MicrosoftExtensionsLoggingProvider.setMicrosoftLoggerFactory + factory logger.LogInformation("Running in verbose mode") let binlogPath = results.TryGetResult <@ BinLog_Path @> - |> Option.map (Path.GetFullPath >> DirectoryInfo) + |> Option.map ( + Path.GetFullPath + >> DirectoryInfo + ) AppDomain.CurrentDomain.UnhandledException.Add(fun args -> let ex = args.ExceptionObject :?> exn match ex with - | :? FileNotFoundException as fnf when fnf.FileName.StartsWith "System.Runtime" -> + | :? FileNotFoundException as fnf when fnf.FileName.StartsWith "System.Runtime" -> // https://github.com/ionide/FSharp.Analyzers.SDK/issues/245 - logger.LogCritical(ex, "FSharp.Analyzers.Cli could not find {0}. If you're using a preview version of the .NET SDK, you may need to set DOTNET_ROLL_FORWARD_TO_PRERELEASE=1 in your environment before running this tool.", fnf.FileName) - | _ -> - logger.LogCritical(ex, "Unhandled exception:") + logger.LogCritical( + ex, + "FSharp.Analyzers.Cli could not find {0}. If you're using a preview version of the .NET SDK, you may need to set DOTNET_ROLL_FORWARD_TO_PRERELEASE=1 in your environment before running this tool.", + fnf.FileName + ) + | _ -> logger.LogCritical(ex, "Unhandled exception:") + factory.Dispose() // Flush any logs https://github.com/dotnet/extensions/issues/2395 exit (int ExitErrorCodes.UnhandledException) ) let severityMapping = { - TreatAsHint = results.GetResult(<@ Treat_As_Hint @>, []) |> Set.ofList - TreatAsInfo = results.GetResult(<@ Treat_As_Info @>, []) |> Set.ofList - TreatAsWarning = results.GetResult(<@ Treat_As_Warning @>, []) |> Set.ofList - TreatAsError = results.GetResult(<@ Treat_As_Error @>, []) |> Set.ofList + TreatAsHint = + results.GetResult(<@ Treat_As_Hint @>, []) + |> Set.ofList + TreatAsInfo = + results.GetResult(<@ Treat_As_Info @>, []) + |> Set.ofList + TreatAsWarning = + results.GetResult(<@ Treat_As_Warning @>, []) + |> Set.ofList + TreatAsError = + results.GetResult(<@ Treat_As_Error @>, []) + |> Set.ofList } - logger.LogInformation("Treat as Hints: [{0}]", (severityMapping.TreatAsHint |> String.concat ", ")) - logger.LogInformation("Treat as Info: [{0}]", (severityMapping.TreatAsInfo |> String.concat ", ")) - logger.LogInformation("Treat as Warning: [{0}]", (severityMapping.TreatAsWarning |> String.concat ", ")) - logger.LogInformation("Treat as Error: [{0}]", (severityMapping.TreatAsError |> String.concat ", ")) + logger.LogInformation( + "Treat as Hints: [{0}]", + (severityMapping.TreatAsHint + |> String.concat ", ") + ) + + logger.LogInformation( + "Treat as Info: [{0}]", + (severityMapping.TreatAsInfo + |> String.concat ", ") + ) + + logger.LogInformation( + "Treat as Warning: [{0}]", + (severityMapping.TreatAsWarning + |> String.concat ", ") + ) + + logger.LogInformation( + "Treat as Error: [{0}]", + (severityMapping.TreatAsError + |> String.concat ", ") + ) if not (severityMapping.IsValid()) then - logger.LogError("An analyzer code may only be listed once in the arguments.") + logger.LogError( + "An analyzer code may only be listed once in the arguments." + ) exit (int ExitErrorCodes.AnalyzerListedMultipleTimesInTreatAsSeverity) - let projOpts = results.GetResults <@ Project @> |> List.concat + let projOpts = + results.GetResults <@ Project @> + |> List.concat + let fscArgs = results.TryGetResult <@ FSC_Args @> let report = results.TryGetResult <@ Report @> let codeRoot = results.TryGetResult <@ Code_Root @> - let cwd = Directory.GetCurrentDirectory() |> DirectoryInfo + + let cwd = + Directory.GetCurrentDirectory() + |> DirectoryInfo let beginsWithCurrentPath (path: string) = - path.StartsWith("./") || path.StartsWith(".\\") + path.StartsWith("./") + || path.StartsWith(".\\") - let scripts = + let scripts = results.GetResult(<@ Script @>, []) - |> List.collect(fun scriptGlob -> - let root, scriptGlob = + |> List.collect (fun scriptGlob -> + let root, scriptGlob = if Path.IsPathRooted scriptGlob then // Glob can't handle absolute paths, so we need to make sure the scriptGlob is a relative path let root = Path.GetPathRoot scriptGlob @@ -672,29 +821,52 @@ let main argv = else cwd, scriptGlob - root.GlobFiles scriptGlob |> Seq.map (fun file -> file.FullName) |> Seq.toList + root.GlobFiles scriptGlob + |> Seq.map (fun file -> file.FullName) + |> Seq.toList ) let exclInclFiles = let excludeFiles = results.GetResult(<@ Exclude_Files @>, []) - logger.LogInformation("Exclude Files: [{0}]", (excludeFiles |> String.concat ", ")) - let excludeFiles = excludeFiles |> List.map Glob + + logger.LogInformation( + "Exclude Files: [{0}]", + (excludeFiles + |> String.concat ", ") + ) + + let excludeFiles = + excludeFiles + |> List.map Glob let includeFiles = results.GetResult(<@ Include_Files @>, []) - logger.LogInformation("Include Files: [{0}]", (includeFiles |> String.concat ", ")) - let includeFiles = includeFiles |> List.map Glob + + logger.LogInformation( + "Include Files: [{0}]", + (includeFiles + |> String.concat ", ") + ) + + let includeFiles = + includeFiles + |> List.map Glob match excludeFiles, includeFiles with | e, [] -> Choice1Of2 e | [], i -> Choice2Of2 i | _e, i -> - logger.LogWarning("--exclude-files and --include-files are mutually exclusive, ignoring --exclude-files") + logger.LogWarning( + "--exclude-files and --include-files are mutually exclusive, ignoring --exclude-files" + ) Choice2Of2 i let properties = getProperties results - if Option.isSome fscArgs && not properties.IsEmpty then + if + Option.isSome fscArgs + && not properties.IsEmpty + then logger.LogError("fsc-args can't be combined with MSBuild properties.") exit (int ExitErrorCodes.FscArgsCombinedWithMsBuildProperties) @@ -707,7 +879,8 @@ let main argv = |> Option.defaultValue (Ok OutputFormat.Default) |> Result.defaultWith (fun errMsg -> logger.LogError("{0} Using default output format.", errMsg) - OutputFormat.Default) + OutputFormat.Default + ) let analyzersPaths = results.GetResults(<@ Analyzers_Path @>) @@ -730,21 +903,33 @@ let main argv = match excludeAnalyzers, includeAnalyzers with | e, [] -> - fun (s: string) -> e |> List.map Glob |> List.exists (fun g -> g.IsMatch s) + fun (s: string) -> + e + |> List.map Glob + |> List.exists (fun g -> g.IsMatch s) |> ExcludeFilter | [], i -> - fun (s: string) -> i |> List.map Glob |> List.exists (fun g -> g.IsMatch s) + fun (s: string) -> + i + |> List.map Glob + |> List.exists (fun g -> g.IsMatch s) |> IncludeFilter | _e, i -> logger.LogWarning( "--exclude-analyzers and --include-analyzers are mutually exclusive, ignoring --exclude-analyzers" ) - fun (s: string) -> i |> List.map Glob |> List.exists (fun g -> g.IsMatch s) + fun (s: string) -> + i + |> List.map Glob + |> List.exists (fun g -> g.IsMatch s) |> IncludeFilter AssemblyLoadContext.Default.add_Resolving (fun _ctx assemblyName -> - if assemblyName.Name <> "FSharp.Core" then + if + assemblyName.Name + <> "FSharp.Core" + then null else @@ -765,9 +950,12 @@ let main argv = ||> List.fold (fun (accDlls, accAnalyzers, accFailed) analyzersPath -> let loadedDlls = client.LoadAnalyzers(analyzersPath, exclInclAnalyzers) - (accDlls + loadedDlls.AnalyzerAssemblies), - (accAnalyzers + loadedDlls.Analyzers), - (accFailed + loadedDlls.FailedAssemblies) + (accDlls + + loadedDlls.AnalyzerAssemblies), + (accAnalyzers + + loadedDlls.Analyzers), + (accFailed + + loadedDlls.FailedAssemblies) ) logger.LogInformation("Registered {0} analyzers from {1} dlls", analyzers, dlls) @@ -777,10 +965,18 @@ let main argv = None else match fscArgs with - | Some _ when projOpts |> List.isEmpty |> not -> + | Some _ when + projOpts + |> List.isEmpty + |> not + -> logger.LogError("`--project` and `--fsc-args` cannot be combined.") exit (int ExitErrorCodes.ProjectAndFscArgs) - | Some _ when scripts |> List.isEmpty |> not -> + | Some _ when + scripts + |> List.isEmpty + |> not + -> logger.LogError("`--script` and `--fsc-args` cannot be combined.") exit (int ExitErrorCodes.ProjectAndFscArgs) | Some fscArgs -> @@ -790,48 +986,75 @@ let main argv = | None -> match projOpts, scripts with | [], [] -> - logger.LogError("No projects or scripts were specified. Use `--project` or `--script` to specify them.") + logger.LogError( + "No projects or scripts were specified. Use `--project` or `--script` to specify them." + ) + exit (int ExitErrorCodes.EmptyFscArgs) | projects, scripts -> for script in scripts do if not (File.Exists(script)) then - logger.LogError("Invalid `--script` argument. File does not exist: '{script}'", script) + logger.LogError( + "Invalid `--script` argument. File does not exist: '{script}'", + script + ) + exit (int ExitErrorCodes.InvalidProjectArguments) let scriptOptions = - scripts - |> List.map(fun script -> async { - let! fileContent = File.ReadAllTextAsync script |> Async.AwaitTask - let sourceText = SourceText.ofString fileContent - // GetProjectOptionsFromScript cannot be run in parallel, it is not thread-safe. - let! options, diagnostics = fcs.GetProjectOptionsFromScript(script, sourceText) - if not (List.isEmpty diagnostics) then - diagnostics - |> List.iter (fun d -> - logger.LogError( - "Script {0} has a diagnostic: {1} at {2}", - script, - d.Message, - d.Range + scripts + |> List.map (fun script -> + async { + let! fileContent = + File.ReadAllTextAsync script + |> Async.AwaitTask + + let sourceText = SourceText.ofString fileContent + // GetProjectOptionsFromScript cannot be run in parallel, it is not thread-safe. + let! options, diagnostics = + fcs.GetProjectOptionsFromScript(script, sourceText) + + if not (List.isEmpty diagnostics) then + diagnostics + |> List.iter (fun d -> + logger.LogError( + "Script {0} has a diagnostic: {1} at {2}", + script, + d.Message, + d.Range + ) ) - ) - return options - } + + return options + } ) |> Async.Sequential for projPath in projects do if not (File.Exists(projPath)) then - logger.LogError("Invalid `--project` argument. File does not exist: '{projPath}'", projPath) + logger.LogError( + "Invalid `--project` argument. File does not exist: '{projPath}'", + projPath + ) + exit (int ExitErrorCodes.InvalidProjectArguments) + async { - let! scriptOptions = scriptOptions |> Async.StartChild - let! loadedProjects = loadProjects toolsPath properties projects binlogPath |> Async.StartChild + let! scriptOptions = + scriptOptions + |> Async.StartChild + + let! loadedProjects = + loadProjects toolsPath properties projects binlogPath + |> Async.StartChild + let! loadedProjects = loadedProjects let! scriptOptions = scriptOptions - let loadedProjects = Array.toList scriptOptions @ loadedProjects + let loadedProjects = + Array.toList scriptOptions + @ loadedProjects return! loadedProjects @@ -852,18 +1075,25 @@ let main argv = | Ok results -> results, false | Error(results, _errors) -> results, true - let results = results |> List.concat + let results = + results + |> List.concat let codeRoot = match codeRoot with - | None -> Directory.GetCurrentDirectory() |> Uri - | Some root -> Path.GetFullPath root |> Uri + | None -> + Directory.GetCurrentDirectory() + |> Uri + | Some root -> + Path.GetFullPath root + |> Uri match outputFormat with | OutputFormat.Default -> printMessagesInDefaultFormat results | OutputFormat.GitHub -> printMessagesInGitHubFormat codeRoot results - report |> Option.iter (writeReport results codeRoot) + report + |> Option.iter (writeReport results codeRoot) let check = results diff --git a/src/FSharp.Analyzers.Cli/Result.fs b/src/FSharp.Analyzers.Cli/Result.fs index f83daeb..6bfdb43 100644 --- a/src/FSharp.Analyzers.Cli/Result.fs +++ b/src/FSharp.Analyzers.Cli/Result.fs @@ -1,12 +1,18 @@ module FSharp.Analyzers.Cli.Result -let allOkOrError<'ok, 'err> (results: Result<'ok, 'err> list) : Result<'ok list, 'ok list * 'err list> = +let allOkOrError<'ok, 'err> + (results: Result<'ok, 'err> list) + : Result<'ok list, 'ok list * 'err list> + = let oks, errs = (([], []), results) ||> List.fold (fun (oks, errs) result -> match result with | Ok ok -> ok :: oks, errs - | Error err -> oks, err :: errs + | Error err -> + oks, + err + :: errs ) let oks = List.rev oks diff --git a/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fs b/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fs index b974c8b..4e01f39 100644 --- a/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fs +++ b/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fs @@ -41,7 +41,13 @@ type Package = exception CompilerDiagnosticErrors of FSharpDiagnostic array -let fsharpFiles = set [| ".fs"; ".fsi"; ".fsx" |] +let fsharpFiles = + set + [| + ".fs" + ".fsi" + ".fsx" + |] let isFSharpFile (file: string) = Set.exists (fun (ext: string) -> file.EndsWith(ext, StringComparison.Ordinal)) fsharpFiles @@ -80,7 +86,8 @@ let readCompilerArgsFromBinLog (build: Build) = ) match args with - | None -> failwith $"Could not parse binlog at {build.LogFilePath}, does it contain CoreCompile?" + | None -> + failwith $"Could not parse binlog at {build.LogFilePath}, does it contain CoreCompile?" | Some args -> let idx = args.IndexOf("-o:", StringComparison.Ordinal) args.Substring(idx).Split [| '\n' |] @@ -88,10 +95,14 @@ let readCompilerArgsFromBinLog (build: Build) = let mkOptions (compilerArgs: string array) = let sourceFiles = compilerArgs - |> Array.filter (fun (line: string) -> isFSharpFile line && File.Exists line) + |> Array.filter (fun (line: string) -> + isFSharpFile line + && File.Exists line + ) let otherOptions = - compilerArgs |> Array.filter (fun line -> not (isFSharpFile line)) + compilerArgs + |> Array.filter (fun line -> not (isFSharpFile line)) { ProjectFileName = "Project" @@ -123,13 +134,19 @@ let getCachedIfOldBuildSucceeded binLogPath = else None -let createProject (binLogPath: string) (tmpProjectDir: string) (framework: string) (additionalPkgs: Package list) = +let createProject + (binLogPath: string) + (tmpProjectDir: string) + (framework: string) + (additionalPkgs: Package list) + = let stdOutBuffer = System.Text.StringBuilder() let stdErrBuffer = System.Text.StringBuilder() task { try - Directory.CreateDirectory(tmpProjectDir) |> ignore + Directory.CreateDirectory(tmpProjectDir) + |> ignore // needed to escape the global.json circle of influence in a unit testing process let envDic = Dictionary() @@ -187,7 +204,9 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = let uniqueBinLogName = let packages = - additionalPkgs |> List.map (fun p -> p.ToString()) |> String.concat "_" + additionalPkgs + |> List.map (fun p -> p.ToString()) + |> String.concat "_" $"v{Utils.currentFSharpAnalyzersSDKVersion}_{framework}_{packages}.binlog" @@ -203,7 +222,9 @@ let mkOptionsFromProject (framework: string) (additionalPkgs: Package list) = | Some f -> task { return f } | None -> task { - Directory.CreateDirectory(binLogCache) |> ignore + Directory.CreateDirectory(binLogCache) + |> ignore + let! _ = createProject binLogPath tmpProjectDir framework additionalPkgs return BinaryLog.ReadBuild binLogPath } @@ -219,7 +240,8 @@ let getContextFor (opts: FSharpProjectOptions) isSignature source = let files = Map.ofArray [| (fileName, SourceText.ofString source) |] let documentSource fileName = - Map.tryFind fileName files |> async.Return + Map.tryFind fileName files + |> async.Return let fcs = Utils.createFCS (Some documentSource) let pathToAnalyzerDlls = Path.GetFullPath(".") @@ -235,15 +257,21 @@ let getContextFor (opts: FSharpProjectOptions) isSignature source = failwith $"no Analyzers found in {pathToAnalyzerDlls}" if assemblyLoadStats.FailedAssemblies > 0 then - failwith $"failed to load %i{assemblyLoadStats.FailedAssemblies} Analyzers in {pathToAnalyzerDlls}" + failwith + $"failed to load %i{assemblyLoadStats.FailedAssemblies} Analyzers in {pathToAnalyzerDlls}" let opts = { opts with SourceFiles = [| fileName |] } - fcs.NotifyFileChanged(fileName, opts) |> Async.RunSynchronously // workaround for https://github.com/dotnet/fsharp/issues/15960 - let checkProjectResults = fcs.ParseAndCheckProject(opts) |> Async.RunSynchronously + fcs.NotifyFileChanged(fileName, opts) + |> Async.RunSynchronously // workaround for https://github.com/dotnet/fsharp/issues/15960 + + let checkProjectResults = + fcs.ParseAndCheckProject(opts) + |> Async.RunSynchronously + let allSymbolUses = checkProjectResults.GetAllUsesOfAllSymbols() let analyzerOpts = BackgroundCompilerOptions opts @@ -267,7 +295,13 @@ let getContextFor (opts: FSharpProjectOptions) isSignature source = raise (CompilerDiagnosticErrors diagErrors) let sourceText = SourceText.ofString source - Utils.createContext checkProjectResults fileName sourceText (parseFileResults, checkFileResults) analyzerOpts + + Utils.createContext + checkProjectResults + fileName + sourceText + (parseFileResults, checkFileResults) + analyzerOpts | Error e -> failwith $"typechecking file failed: %O{e}" let getContext (opts: FSharpProjectOptions) source = getContextFor opts false source @@ -276,7 +310,11 @@ let getContextForSignature (opts: FSharpProjectOptions) source = getContextFor o module Assert = let hasWarningsInLines (expectedLines: Set) (msgs: FSharp.Analyzers.SDK.Message list) = - let msgLines = msgs |> List.map (fun m -> m.Range.StartLine) |> Set.ofList + let msgLines = + msgs + |> List.map (fun m -> m.Range.StartLine) + |> Set.ofList + msgLines = expectedLines let messageContains (expectedContent: string) (msg: FSharp.Analyzers.SDK.Message) = @@ -284,10 +322,16 @@ module Assert = && msg.Message.Contains(expectedContent) let allMessagesContain (expectedContent: string) (msgs: FSharp.Analyzers.SDK.Message list) = - msgs |> List.forall (messageContains expectedContent) + msgs + |> List.forall (messageContains expectedContent) let messageContainsAny (expectedContents: string list) (msg: FSharp.Analyzers.SDK.Message) = - expectedContents |> List.exists msg.Message.Contains - - let messagesContainAny (expectedContents: string list) (msgs: FSharp.Analyzers.SDK.Message list) = - msgs |> List.forall (messageContainsAny expectedContents) + expectedContents + |> List.exists msg.Message.Contains + + let messagesContainAny + (expectedContents: string list) + (msgs: FSharp.Analyzers.SDK.Message list) + = + msgs + |> List.forall (messageContainsAny expectedContents) diff --git a/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fsi b/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fsi index 97c278c..bea4448 100644 --- a/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fsi +++ b/src/FSharp.Analyzers.SDK.Testing/FSharp.Analyzers.SDK.Testing.fsi @@ -16,7 +16,8 @@ exception CompilerDiagnosticErrors of FSharpDiagnostic array /// The target framework for the tested code to use. E.g. net6.0, net7.0 /// A list of additional packages that should be referenced. The tested code can use these. /// FSharpProjectOptions -val mkOptionsFromProject: framework: string -> additionalPkgs: Package list -> Task +val mkOptionsFromProject: + framework: string -> additionalPkgs: Package list -> Task val getContext: opts: FSharpProjectOptions -> source: string -> CliContext val getContextForSignature: opts: FSharpProjectOptions -> source: string -> CliContext diff --git a/src/FSharp.Analyzers.SDK/ASTCollecting.fs b/src/FSharp.Analyzers.SDK/ASTCollecting.fs index 901fc44..601e272 100644 --- a/src/FSharp.Analyzers.SDK/ASTCollecting.fs +++ b/src/FSharp.Analyzers.SDK/ASTCollecting.fs @@ -5,13 +5,24 @@ module ASTCollecting = /// A pattern that collects all attributes from a `SynAttributes` into a single flat list let (|AllAttrs|) (attrs: SynAttributes) = - attrs |> List.collect (fun attrList -> attrList.Attributes) + attrs + |> List.collect (fun attrList -> attrList.Attributes) /// An recursive pattern that collect all sequential expressions to avoid StackOverflowException let (|Sequentials|_|) e = - let rec visit (e: SynExpr) (finalContinuation: SynExpr list -> SynExpr list) : SynExpr list = + let rec visit + (e: SynExpr) + (finalContinuation: SynExpr list -> SynExpr list) + : SynExpr list + = match e with - | SynExpr.Sequential(expr1 = e1; expr2 = e2) -> visit e2 (fun xs -> e1 :: xs |> finalContinuation) + | SynExpr.Sequential(expr1 = e1; expr2 = e2) -> + visit + e2 + (fun xs -> + e1 :: xs + |> finalContinuation + ) | e -> finalContinuation [ e ] match e with @@ -23,12 +34,17 @@ module ASTCollecting = let (|ConstructorPats|) = function | SynArgPats.Pats ps -> ps - | SynArgPats.NamePatPairs(pats = xs) -> xs |> List.map (fun (_, _, pat) -> pat) + | SynArgPats.NamePatPairs(pats = xs) -> + xs + |> List.map (fun (_, _, pat) -> pat) type SyntaxCollectorBase() = abstract WalkSynModuleOrNamespace: path: SyntaxVisitorPath * SynModuleOrNamespace -> unit default _.WalkSynModuleOrNamespace(_, _) = () - abstract WalkSynModuleOrNamespaceSig: path: SyntaxVisitorPath * SynModuleOrNamespaceSig -> unit + + abstract WalkSynModuleOrNamespaceSig: + path: SyntaxVisitorPath * SynModuleOrNamespaceSig -> unit + default _.WalkSynModuleOrNamespaceSig(_, _) = () abstract WalkAttribute: path: SyntaxVisitorPath * SynAttribute -> unit default _.WalkAttribute(_, _) = () @@ -60,7 +76,10 @@ module ASTCollecting = default _.WalkInterfaceImpl(_, _) = () abstract WalkClause: path: SyntaxVisitorPath * SynMatchClause -> unit default _.WalkClause(_, _) = () - abstract WalkInterpolatedStringPart: path: SyntaxVisitorPath * SynInterpolatedStringPart -> unit + + abstract WalkInterpolatedStringPart: + path: SyntaxVisitorPath * SynInterpolatedStringPart -> unit + default _.WalkInterpolatedStringPart(_, _) = () abstract WalkMeasure: path: SyntaxVisitorPath * SynMeasure -> unit default _.WalkMeasure(_, _) = () @@ -97,7 +116,9 @@ module ASTCollecting = and walkSigFileInput (ParsedSigFileInput(contents = moduleOrNamespaceList)) = List.iter walkSynModuleOrNamespaceSig moduleOrNamespaceList - and walkSynModuleOrNamespace (SynModuleOrNamespace(decls = decls; attribs = AllAttrs attrs; range = _) as s) = + and walkSynModuleOrNamespace + (SynModuleOrNamespace(decls = decls; attribs = AllAttrs attrs; range = _) as s) + = walker.WalkSynModuleOrNamespace([], s) let path = [ SyntaxNode.SynModuleOrNamespace s ] List.iter (walkAttribute path) attrs @@ -111,7 +132,8 @@ module ASTCollecting = List.iter (walkAttribute path) attrs List.iter (walkSynModuleSigDecl path) decls - and walkAttribute (path: SyntaxVisitorPath) (attr: SynAttribute) = walkExpr path attr.ArgExpr + and walkAttribute (path: SyntaxVisitorPath) (attr: SynAttribute) = + walkExpr path attr.ArgExpr and walkTyparDecl (path: SyntaxVisitorPath) @@ -122,8 +144,11 @@ module ASTCollecting = List.iter (walkType path) ts and walkTyparDecls (path: SyntaxVisitorPath) (typars: SynTyparDecls) = - typars.TyparDecls |> List.iter (walkTyparDecl path) - typars.Constraints |> List.iter (walkTypeConstraint path) + typars.TyparDecls + |> List.iter (walkTyparDecl path) + + typars.Constraints + |> List.iter (walkTypeConstraint path) and walkSynValTyparDecls (path: SyntaxVisitorPath) (SynValTyparDecls(typars, _)) = Option.iter (walkTyparDecls path) typars @@ -155,7 +180,9 @@ module ASTCollecting = and walkPat (path: SyntaxVisitorPath) s = walker.WalkPat(path, s) - let nextPath = SyntaxNode.SynPat s :: path + let nextPath = + SyntaxNode.SynPat s + :: path match s with | SynPat.Tuple(elementPats = pats) @@ -168,7 +195,13 @@ module ASTCollecting = | SynPat.Attrib(pat, AllAttrs attrs, _) -> walkPat nextPath pat List.iter (walkAttribute nextPath) attrs - | SynPat.Or(lhsPat = pat1; rhsPat = pat2) -> List.iter (walkPat nextPath) [ pat1; pat2 ] + | SynPat.Or(lhsPat = pat1; rhsPat = pat2) -> + List.iter + (walkPat nextPath) + [ + pat1 + pat2 + ] | SynPat.LongIdent(typarDecls = typars; argPats = ConstructorPats pats; range = _) -> Option.iter (walkSynValTyparDecls nextPath) typars List.iter (walkPat nextPath) pats @@ -193,10 +226,19 @@ module ASTCollecting = and walkBinding (path: SyntaxVisitorPath) - (SynBinding(attributes = AllAttrs attrs; headPat = pat; returnInfo = returnInfo; expr = e; range = _) as s) + (SynBinding( + attributes = AllAttrs attrs + headPat = pat + returnInfo = returnInfo + expr = e + range = _) as s) = walker.WalkBinding(path, s) - let nextPath = SyntaxNode.SynBinding s :: path + + let nextPath = + SyntaxNode.SynBinding s + :: path + List.iter (walkAttribute nextPath) attrs walkPat nextPath pat walkExpr nextPath e @@ -208,15 +250,25 @@ module ASTCollecting = ) and walkAttributes (path: SyntaxVisitorPath) (attrs: SynAttributes) = - List.iter (fun (attrList: SynAttributeList) -> List.iter (walkAttribute path) attrList.Attributes) attrs + List.iter + (fun (attrList: SynAttributeList) -> + List.iter (walkAttribute path) attrList.Attributes + ) + attrs - and walkInterfaceImpl (path: SyntaxVisitorPath) (SynInterfaceImpl(bindings = bindings; range = _) as s) = + and walkInterfaceImpl + (path: SyntaxVisitorPath) + (SynInterfaceImpl(bindings = bindings; range = _) as s) + = walker.WalkInterfaceImpl(path, s) List.iter (walkBinding path) bindings and walkType (path: SyntaxVisitorPath) s = walker.WalkType(path, s) - let nextPath = SyntaxNode.SynType s :: path + + let nextPath = + SyntaxNode.SynType s + :: path match s with | SynType.Array(_, t, _) @@ -258,16 +310,26 @@ module ASTCollecting = Option.iter (walkTypar nextPath) typar List.iter (walkType nextPath) types - and walkClause (path: SyntaxVisitorPath) (SynMatchClause(pat = pat; whenExpr = e1; resultExpr = e2) as s) = + and walkClause + (path: SyntaxVisitorPath) + (SynMatchClause(pat = pat; whenExpr = e1; resultExpr = e2) as s) + = walker.WalkClause(path, s) - let nextPath = SyntaxNode.SynMatchClause s :: path + + let nextPath = + SyntaxNode.SynMatchClause s + :: path + walkPat nextPath pat walkExpr nextPath e2 - e1 |> Option.iter (walkExpr nextPath) + + e1 + |> Option.iter (walkExpr nextPath) and walkSimplePats (path: SyntaxVisitorPath) = function - | SynSimplePats.SimplePats(pats = pats; range = _) -> List.iter (walkSimplePat path) pats + | SynSimplePats.SimplePats(pats = pats; range = _) -> + List.iter (walkSimplePat path) pats and walkInterpolatedStringPart (path: SyntaxVisitorPath) s = walker.WalkInterpolatedStringPart(path, s) @@ -279,7 +341,9 @@ module ASTCollecting = and walkExpr (path: SyntaxVisitorPath) s = walker.WalkExpr(path, s) - let nextPath = SyntaxNode.SynExpr s :: path + let nextPath = + SyntaxNode.SynExpr s + :: path match s with | SynExpr.Typed(expr = e) -> walkExpr nextPath e @@ -314,22 +378,45 @@ module ASTCollecting = | SynExpr.ArrayOrList(_, es, _) -> List.iter (walkExpr nextPath) es | SynExpr.App(funcExpr = e1; argExpr = e2) | SynExpr.TryFinally(tryExpr = e1; finallyExpr = e2) - | SynExpr.While(_, e1, e2, _) -> List.iter (walkExpr nextPath) [ e1; e2 ] + | SynExpr.While(_, e1, e2, _) -> + List.iter + (walkExpr nextPath) + [ + e1 + e2 + ] | SynExpr.Record(recordFields = fields) -> fields - |> List.iter (fun (SynExprRecordField(expr = e)) -> e |> Option.iter (walkExpr nextPath)) - | SynExpr.ObjExpr(objType = ty; argOptions = argOpt; bindings = bindings; extraImpls = ifaces) -> + |> List.iter (fun (SynExprRecordField(expr = e)) -> + e + |> Option.iter (walkExpr nextPath) + ) + | SynExpr.ObjExpr( + objType = ty; argOptions = argOpt; bindings = bindings; extraImpls = ifaces) -> - argOpt |> Option.iter (fun (e, _) -> walkExpr nextPath e) + argOpt + |> Option.iter (fun (e, _) -> walkExpr nextPath e) walkType nextPath ty List.iter (walkBinding nextPath) bindings List.iter (walkInterfaceImpl nextPath) ifaces | SynExpr.For(identBody = e1; toBody = e2; doBody = e3; range = _) -> - List.iter (walkExpr nextPath) [ e1; e2; e3 ] + List.iter + (walkExpr nextPath) + [ + e1 + e2 + e3 + ] | SynExpr.ForEach(pat = pat; enumExpr = e1; bodyExpr = e2) -> walkPat nextPath pat - List.iter (walkExpr nextPath) [ e1; e2 ] + + List.iter + (walkExpr nextPath) + [ + e1 + e2 + ] | SynExpr.MatchLambda(matchClauses = synMatchClauseList) -> List.iter (walkClause nextPath) synMatchClauseList | SynExpr.Match(expr = e; clauses = synMatchClauseList; range = _) -> @@ -345,8 +432,15 @@ module ASTCollecting = List.iter (walkClause nextPath) clauses walkExpr nextPath e | SynExpr.IfThenElse(ifExpr = e1; thenExpr = e2; elseExpr = e3; range = _) -> - List.iter (walkExpr nextPath) [ e1; e2 ] - e3 |> Option.iter (walkExpr nextPath) + List.iter + (walkExpr nextPath) + [ + e1 + e2 + ] + + e3 + |> Option.iter (walkExpr nextPath) | SynExpr.LongIdentSet(expr = e) | SynExpr.DotGet(expr = e) -> walkExpr nextPath e | SynExpr.DotSet(targetExpr = e1; rhsExpr = e2) -> @@ -359,10 +453,28 @@ module ASTCollecting = walkExpr nextPath e1 walkExpr nextPath args walkExpr nextPath e2 - | SynExpr.NamedIndexedPropertySet(_, e1, e2, _) -> List.iter (walkExpr nextPath) [ e1; e2 ] + | SynExpr.NamedIndexedPropertySet(_, e1, e2, _) -> + List.iter + (walkExpr nextPath) + [ + e1 + e2 + ] | SynExpr.DotNamedIndexedPropertySet(targetExpr = e1; argExpr = e2; rhsExpr = e3) -> - List.iter (walkExpr nextPath) [ e1; e2; e3 ] - | SynExpr.JoinIn(lhsExpr = e1; rhsExpr = e2) -> List.iter (walkExpr nextPath) [ e1; e2 ] + List.iter + (walkExpr nextPath) + [ + e1 + e2 + e3 + ] + | SynExpr.JoinIn(lhsExpr = e1; rhsExpr = e2) -> + List.iter + (walkExpr nextPath) + [ + e1 + e2 + ] | SynExpr.LetOrUseBang(pat = pat; rhs = e1; andBangs = ands; body = e2; range = _) -> walkPat nextPath pat walkExpr nextPath e1 @@ -441,34 +553,52 @@ module ASTCollecting = walkType path t | SynSimplePat.Id _ -> () - and walkField (path: SyntaxVisitorPath) (SynField(attributes = AllAttrs attrs; fieldType = t; range = _) as s) = + and walkField + (path: SyntaxVisitorPath) + (SynField(attributes = AllAttrs attrs; fieldType = t; range = _) as s) + = walker.WalkField(path, s) List.iter (walkAttribute path) attrs walkType path t and walkValSig (path: SyntaxVisitorPath) - (SynValSig(attributes = AllAttrs attrs; synType = t; arity = SynValInfo(argInfos, argInfo); range = _) as s) + (SynValSig( + attributes = AllAttrs attrs + synType = t + arity = SynValInfo(argInfos, argInfo) + range = _) as s) = walker.WalkValSig(path, s) - let nextPath = SyntaxNode.SynValSig s :: path + + let nextPath = + SyntaxNode.SynValSig s + :: path + List.iter (walkAttribute nextPath) attrs walkType nextPath t - argInfo :: (argInfos |> List.concat) + argInfo + :: (argInfos + |> List.concat) |> List.collect (fun (SynArgInfo(attributes = AllAttrs attrs)) -> attrs) |> List.iter (walkAttribute nextPath) and walkMemberSig (path: SyntaxVisitorPath) s = walker.WalkMemberSig(path, s) - let nextPath = SyntaxNode.SynMemberSig s :: path + + let nextPath = + SyntaxNode.SynMemberSig s + :: path match s with | SynMemberSig.Inherit(t, _) | SynMemberSig.Interface(t, _) -> walkType nextPath t | SynMemberSig.Member(memberSig = vs) -> walkValSig nextPath vs | SynMemberSig.ValField(f, _) -> walkField nextPath f - | SynMemberSig.NestedType(SynTypeDefnSig(typeInfo = info; typeRepr = repr; members = memberSigs), _) -> + | SynMemberSig.NestedType(SynTypeDefnSig( + typeInfo = info; typeRepr = repr; members = memberSigs), + _) -> let isTypeExtensionOrAlias = match repr with @@ -483,7 +613,10 @@ module ASTCollecting = and walkMember (path: SyntaxVisitorPath) s = walker.WalkMember(path, s) - let nextPath = SyntaxNode.SynMemberDefn s :: path + + let nextPath = + SyntaxNode.SynMemberDefn s + :: path match s with | SynMemberDefn.AbstractSlot(slotSig = valSig) -> walkValSig nextPath valSig @@ -494,23 +627,33 @@ module ASTCollecting = | SynMemberDefn.ImplicitInherit(inheritType = t; inheritArgs = e) -> walkType nextPath t walkExpr nextPath e - | SynMemberDefn.LetBindings(bindings = bindings) -> List.iter (walkBinding nextPath) bindings + | SynMemberDefn.LetBindings(bindings = bindings) -> + List.iter (walkBinding nextPath) bindings | SynMemberDefn.Interface(t, _, members, _) -> walkType nextPath t - members |> Option.iter (List.iter (walkMember nextPath)) - | SynMemberDefn.Inherit(baseType = t) -> t |> Option.iter (walkType nextPath) + + members + |> Option.iter (List.iter (walkMember nextPath)) + | SynMemberDefn.Inherit(baseType = t) -> + t + |> Option.iter (walkType nextPath) | SynMemberDefn.ValField(field, _) -> walkField nextPath field | SynMemberDefn.NestedType(typeDefn = tdef) -> walkTypeDefn nextPath tdef - | SynMemberDefn.AutoProperty(attributes = AllAttrs attrs; typeOpt = t; synExpr = e; range = _) -> + | SynMemberDefn.AutoProperty( + attributes = AllAttrs attrs; typeOpt = t; synExpr = e; range = _) -> List.iter (walkAttribute nextPath) attrs Option.iter (walkType nextPath) t walkExpr nextPath e | SynMemberDefn.Open _ -> () - | SynMemberDefn.GetSetMember(memberDefnForGet = getter; memberDefnForSet = setter; range = _) -> + | SynMemberDefn.GetSetMember( + memberDefnForGet = getter; memberDefnForSet = setter; range = _) -> Option.iter (walkBinding nextPath) getter Option.iter (walkBinding nextPath) setter - and walkEnumCase (path: SyntaxVisitorPath) (SynEnumCase(attributes = AllAttrs attrs; range = _) as s) = + and walkEnumCase + (path: SyntaxVisitorPath) + (SynEnumCase(attributes = AllAttrs attrs; range = _) as s) + = walker.WalkEnumCase(path, s) List.iter (walkAttribute path) attrs @@ -546,7 +689,11 @@ module ASTCollecting = (path: SyntaxVisitorPath) _ (SynComponentInfo( - attributes = AllAttrs attrs; typeParams = typars; constraints = constraints; longId = _; range = _) as s) + attributes = AllAttrs attrs + typeParams = typars + constraints = constraints + longId = _ + range = _) as s) = walker.WalkComponentInfo(path, s) List.iter (walkAttribute path) attrs @@ -571,10 +718,17 @@ module ASTCollecting = and walkTypeDefn (path: SyntaxVisitorPath) - (SynTypeDefn(typeInfo = info; typeRepr = repr; members = members; implicitConstructor = implicitCtor) as s) + (SynTypeDefn( + typeInfo = info + typeRepr = repr + members = members + implicitConstructor = implicitCtor) as s) = walker.WalkTypeDefn(path, s) - let nextPath = SyntaxNode.SynTypeDefn s :: path + + let nextPath = + SyntaxNode.SynTypeDefn s + :: path let isTypeExtensionOrAlias = match repr with @@ -593,7 +747,10 @@ module ASTCollecting = (SynTypeDefnSig(typeInfo = info; typeRepr = repr; members = members) as s) = walker.WalkTypeDefnSig(path, s) - let nextPath = SyntaxNode.SynTypeDefnSig s :: path + + let nextPath = + SyntaxNode.SynTypeDefnSig s + :: path let isTypeExtensionOrAlias = match repr with @@ -608,27 +765,35 @@ module ASTCollecting = and walkSynModuleDecl (path: SyntaxVisitorPath) (decl: SynModuleDecl) = walker.WalkSynModuleDecl(path, decl) - let nextPath = SyntaxNode.SynModule decl :: path + + let nextPath = + SyntaxNode.SynModule decl + :: path match decl with | SynModuleDecl.NamespaceFragment fragment -> walkSynModuleOrNamespace fragment | SynModuleDecl.NestedModule(moduleInfo = info; decls = modules) -> walkComponentInfo nextPath false info List.iter (walkSynModuleDecl nextPath) modules - | SynModuleDecl.Let(bindings = bindings; range = _) -> List.iter (walkBinding nextPath) bindings + | SynModuleDecl.Let(bindings = bindings; range = _) -> + List.iter (walkBinding nextPath) bindings | SynModuleDecl.Expr(expr, _) -> walkExpr nextPath expr | SynModuleDecl.Types(types, _) -> List.iter (walkTypeDefn nextPath) types | SynModuleDecl.Attributes(attributes = AllAttrs attrs; range = _) -> List.iter (walkAttribute nextPath) attrs | SynModuleDecl.ModuleAbbrev _ -> () - | SynModuleDecl.Exception(exnDefn = SynExceptionDefn(exnRepr = SynExceptionDefnRepr(caseName = unionCase))) -> + | SynModuleDecl.Exception( + exnDefn = SynExceptionDefn(exnRepr = SynExceptionDefnRepr(caseName = unionCase))) -> walkUnionCase nextPath unionCase | SynModuleDecl.Open _ -> () | SynModuleDecl.HashDirective(range = _) -> () and walkSynModuleSigDecl (path: SyntaxVisitorPath) (decl: SynModuleSigDecl) = walker.WalkSynModuleSigDecl(path, decl) - let nextPath = SyntaxNode.SynModuleSigDecl decl :: path + + let nextPath = + SyntaxNode.SynModuleSigDecl decl + :: path match decl with | SynModuleSigDecl.ModuleAbbrev _ -> () @@ -637,7 +802,8 @@ module ASTCollecting = List.iter (walkSynModuleSigDecl nextPath) decls | SynModuleSigDecl.Val(s, _range) -> walkValSig nextPath s | SynModuleSigDecl.Types(types, _) -> List.iter (walkTypeDefnSig nextPath) types - | SynModuleSigDecl.Exception(exnSig = SynExceptionSig(exnRepr = SynExceptionDefnRepr(caseName = unionCase))) -> + | SynModuleSigDecl.Exception( + exnSig = SynExceptionSig(exnRepr = SynExceptionDefnRepr(caseName = unionCase))) -> walkUnionCase nextPath unionCase | SynModuleSigDecl.Open _ -> () | SynModuleSigDecl.HashDirective _ -> () diff --git a/src/FSharp.Analyzers.SDK/ASTCollecting.fsi b/src/FSharp.Analyzers.SDK/ASTCollecting.fsi index bb32f48..18f0020 100644 --- a/src/FSharp.Analyzers.SDK/ASTCollecting.fsi +++ b/src/FSharp.Analyzers.SDK/ASTCollecting.fsi @@ -9,8 +9,11 @@ module ASTCollecting = new: unit -> SyntaxCollectorBase /// Overwriting this member hooks up a custom operation for a module or namespace syntax element. - abstract WalkSynModuleOrNamespace: path: SyntaxVisitorPath * moduleOrNamespace: SynModuleOrNamespace -> unit - default WalkSynModuleOrNamespace: path: SyntaxVisitorPath * moduleOrNamespace: SynModuleOrNamespace -> unit + abstract WalkSynModuleOrNamespace: + path: SyntaxVisitorPath * moduleOrNamespace: SynModuleOrNamespace -> unit + + default WalkSynModuleOrNamespace: + path: SyntaxVisitorPath * moduleOrNamespace: SynModuleOrNamespace -> unit /// Overwriting this member hooks up a custom operation for a module or namespace syntax element in a signature file. abstract WalkSynModuleOrNamespaceSig: @@ -28,8 +31,11 @@ module ASTCollecting = default WalkSynModuleDecl: path: SyntaxVisitorPath * moduleDecl: SynModuleDecl -> unit /// Overwriting this member hooks up a custom operation for declarations inside a module or namespace in a signature file. - abstract WalkSynModuleSigDecl: path: SyntaxVisitorPath * moduleSigDecl: SynModuleSigDecl -> unit - default WalkSynModuleSigDecl: path: SyntaxVisitorPath * moduleSigDecl: SynModuleSigDecl -> unit + abstract WalkSynModuleSigDecl: + path: SyntaxVisitorPath * moduleSigDecl: SynModuleSigDecl -> unit + + default WalkSynModuleSigDecl: + path: SyntaxVisitorPath * moduleSigDecl: SynModuleSigDecl -> unit /// Overwriting this member hooks up a custom operation for syntax expressions. abstract WalkExpr: path: SyntaxVisitorPath * expr: SynExpr -> unit @@ -44,8 +50,11 @@ module ASTCollecting = default WalkTyparDecl: path: SyntaxVisitorPath * typarDecl: SynTyparDecl -> unit /// Overwriting this member hooks up a custom operation for type constraints. - abstract WalkTypeConstraint: path: SyntaxVisitorPath * typeConstraint: SynTypeConstraint -> unit - default WalkTypeConstraint: path: SyntaxVisitorPath * typeConstraint: SynTypeConstraint -> unit + abstract WalkTypeConstraint: + path: SyntaxVisitorPath * typeConstraint: SynTypeConstraint -> unit + + default WalkTypeConstraint: + path: SyntaxVisitorPath * typeConstraint: SynTypeConstraint -> unit /// Overwriting this member hooks up a custom operation for types. abstract WalkType: path: SyntaxVisitorPath * ``type``: SynType -> unit @@ -60,7 +69,9 @@ module ASTCollecting = default WalkPat: path: SyntaxVisitorPath * pat: SynPat -> unit /// Overwriting this member hooks up a custom operation for type parameters for a member of function. - abstract WalkValTyparDecls: path: SyntaxVisitorPath * valTyparDecls: SynValTyparDecls -> unit + abstract WalkValTyparDecls: + path: SyntaxVisitorPath * valTyparDecls: SynValTyparDecls -> unit + default WalkValTyparDecls: path: SyntaxVisitorPath * valTyparDecls: SynValTyparDecls -> unit /// Overwriting this member hooks up a custom operation for a binding of a 'let' or 'member' declaration. @@ -72,7 +83,9 @@ module ASTCollecting = default WalkSimplePat: path: SyntaxVisitorPath * simplePat: SynSimplePat -> unit /// Overwriting this member hooks up a custom operation for interface implementations. - abstract WalkInterfaceImpl: path: SyntaxVisitorPath * interfaceImpl: SynInterfaceImpl -> unit + abstract WalkInterfaceImpl: + path: SyntaxVisitorPath * interfaceImpl: SynInterfaceImpl -> unit + default WalkInterfaceImpl: path: SyntaxVisitorPath * interfaceImpl: SynInterfaceImpl -> unit /// Overwriting this member hooks up a custom operation for clauses in a 'match' expression. @@ -91,15 +104,22 @@ module ASTCollecting = default WalkMeasure: path: SyntaxVisitorPath * measure: SynMeasure -> unit /// Overwriting this member hooks up a custom operation for the name of a type definition or module. - abstract WalkComponentInfo: path: SyntaxVisitorPath * componentInfo: SynComponentInfo -> unit + abstract WalkComponentInfo: + path: SyntaxVisitorPath * componentInfo: SynComponentInfo -> unit + default WalkComponentInfo: path: SyntaxVisitorPath * componentInfo: SynComponentInfo -> unit /// Overwriting this member hooks up a custom operation for the right-hand-side of a type definition. - abstract WalkTypeDefnSigRepr: path: SyntaxVisitorPath * typeDefnSigRepr: SynTypeDefnSigRepr -> unit - default WalkTypeDefnSigRepr: path: SyntaxVisitorPath * typeDefnSigRepr: SynTypeDefnSigRepr -> unit + abstract WalkTypeDefnSigRepr: + path: SyntaxVisitorPath * typeDefnSigRepr: SynTypeDefnSigRepr -> unit + + default WalkTypeDefnSigRepr: + path: SyntaxVisitorPath * typeDefnSigRepr: SynTypeDefnSigRepr -> unit /// Overwriting this member hooks up a custom operation for the right-hand-side of union definition, excluding members. - abstract WalkUnionCaseType: path: SyntaxVisitorPath * unionCaseKind: SynUnionCaseKind -> unit + abstract WalkUnionCaseType: + path: SyntaxVisitorPath * unionCaseKind: SynUnionCaseKind -> unit + default WalkUnionCaseType: path: SyntaxVisitorPath * unionCaseKind: SynUnionCaseKind -> unit /// Overwriting this member hooks up a custom operation for the cases of an enum definition. @@ -111,8 +131,11 @@ module ASTCollecting = default WalkField: path: SyntaxVisitorPath * field: SynField -> unit /// Overwriting this member hooks up a custom operation for the core of a simple type definition. - abstract WalkTypeDefnSimple: path: SyntaxVisitorPath * typeDefnSimpleRepr: SynTypeDefnSimpleRepr -> unit - default WalkTypeDefnSimple: path: SyntaxVisitorPath * typeDefnSimpleRepr: SynTypeDefnSimpleRepr -> unit + abstract WalkTypeDefnSimple: + path: SyntaxVisitorPath * typeDefnSimpleRepr: SynTypeDefnSimpleRepr -> unit + + default WalkTypeDefnSimple: + path: SyntaxVisitorPath * typeDefnSimpleRepr: SynTypeDefnSimpleRepr -> unit /// Overwriting this member hooks up a custom operation for a 'val' definition in an abstract slot or a signature file. abstract WalkValSig: path: SyntaxVisitorPath * valSig: SynValSig -> unit diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs index 73c79f4..c9cb63c 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fs @@ -49,13 +49,21 @@ module Client = "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 + elif + t.Name = "FSharpAsync`1" + && t.GenericTypeArguments.Length = 1 + then let listType = t.GenericTypeArguments.[0] - if listType.Name = "FSharpList`1" && listType.GenericTypeArguments.Length = 1 then + if + listType.Name = "FSharpList`1" + && listType.GenericTypeArguments.Length = 1 + then // This could still be generic, as in an empty list is returned from the analyzer let msgType = listType.GenericTypeArguments.[0] - msgType.Name = "a" || msgType = typeof + + msgType.Name = "a" + || msgType = typeof else false else @@ -65,15 +73,25 @@ module Client = match box mi with | :? FieldInfo as m -> if m.FieldType = typeof> then - Some(m.GetValue(null) |> unboxAnalyzer) + Some( + m.GetValue(null) + |> unboxAnalyzer + ) else None | :? MethodInfo as m -> if m.ReturnType = typeof> then - Some(m.Invoke(null, null) |> unboxAnalyzer) + Some( + m.Invoke(null, null) + |> unboxAnalyzer + ) elif hasExpectReturnType m.ReturnType then try - let analyzer: Analyzer<'TContext> = fun ctx -> m.Invoke(null, [| ctx |]) |> unbox + let analyzer: Analyzer<'TContext> = + fun ctx -> + m.Invoke(null, [| ctx |]) + |> unbox + Some analyzer with ex -> None @@ -81,7 +99,10 @@ module Client = None | :? PropertyInfo as m -> if m.PropertyType = typeof> then - Some(m.GetValue(null, null) |> unboxAnalyzer) + Some( + m.GetValue(null, null) + |> unboxAnalyzer + ) else None | _ -> None @@ -109,26 +130,36 @@ module Client = | None -> None let shouldIgnoreMessage (ctx: 'Context :> #Context) message = - match ctx.AnalyzerIgnoreRanges |> Map.tryFind message.Code with + match + ctx.AnalyzerIgnoreRanges + |> Map.tryFind message.Code + with | Some ignoreRanges -> ignoreRanges - |> List.exists (function + |> List.exists ( + function | File -> true - | Range (commentStart, commentEnd) -> - if message.Range.StartLine - 1 >= commentStart && message.Range.EndLine - 1 <= commentEnd then + | Range(commentStart, commentEnd) -> + if + message.Range.StartLine + - 1 + >= commentStart + && message.Range.EndLine + - 1 + <= commentEnd + then true else false | NextLine line -> - if message.Range.StartLine - 1 = line then - true - else - false - | CurrentLine line -> - if message.Range.StartLine = line then + if + message.Range.StartLine + - 1 = line + then true else false + | CurrentLine line -> if message.Range.StartLine = line then true else false ) | None -> false @@ -139,13 +170,19 @@ module Client = : RegisteredAnalyzer<'TContext> list = let asMembers x = Seq.map (fun m -> m :> MemberInfo) x - let bindingFlags = BindingFlags.Public ||| BindingFlags.Static + + let bindingFlags = + BindingFlags.Public + ||| BindingFlags.Static let members = [ - t.GetTypeInfo().GetMethods bindingFlags |> asMembers - t.GetTypeInfo().GetProperties bindingFlags |> asMembers - t.GetTypeInfo().GetFields bindingFlags |> asMembers + t.GetTypeInfo().GetMethods bindingFlags + |> asMembers + t.GetTypeInfo().GetProperties bindingFlags + |> asMembers + t.GetTypeInfo().GetFields bindingFlags + |> asMembers ] |> Seq.collect id @@ -164,7 +201,9 @@ type ExcludeInclude = | ExcludeFilter of (AnalyzerName -> bool) | IncludeFilter of (AnalyzerName -> bool) -type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TContext :> Context>(logger: ILogger) = +type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TContext :> Context> + (logger: ILogger) + = do TASTCollecting.logger <- logger let registeredAnalyzers = @@ -205,7 +244,11 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC let findFSharpAnalyzerSDKVersion (assembly: Assembly) = let references = assembly.GetReferencedAssemblies() - let fas = references |> Array.find (fun ra -> ra.Name = "FSharp.Analyzers.SDK") + + let fas = + references + |> Array.find (fun ra -> ra.Name = "FSharp.Analyzers.SDK") + fas.Version let skippedAssemblies = ref 0 @@ -221,7 +264,8 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC then true else - System.Threading.Interlocked.Increment skippedAssemblies |> ignore + System.Threading.Interlocked.Increment skippedAssemblies + |> ignore logger.LogError( "Trying to load {Name} which was built using SDK version {Version}. Expect {SdkVersion} instead. Assembly will be skipped.", @@ -272,7 +316,13 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC |> ignore let assemblyCount = Array.length analyzers - let analyzerCount = analyzers |> Seq.sumBy (snd >> Seq.length) + + let analyzerCount = + analyzers + |> Seq.sumBy ( + snd + >> Seq.length + ) { AnalyzerAssemblies = assemblyCount @@ -291,9 +341,15 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC member x.RunAnalyzers(ctx: 'TContext) : Async = x.RunAnalyzers(ctx, fun _ -> true) - member x.RunAnalyzers(ctx: 'TContext, analyzerPredicate: Client.RegisteredAnalyzer<'TContext> -> bool) : Async = + member x.RunAnalyzers + (ctx: 'TContext, analyzerPredicate: Client.RegisteredAnalyzer<'TContext> -> bool) + : Async + = async { - let analyzers = registeredAnalyzers.Values |> Seq.collect id |> Seq.filter analyzerPredicate + let analyzers = + registeredAnalyzers.Values + |> Seq.collect id + |> Seq.filter analyzerPredicate let! messagesPerAnalyzer = analyzers @@ -305,7 +361,7 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC return messages |> List.choose (fun message -> - let analyzerMessage = + let analyzerMessage = { Message = message Name = registeredAnalyzer.Name @@ -313,7 +369,7 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC ShortDescription = registeredAnalyzer.ShortDescription HelpUri = registeredAnalyzer.HelpUri } - + if Client.shouldIgnoreMessage ctx message then None else @@ -335,20 +391,29 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC member x.RunAnalyzersSafely(ctx: 'TContext) : Async = x.RunAnalyzersSafely(ctx, fun _ -> true) - member x.RunAnalyzersSafely(ctx: 'TContext, analyzerPredicate: Client.RegisteredAnalyzer<'TContext> -> bool) : Async = + member x.RunAnalyzersSafely + (ctx: 'TContext, analyzerPredicate: Client.RegisteredAnalyzer<'TContext> -> bool) + : Async + = async { - let analyzers = registeredAnalyzers.Values |> Seq.collect id |> Seq.filter analyzerPredicate + let analyzers = + registeredAnalyzers.Values + |> Seq.collect id + |> Seq.filter analyzerPredicate let! results = analyzers |> Seq.map (fun registeredAnalyzer -> async { try - let! messages = - registeredAnalyzer.Analyzer ctx - + let! messages = registeredAnalyzer.Analyzer ctx + let messages = - messages |> List.filter (Client.shouldIgnoreMessage ctx >> not) + messages + |> List.filter ( + Client.shouldIgnoreMessage ctx + >> not + ) return { diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi index fd5d25d..d367f66 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.Client.fsi @@ -53,7 +53,9 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC /// The context (file) to analyze. /// A predicate function to filter which analyzers to run. /// list of messages. Ignores errors from the analyzers - member RunAnalyzers: ctx: 'TContext * analyzerPredicate: (Client.RegisteredAnalyzer<'TContext> -> bool) -> Async + member RunAnalyzers: + ctx: 'TContext * analyzerPredicate: (Client.RegisteredAnalyzer<'TContext> -> bool) -> + Async /// Runs all registered analyzers for given context (file). /// The context (file) to analyze. @@ -64,4 +66,6 @@ type Client<'TAttribute, 'TContext when 'TAttribute :> AnalyzerAttribute and 'TC /// The context (file) to analyze. /// A predicate function to filter which analyzers to run. /// list of results per analyzer which can either be messages or an exception. - member RunAnalyzersSafely: ctx: 'TContext * analyzerPredicate: (Client.RegisteredAnalyzer<'TContext> -> bool) -> Async + member RunAnalyzersSafely: + ctx: 'TContext * analyzerPredicate: (Client.RegisteredAnalyzer<'TContext> -> bool) -> + Async diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs index 3ff4c29..aab0db8 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fs @@ -20,7 +20,7 @@ type IgnoreComment = | RegionStart of startLine: int * codes: string list | RegionEnd of endLine: int -type AnalyzerIgnoreRange = +type AnalyzerIgnoreRange = | File | Range of commentStart: int * commentEnd: int | NextLine of commentLine: int @@ -39,7 +39,9 @@ module Ignore = [] let (|ParseRegexWithOptions|_|) options (pattern: string) (s: string) = match Regex.Match(s, pattern, options) with - | m when m.Success -> List.tail [ for x in m.Groups -> x.Value ] |> ValueSome + | m when m.Success -> + List.tail [ for x in m.Groups -> x.Value ] + |> ValueSome | _ -> ValueNone [] @@ -47,10 +49,13 @@ module Ignore = [] let (|SplitBy|_|) x (text: string) = - text.Split(x) |> Array.toList |> ValueSome + text.Split(x) + |> Array.toList + |> ValueSome let trimCodes (codes: string list) = - codes |> List.map (fun s -> s.Trim()) + codes + |> List.map (fun s -> s.Trim()) let tryGetIgnoreComment splitBy (sourceText: ISourceText) (ct: CommentTrivia) = match ct with @@ -58,56 +63,77 @@ module Ignore = | CommentTrivia.LineComment r -> // pattern to match is: // prefix: command [codes] - match sourceText.GetLineString(r.StartLine - 1) with + match + sourceText.GetLineString( + r.StartLine + - 1 + ) + with | ParseRegexCompiled @"fsharpanalyzer:\signore-line-next\s(.*)$" [ SplitBy splitBy codes ] -> - Some <| IgnoreComment.NextLine(r.StartLine, trimCodes codes) + Some + <| IgnoreComment.NextLine(r.StartLine, trimCodes codes) | ParseRegexCompiled @"fsharpanalyzer:\signore-line\s(.*)$" [ SplitBy splitBy codes ] -> - Some <| IgnoreComment.CurrentLine(r.StartLine, trimCodes codes) - | ParseRegexCompiled @"fsharpanalyzer:\signore-file\s(.*)$" [ SplitBy splitBy codes ] -> - Some <| IgnoreComment.File (trimCodes codes) + Some + <| IgnoreComment.CurrentLine(r.StartLine, trimCodes codes) + | ParseRegexCompiled @"fsharpanalyzer:\signore-file\s(.*)$" [ SplitBy splitBy codes ] -> + Some + <| IgnoreComment.File(trimCodes codes) | ParseRegexCompiled @"fsharpanalyzer:\signore-region-start\s(.*)$" [ SplitBy splitBy codes ] -> - Some <| IgnoreComment.RegionStart(r.StartLine, trimCodes codes) - | ParseRegexCompiled @"fsharpanalyzer:\signore-region-end.*$" _ -> Some <| IgnoreComment.RegionEnd r.StartLine + Some + <| IgnoreComment.RegionStart(r.StartLine, trimCodes codes) + | ParseRegexCompiled @"fsharpanalyzer:\signore-region-end.*$" _ -> + Some + <| IgnoreComment.RegionEnd r.StartLine | _ -> None let getIgnoreComments (sourceText: ISourceText) (comments: CommentTrivia list) = comments |> List.choose (tryGetIgnoreComment [| ',' |] sourceText) - let getIgnoreRanges (ignoreComments: IgnoreComment list) : Map = + let getIgnoreRanges + (ignoreComments: IgnoreComment list) + : Map + = let mutable codeToRanges = Map.empty - + let addRangeForCodes (codes: string list) (range: AnalyzerIgnoreRange) = for code in codes do - let existingRanges = Map.tryFind code codeToRanges |> Option.defaultValue [] - codeToRanges <- Map.add code (range :: existingRanges) codeToRanges - + let existingRanges = + Map.tryFind code codeToRanges + |> Option.defaultValue [] + + codeToRanges <- + Map.add + code + (range + :: existingRanges) + codeToRanges + let mutable rangeStack = [] - + for comment in ignoreComments do match comment with - | IgnoreComment.File codes -> - addRangeForCodes codes File - - | IgnoreComment.NextLine (line, codes) -> - addRangeForCodes codes (NextLine line) - - | IgnoreComment.CurrentLine (line, codes) -> - addRangeForCodes codes (CurrentLine line) - - | IgnoreComment.RegionStart (startLine, codes) -> - rangeStack <- (startLine, codes) :: rangeStack - + | IgnoreComment.File codes -> addRangeForCodes codes File + + | IgnoreComment.NextLine(line, codes) -> addRangeForCodes codes (NextLine line) + + | IgnoreComment.CurrentLine(line, codes) -> addRangeForCodes codes (CurrentLine line) + + | IgnoreComment.RegionStart(startLine, codes) -> + rangeStack <- + (startLine, codes) + :: rangeStack + | IgnoreComment.RegionEnd endLine -> match rangeStack with - | [] -> + | [] -> // Ignore END without matching START - do nothing // to-do: create analyzer for finding unmatched END comments () | (startLine, codes) :: rest -> rangeStack <- rest - addRangeForCodes codes (Range (startLine, endLine)) - + addRangeForCodes codes (Range(startLine, endLine)) + codeToRanges module EntityCache = @@ -139,7 +165,11 @@ module EntityCache = AssemblyContentType.Full let content = - AssemblyContent.GetAssemblyContent entityCache.Locking contentType fileName signatures + AssemblyContent.GetAssemblyContent + entityCache.Locking + contentType + fileName + signatures yield! content ] @@ -156,18 +186,24 @@ module Extensions = member x.ProjectFilePath = match x with - | FSharpReferencedProjectSnapshot.FSharpReference(snapshot = snapshot) -> snapshot.ProjectFileName |> Some + | FSharpReferencedProjectSnapshot.FSharpReference(snapshot = snapshot) -> + snapshot.ProjectFileName + |> Some | _ -> None type FSharpReferencedProject with member x.ProjectFilePath = match x with - | FSharpReferencedProject.FSharpReference(options = options) -> options.ProjectFileName |> Some + | FSharpReferencedProject.FSharpReference(options = options) -> + options.ProjectFileName + |> Some | _ -> None [] -[] +[] type AnalyzerAttribute(name: string, shortDescription: string, helpUri: string) = inherit Attribute() member val Name: string = name @@ -184,7 +220,9 @@ type AnalyzerAttribute(name: string, shortDescription: string, helpUri: string) else Some helpUri -[] +[] type CliAnalyzerAttribute ( [] name: string, @@ -196,7 +234,9 @@ type CliAnalyzerAttribute member _.Name = name -[] +[] type EditorAnalyzerAttribute ( [] name: string, @@ -227,17 +267,23 @@ type AnalyzerProjectOptions = member x.SourceFiles = match x with - | BackgroundCompilerOptions(options) -> options.SourceFiles |> Array.toList - | TransparentCompilerOptions(snapshot) -> snapshot.SourceFiles |> List.map (fun f -> f.FileName) + | BackgroundCompilerOptions(options) -> + options.SourceFiles + |> Array.toList + | TransparentCompilerOptions(snapshot) -> + snapshot.SourceFiles + |> List.map (fun f -> f.FileName) |> List.map System.IO.Path.GetFullPath member x.ReferencedProjectsPath = match x with | BackgroundCompilerOptions(options) -> - options.ReferencedProjects - |> Array.choose (fun p -> p.ProjectFilePath) - |> Array.toList - | TransparentCompilerOptions(snapshot) -> snapshot.ReferencedProjects |> List.choose (fun p -> p.ProjectFilePath) + options.ReferencedProjects + |> Array.choose (fun p -> p.ProjectFilePath) + |> Array.toList + | TransparentCompilerOptions(snapshot) -> + snapshot.ReferencedProjects + |> List.choose (fun p -> p.ProjectFilePath) member x.LoadTime = match x with @@ -246,7 +292,9 @@ type AnalyzerProjectOptions = member x.OtherOptions = match x with - | BackgroundCompilerOptions(options) -> options.OtherOptions |> Array.toList + | BackgroundCompilerOptions(options) -> + options.OtherOptions + |> Array.toList | TransparentCompilerOptions(snapshot) -> snapshot.OtherOptions type CliContext = @@ -349,7 +397,10 @@ module Utils = let currentFSharpCoreVersion = let currentAssembly = Assembly.GetExecutingAssembly() let references = currentAssembly.GetReferencedAssemblies() - let fc = references |> Array.tryFind (fun ra -> ra.Name = "FSharp.Core") + + let fc = + references + |> Array.tryFind (fun ra -> ra.Name = "FSharp.Core") match fc with | None -> failwith "FSharp.Core could not be found as a reference assembly of the SDK." diff --git a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi index e17396e..5579f3e 100644 --- a/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi +++ b/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi @@ -8,14 +8,16 @@ open FSharp.Compiler.Symbols open FSharp.Compiler.EditorServices open FSharp.Compiler.Text -type AnalyzerIgnoreRange = +type AnalyzerIgnoreRange = | File | Range of commentStart: int * commentEnd: int | NextLine of commentLine: int | CurrentLine of commentLine: int [] -[] +[] type AnalyzerAttribute = new: [ obj)>] name: string * @@ -58,7 +60,7 @@ type Context = type AnalyzerProjectOptions = | BackgroundCompilerOptions of FSharpProjectOptions | TransparentCompilerOptions of FSharpProjectSnapshot - + /// The current project name. member ProjectFileName: string /// The identifier of the current project. @@ -217,5 +219,5 @@ module Utils = fileName: string -> sourceText: ISourceText -> parseFileResults: FSharpParseFileResults * checkFileResults: FSharpCheckFileResults -> - projectOptions: AnalyzerProjectOptions -> - CliContext + projectOptions: AnalyzerProjectOptions -> + CliContext diff --git a/src/FSharp.Analyzers.SDK/TASTCollecting.fs b/src/FSharp.Analyzers.SDK/TASTCollecting.fs index 774d03d..8e75291 100644 --- a/src/FSharp.Analyzers.SDK/TASTCollecting.fs +++ b/src/FSharp.Analyzers.SDK/TASTCollecting.fs @@ -18,7 +18,9 @@ module TASTCollecting = abstract WalkAddressSet: lvalueExpr: FSharpExpr -> rvalueExpr: FSharpExpr -> unit default _.WalkAddressSet _ _ = () - abstract WalkApplication: funcExpr: FSharpExpr -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + abstract WalkApplication: + funcExpr: FSharpExpr -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + default _.WalkApplication _ _ _ = () abstract WalkCall: @@ -36,34 +38,55 @@ module TASTCollecting = default _.WalkCoerce _ _ = () abstract WalkFastIntegerForLoop: - startExpr: FSharpExpr -> limitExpr: FSharpExpr -> consumeExpr: FSharpExpr -> isUp: bool -> unit + startExpr: FSharpExpr -> + limitExpr: FSharpExpr -> + consumeExpr: FSharpExpr -> + isUp: bool -> + unit default _.WalkFastIntegerForLoop _ _ _ _ = () - abstract WalkILAsm: asmCode: string -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + abstract WalkILAsm: + asmCode: string -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + default _.WalkILAsm _ _ _ = () - abstract WalkILFieldGet: objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> unit + abstract WalkILFieldGet: + objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> unit + default _.WalkILFieldGet _ _ _ = () abstract WalkILFieldSet: - objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> valueExpr: FSharpExpr -> unit + objExprOpt: FSharpExpr option -> + fieldType: FSharpType -> + fieldName: string -> + valueExpr: FSharpExpr -> + unit default _.WalkILFieldSet _ _ _ _ = () - abstract WalkIfThenElse: guardExpr: FSharpExpr -> thenExpr: FSharpExpr -> elseExpr: FSharpExpr -> unit + abstract WalkIfThenElse: + guardExpr: FSharpExpr -> thenExpr: FSharpExpr -> elseExpr: FSharpExpr -> unit + default _.WalkIfThenElse _ _ _ = () - abstract WalkLambda: lambdaVar: FSharpMemberOrFunctionOrValue -> bodyExpr: FSharpExpr -> unit + abstract WalkLambda: + lambdaVar: FSharpMemberOrFunctionOrValue -> bodyExpr: FSharpExpr -> unit + default _.WalkLambda _ _ = () abstract WalkLet: - bindingVar: FSharpMemberOrFunctionOrValue -> bindingExpr: FSharpExpr -> bodyExpr: FSharpExpr -> unit + bindingVar: FSharpMemberOrFunctionOrValue -> + bindingExpr: FSharpExpr -> + bodyExpr: FSharpExpr -> + unit default _.WalkLet _ _ _ = () abstract WalkLetRec: - recursiveBindings: (FSharpMemberOrFunctionOrValue * FSharpExpr) list -> bodyExpr: FSharpExpr -> unit + recursiveBindings: (FSharpMemberOrFunctionOrValue * FSharpExpr) list -> + bodyExpr: FSharpExpr -> + unit default _.WalkLetRec _ _ = () @@ -74,11 +97,16 @@ module TASTCollecting = default _.WalkNewDelegate _ _ = () abstract WalkNewObject: - objType: FSharpMemberOrFunctionOrValue -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + objType: FSharpMemberOrFunctionOrValue -> + typeArgs: FSharpType list -> + argExprs: FSharpExpr list -> + unit default _.WalkNewObject _ _ _ = () - abstract WalkNewRecord: recordType: FSharpType -> argExprs: FSharpExpr list -> exprRange: range -> unit + abstract WalkNewRecord: + recordType: FSharpType -> argExprs: FSharpExpr list -> exprRange: range -> unit + default _.WalkNewRecord _ _ _ = () abstract WalkNewTuple: tupleType: FSharpType -> argExprs: FSharpExpr list -> unit @@ -93,7 +121,10 @@ module TASTCollecting = default _.WalkQuote _ = () abstract WalkFSharpFieldGet: - objExprOpt: FSharpExpr option -> recordOrClassType: FSharpType -> fieldInfo: FSharpField -> unit + objExprOpt: FSharpExpr option -> + recordOrClassType: FSharpType -> + fieldInfo: FSharpField -> + unit default _.WalkFSharpFieldGet _ _ _ = () @@ -122,18 +153,26 @@ module TASTCollecting = default _.WalkTryWith _ _ _ _ _ = () - abstract WalkTupleGet: tupleType: FSharpType -> tupleElemIndex: int -> tupleExpr: FSharpExpr -> unit + abstract WalkTupleGet: + tupleType: FSharpType -> tupleElemIndex: int -> tupleExpr: FSharpExpr -> unit + default _.WalkTupleGet _ _ _ = () abstract WalkDecisionTree: - decisionExpr: FSharpExpr -> decisionTargets: (FSharpMemberOrFunctionOrValue list * FSharpExpr) list -> unit + decisionExpr: FSharpExpr -> + decisionTargets: (FSharpMemberOrFunctionOrValue list * FSharpExpr) list -> + unit default _.WalkDecisionTree _ _ = () - abstract WalkDecisionTreeSuccess: decisionTargetIdx: int -> decisionTargetExprs: FSharpExpr list -> unit + abstract WalkDecisionTreeSuccess: + decisionTargetIdx: int -> decisionTargetExprs: FSharpExpr list -> unit + default _.WalkDecisionTreeSuccess _ _ = () - abstract WalkTypeLambda: genericParam: FSharpGenericParameter list -> bodyExpr: FSharpExpr -> unit + abstract WalkTypeLambda: + genericParam: FSharpGenericParameter list -> bodyExpr: FSharpExpr -> unit + default _.WalkTypeLambda _ _ = () abstract WalkTypeTest: ty: FSharpType -> inpExpr: FSharpExpr -> unit @@ -158,7 +197,9 @@ module TASTCollecting = default _.WalkUnionCaseGet _ _ _ _ = () - abstract WalkUnionCaseTest: unionExpr: FSharpExpr -> unionType: FSharpType -> unionCase: FSharpUnionCase -> unit + abstract WalkUnionCaseTest: + unionExpr: FSharpExpr -> unionType: FSharpType -> unionCase: FSharpUnionCase -> unit + default _.WalkUnionCaseTest _ _ _ = () abstract WalkUnionCaseTag: unionExpr: FSharpExpr -> unionType: FSharpType -> unit @@ -184,7 +225,9 @@ module TASTCollecting = default _.WalkTraitCall _ _ _ _ _ _ = () - abstract WalkValueSet: valToSet: FSharpMemberOrFunctionOrValue -> valueExpr: FSharpExpr -> unit + abstract WalkValueSet: + valToSet: FSharpMemberOrFunctionOrValue -> valueExpr: FSharpExpr -> unit + default _.WalkValueSet _ _ = () abstract WalkWhileLoop: guardExpr: FSharpExpr -> bodyExpr: FSharpExpr -> unit @@ -220,13 +263,25 @@ module TASTCollecting = visitExpr handler funcExpr visitExprs handler argExprs | Call(objExprOpt, memberOrFunc, objExprTypeArgs, memberOrFuncTypeArgs, argExprs) -> - handler.WalkCall objExprOpt memberOrFunc objExprTypeArgs memberOrFuncTypeArgs argExprs e.Range + handler.WalkCall + objExprOpt + memberOrFunc + objExprTypeArgs + memberOrFuncTypeArgs + argExprs + e.Range + visitObjArg handler objExprOpt visitExprs handler argExprs | Coerce(targetType, inpExpr) -> handler.WalkCoerce targetType inpExpr visitExpr handler inpExpr - | FastIntegerForLoop(startExpr, limitExpr, consumeExpr, isUp, _debugPointAtFor, _debugPointAtInOrTo) -> + | FastIntegerForLoop(startExpr, + limitExpr, + consumeExpr, + isUp, + _debugPointAtFor, + _debugPointAtInOrTo) -> handler.WalkFastIntegerForLoop startExpr limitExpr consumeExpr isUp visitExpr handler startExpr visitExpr handler limitExpr @@ -254,10 +309,16 @@ module TASTCollecting = visitExpr handler bodyExpr | LetRec(recursiveBindings, bodyExpr) -> let recursiveBindings' = - recursiveBindings |> List.map (fun (mfv, expr, _dp) -> (mfv, expr)) + recursiveBindings + |> List.map (fun (mfv, expr, _dp) -> (mfv, expr)) handler.WalkLetRec recursiveBindings' bodyExpr - List.iter (snd >> visitExpr handler) recursiveBindings' + + List.iter + (snd + >> visitExpr handler) + recursiveBindings' + visitExpr handler bodyExpr | NewArray(arrayType, argExprs) -> handler.WalkNewArray arrayType argExprs @@ -295,7 +356,13 @@ module TASTCollecting = handler.WalkTryFinally bodyExpr finalizeExpr visitExpr handler bodyExpr visitExpr handler finalizeExpr - | TryWith(bodyExpr, filterVar, filterExpr, catchVar, catchExpr, _debugPointAtTry, _debugPointAtWith) -> + | TryWith(bodyExpr, + filterVar, + filterExpr, + catchVar, + catchExpr, + _debugPointAtTry, + _debugPointAtWith) -> handler.WalkTryWith bodyExpr filterVar filterExpr catchVar catchExpr visitExpr handler bodyExpr visitExpr handler catchExpr @@ -305,7 +372,11 @@ module TASTCollecting = | DecisionTree(decisionExpr, decisionTargets) -> handler.WalkDecisionTree decisionExpr decisionTargets visitExpr handler decisionExpr - List.iter (snd >> visitExpr handler) decisionTargets + + List.iter + (snd + >> visitExpr handler) + decisionTargets | DecisionTreeSuccess(decisionTargetIdx, decisionTargetExprs) -> handler.WalkDecisionTreeSuccess decisionTargetIdx decisionTargetExprs visitExprs handler decisionTargetExprs @@ -332,7 +403,11 @@ module TASTCollecting = handler.WalkObjectExpr objType baseCallExpr overrides interfaceImplementations visitExpr handler baseCallExpr List.iter (visitObjMember handler) overrides - List.iter (snd >> List.iter (visitObjMember handler)) interfaceImplementations + + List.iter + (snd + >> List.iter (visitObjMember handler)) + interfaceImplementations | TraitCall(sourceTypes, traitName, typeArgs, typeInstantiation, argTypes, argExprs) -> handler.WalkTraitCall sourceTypes traitName typeArgs typeInstantiation argTypes argExprs visitExprs handler argExprs @@ -356,10 +431,20 @@ module TASTCollecting = and visitObjMember f memb = visitExpr f memb.Body - let membersToIgnore = set [ "CompareTo"; "GetHashCode"; "Equals" ] + let membersToIgnore = + set + [ + "CompareTo" + "GetHashCode" + "Equals" + ] let exprTypesToIgnore = - set [ "Microsoft.FSharp.Core.int"; "Microsoft.FSharp.Core.bool" ] + set + [ + "Microsoft.FSharp.Core.int" + "Microsoft.FSharp.Core.bool" + ] let rec visitDeclaration f d = @@ -381,10 +466,16 @@ module TASTCollecting = try visitExpr f e with ex -> - logger.LogDebug("unhandled expression at {0}:{1}", e.Range.FileName, e.Range.ToString()) + logger.LogDebug( + "unhandled expression at {0}:{1}", + e.Range.FileName, + e.Range.ToString() + ) + logger.LogDebug("{0}", ex.Message) logger.LogDebug("{0}", ex.StackTrace) | FSharpImplementationFileDeclaration.InitAction e -> visitExpr f e let walkTast (walker: TypedTreeCollectorBase) (tast: FSharpImplementationFileContents) : unit = - tast.Declarations |> List.iter (visitDeclaration walker) + tast.Declarations + |> List.iter (visitDeclaration walker) diff --git a/src/FSharp.Analyzers.SDK/TASTCollecting.fsi b/src/FSharp.Analyzers.SDK/TASTCollecting.fsi index 4cd1380..1a299d6 100644 --- a/src/FSharp.Analyzers.SDK/TASTCollecting.fsi +++ b/src/FSharp.Analyzers.SDK/TASTCollecting.fsi @@ -21,8 +21,11 @@ module TASTCollecting = default WalkAddressSet: lvalueExpr: FSharpExpr -> rvalueExpr: FSharpExpr -> unit /// Overwriting this member hooks up a custom operation for applications. - abstract WalkApplication: funcExpr: FSharpExpr -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit - default WalkApplication: funcExpr: FSharpExpr -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + abstract WalkApplication: + funcExpr: FSharpExpr -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + + default WalkApplication: + funcExpr: FSharpExpr -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit /// Overwriting this member hooks up a custom operation for a call of a member or function. abstract WalkCall: @@ -49,47 +52,84 @@ module TASTCollecting = /// Overwriting this member hooks up a custom operation for fast integer loops. abstract WalkFastIntegerForLoop: - startExpr: FSharpExpr -> limitExpr: FSharpExpr -> consumeExpr: FSharpExpr -> isUp: bool -> unit + startExpr: FSharpExpr -> + limitExpr: FSharpExpr -> + consumeExpr: FSharpExpr -> + isUp: bool -> + unit default WalkFastIntegerForLoop: - startExpr: FSharpExpr -> limitExpr: FSharpExpr -> consumeExpr: FSharpExpr -> isUp: bool -> unit + startExpr: FSharpExpr -> + limitExpr: FSharpExpr -> + consumeExpr: FSharpExpr -> + isUp: bool -> + unit /// Overwriting this member hooks up a custom operation for ILAsm code. - abstract WalkILAsm: asmCode: string -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit - default WalkILAsm: asmCode: string -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + abstract WalkILAsm: + asmCode: string -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + + default WalkILAsm: + asmCode: string -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit /// Overwriting this member hooks up a custom operation for ILFieldGet expressions. - abstract WalkILFieldGet: objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> unit - default WalkILFieldGet: objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> unit + abstract WalkILFieldGet: + objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> unit + + default WalkILFieldGet: + objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> unit /// Overwriting this member hooks up a custom operation for ILFieldSet expressions. abstract WalkILFieldSet: - objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> valueExpr: FSharpExpr -> unit + objExprOpt: FSharpExpr option -> + fieldType: FSharpType -> + fieldName: string -> + valueExpr: FSharpExpr -> + unit default WalkILFieldSet: - objExprOpt: FSharpExpr option -> fieldType: FSharpType -> fieldName: string -> valueExpr: FSharpExpr -> unit + objExprOpt: FSharpExpr option -> + fieldType: FSharpType -> + fieldName: string -> + valueExpr: FSharpExpr -> + unit /// Overwriting this member hooks up a custom operation for if-then-else expressions. - abstract WalkIfThenElse: guardExpr: FSharpExpr -> thenExpr: FSharpExpr -> elseExpr: FSharpExpr -> unit - default WalkIfThenElse: guardExpr: FSharpExpr -> thenExpr: FSharpExpr -> elseExpr: FSharpExpr -> unit + abstract WalkIfThenElse: + guardExpr: FSharpExpr -> thenExpr: FSharpExpr -> elseExpr: FSharpExpr -> unit + + default WalkIfThenElse: + guardExpr: FSharpExpr -> thenExpr: FSharpExpr -> elseExpr: FSharpExpr -> unit /// Overwriting this member hooks up a custom operation for lambda expressions. - abstract WalkLambda: lambdaVar: FSharpMemberOrFunctionOrValue -> bodyExpr: FSharpExpr -> unit + abstract WalkLambda: + lambdaVar: FSharpMemberOrFunctionOrValue -> bodyExpr: FSharpExpr -> unit + default WalkLambda: lambdaVar: FSharpMemberOrFunctionOrValue -> bodyExpr: FSharpExpr -> unit /// Overwriting this member hooks up a custom operation for let expressions. abstract WalkLet: - bindingVar: FSharpMemberOrFunctionOrValue -> bindingExpr: FSharpExpr -> bodyExpr: FSharpExpr -> unit + bindingVar: FSharpMemberOrFunctionOrValue -> + bindingExpr: FSharpExpr -> + bodyExpr: FSharpExpr -> + unit default WalkLet: - bindingVar: FSharpMemberOrFunctionOrValue -> bindingExpr: FSharpExpr -> bodyExpr: FSharpExpr -> unit + bindingVar: FSharpMemberOrFunctionOrValue -> + bindingExpr: FSharpExpr -> + bodyExpr: FSharpExpr -> + unit /// Overwriting this member hooks up a custom operation for let-rec expressions. abstract WalkLetRec: - recursiveBindings: (FSharpMemberOrFunctionOrValue * FSharpExpr) list -> bodyExpr: FSharpExpr -> unit + recursiveBindings: (FSharpMemberOrFunctionOrValue * FSharpExpr) list -> + bodyExpr: FSharpExpr -> + unit default WalkLetRec: - recursiveBindings: (FSharpMemberOrFunctionOrValue * FSharpExpr) list -> bodyExpr: FSharpExpr -> unit + recursiveBindings: (FSharpMemberOrFunctionOrValue * FSharpExpr) list -> + bodyExpr: FSharpExpr -> + unit /// Overwriting this member hooks up a custom operation for a new array instance. abstract WalkNewArray: arrayType: FSharpType -> argExprs: FSharpExpr list -> unit @@ -101,14 +141,23 @@ module TASTCollecting = /// Overwriting this member hooks up a custom operation for a new object. abstract WalkNewObject: - objType: FSharpMemberOrFunctionOrValue -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + objType: FSharpMemberOrFunctionOrValue -> + typeArgs: FSharpType list -> + argExprs: FSharpExpr list -> + unit default WalkNewObject: - objType: FSharpMemberOrFunctionOrValue -> typeArgs: FSharpType list -> argExprs: FSharpExpr list -> unit + objType: FSharpMemberOrFunctionOrValue -> + typeArgs: FSharpType list -> + argExprs: FSharpExpr list -> + unit /// Overwriting this member hooks up a custom operation for the creation of a new record instance. - abstract WalkNewRecord: recordType: FSharpType -> argExprs: FSharpExpr list -> exprRange: range -> unit - default WalkNewRecord: recordType: FSharpType -> argExprs: FSharpExpr list -> exprRange: range -> unit + abstract WalkNewRecord: + recordType: FSharpType -> argExprs: FSharpExpr list -> exprRange: range -> unit + + default WalkNewRecord: + recordType: FSharpType -> argExprs: FSharpExpr list -> exprRange: range -> unit /// Overwriting this member hooks up a custom operation for the creation of a new tuple instance. abstract WalkNewTuple: tupleType: FSharpType -> argExprs: FSharpExpr list -> unit @@ -127,10 +176,16 @@ module TASTCollecting = /// Overwriting this member hooks up a custom operation for field-get expressions. abstract WalkFSharpFieldGet: - objExprOpt: FSharpExpr option -> recordOrClassType: FSharpType -> fieldInfo: FSharpField -> unit + objExprOpt: FSharpExpr option -> + recordOrClassType: FSharpType -> + fieldInfo: FSharpField -> + unit default WalkFSharpFieldGet: - objExprOpt: FSharpExpr option -> recordOrClassType: FSharpType -> fieldInfo: FSharpField -> unit + objExprOpt: FSharpExpr option -> + recordOrClassType: FSharpType -> + fieldInfo: FSharpField -> + unit /// Overwriting this member hooks up a custom operation for field-set expressions. abstract WalkFSharpFieldSet: @@ -173,23 +228,36 @@ module TASTCollecting = unit /// Overwriting this member hooks up a custom operation for tuple-get expressions. - abstract WalkTupleGet: tupleType: FSharpType -> tupleElemIndex: int -> tupleExpr: FSharpExpr -> unit - default WalkTupleGet: tupleType: FSharpType -> tupleElemIndex: int -> tupleExpr: FSharpExpr -> unit + abstract WalkTupleGet: + tupleType: FSharpType -> tupleElemIndex: int -> tupleExpr: FSharpExpr -> unit + + default WalkTupleGet: + tupleType: FSharpType -> tupleElemIndex: int -> tupleExpr: FSharpExpr -> unit /// Overwriting this member hooks up a custom operation for decision trees. abstract WalkDecisionTree: - decisionExpr: FSharpExpr -> decisionTargets: (FSharpMemberOrFunctionOrValue list * FSharpExpr) list -> unit + decisionExpr: FSharpExpr -> + decisionTargets: (FSharpMemberOrFunctionOrValue list * FSharpExpr) list -> + unit default WalkDecisionTree: - decisionExpr: FSharpExpr -> decisionTargets: (FSharpMemberOrFunctionOrValue list * FSharpExpr) list -> unit + decisionExpr: FSharpExpr -> + decisionTargets: (FSharpMemberOrFunctionOrValue list * FSharpExpr) list -> + unit /// Overwriting this member hooks up a custom operation for decision tree success expressions. - abstract WalkDecisionTreeSuccess: decisionTargetIdx: int -> decisionTargetExprs: FSharpExpr list -> unit - default WalkDecisionTreeSuccess: decisionTargetIdx: int -> decisionTargetExprs: FSharpExpr list -> unit + abstract WalkDecisionTreeSuccess: + decisionTargetIdx: int -> decisionTargetExprs: FSharpExpr list -> unit + + default WalkDecisionTreeSuccess: + decisionTargetIdx: int -> decisionTargetExprs: FSharpExpr list -> unit /// Overwriting this member hooks up a custom operation for type lambdas. - abstract WalkTypeLambda: genericParam: FSharpGenericParameter list -> bodyExpr: FSharpExpr -> unit - default WalkTypeLambda: genericParam: FSharpGenericParameter list -> bodyExpr: FSharpExpr -> unit + abstract WalkTypeLambda: + genericParam: FSharpGenericParameter list -> bodyExpr: FSharpExpr -> unit + + default WalkTypeLambda: + genericParam: FSharpGenericParameter list -> bodyExpr: FSharpExpr -> unit /// Overwriting this member hooks up a custom operation for type tests. abstract WalkTypeTest: ty: FSharpType -> inpExpr: FSharpExpr -> unit @@ -228,8 +296,11 @@ module TASTCollecting = unit /// Overwriting this member hooks up a custom operation for union case test expressions. - abstract WalkUnionCaseTest: unionExpr: FSharpExpr -> unionType: FSharpType -> unionCase: FSharpUnionCase -> unit - default WalkUnionCaseTest: unionExpr: FSharpExpr -> unionType: FSharpType -> unionCase: FSharpUnionCase -> unit + abstract WalkUnionCaseTest: + unionExpr: FSharpExpr -> unionType: FSharpType -> unionCase: FSharpUnionCase -> unit + + default WalkUnionCaseTest: + unionExpr: FSharpExpr -> unionType: FSharpType -> unionCase: FSharpUnionCase -> unit /// Overwriting this member hooks up a custom operation for union case tag expressions. abstract WalkUnionCaseTag: unionExpr: FSharpExpr -> unionType: FSharpType -> unit @@ -270,8 +341,11 @@ module TASTCollecting = unit /// Overwriting this member hooks up a custom operation for value sets expressions. - abstract WalkValueSet: valToSet: FSharpMemberOrFunctionOrValue -> valueExpr: FSharpExpr -> unit - default WalkValueSet: valToSet: FSharpMemberOrFunctionOrValue -> valueExpr: FSharpExpr -> unit + abstract WalkValueSet: + valToSet: FSharpMemberOrFunctionOrValue -> valueExpr: FSharpExpr -> unit + + default WalkValueSet: + valToSet: FSharpMemberOrFunctionOrValue -> valueExpr: FSharpExpr -> unit /// Overwriting this member hooks up a custom operation for while loops. abstract WalkWhileLoop: guardExpr: FSharpExpr -> bodyExpr: FSharpExpr -> unit