From e02e55e5fdf70511cfb53cab64f368b972272406 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Fri, 26 Sep 2025 23:37:56 +0200 Subject: [PATCH 01/24] Add Roslyn analyzers to detect incorrect usage of BenchmarkDotNet --- .gitignore | 3 + BenchmarkDotNet.Analyzers.sln | 55 + NuGet.Config | 39 +- .../BenchmarkDotNet.Analyzers.Package.csproj | 36 + .../tools/install.ps1 | 250 ++++ .../tools/uninstall.ps1 | 257 ++++ .../ArgumentsAttributeAnalyzerTests.cs | 750 ++++++++++ ...GeneralParameterAttributesAnalyzerTests.cs | 1332 +++++++++++++++++ .../ParamsAllValuesAttributeAnalyzerTests.cs | 237 +++ .../ParamsAttributeAnalyzerTests.cs | 678 +++++++++ .../BenchmarkRunner/RunAnalyzerTests.cs | 124 ++ .../General/BenchmarkClassAnalyzerTests.cs | 777 ++++++++++ .../BenchmarkDotNet.Analyzers.Tests.csproj | 53 + ...kDotNet.Analyzers.Tests.csproj.DotSettings | 5 + .../Fixtures/AnalyzerTestFixture.cs | 234 +++ .../Extensions/TheoryDataExtensions.cs | 14 + .../Generators/CombinationsGenerator.cs | 66 + .../FieldOrPropertyDeclarationTheoryData.cs | 19 + ...NonPublicClassAccessModifiersTheoryData.cs | 20 + ...licClassMemberAccessModifiersTheoryData.cs | 19 + ...PropertySetterAccessModifiersTheoryData.cs | 18 + .../BenchmarkDotNet.Analyzers.Vsix.csproj | 47 + .../source.extension.vsixmanifest | 24 + .../AnalyzerHelper.cs | 73 + .../AnalyzerReleases.Shipped.md | 2 + .../AnalyzerReleases.Unshipped.md | 35 + .../Attributes/ArgumentsAttributeAnalyzer.cs | 331 ++++ .../GeneralParameterAttributesAnalyzer.cs | 329 ++++ .../ParamsAllValuesAttributeAnalyzer.cs | 133 ++ .../Attributes/ParamsAttributeAnalyzer.cs | 283 ++++ .../BenchmarkDotNet.Analyzers.csproj | 38 + ...nchmarkDotNetAnalyzerResources.Designer.cs | 778 ++++++++++ .../BenchmarkDotNetAnalyzerResources.resx | 355 +++++ .../BenchmarkRunner/RunAnalyzer.cs | 128 ++ .../DiagnosticIds.cs | 34 + .../General/BenchmarkClassAnalyzer.cs | 286 ++++ .../BenchmarkDotNet.Annotations.csproj | 6 + .../BenchmarkDotNet.Disassembler.x64.csproj | 45 +- .../BenchmarkDotNet.Disassembler.x86.csproj | 57 +- 39 files changed, 7904 insertions(+), 66 deletions(-) create mode 100644 BenchmarkDotNet.Analyzers.sln create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/BenchmarkDotNet.Analyzers.Package.csproj create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/install.ps1 create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/uninstall.ps1 create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/BenchmarkDotNet.Analyzers.Vsix.csproj create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/source.extension.vsixmanifest create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Shipped.md create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/DiagnosticIds.cs create mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs diff --git a/.gitignore b/.gitignore index 96012efb78..ac368b610f 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,9 @@ src/BenchmarkDotNet/Disassemblers/BenchmarkDotNet.Disassembler.*.nupkg # Visual Studio 2015 cache/options directory .vs/ +# VSCode directory +.vscode/ + # Cake tools/** .dotnet diff --git a/BenchmarkDotNet.Analyzers.sln b/BenchmarkDotNet.Analyzers.sln new file mode 100644 index 0000000000..b82b4873d2 --- /dev/null +++ b/BenchmarkDotNet.Analyzers.sln @@ -0,0 +1,55 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31710.8 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.csproj", "{B7664DD5-DCDB-4324-91A9-16D242CC4498}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers.Package", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.Package\BenchmarkDotNet.Analyzers.Package.csproj", "{B7500BDE-4DC7-4858-968F-11889AA4F289}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers.Tests", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.Tests\BenchmarkDotNet.Analyzers.Tests.csproj", "{5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers.Vsix", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.Vsix\BenchmarkDotNet.Analyzers.Vsix.csproj", "{F3163F56-3EC2-44F8-872E-02E3114D7849}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet", "src\BenchmarkDotNet\BenchmarkDotNet.csproj", "{B5F58AA0-88F8-4C8C-B734-E1217E23079E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Annotations", "src\BenchmarkDotNet.Annotations\BenchmarkDotNet.Annotations.csproj", "{F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Release|Any CPU.Build.0 = Release|Any CPU + {B7500BDE-4DC7-4858-968F-11889AA4F289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7500BDE-4DC7-4858-968F-11889AA4F289}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7500BDE-4DC7-4858-968F-11889AA4F289}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7500BDE-4DC7-4858-968F-11889AA4F289}.Release|Any CPU.Build.0 = Release|Any CPU + {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Release|Any CPU.Build.0 = Release|Any CPU + {F3163F56-3EC2-44F8-872E-02E3114D7849}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F3163F56-3EC2-44F8-872E-02E3114D7849}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F3163F56-3EC2-44F8-872E-02E3114D7849}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F3163F56-3EC2-44F8-872E-02E3114D7849}.Release|Any CPU.Build.0 = Release|Any CPU + {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Release|Any CPU.Build.0 = Release|Any CPU + {F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {27411BE6-6445-400B-AB04-29B993B39CFF} + EndGlobalSection +EndGlobal diff --git a/NuGet.Config b/NuGet.Config index 7507704b8b..0ed38438e6 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,18 +1,21 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/BenchmarkDotNet.Analyzers.Package.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/BenchmarkDotNet.Analyzers.Package.csproj new file mode 100644 index 0000000000..cfd362d45a --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/BenchmarkDotNet.Analyzers.Package.csproj @@ -0,0 +1,36 @@ + + + + netstandard2.0 + false + true + true + + + + BenchmarkDotNet.Analyzers + 1.0.0.0 + + Analyzers for the BenchmarkDotNet package. + + true + true + + $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput + + + + + + + + + + + + + + + + + diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/install.ps1 b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/install.ps1 new file mode 100644 index 0000000000..be2f74c118 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/install.ps1 @@ -0,0 +1,250 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not install analyzers via install.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + if (Test-Path $analyzersPath) + { + # Install the language agnostic analyzers. + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Install language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} +# SIG # Begin signature block +# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA/i+qRUHsWzI0s +# FVk99zLgt/HOEQ33uvkFsWtHTHZgf6CCDYEwggX/MIID56ADAgECAhMzAAAB32vw +# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn +# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw +# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS +# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG +# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh +# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH +# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS +# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp +# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok +# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 +# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao +# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD +# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt +# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G +# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ +# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 +# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgRjg7DcI6 +# uhYfXWwAQ6hK0mPW7iyr2tzHR0DHSDJkscIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQCt+qfRFudb8jpGDEFmJnD3uqC44a6SmTO+nFx0s2tf +# o+OTFNnRhwUMuTz/KVCfSwTzD/p2mD6JmuOuNmtTKyanjVNbYvwzO7LWedJh3d+T +# WEKZ6KYFbm6rPM+9DIINZNuK5Vb15vvNBUYI4PgFrSLGXdmRIB5xGiLRYWM/UET/ +# Sb4T6edTQKYx4vkDX9UcM4cYCx1u59hR6FgdCCHzU9/ZHYqN0AhBrHrTWGuqxx3E +# Oo0wdYJMRLH8zPFbzRNcG4qVlq95yDtWqzNcYMWybejIZenDg6am3ZldQFMoGU38 +# 76WP/a5unw8DKpkL/4ZO686G9Boh5Jc6U8mMGlLctW43oYIS8TCCEu0GCisGAQQB +# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIITxzR9P1o4UBFnvUGa4yCqvmQhov1ZeA/XM1qBB +# 5/5xAgZgieV7UmkYEzIwMjEwNTEzMTkwNDA3LjM3MVowBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjpEOURFLUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABYfWiM16gKiRpAAAA +# AAFhMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTIxMDExNDE5MDIyMVoXDTIyMDQxMTE5MDIyMVowgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpEOURF +# LUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJeInahBrU//GzTqhxUy +# AC8UXct6UJCkb2xEZKV3gjggmLAheBrxJk7tH+Pw2tTcyarLRfmV2xo5oBk5pW/O +# cDc/n/TcTeQU6JIN5PlTcn0C9RlKQ6t9OuU/WAyAxGTjKE4ENnUjXtxiNlD/K2ZG +# MLvjpROBKh7TtkUJK6ZGWw/uTRabNBxRg13TvjkGHXEUEDJ8imacw9BCeR9L6und +# r32tj4duOFIHD8m1es3SNN98Zq4IDBP3Ccb+HQgxpbeHIUlK0y6zmzIkvfN73Zxw +# fGvFv0/Max79WJY0cD8poCnZFijciWrf0eD1T2/+7HgewzrdxPdSFockUQ8QovID +# IYkCAwEAAaOCARswggEXMB0GA1UdDgQWBBRWHpqd1hv71SVj5LAdPfNE7PhLLzAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAQTA9bqVBmx5TTMhzj+Q8zWkPQXgCc +# SQiqy2YYWF0hWr5GEiN2LtA+EWdu1y8oysZau4CP7SzM8VTSq31CLJiOy39Z4RvE +# q2mr0EftFvmX2CxQ7ZyzrkhWMZaZQLkYbH5oabIFwndW34nh980BOY395tfnNS/Y +# 6N0f+jXdoFn7fI2c43TFYsUqIPWjOHJloMektlD6/uS6Zn4xse/lItFm+fWOcB2A +# xyXEB3ZREeSg9j7+GoEl1xT/iJuV/So7TlWdwyacQu4lv3MBsvxzRIbKhZwrDYog +# moyJ+rwgQB8mKS4/M1SDRtIptamoTFJ56Tk6DuUXx1JudToelgjEZPa5MIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpE +# OURFLUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUAFW5ShAw5ekTEXvL/4V1s0rbDz3mggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AORHgWMwIhgPMjAyMTA1MTMxNDQzNDdaGA8yMDIxMDUxNDE0NDM0N1owdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA5EeBYwIBADAKAgEAAgIYbAIB/zAHAgEAAgIRJjAK +# AgUA5EjS4wIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJ7JAjVdIqigM77q +# X+WQlagNMRjqaFC99qAjVz3kuffmZJoaEZAUFl+ynf6+HFfOhtbygpb3Inb1ewPz +# sZH0SoRd1eGUpvXk0rzjFl8jKiV/FWTV/xJDdRyKf4I6Pl4hzA1gpsB0sNO3Qqr3 +# u8dTOzbh3DWucOQgfLBWoq3e/UuUMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAFh9aIzXqAqJGkAAAAAAWEwDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQgTf39WlLL62P8pSZibw+7Xw+a3N3OO0YRek+60a+WpNswgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCBhz4un6mkSLd/zA+0N5YLDGp4vW/VBtNW/lpmh +# tAk4bzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# YfWiM16gKiRpAAAAAAFhMCIEIIqi7Un+0eNrtRg58qtA+fKclBz8FTjtf7MCNv7U +# MEInMA0GCSqGSIb3DQEBCwUABIIBADPu+hGKCfhBayhOOzglyKPK9RBeNCsRzMKp +# Uymhnad7xNyu+78hq0nWjS/DWgiCYRSF4g0Hl2ls9AfHbz7vT0GLZelJOfaTW+M7 +# gA6HGctwkDhfQg1tG0+pJ5D+pdCrvlMzp4K0EF5pE8FSib3BWOIBu5Ja4D4IbmE4 +# 3kkbHw/FWQLJDEhPFLIpQg45p4dsMLlR39QaPXQpX3hu2Tp+LgzQYA+meIpt95W0 +# gKA/jb0H26x7TncDwyi5bgMt7cKDhkiLSm6y1yHDnd9yJa3XkbcU9Ez7MjEBvG35 +# RXImHA84+QsRDHzx8MlAjy8f3ln/JwUt/U/OtGl43qbTKt/ZX78= +# SIG # End signature block diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/uninstall.ps1 b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/uninstall.ps1 new file mode 100644 index 0000000000..af3d04f297 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/uninstall.ps1 @@ -0,0 +1,257 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall the language agnostic analyzers. + if (Test-Path $analyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + try + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + catch + { + + } + } + } + } +} +# SIG # Begin signature block +# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDC68wb97fg0QGL +# yXrxJhYfmibzcOh8caqC0uZprfczDaCCDYEwggX/MIID56ADAgECAhMzAAAB32vw +# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn +# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw +# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS +# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG +# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh +# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH +# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS +# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp +# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok +# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 +# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao +# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD +# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt +# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G +# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ +# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 +# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgF1ypFyzl +# AvvWGVCeXczrfpXmJNm9vpyjcwd4y4ivfqowQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQBtnC8PmVgmkVoKR15F39ljf7fpP29Vlo9dnBmOcDAw +# lHnt37zK9UIQSoyqiMhY/lt8iltz49NEj3D3ddXTXdIOXlfjUYvoIIrNUahgDF/Z +# 3iQVhBW6Me8FEF4ImSntGkkvf86Hp5dlLCrpdDDWVVJkCxRGvCFXC0aNPHlFHRF1 +# Hbqqstm9xbYliNTk3BhJoo56j8XO61JkNEjzva3gemuG4dVhFSz9OF5HsjPTpTiJ +# pg5//GDE5xeho5kwxk8Algyfac3vseJXLr6388cIP556sruynQumo0+K0cxyxhVI +# cyvYlZAi4WzRpNNnWP8VXFb0ITFbgr0SLBIYUrQGFr2QoYIS8TCCEu0GCisGAQQB +# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIJp8zyESzF9BGcJWXqSm1vrCJ/LFDEjr5Yc3y0OW +# 46OzAgZgieX0wY0YEzIwMjEwNTEzMTkwNDA4Ljg1NlowBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjo0RDJGLUUzREQtQkVFRjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABX8OuZVblU1jsAAAA +# AAFfMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTIxMDExNDE5MDIxOVoXDTIyMDQxMTE5MDIxOVowgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0RDJG +# LUUzREQtQkVFRjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALw9efmC2WQ9uaaw7k4g +# xHSSCEoJLk22FTAaF8jYbAMkQC6DQF0WPnIheLM1ERTuQ9FWbglf0mXbDd2KjezR +# Nlz53ycJIReiGUQOnw5vd4TgjLUxL17g3K0MP2nNhY/LyP98Ml/40X905egDbiIn +# dZdtHiDb1xfY17a7v1j9o3muc+MCgFt9fO+U4CDNUpMMMQJFr/9QlU4YdJawjbyK +# fK3Ltvqfq3lvgK0/HphiDtX5ch3beGNBKowKSTXhft8pwuXQProutWgB5PZmAN8X +# ZhACo4jWi/a0zgAJJcBqoXvS6InrWcH/Eqi/qVaj8Vs56/Z/6kaYZZu/1mSzLn5E +# ALMCAwEAAaOCARswggEXMB0GA1UdDgQWBBQl7OnTlc0rgZ7Fd7qlDFguYTU49TAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAOgtfZLJYSbsE3W73nd0hLnqQqHSFl +# 2spHxzeXxM4uJT2uAVk/SLVzzjvZemUDBeOedKeXG8hctprpoQMpU3gbsNUnUaDe +# sDcmR+eELCwYa+VBkUCqsIGJmQlDwuDwNa67kyCEPyPW59Yu2w/djNrwNWSjtuRw +# fUFoDkjYyDjnXD0josi67qxJgW8rRqjl9a62hGzlzgE+aVLTT5IhK5z2X62Lph8j +# 9f4XjtCPnyeFKFmgBWHPY1HbbjUHfg91StCLxueH2LjZoQETWOJ+pxElicXwVP5B +# 0wlWkiauwug3rTKnDb5WKUb2llsnQgaegV+MQjMI7K6v+spvsMgRjPlhMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0 +# RDJGLUUzREQtQkVFRjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUA+gfSqjdAndOFEaXOQyBCdupmQoeggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AORHga4wIhgPMjAyMTA1MTMxNDQ1MDJaGA8yMDIxMDUxNDE0NDUwMlowdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA5EeBrgIBADAKAgEAAgIVQwIB/zAHAgEAAgIRLTAK +# AgUA5EjTLgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAHcp665K7IGKPBn3 +# +FtC8KgxAbGQbXp2oOCS8f+3Pu54iO/Ek4BR2F/YsanLXnr6nM/J1Qd9KVu8C6UJ +# a41UfaEkEwbkLWBdEbr7bTbFReOfVlhObtYW2IrLXREmyeEgnce+7cGZZ0QLERSu +# iQTNffmseSvEiARxDVXSpPsO3WsaMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAFfw65lVuVTWOwAAAAAAV8wDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQggG5jZDP+yZVGKxHthsrzCc0tL7YUokx1zgz/FJhsf0gwgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCDQzXq1KxGsLuj0szktrnlhIRqmbwp5bVGc6Bu6 +# hglMXDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# X8OuZVblU1jsAAAAAAFfMCIEIPpiRGFlqZgftkQa9jNO7zYg4bdv1ZAveCzZoH8k +# BxL1MA0GCSqGSIb3DQEBCwUABIIBAKgTDD8eJkOyyk9VEeHVmR8wisdcPHgu4vJa +# yZx4290MVwWAZ8aFYnsrmocb1csIEFlKelbIeB2gJKrdFQwoLiTyN1suVC8mOToz +# FICo6csyu9i5UTNYvidCZXaOZDom6cqamlCjA83npO+UERYvcldkiS3sjK8ejk01 +# OKwCMT8qxws2Csa3D/lm46ig5D4I0a5HccUiaoVMXk3RJtypvyutoH27pBAu+PhW +# jwY0yW4YgS+ZaNgmlCSNkywUKzM3GHpVZd9hAmiIehr52FXIjtGHg6t5VOWlUVXT +# CP5QCuqwUB4RxJUNJ1+yYuLCryZ0eYurv3Kw2yuTOvqyvVAyu9c= +# SIG # End signature block diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs new file mode 100644 index 0000000000..a346032365 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -0,0 +1,750 @@ +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes +{ + using Fixtures; + + using Analyzers.Attributes; + + using Xunit; + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Threading.Tasks; + + public class ArgumentsAttributeAnalyzerTests + { + public class General : AnalyzerTestFixture + { + [Theory, CombinatorialData] + public async Task A_method_annotated_with_an_arguments_attribute_with_no_values_and_the_benchmark_attribute_and_having_no_parameters_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(EmptyArgumentsAttributeUsages))] string emptyArgumentsAttributeUsage, + [CombinatorialRange(1, 2)] int attributeUsageMultiplier) + { + var emptyArgumentsAttributeUsages = new List(); + + for (var i = 0; i < attributeUsageMultiplier; i++) + { + emptyArgumentsAttributeUsages.Add(emptyArgumentsAttributeUsage); + } + + var testCode = /* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {string.Join("\n", emptyArgumentsAttributeUsages)} + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + public static IEnumerable EmptyArgumentsAttributeUsages() + { + yield return "[Arguments]"; + yield return "[Arguments()]"; + yield return "[Arguments(Priority = 1)]"; + + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "[Arguments({0}new object[] {{ }}{1})]", + "[Arguments({0}new object[0]{1})]", +#if NET8_0_OR_GREATER + "[Arguments({0}[]{1})]", +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + } + + public class RequiresBenchmarkAttribute : AnalyzerTestFixture + { + public RequiresBenchmarkAttribute() : base(ArgumentsAttributeAnalyzer.RequiresBenchmarkAttributeRule) { } + + [Theory] + [MemberData(nameof(ArgumentAttributeUsagesListLength))] + public async Task A_method_annotated_with_at_least_one_arguments_attribute_together_with_the_benchmark_attribute_should_not_trigger_diagnostic(int argumentAttributeUsagesListLength) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + [{string.Join("]\n[", ArgumentAttributeUsages.Take(argumentAttributeUsagesListLength))}] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ArgumentAttributeUsagesListLength))] + public async Task A_method_with_at_least_one_arguments_attribute_but_no_benchmark_attribute_should_trigger_diagnostic(int argumentAttributeUsagesListLength) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join("\n", ArgumentAttributeUsages.Take(argumentAttributeUsagesListLength).Select((a, i) => $"[{{|#{i}:{a}|}}]"))} + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + + for (var i = 0; i < argumentAttributeUsagesListLength; i++) + { + AddExpectedDiagnostic(i); + } + + await RunAsync(); + } + + public static TheoryData ArgumentAttributeUsagesListLength => new TheoryData(Enumerable.Range(1, ArgumentAttributeUsages.Count)); + + private static ReadOnlyCollection ArgumentAttributeUsages => new List { + "Arguments", + "Arguments()", + "Arguments(42, \"test\")" + }.AsReadOnly(); + } + + public class MethodWithoutAttributeMustHaveNoParameters : AnalyzerTestFixture + { + public MethodWithoutAttributeMustHaveNoParameters() : base(ArgumentsAttributeAnalyzer.MethodWithoutAttributeMustHaveNoParametersRule) { } + + [Fact] + public async Task A_method_annotated_with_an_arguments_attribute_and_the_benchmark_attribute_and_having_parameters_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + [Arguments(42, ""test"")] + public void BenchmarkMethod(int a, string b) + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ParametersListLength))] + public async Task A_method_with_parameters_and_no_arguments_or_benchmark_attributes_should_not_trigger_diagnostic(int parametersListLength) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + public void BenchmarkMethod({string.Join(", ", Parameters.Take(parametersListLength))}) + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ParametersListLength))] + public async Task A_method_annotated_with_the_benchmark_attribute_but_no_arguments_attribute_with_parameters_should_trigger_diagnostic(int parametersListLength) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + public void BenchmarkMethod({{|#0:{string.Join(", ", Parameters.Take(parametersListLength))}|}}) + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(); + + await RunAsync(); + } + + public static TheoryData ParametersListLength => new TheoryData(Enumerable.Range(1, Parameters.Count)); + + private static ReadOnlyCollection Parameters => new List { + "int a", + "string b", + "bool c" + }.AsReadOnly(); + } + + public class MustHaveMatchingValueCount : AnalyzerTestFixture + { + public MustHaveMatchingValueCount() : base(ArgumentsAttributeAnalyzer.MustHaveMatchingValueCountRule) { } + + [Fact] + public async Task A_method_not_annotated_with_any_arguments_attributes_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ArgumentsAttributeUsages))] + public async Task Having_a_matching_value_count_should_not_trigger_diagnostic(string argumentsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void BenchmarkMethod(string a, bool b) + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(EmptyArgumentsAttributeUsagesWithLocationMarker))] + public async Task Having_a_mismatching_empty_value_count_targeting_a_method_with_parameters_should_trigger_diagnostic(string argumentsAttributeUsage) + { + const string benchmarkMethodName = "BenchmarkMethod"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void {benchmarkMethodName}(string a) + {{ + + }} +}}"; + TestCode = testCode; + AddDefaultExpectedDiagnostic(1, "", benchmarkMethodName, 0); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Having_a_mismatching_value_count_should_trigger_diagnostic([CombinatorialMemberData(nameof(ArgumentsAttributeUsagesWithLocationMarker))] string argumentsAttributeUsage, + [CombinatorialMemberData(nameof(ParameterLists))] (string Parameters, int ParameterCount, string PluralSuffix) parameterData) + { + const string benchmarkMethodName = "BenchmarkMethod"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void {benchmarkMethodName}({parameterData.Parameters}) + {{ + + }} +}}"; + TestCode = testCode; + AddExpectedDiagnostic(0, parameterData.ParameterCount, parameterData.PluralSuffix, benchmarkMethodName, 2); + AddExpectedDiagnostic(1, parameterData.ParameterCount, parameterData.PluralSuffix, benchmarkMethodName, 3); + + await RunAsync(); + } + + public static IEnumerable<(string, int, string)> ParameterLists => new [] { ("string a", 1, ""), ("", 0, "s") }; + + public static TheoryData ArgumentsAttributeUsages() + { + return new TheoryData(GenerateData()); + +#if NET6_0_OR_GREATER + static IEnumerable GenerateData() +#else + IEnumerable GenerateData() +#endif + { + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "[Arguments({1}{2})]", + "[Arguments({0}new object[] {{ {1} }}{2})]", +#if NET8_0_OR_GREATER + "[Arguments({0}[ {1} ]{2})]" +#endif + }; + + var valueLists = new List + { + "42, \"test\"", + "\"value\", 100" + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Join("\n ", valueLists.Select(vv => string.Format(attributeUsageBase, nameColonUsage, vv, priorityNamedParameterUsage))); + } + } + } + } + } + + public static TheoryData EmptyArgumentsAttributeUsagesWithLocationMarker() + { + return new TheoryData(GenerateData()); + +#if NET6_0_OR_GREATER + static IEnumerable GenerateData() +#else + IEnumerable GenerateData() +#endif + { + yield return "[{|#0:Arguments|}]"; + yield return "[Arguments{|#0:()|}]"; + yield return "[Arguments({|#0:Priority = 1|})]"; + + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "[Arguments({0}new object[] {{|#0:{{ }}|}}{1})]", + "[Arguments({0}new object[{{|#0:0|}}]{1})]", +#if NET8_0_OR_GREATER + "[Arguments({0}{{|#0:[]|}}{1})]", +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + } + + public static IEnumerable ArgumentsAttributeUsagesWithLocationMarker() + { + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "[Arguments({{|#{1}:{2}|}}{3})]", + "[Arguments({0}new object[] {{ {{|#{1}:{2}|}} }}{3})]", +#if NET8_0_OR_GREATER + "[Arguments({0}[ {{|#{1}:{2}|}} ]{3})]" +#endif + }; + + var valueLists = new List + { + "42, \"test\"", + "\"value\", 100, false" + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Join("\n ", valueLists.Select((vv, i) => string.Format(attributeUsageBase, nameColonUsage, i, vv, priorityNamedParameterUsage))); + } + } + } + } + } + + public class MustHaveMatchingValueType : AnalyzerTestFixture + { + public MustHaveMatchingValueType() : base(ArgumentsAttributeAnalyzer.MustHaveMatchingValueTypeRule) { } + + [Fact] + public async Task A_method_not_annotated_with_any_arguments_attributes_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(EmptyArgumentsAttributeUsagesWithMismatchingValueCount))] + public async Task Having_a_mismatching_value_count_with_empty_argument_attribute_usages_should_not_trigger_diagnostic(string argumentsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void BenchmarkMethod(string a) + {{ + + }} +}}"; + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Having_a_mismatching_value_count_with_nonempty_argument_attribute_usages_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ArgumentsAttributeUsagesWithMismatchingValueCount))] string argumentsAttributeUsage, + [CombinatorialValues("string a", "")] string parameters) + { + var testCode = /* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void BenchmarkMethod({parameters}) + {{ + + }} +}}"; + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ArgumentsAttributeUsagesWithMatchingValueTypes))] + public async Task Having_matching_value_types_should_not_trigger_diagnostic(string argumentsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void BenchmarkMethod(int a, string b) + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ArgumentsAttributeUsagesWithConvertibleValueTypes))] + public async Task Providing_convertible_value_types_should_not_trigger_diagnostic(string argumentsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void BenchmarkMethod(int a, string b) + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ArgumentsAttributeUsagesWithMatchingValueTypes))] + public async Task Having_unknown_parameter_type_should_not_trigger_diagnostic(string argumentsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void BenchmarkMethod(unkown a, string b) + {{ + + }} +}}"; + + TestCode = testCode; + + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ArgumentsAttributeUsagesWithMismatchingValueTypesWithLocationMarker))] + public async Task Having_mismatching_or_not_convertible_value_types_should_trigger_diagnostic(string argumentsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void BenchmarkMethod(byte a, bool b) + {{ + + }} +}}"; + + TestCode = testCode; + + AddExpectedDiagnostic(0, "typeof(string)", "byte", "System.Type"); + AddExpectedDiagnostic(1, "\"test\"", "bool", "string"); + AddExpectedDiagnostic(2, "999", "byte", "int"); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ArgumentsAttributeUsagesWithUnknownValueTypesWithLocationMarker))] + public async Task Providing_an_unkown_value_type_should_trigger_diagnostic(string argumentsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {argumentsAttributeUsage} + public void BenchmarkMethod(byte a, bool b) + {{ + + }} +}}"; + + TestCode = testCode; + + DisableCompilerDiagnostics(); + AddDefaultExpectedDiagnostic("dummy_literal", "byte", ""); + + await RunAsync(); + } + + public static TheoryData EmptyArgumentsAttributeUsagesWithMismatchingValueCount() + { + return new TheoryData(GenerateData()); + +#if NET6_0_OR_GREATER + static IEnumerable GenerateData() +#else + IEnumerable GenerateData() +#endif + { + yield return "[Arguments]"; + yield return "[Arguments()]"; + yield return "[Arguments(Priority = 1)]"; + + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "[Arguments({0}new object[] {{ }}{1})]", + "[Arguments({0}new object[0]{1})]", +#if NET8_0_OR_GREATER + "[Arguments({0}[]{1})]", +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + } + + public static IEnumerable ArgumentsAttributeUsagesWithMismatchingValueCount() => GenerateAttributeUsages(new List { + "42, \"test\"", + "\"value\", 100, true" + }); + + public static TheoryData ArgumentsAttributeUsagesWithMatchingValueTypes() => new TheoryData(GenerateAttributeUsages(new List { + "42, \"test\"", + "43, \"test2\"" + })); + + public static TheoryData ArgumentsAttributeUsagesWithConvertibleValueTypes() => new TheoryData(GenerateAttributeUsages(new List { + "42, \"test\"", + "(byte)5, \"test2\"" + })); + + public static TheoryData ArgumentsAttributeUsagesWithMismatchingValueTypesWithLocationMarker() => new TheoryData(GenerateAttributeUsages(new List { + "{|#0:typeof(string)|}, {|#1:\"test\"|}", + "{|#2:999|}, true" + })); + + public static TheoryData ArgumentsAttributeUsagesWithUnknownValueTypesWithLocationMarker() => new TheoryData(GenerateAttributeUsages(new List { + "{|#0:dummy_literal|}, true" + })); + } + + private static IEnumerable GenerateAttributeUsages(List valueLists) + { + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "[Arguments({1}{2})]", + "[Arguments({0}new object[] {{ {1} }}{2})]", +#if NET8_0_OR_GREATER + "[Arguments({0}[ {1} ]{2})]" +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Join("\n ", valueLists.Select(vv => string.Format(attributeUsageBase, nameColonUsage, vv, priorityNamedParameterUsage))); + } + } + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs new file mode 100644 index 0000000000..e116912a56 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs @@ -0,0 +1,1332 @@ +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes +{ + using Fixtures; + + using Analyzers.Attributes; + + using Xunit; + + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Threading.Tasks; + + public class GeneralParameterAttributesAnalyzerTests + { + public class MutuallyExclusiveOnField : AnalyzerTestFixture + { + public MutuallyExclusiveOnField() : base(GeneralParameterAttributesAnalyzer.MutuallyExclusiveOnFieldRule) { } + + [Fact] + public async Task A_field_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + public int _field = 0, _field2 = 1; +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task A_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + [Dummy] + public int _field = 0, _field2 = 1; +}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Fact] + public async Task A_field_annotated_with_a_duplicate_nonparameter_attribute_should_not_trigger_diagnostic() + { + const string testCode += /* lang=c#-test */ @"public class BenchmarkClass +{ + [Dummy] + [Dummy] + public int _field = 0, _field2 = 1; +}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributeUsages))] + public async Task A_field_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateSameParameterAttributeUsages))] + public async Task A_field_annotated_with_the_same_duplicate_parameter_attribute_should_not_trigger_diagnostic(string currentUniqueAttributeUsage, int currentUniqueAttributeUsagePosition, int[] counts) + { + var duplicateAttributeUsages = new List(1 + counts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < counts.Length; i++) + { + if (i == currentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{currentUniqueAttributeUsage}]"); + } + + for (var j = 0; j < counts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateParameterAttributeUsageCounts))] + public async Task A_field_annotated_with_more_than_one_parameter_attribute_should_trigger_diagnostic_for_each_attribute_usage(int[] duplicateAttributeUsageCounts) + { + const string fieldIdentifier = "_field"; + + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + var diagnosticCounter = 0; + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{{|#{diagnosticCounter++}:{uniqueParameterAttributeUsages[i]}|}}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public int {fieldIdentifier} = 0, field2 = 1; +}}"; + + TestCode = testCode; + + for (var i = 0; i < diagnosticCounter; i++) + { + AddExpectedDiagnostic(i, fieldIdentifier); + } + + await RunAsync(); + } + +#if NET6_0_OR_GREATER + public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); +#else + public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); +#endif + + public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + + public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + } + + public class MutuallyExclusiveOnProperty : AnalyzerTestFixture + { + public MutuallyExclusiveOnProperty() : base(GeneralParameterAttributesAnalyzer.MutuallyExclusiveOnPropertyRule) { } + + [Fact] + public async Task A_property_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + public int Property { get; set; } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task A_property_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + [Dummy] + public int Property { get; set; } +}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Fact] + public async Task A_property_annotated_with_a_duplicate_nonparameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + [Dummy] + [Dummy] + public int Property { get; set; } +}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributeUsages))] + public async Task A_property_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public int Property {{ get; set; }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateSameParameterAttributeUsages))] + public async Task A_property_annotated_with_the_same_duplicate_parameter_attribute_should_not_trigger_diagnostic(string currentAttributeUsage, int currentUniqueAttributeUsagePosition, int[] duplicateSameAttributeUsageCounts) + { + var duplicateAttributeUsages = new List(1 + duplicateSameAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + { + if (i == currentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); + } + + for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public int Property {{ get; set; }} +}}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateParameterAttributeUsages))] + public async Task A_property_annotated_with_more_than_one_parameter_attribute_should_trigger_diagnostic_for_each_attribute_usage(int[] duplicateAttributeUsageCounts) + { + const string propertyIdentifier = "Property"; + + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + var diagnosticCounter = 0; + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{{|#{diagnosticCounter++}:{uniqueParameterAttributeUsages[i]}|}}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public int {propertyIdentifier} {{ get; set; }} +}}"; + + TestCode = testCode; + + for (var i = 0; i < diagnosticCounter; i++) + { + AddExpectedDiagnostic(i, propertyIdentifier); + } + + await RunAsync(); + } + +#if NET6_0_OR_GREATER + public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); +#else + public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); +#endif + + public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + + public static TheoryData DuplicateParameterAttributeUsages => DuplicateAttributeUsageCountsTheoryData; + } + + public class FieldMustBePublic : AnalyzerTestFixture + { + public FieldMustBePublic() : base(GeneralParameterAttributesAnalyzer.FieldMustBePublic) { } + + [Theory] + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task A_nonpublic_field_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) + { + var testCode = +/* lang=c#-test */ $@"public class BenchmarkClass +{{ + {classMemberAccessModifier}int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task A_nonpublic_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) + { + var testCode = +/* lang=c#-test */ $@"public class BenchmarkClass +{{ + [Dummy] + {classMemberAccessModifier}int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributeUsages))] + public async Task A_public_field_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public int _field = 0, _field2 = 2; +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_nonpublic_field_annotated_with_the_same_duplicate_parameter_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DuplicateSameParameterAttributeUsages))] (string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts) duplicateSameParameterAttributeUsages, + [CombinatorialMemberData(nameof(NonPublicClassMemberAccessModifiers))] string classMemberAccessModifier) + { + var duplicateAttributeUsages = new List(1 + duplicateSameParameterAttributeUsages.Counts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) + { + if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); + } + + for (var j = 0; j < duplicateSameParameterAttributeUsages.Counts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + {classMemberAccessModifier}int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations))] + public async Task A_nonpublic_field_annotated_with_more_than_one_parameter_attribute_should_not_trigger_diagnostic(int[] duplicateAttributeUsageCounts, string classMemberAccessModifier) + { + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + {classMemberAccessModifier}int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_nonpublic_field_annotated_with_a_unique_parameter_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(UniqueParameterAttributes))] (string AttributeName, string AttributeUsage) attribute, + [CombinatorialMemberData(nameof(NonPublicClassMemberAccessModifiers))] string classMemberAccessModifier) + { + const string fieldIdentifier = "_field"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attribute.AttributeUsage}] + {classMemberAccessModifier}int {{|#0:{fieldIdentifier}|}} = 0, field2 = 0; +}}"; + TestCode = testCode; + AddDefaultExpectedDiagnostic(fieldIdentifier, attribute.AttributeName); + + await RunAsync(); + } + + public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicClassMemberAccessModifiers); + +#if NET6_0_OR_GREATER + public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); +#else + public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); +#endif + +#if NET6_0_OR_GREATER + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); +#else + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => (tdr[0] as string, tdr[1] as string)); +#endif + public static IEnumerable NonPublicClassMemberAccessModifiers => new NonPublicClassMemberAccessModifiersTheoryData(); + +#if NET6_0_OR_GREATER + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); +#else + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => (tdr[0] as string, (int)tdr[1], tdr[2] as int[])); +#endif + public static IEnumerable DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + } + + public class PropertyMustBePublic : AnalyzerTestFixture + { + public PropertyMustBePublic() : base(GeneralParameterAttributesAnalyzer.PropertyMustBePublic) { } + + [Theory] + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task A_nonpublic_property_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) + { + var testCode = +/* lang=c#-test */ $@"public class BenchmarkClass +{{ + {classMemberAccessModifier}int Property {{ get; set; }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task A_nonpublic_property_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) + { + var testCode = +/* lang=c#-test */ $@"public class BenchmarkClass +{{ + [Dummy] + {classMemberAccessModifier}int Property {{ get; set; }} +}}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributeUsages))] + public async Task A_public_property_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public int Property {{ get; set; }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_nonpublic_property_annotated_with_the_same_duplicate_parameter_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DuplicateSameParameterAttributeUsages))] (string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts) duplicateSameParameterAttributeUsages, + [CombinatorialMemberData(nameof(NonPublicClassMemberAccessModifiers))] string classMemberAccessModifier) + { + var duplicateAttributeUsages = new List(1 + duplicateSameParameterAttributeUsages.Counts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) + { + if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); + } + + for (var j = 0; j < duplicateSameParameterAttributeUsages.Counts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + {classMemberAccessModifier}int Property {{ get; set; }} +}}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations))] + public async Task A_nonpublic_property_annotated_with_more_than_one_parameter_attribute_should_not_trigger_diagnostic(int[] duplicateAttributeUsageCounts, string classMemberAccessModifier) + { + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + {classMemberAccessModifier}int Property {{ get; set; }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_nonpublic_property_annotated_with_a_unique_parameter_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(UniqueParameterAttributes))] (string AttributeName, string AttributeUsage) attribute, + [CombinatorialMemberData(nameof(NonPublicClassMemberAccessModifiers))] string classMemberAccessModifier) + { + const string propertyIdentifier = "Property"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attribute.AttributeUsage}] + {classMemberAccessModifier}int {{|#0:{propertyIdentifier}|}} {{ get; set; }} +}}"; + TestCode = testCode; + AddDefaultExpectedDiagnostic(propertyIdentifier, attribute.AttributeName); + + await RunAsync(); + } + + public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicClassMemberAccessModifiers); + +#if NET6_0_OR_GREATER + public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); +#else + public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); +#endif + +#if NET6_0_OR_GREATER + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); +#else + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => (tdr[0] as string, tdr[1] as string)); +#endif + + public static IEnumerable NonPublicClassMemberAccessModifiers => new NonPublicClassMemberAccessModifiersTheoryData(); + +#if NET6_0_OR_GREATER + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); +#else + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => (tdr[0] as string, (int)tdr[1], tdr[2] as int[])); +#endif + + public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + } + + public class NotValidOnReadonlyField : AnalyzerTestFixture + { + public NotValidOnReadonlyField() : base(GeneralParameterAttributesAnalyzer.NotValidOnReadonlyFieldRule) { } + + [Fact] + public async Task A_readonly_field_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + public readonly int _field = 0, _field2 = 1; +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task A_readonly_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + [Dummy] + public readonly int _field = 0, _field2 = 1; +}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributeUsages))] + public async Task A_field_without_a_readonly_modifier_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateSameParameterAttributeUsages))] + public async Task A_readonly_field_annotated_with_the_same_duplicate_parameter_attribute_should_not_trigger_diagnostic(string currentAttributeUsage, int currentUniqueAttributeUsagePosition, int[] duplicateSameAttributeUsageCounts) + { + var duplicateAttributeUsages = new List(1 + duplicateSameAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + { + if (i == currentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); + } + + for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public readonly int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateParameterAttributeUsageCounts))] + public async Task A_readonly_field_annotated_with_more_than_one_parameter_attribute_should_not_trigger_diagnostic(int[] duplicateAttributeUsageCounts) + { + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public readonly int _field = 0, _field2 = 1; +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributes))] + public async Task A_readonly_field_annotated_with_a_unique_parameter_attribute_should_trigger_diagnostic(string attributeName, string attributeUsage) + { + const string fieldIdentifier = "_field"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public {{|#0:readonly|}} int {fieldIdentifier} = 0, field2 = 1; +}}"; + TestCode = testCode; + AddDefaultExpectedDiagnostic(fieldIdentifier, attributeName); + + await RunAsync(); + } + +#if NET6_0_OR_GREATER + public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); +#else + public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); +#endif + + public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; + + public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + + public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + } + + public class NotValidOnConstantField : AnalyzerTestFixture + { + public NotValidOnConstantField() : base(GeneralParameterAttributesAnalyzer.NotValidOnConstantFieldRule) { } + + [Fact] + public async Task A_constant_field_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + public const int Constant = 0; +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task A_constant_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + [Dummy] + public const int Constant = 0; +}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateSameParameterAttributeUsages))] + public async Task A_constant_field_annotated_with_the_same_duplicate_parameter_attribute_should_not_trigger_diagnostic(string currentAttributeUsage, int currentUniqueAttributeUsagePosition, int[] duplicateSameAttributeUsageCounts) + { + var duplicateAttributeUsages = new List(1 + duplicateSameAttributeUsageCounts.Sum()); + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + { + if (i == currentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); + } + + for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public const int Constant = 0; +}}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateParameterAttributeUsageCounts))] + public async Task A_constant_field_annotated_with_more_than_one_parameter_attribute_should_not_trigger_diagnostic(int[] duplicateAttributeUsageCounts) + { + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public const int Constant = 0; +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributes))] + public async Task A_constant_field_annotated_with_a_unique_parameter_attribute_should_trigger_diagnostic(string attributeName, string attributeUsage) + { + const string constantIdentifier = "Constant"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public {{|#0:const|}} int {constantIdentifier} = 0; +}}"; + TestCode = testCode; + AddDefaultExpectedDiagnostic(constantIdentifier, attributeName); + + await RunAsync(); + } + +#if NET6_0_OR_GREATER + public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); +#else + public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); +#endif + + public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; + + public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + + public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + } +#if NET5_0_OR_GREATER + public class PropertyCannotBeInitOnly : AnalyzerTestFixture + { + public PropertyCannotBeInitOnly() : base(GeneralParameterAttributesAnalyzer.PropertyCannotBeInitOnlyRule) { } + + [Fact] + public async Task An_initonly_property_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + public int Property { get; init; } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task An_initonly_property_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"public class BenchmarkClass +{ + [Dummy] + public int Property { get; init; } +}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributeUsages))] + public async Task A_property_with_an_assignable_setter_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public int Property {{ get; set; }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateSameParameterAttributeUsages))] + public async Task An_initonly_property_annotated_with_the_same_duplicate_parameter_attribute_should_not_trigger_diagnostic(string currentAttributeUsage, int currentUniqueAttributeUsagePosition, int[] duplicateSameAttributeUsageCounts) + { + var duplicateAttributeUsages = new List(1 + duplicateSameAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + { + if (i == currentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); + } + + for (var j = 0; j (duplicateAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public int Property {{ get; init; }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributes))] + public async Task An_initonly_property_annotated_with_a_unique_parameter_attribute_should_trigger_diagnostic(string attributeName, string attributeUsage) + { + const string propertyIdentifier = "Property"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public int {propertyIdentifier} {{ get; {{|#0:init|}}; }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(propertyIdentifier, attributeName); + + await RunAsync(); + } + +#if NET6_0_OR_GREATER + public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); +#else + public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); +#endif + + public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; + + public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + + public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + } +#endif + public class PropertyMustHavePublicSetter : AnalyzerTestFixture + { + public PropertyMustHavePublicSetter() : base(GeneralParameterAttributesAnalyzer.PropertyMustHavePublicSetterRule) { } + + [Theory] + [MemberData(nameof(NonPublicPropertySettersTheoryData))] + public async Task A_property_with_a_nonpublic_setter_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic(string nonPublicPropertySetter) + { + var testCode = +/* lang=c#-test */ $@"public class BenchmarkClass +{{ + public int Property {nonPublicPropertySetter} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(NonPublicPropertySettersTheoryData))] + public async Task A_property_with_a_nonpublic_setter_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic(string nonPublicPropertySetter) + { + var testCode = +/* lang=c#-test */ $@"public class BenchmarkClass +{{ + [Dummy] + public int Property {nonPublicPropertySetter} +}}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(UniqueParameterAttributeUsages))] + public async Task A_property_with_an_assignable_setter_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attributeUsage}] + public int Property {{ get; set; }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_property_with_a_nonpublic_setter_annotated_with_the_same_duplicate_parameter_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DuplicateSameParameterAttributeUsages))] (string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts) duplicateSameParameterAttributeUsages, + [CombinatorialMemberData(nameof(NonPublicPropertySetters))] string nonPublicPropertySetter) + { + var duplicateAttributeUsages = new List(1 + duplicateSameParameterAttributeUsages.Counts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) + { + if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); + } + + for (var j = 0; j < duplicateSameParameterAttributeUsages.Counts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public int Property {nonPublicPropertySetter} +}}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(DuplicateAttributeUsageCountsAndNonPublicPropertySetterCombinations))] + public async Task A_property_with_a_nonpublic_setter_annotated_with_more_than_one_parameter_attribute_should_not_trigger_diagnostic(int[] duplicateAttributeUsageCounts, string nonPublicPropertySetter) + { + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} + public int Property {nonPublicPropertySetter} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_property_with_a_nonpublic_setter_annotated_with_a_unique_parameter_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(UniqueParameterAttributes))] (string AttributeName, string AttributeUsage) attribute, + [CombinatorialMemberData(nameof(NonPublicPropertySetters))] string nonPublicPropertySetter) + { + const string propertyIdentifier = "Property"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{attribute.AttributeUsage}] + public int {{|#0:{propertyIdentifier}|}} {nonPublicPropertySetter} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(propertyIdentifier, attribute.AttributeName); + + await RunAsync(); + } + + public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicPropertySetterCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicPropertySetters()); + +#if NET6_0_OR_GREATER + public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); +#else + public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); +#endif + +#if NET6_0_OR_GREATER + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); +#else + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => (tdr[0] as string, tdr[1] as string)); +#endif + +#if NET6_0_OR_GREATER + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); +#else + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => (tdr[0] as string, (int)tdr[1], tdr[2] as int[])); +#endif + + public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + + public static IEnumerable NonPublicPropertySetters() => new NonPublicPropertySetterAccessModifiersTheoryData().Select(m => $"{{ get; {m} set; }}") + .Concat(new[] { + "{ get; }", + "=> 0;" + }); + public static TheoryData NonPublicPropertySettersTheoryData() => new TheoryData(NonPublicPropertySetters()); + } + + public static TheoryData UniqueParameterAttributesTheoryData => new TheoryData + { + { "Params", "Params(3)" }, + { "ParamsSource", "ParamsSource(\"test\")" }, + { "ParamsAllValues", "ParamsAllValues" } + }; + + public static TheoryData DuplicateSameAttributeUsagesTheoryData + { + get + { + var theoryData = new TheoryData(); + + foreach (var duplicateSameAttributeUsageCombination in GenerateDuplicateSameAttributeUsageCombinations(UniqueParameterAttributesTheoryData)) + { + theoryData.Add(duplicateSameAttributeUsageCombination.CurrentUniqueAttributeUsage, duplicateSameAttributeUsageCombination.CurrentUniqueAttributeUsagePosition, duplicateSameAttributeUsageCombination.Counts); + } + + return theoryData; + } + } + + public static TheoryData DuplicateAttributeUsageCountsTheoryData => new TheoryData(GenerateDuplicateAttributeUsageCombinations(UniqueParameterAttributesTheoryData)); + + private static IEnumerable GenerateDuplicateAttributeUsageCombinations(TheoryData uniqueAttributeUsages) + { + var uniqueAttributeUsagesList = uniqueAttributeUsages.ToList() + .AsReadOnly(); + + var allCombinations = CombinationsGenerator.GenerateCombinationsCounts(uniqueAttributeUsagesList.Count, 1); + + foreach (var currentCombination in allCombinations) + { + if (currentCombination.Sum() >= 2) + { + yield return currentCombination; + } + } + } + + private static ReadOnlyCollection<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> GenerateDuplicateSameAttributeUsageCombinations(TheoryData uniqueAttributeUsages) + { +#if NET6_0_OR_GREATER + var uniqueAttributeUsagesList = uniqueAttributeUsages.Select(tdr => (tdr[1] as string)!) + .ToList() + .AsReadOnly(); +#else + var uniqueAttributeUsagesList = uniqueAttributeUsages.Select(tdr => tdr[1] as string) + .ToList() + .AsReadOnly(); +#endif + + var finalCombinationsList = new List<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)>(); + + var allCombinations = CombinationsGenerator.GenerateCombinationsCounts(uniqueAttributeUsagesList.Count, 2).ToList() + .AsReadOnly(); + + for (var i = 0; i < uniqueAttributeUsagesList.Count; i++) + { + foreach (var currentCombination in allCombinations) + { + if (currentCombination[i] > 0) + { + finalCombinationsList.Add((uniqueAttributeUsagesList[i], i, currentCombination)); + } + } + } + + return finalCombinationsList.AsReadOnly(); + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs new file mode 100644 index 0000000000..bbfd414254 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs @@ -0,0 +1,237 @@ +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes +{ + using Fixtures; + + using BenchmarkDotNet.Analyzers.Attributes; + using Xunit; + + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + public class ParamsAllValuesAttributeAnalyzerTests + { + public class General : AnalyzerTestFixture + { + [Theory, CombinatorialData] + public async Task A_field_or_property_not_annotated_with_the_paramsallvalues_attribute_should_not_trigger_diagnostic([CombinatorialValues("", "[Dummy]")] string missingParamsAttributeUsage, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(InvalidTypes))] string invalidType) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {missingParamsAttributeUsage} + public {invalidType} {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnumWithFlagsAttribute(); + + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + + public static IEnumerable InvalidTypes => new TheoryData + { + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "string", + "uint", + "ulong", + "ushort", + + "DummyEnumWithFlagsAttribute", + + "object", + "System.Type" + }; + } + + public class NotAllowedOnFlagsEnumPropertyOrFieldType : AnalyzerTestFixture + { + public NotAllowedOnFlagsEnumPropertyOrFieldType() : base(ParamsAllValuesAttributeAnalyzer.NotAllowedOnFlagsEnumPropertyOrFieldTypeRule) { } + + [Theory, CombinatorialData] + public async Task A_field_or_property_of_nonnullable_nonenum_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, [CombinatorialMemberData(nameof(NonEnumTypes))] string nonEnumType) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [ParamsAllValues] + public {nonEnumType} {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_field_or_property_of_nullable_nonenum_type_should_not_trigger_diagnostic(bool isNullable, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, [CombinatorialMemberData(nameof(NonEnumStructs))] string nonEnumType) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [ParamsAllValues] + public {nonEnumType}{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_field_or_property_of_enum_type_without_a_flags_attribute_should_not_trigger_diagnostic(bool isNullable, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [ParamsAllValues] + public DummyEnum{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyEnum(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_field_or_property_of_enum_type_with_a_flags_attribute_should_trigger_diagnostic(bool isNullable, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + const string enumTypeName = "DummyEnumWithFlagsAttribute"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [ParamsAllValues] + public {{|#0:{enumTypeName}|}}{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyEnumWithFlagsAttribute(); + AddDefaultExpectedDiagnostic(enumTypeName); + + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + + public static IEnumerable NonEnumStructs => new List { + "bool", + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort", + }.AsReadOnly(); + + public static IEnumerable NonEnumTypes => NonEnumStructs.Concat(new[]{ "string", "object", "System.Type" }); + } + + public class PropertyOrFieldTypeMustBeEnumOrBool : AnalyzerTestFixture + { + public PropertyOrFieldTypeMustBeEnumOrBool() : base(ParamsAllValuesAttributeAnalyzer.PropertyOrFieldTypeMustBeEnumOrBoolRule) { } + + [Theory, CombinatorialData] + public async Task A_field_or_property_of_enum_or_bool_type_should_not_trigger_diagnostic(bool isNullable, [CombinatorialValues("DummyEnum", "bool")] string enumOrBoolType, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [ParamsAllValues] + public {enumOrBoolType}{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyEnum(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_field_or_property_not_of_nonnullable_enum_or_bool_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(NonEnumOrBoolTypes))] string nonEnumOrBoolType, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [ParamsAllValues] + public {{|#0:{nonEnumOrBoolType}|}} {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyEnum(); + AddDefaultExpectedDiagnostic(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_field_or_property_not_of_nullable_enum_or_bool_type_should_trigger_diagnostic(bool isNullable, [CombinatorialMemberData(nameof(NonEnumOrBoolStructs))] string nonEnumOrBoolType, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [ParamsAllValues] + public {{|#0:{nonEnumOrBoolType}|}}{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyEnum(); + AddDefaultExpectedDiagnostic(); + + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + + public static IEnumerable NonEnumOrBoolStructs => new List { + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort" + }.AsReadOnly(); + + public static IEnumerable NonEnumOrBoolTypes => NonEnumOrBoolStructs.Concat(new[] { "string", "object", "System.Type" }); + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs new file mode 100644 index 0000000000..eb2403012c --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -0,0 +1,678 @@ +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes +{ + using Fixtures; + + using BenchmarkDotNet.Analyzers.Attributes; + using Xunit; + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Threading.Tasks; + + public class ParamsAttributeAnalyzerTests + { + public class General : AnalyzerTestFixture + { + [Theory, CombinatorialData] + public async Task A_field_or_property_not_annotated_with_the_params_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialValues("", "[Dummy]")] string missingParamsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + {missingParamsAttributeUsage} + public string {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + } + + public class MustHaveValues : AnalyzerTestFixture + { + public MustHaveValues() : base(ParamsAttributeAnalyzer.MustHaveValuesRule) { } + + [Theory, CombinatorialData] + public async Task Providing_one_or_more_values_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesListLength))] int scalarValuesListLength, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, string.Join(", ", ScalarValues.Take(scalarValuesListLength)))})] + public string {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_array_with_a_rank_specifier_size_higher_than_zero_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialRange(1, 2)] int rankSpecifierSize) + { + Assert.True(rankSpecifierSize > 0); + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params(new object[{rankSpecifierSize}])] + public string {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_no_values_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(EmptyParamsAttributeUsagesWithLocationMarker))] string emptyParamsAttributeUsage) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}{emptyParamsAttributeUsage}] + public string {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(); + + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + + public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + + public static IEnumerable ScalarValuesListLength => Enumerable.Range(1, ScalarValues.Count); + + private static ReadOnlyCollection ScalarValues => Enumerable.Range(1, 3) + .Select(i => $"\"test{i}\"") + .ToList() + .AsReadOnly(); + public static IEnumerable ScalarValuesContainerAttributeArgument => ScalarValuesContainerAttributeArgumentTheoryData(); + + public static IEnumerable EmptyParamsAttributeUsagesWithLocationMarker() + { + yield return "{|#0:Params|}"; + yield return "Params{|#0:()|}"; + yield return "Params({|#0:Priority = 1|})"; + + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "Params({0}new object[] {{|#0:{{ }}|}}{1})", + "Params({0}{{|#0:new object[0]|}}{1})", +#if NET8_0_OR_GREATER + "Params({0}{{|#0:[ ]|}}{1})", +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + } + + public class UnexpectedValueType : AnalyzerTestFixture + { + public UnexpectedValueType() : base(ParamsAttributeAnalyzer.UnexpectedValueTypeRule) { } + + [Theory, CombinatorialData] + public async Task Providing_a_field_or_property_with_an_unknown_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, "42, 51, 33")})] + public unknown {fieldOrPropertyDeclaration} +}}"; + TestCode = testCode; + + ReferenceDummyAttribute(); + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_convertible_value_types_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, "(byte)42")})] + public int {fieldOrPropertyDeclaration} +}}"; + TestCode = testCode; + + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_both_expected_and_unexpected_value_types_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + { + const string expectedFieldOrPropertyType = "int"; + const string valueWithUnexpectedType = "\"test1\""; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnexpectedType}|}}, 33")})] + public {expectedFieldOrPropertyType} {fieldOrPropertyDeclaration} +}}"; + TestCode = testCode; + ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(valueWithUnexpectedType, expectedFieldOrPropertyType, "string"); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_unknown_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + { + const string expectedFieldOrPropertyType = "int"; + const string valueWithUnknownType = "dummy_literal"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnknownType}|}}, 33")})] + public {expectedFieldOrPropertyType} {fieldOrPropertyDeclaration} +}}"; + TestCode = testCode; + + ReferenceDummyAttribute(); + DisableCompilerDiagnostics(); + AddDefaultExpectedDiagnostic(valueWithUnknownType, expectedFieldOrPropertyType, ""); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(NotConvertibleValueTypeCombinations))] + public async Task Providing_an_unexpected_or_not_convertible_value_type_should_trigger_diagnostic(string fieldOrPropertyDeclaration, + string dummyAttributeUsage, + string[] valueAndType, + string scalarValuesContainerAttributeArgument) + { + const string expectedFieldOrPropertyType = "decimal"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType[0]}|}}")})] + public {expectedFieldOrPropertyType} {fieldOrPropertyDeclaration} +}}"; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + AddDefaultExpectedDiagnostic(valueAndType[0], expectedFieldOrPropertyType, valueAndType[1]); + + await RunAsync(); + } + + //[Theory, CombinatorialData] + //public async Task Providing_an_unexpected_array_value_type_to_params_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + // [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + // [CombinatorialMemberData(nameof(ValuesAndTypes))] string[] valueAndType, + // [CombinatorialMemberData(nameof(ArrayValuesContainerAttributeArgumentWithLocationMarker))] string[] arrayValuesContainerAttributeArgument) + + [Theory] + [MemberData(nameof(UnexpectedArrayValueTypeCombinations))] + public async Task Providing_an_unexpected_array_value_type_to_params_attribute_should_trigger_diagnostic(string fieldOrPropertyDeclaration, + string dummyAttributeUsage, + string[] valueAndType, + string[] arrayValuesContainerAttributeArgument) + { + const string expectedFieldOrPropertyType = "decimal"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(arrayValuesContainerAttributeArgument[0], valueAndType[0], valueAndType[1])})] + public {expectedFieldOrPropertyType} {fieldOrPropertyDeclaration} +}}"; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + AddDefaultExpectedDiagnostic( + string.Format(arrayValuesContainerAttributeArgument[1], + valueAndType[0], + valueAndType[1]), + expectedFieldOrPropertyType, + $"{valueAndType[1]}[]"); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_empty_array_value_when_type_of_attribute_target_is_not_object_array_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params{emptyValuesAttributeArgument}] + public decimal {fieldOrPropertyDeclaration} +}}"; + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_empty_array_value_when_type_of_attribute_target_is_object_array_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params{emptyValuesAttributeArgument}] + public object[] {fieldOrPropertyDeclaration} +}}"; + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + + public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + + public static IEnumerable EmptyValuesAttributeArgument() + { + yield return ""; + yield return "()"; + yield return "(Priority = 1)"; + + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "({0}new object[] {{ }}{1})", + "({0}new object[0]{1})", +#if NET8_0_OR_GREATER + "({0}[ ]{1})" +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + + public static IEnumerable UnexpectedArrayValueTypeCombinations => CombinationsGenerator.CombineArguments(FieldOrPropertyDeclarations, DummyAttributeUsage, ValuesAndTypes, ArrayValuesContainerAttributeArgumentWithLocationMarker()); + + public static IEnumerable NotConvertibleValueTypeCombinations => CombinationsGenerator.CombineArguments(FieldOrPropertyDeclarations, DummyAttributeUsage, NotConvertibleValuesAndTypes, ScalarValuesContainerAttributeArgument); + + public static IEnumerable ScalarValuesContainerAttributeArgument => ScalarValuesContainerAttributeArgumentTheoryData(); + + public static IEnumerable ArrayValuesContainerAttributeArgumentWithLocationMarker() + { + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List<(string, string)> + { + ("{0}new object[] {{{{ {{{{|#0:new[] {{{{ {{0}} }}}}|}}}} }}}}{1}", "new[] {{ {0} }}"), // new object[] { new[] { 42 } } + ("{0}new object[] {{{{ {{{{|#0:new {{1}}[] {{{{ {{0}} }}}}|}}}} }}}}{1}", "new {1}[] {{ {0} }}"), // new object[] { new int[] { 42 } } +#if NET8_0_OR_GREATER + ("{0}[ {{{{|#0:new[] {{{{ {{0}} }}}}|}}}} ]{1}", "new[] {{ {0} }}"), // [ new[] { 42 } ] + ("{0}[ {{{{|#0:new {{1}}[] {{{{ {{0}} }}}}|}}}} ]{1}", "new {1}[] {{ {0} }}"), // [ new int[] { 42 } ] +#endif + ("{0}new object[] {{{{ {{{{|#0:new {{1}}[] {{{{ }}}}|}}}} }}}}{1}", "new {1}[] {{ }}"), // new object[] { new int[] { } } +#if NET8_0_OR_GREATER + ("{0}[ {{{{|#0:new {{1}}[] {{{{ }}}}|}}}} ]{1}", "new {1}[] {{ }}"), // [ new int[] { } ] +#endif + ("{0}new object[] {{{{ {{{{|#0:new {{1}}[0]|}}}} }}}}{1}", "new {1}[0]"), // new object[] { new int[0] } +#if NET8_0_OR_GREATER + ("{0}[ {{{{|#0:new {{1}}[0]|}}}} ]{1}", "new {1}[0]"), // [ new int[0] ] +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { +#if NET8_0_OR_GREATER + yield return [ + string.Format(attributeUsageBase.Item1, nameColonUsage, priorityNamedParameterUsage), + attributeUsageBase.Item2 + ]; +#else + yield return new[] { + string.Format(attributeUsageBase.Item1, nameColonUsage, priorityNamedParameterUsage), + attributeUsageBase.Item2 + }; +#endif + } + } + } + } + + public static IEnumerable ValuesAndTypes => +#if NET8_0_OR_GREATER + [ + [ "true", "bool" ], + [ "(byte)123", "byte" ], + [ "'A'", "char" ], + [ "1.0D", "double" ], + [ "1.0F", "float" ], + [ "123", "int" ], + [ "123L", "long" ], + [ "(sbyte)-100", "sbyte" ], + [ "(short)-123", "short" ], + [ """ + "test" + """, "string" ], + [ "123U", "uint" ], + [ "123UL", "ulong" ], + [ "(ushort)123", "ushort" ], + + [ """ + (object)"test_object" + """, "object" ], + [ "typeof(string)", "System.Type" ], + [ "DummyEnum.Value1", "DummyEnum" ] + ]; +#else + new[] + { + new[] { "true", "bool" }, + new[] { "(byte)123", "byte" }, + new[] { "'A'", "char" }, + new[] { "1.0D", "double" }, + new[] { "1.0F", "float" }, + new[] { "123", "int" }, + new[] { "123L", "long" }, + new[] { "(sbyte)-100", "sbyte" }, + new[] { "(short)-123", "short" }, + new[] { "\"test\"", "string" }, + new[] { "123U", "uint" }, + new[] { "123UL", "ulong" }, + new[] { "(ushort)123", "ushort" }, + + new[] { "(object)\"test_object\"", "object" }, + new[] { "typeof(string)", "System.Type" }, + new[] { "DummyEnum.Value1", "DummyEnum" } + }; +#endif + + public static IEnumerable NotConvertibleValuesAndTypes => +#if NET8_0_OR_GREATER + [ + [ "true", "bool" ], + [ "1.0D", "double" ], + [ "1.0F", "float" ], + [ """ + "test" + """, "string" ], + + [ """ + (object)"test_object" + """, "object" ], + [ "typeof(string)", "System.Type" ], + [ "DummyEnum.Value1", "DummyEnum" ] + ]; +#else + new[] + { + new[] { "true", "bool" }, + new[] { "1.0D", "double" }, + new[] { "1.0F", "float" }, + new[] { "\"test\"", "string" }, + + new[] {"(object)\"test_object\"", "object" }, + new[] { "typeof(string)", "System.Type" }, + new[] { "DummyEnum.Value1", "DummyEnum" } + }; +#endif + } + + public class UnnecessarySingleValuePassedToAttribute : AnalyzerTestFixture + { + public UnnecessarySingleValuePassedToAttribute() : base(ParamsAttributeAnalyzer.UnnecessarySingleValuePassedToAttributeRule) { } + + [Theory, CombinatorialData] + public async Task Providing_two_or_more_values_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesListLength))] int scalarValuesListLength, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, string.Join(", ", ScalarValues.Take(scalarValuesListLength)))})] + public string {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_only_a_single_value_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentWithLocationMarker))] string scalarValuesContainerAttributeArgument) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, 42)})] + public string {fieldOrPropertyDeclaration} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(); + ReferenceDummyAttribute(); + + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + + public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + + public static IEnumerable ScalarValuesListLength => Enumerable.Range(2, ScalarValues.Count); + + private static ReadOnlyCollection ScalarValues => Enumerable.Range(1, 2) + .Select(i => $"\"test{i}\"") + .ToList() + .AsReadOnly(); + public static IEnumerable ScalarValuesContainerAttributeArgument => ScalarValuesContainerAttributeArgumentTheoryData(); + + public static IEnumerable ScalarValuesContainerAttributeArgumentWithLocationMarker() + { + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "{{{{|#0:{{0}}|}}}}{1}", + "{0}new object[] {{{{ {{{{|#0:{{0}}|}}}} }}}}{1}", +#if NET8_0_OR_GREATER + "{0}[ {{{{|#0:{{0}}|}}}} ]{1}", +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + } + + public static TheoryData DummyAttributeUsageTheoryData => new TheoryData + { + "", + "Dummy, " + }; + + public static TheoryData ScalarValuesContainerAttributeArgumentTheoryData() + { + return new TheoryData(GenerateData()); + +#if NET6_0_OR_GREATER + static IEnumerable GenerateData() +#else + IEnumerable GenerateData() +#endif + { + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "{{0}}{1}", + "{0}new object[] {{{{ {{0}} }}}}{1}", +#if NET8_0_OR_GREATER + "{0}[ {{0}} ]{1}" +#endif + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs new file mode 100644 index 0000000000..f960fab1ac --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -0,0 +1,124 @@ +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.BenchmarkRunner +{ + using Fixtures; + + using Analyzers.BenchmarkRunner; + + using Xunit; + + using System.Threading.Tasks; + + public class RunAnalyzerTests + { + public class TypeArgumentClassMissingBenchmarkMethods : AnalyzerTestFixture + { + public TypeArgumentClassMissingBenchmarkMethods() : base(RunAnalyzer.TypeArgumentClassMissingBenchmarkMethodsRule) { } + + [Fact] + public async Task Invoking_with_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() + { + const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Running; + +public class Program +{{ + public static void Main(string[] args) {{ + BenchmarkRunner.Run<{classWithOneBenchmarkMethodName}>(); + }} +}}"; + + var benchmarkClassDocument = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class {classWithOneBenchmarkMethodName} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + + await RunAsync(); + } + + [Fact] + public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_should_trigger_diagnostic() + { + const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Running; + +public class Program +{{ + public static void Main(string[] args) {{ + BenchmarkRunner.Run<{{|#0:{classWithOneBenchmarkMethodName}|}}>(); + }} +}}"; + + var benchmarkClassDocument = +/* lang=c#-test */ $@"public class {classWithOneBenchmarkMethodName} +{{ + public void BenchmarkMethod() + {{ + + }} +}}"; + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); + + await RunAsync(); + } + + [Fact] + public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() + { + const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Running; + +public class Program +{{ + public static void Main(string[] args) {{ + BenchmarkRunner.Run<{classWithOneBenchmarkMethodName}>(); + }} +}}"; + + var benchmarkClassDocument = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class {classWithOneBenchmarkMethodName} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} + + public void BenchmarkMethod2() + {{ + + }} + + private void BenchmarkMethod3() + {{ + + }} +}}"; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + + await RunAsync(); + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs new file mode 100644 index 0000000000..370c31a674 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -0,0 +1,777 @@ +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.General +{ + using Fixtures; + + using Analyzers.General; + + using Xunit; + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Threading.Tasks; + + public class BenchmarkClassAnalyzerTests + { + public class General : AnalyzerTestFixture + { + [Fact] + public async Task Class_with_no_methods_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + public void BenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + } + + public class MethodMustBePublic : AnalyzerTestFixture + { + public MethodMustBePublic() : base(BenchmarkClassAnalyzer.MethodMustBePublicRule) { } + + [Fact] + public async Task Public_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string nonPublicClassAccessModifier) + { + const string benchmarkMethodName = "BenchmarkMethod"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + {nonPublicClassAccessModifier}void {{|#0:{benchmarkMethodName}|}}() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkMethodName); + + await RunAsync(); + } + } + + public class MethodMustBeNonGeneric : AnalyzerTestFixture + { + public MethodMustBeNonGeneric() : base(BenchmarkClassAnalyzer.MethodMustBeNonGenericRule) { } + + [Fact] + public async Task Nongeneric_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void NonGenericBenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task Generic_method_not_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + public void GenericMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(TypeParametersListLength))] + public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) + { + const string benchmarkMethodName = "GenericBenchmarkMethod"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{{ + [Benchmark] + public void {benchmarkMethodName}{{|#0:<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}>|}}() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkMethodName); + + await RunAsync(); + } + + public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; + + private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; + } + + public class ClassMustBePublic : AnalyzerTestFixture + { + public ClassMustBePublic() : base(BenchmarkClassAnalyzer.ClassMustBePublicRule) { } + + [Fact] + public async Task Public_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(NonPublicClassAccessModifiersExceptFileTheoryData))] + public async Task Nonpublic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string nonPublicClassAccessModifier) + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class Wrapper {{ + {nonPublicClassAccessModifier}class {{|#0:{benchmarkClassName}|}} + {{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } +#if NET7_0_OR_GREATER + [Fact] + public async Task File_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +file class {{|#0:{benchmarkClassName}|}} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } +#endif + public static TheoryData NonPublicClassAccessModifiersExceptFileTheoryData => new TheoryData(new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file ")); + } + + public class ClassMustBeNonStatic : AnalyzerTestFixture + { + public ClassMustBeNonStatic() : base(BenchmarkClassAnalyzer.ClassMustBeNonStaticRule) { } + + [Fact] + public async Task Instance_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task Static_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public {{|#0:static|}} class {benchmarkClassName} +{{ + [Benchmark] + public static void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + } + + public class BenchmarkClassMustBeNonAbstract : AnalyzerTestFixture + { + public BenchmarkClassMustBeNonAbstract() : base(BenchmarkClassAnalyzer.ClassMustBeNonAbstractRule) { } + + [Fact] + public async Task Nonabstract_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } + + private static void NonBenchmarkMethod2() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task Abstract_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public {{|#0:abstract|}} class {benchmarkClassName} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + } + + public class BenchmarkClassMustBeNonGeneric : AnalyzerTestFixture + { + public BenchmarkClassMustBeNonGeneric() : base(BenchmarkClassAnalyzer.ClassMustBeNonGenericRule) { } + + [Fact] + public async Task Nongeneric_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(TypeParametersListLength))] + public async Task Generic_class_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +[GenericTypeArguments({string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))})] +public class {benchmarkClassName}{{|#0:<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}>|}} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(TypeParametersListLength))] + public async Task Generic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public class {benchmarkClassName}{{|#0:<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}>|}} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + + public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; + + private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; + + private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; + } + + public class ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters : AnalyzerTestFixture + { + public ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustHaveTypeParametersRule) { } + + [Fact] + public async Task Nongeneric_class_not_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(TypeParametersListLength))] + public async Task Generic_class_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +[GenericTypeArguments({string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))})] +public class BenchmarkClass<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}> +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task Class_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_having_no_type_parameters_should_trigger_diagnostic() + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +[GenericTypeArguments(typeof(int))] +public class {{|#0:{benchmarkClassName}|}} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + + public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; + + private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; + + private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; + } + + public class GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount : AnalyzerTestFixture + { + public GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount() : base(BenchmarkClassAnalyzer.GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule) { } + + [Fact] + public async Task Nongeneric_class_not_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(TypeParametersListLength))] + public async Task Generic_class_annotated_with_the_generictypearguments_attribute_having_matching_type_argument_count_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) + { + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +[GenericTypeArguments({string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))})] +public class BenchmarkClass<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}> +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [InlineData("typeof(int), typeof(string)", 2)] + [InlineData("typeof(int), typeof(string), typeof(bool)", 3)] + public async Task Generic_class_annotated_with_the_generictypearguments_attribute_having_mismatching_type_argument_count_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string typeArguments, int typeArgumentCount) + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +[GenericTypeArguments({{|#0:{typeArguments}|}})] +public class {benchmarkClassName} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(1, "", benchmarkClassName, typeArgumentCount); + + await RunAsync(); + } + + public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; + + private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; + + private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; + } + + public class BenchmarkClassMustBeUnsealed : AnalyzerTestFixture + { + public BenchmarkClassMustBeUnsealed() : base(BenchmarkClassAnalyzer.ClassMustBeUnsealedRule) { } + + [Fact] + public async Task Unsealed_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task Sealed_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = +/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + +public {{|#0:sealed|}} class {benchmarkClassName} +{{ + [Benchmark] + public void BenchmarkMethod() + {{ + + }} +}}"; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + } + + public class OnlyOneMethodCanBeBaseline : AnalyzerTestFixture + { + public OnlyOneMethodCanBeBaseline() : base(BenchmarkClassAnalyzer.OnlyOneMethodCanBeBaselineRule) { } + + [Fact] + public async Task Class_with_only_one_benchmark_method_marked_as_baseline_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark(Baseline = true)] + public void BaselineBenchmarkMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark(Baseline = false)] + public void NonBaselineBenchmarkMethod2() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task Class_with_no_benchmark_methods_marked_as_baseline_should_not_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + + [Benchmark(Baseline = false)] + public void NonBaselineBenchmarkMethod3() + { + + } +}"; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_should_trigger_diagnostic() + { + const string testCode = +/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + +public class BenchmarkClass +{ + [Benchmark({|#0:Baseline = true|})] + [Benchmark] + public void BaselineBenchmarkMethod1() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark(Baseline = false)] + public void NonBaselineBenchmarkMethod2() + { + + } + + [Benchmark({|#1:Baseline = true|})] + public void BaselineBenchmarkMethod2() + { + + } + + [Benchmark({|#2:Baseline = true|})] + [Benchmark({|#3:Baseline = true|})] + public void BaselineBenchmarkMethod3() + { + + } +}"; + + TestCode = testCode; + DisableCompilerDiagnostics(); + AddExpectedDiagnostic(0); + AddExpectedDiagnostic(1); + AddExpectedDiagnostic(2); + AddExpectedDiagnostic(3); + + await RunAsync(); + } + } + + public static TheoryData TypeParametersListLengthTheoryData => new TheoryData(Enumerable.Range(1, TypeParametersTheoryData.Count)); + + private static ReadOnlyCollection TypeParametersTheoryData => Enumerable.Range(0, 3) + .Select(i => $"TParameter{i}") + .ToList() + .AsReadOnly(); + private static ReadOnlyCollection GenericTypeArgumentsTheoryData => new List { "typeof(int)", "typeof(string)", "typeof(bool)" }.AsReadOnly(); + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj new file mode 100644 index 0000000000..bc7c74d91b --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj @@ -0,0 +1,53 @@ + + + + net462;net6.0;net8.0 + enable + + true + true + true + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings new file mode 100644 index 0000000000..b8e08e1b17 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings @@ -0,0 +1,5 @@ + + True + True + True + True \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs new file mode 100644 index 0000000000..32ce2a871f --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs @@ -0,0 +1,234 @@ +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Testing; + using Microsoft.CodeAnalysis.Diagnostics; + using Microsoft.CodeAnalysis.Testing; + using Xunit; + + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + public abstract class AnalyzerTestFixture + where TAnalyzer : DiagnosticAnalyzer, new() + { + private readonly CSharpAnalyzerTest _analyzerTest; + +#if NET6_0_OR_GREATER + private readonly DiagnosticDescriptor? _ruleUnderTest; +#else + private readonly DiagnosticDescriptor _ruleUnderTest; +#endif + + private AnalyzerTestFixture(bool assertUniqueSupportedDiagnostics) + { + _analyzerTest = new InternalAnalyzerTest + { +#if NET8_0_OR_GREATER + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, +#elif NET6_0_OR_GREATER + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, +#else + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard20, +#endif + SolutionTransforms = + { + (s, pId) => s.WithProjectParseOptions(pId, new CSharpParseOptions( +#if NET8_0_OR_GREATER + LanguageVersion.CSharp12 +#elif NET6_0_OR_GREATER + LanguageVersion.CSharp10 +#else + LanguageVersion.CSharp7_3 +#endif + )) + }, + + TestState = + { + AdditionalReferences = + { + "BenchmarkDotNet.dll", + "BenchmarkDotNet.Annotations.dll" + } + } + }; + + if (assertUniqueSupportedDiagnostics) + { + AssertUniqueSupportedDiagnostics(); + } + } + + protected AnalyzerTestFixture() : this(true) { } + + protected AnalyzerTestFixture(DiagnosticDescriptor diagnosticDescriptor) : this(false) + { + var analyzer = AssertUniqueSupportedDiagnostics(); + +#if NET6_0_OR_GREATER + if (diagnosticDescriptor == null!) +#else + if (diagnosticDescriptor == null) +#endif + { + Assert.Fail("Diagnostic under test cannot be null when using this constructor"); + } + + AssertDiagnosticUnderTestIsSupportedByAnalyzer(); + DisableAllSupportedDiagnosticsExceptDiagnosticUnderTest(); + + _ruleUnderTest = diagnosticDescriptor; + + return; + + void AssertDiagnosticUnderTestIsSupportedByAnalyzer() + { + if (!analyzer.SupportedDiagnostics.Any(dd => dd.Id == diagnosticDescriptor.Id)) + { + Assert.Fail($"Diagnostic descriptor with ID {diagnosticDescriptor.Id} is not supported by the analyzer {typeof(TAnalyzer).Name}"); + } + } + + void DisableAllSupportedDiagnosticsExceptDiagnosticUnderTest() + { + _analyzerTest.DisabledDiagnostics.Clear(); + _analyzerTest.DisabledDiagnostics.AddRange(analyzer.SupportedDiagnostics.Select(dd => dd.Id).Except(new[] { diagnosticDescriptor.Id })); + } + } + + private static TAnalyzer AssertUniqueSupportedDiagnostics() + { + var allSupportedDiagnostics = new Dictionary(); + + var analyzer = new TAnalyzer(); + foreach (var supportedDiagnostic in analyzer.SupportedDiagnostics) + { + if (allSupportedDiagnostics.ContainsKey(supportedDiagnostic.Id)) + { + allSupportedDiagnostics[supportedDiagnostic.Id]++; + } + else + { + allSupportedDiagnostics[supportedDiagnostic.Id] = 1; + } + } + + var duplicateSupportedDiagnostics = allSupportedDiagnostics.Where(kvp => kvp.Value > 1) + .OrderBy(kvp => kvp.Key) + .ToList(); + + if (duplicateSupportedDiagnostics.Count > 0) + { + Assert.Fail($"The analyzer {typeof(TAnalyzer).FullName} contains duplicate supported diagnostics:{Environment.NewLine}{Environment.NewLine}{string.Join(", ", duplicateSupportedDiagnostics.Select(kvp => $"❌ {kvp.Key} (x{kvp.Value})"))}{Environment.NewLine}"); + } + + return analyzer; + } + + protected string TestCode + { + set => _analyzerTest.TestCode = value; + } + + protected void AddSource(string filename, string content) => _analyzerTest.TestState.Sources.Add((filename, content)); + + protected void AddSource(string content) => _analyzerTest.TestState.Sources.Add(content); + + protected void AddDefaultExpectedDiagnostic() + { + AddExpectedDiagnostic(); + } + + protected void AddDefaultExpectedDiagnostic(params object[] arguments) + { + AddExpectedDiagnostic(arguments); + } + + protected void AddExpectedDiagnostic(int markupKey) + { + AddExpectedDiagnostic(null, markupKey); + } + + protected void AddExpectedDiagnostic(int markupKey, params object[] arguments) + { + AddExpectedDiagnostic(arguments, markupKey); + } +#if NET6_0_OR_GREATER + private void AddExpectedDiagnostic(object[]? arguments = null, int markupKey = 0) +#else + private void AddExpectedDiagnostic(object[] arguments = null, int markupKey = 0) +#endif + { + if (_ruleUnderTest == null) + { + throw new InvalidOperationException("Failed to add expected diagnostic: no diagnostic rule specified for this fixture"); + } + + var diagnosticResult = new DiagnosticResult(_ruleUnderTest).WithLocation(markupKey) + .WithMessageFormat(_ruleUnderTest.MessageFormat); + + if (arguments != null) + { + diagnosticResult = diagnosticResult.WithArguments(arguments); + } + + _analyzerTest.ExpectedDiagnostics.Add(diagnosticResult); + } + + protected void DisableCompilerDiagnostics() + { + _analyzerTest.CompilerDiagnostics = CompilerDiagnostics.None; + } + + protected Task RunAsync() + { + return _analyzerTest.RunAsync(CancellationToken.None); + } + + protected void ReferenceDummyAttribute() + { + _analyzerTest.TestState.Sources.Add( +@"using System; + +public class DummyAttribute : Attribute +{ + +}"); + } + + protected void ReferenceDummyEnum() + { + _analyzerTest.TestState.Sources.Add( +@"public enum DummyEnum +{ + Value1, + Value2, + Value3 +}"); + } + + protected void ReferenceDummyEnumWithFlagsAttribute() + { + _analyzerTest.TestState.Sources.Add( +@"using System; + +[Flags] +public enum DummyEnumWithFlagsAttribute +{ + Value1, + Value2, + Value3 +}"); + } + + private sealed class InternalAnalyzerTest : CSharpAnalyzerTest + { + protected override string DefaultTestProjectName => "BenchmarksAssemblyUnderAnalysis"; + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs new file mode 100644 index 0000000000..e51a6fbc00 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs @@ -0,0 +1,14 @@ +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + using Xunit; + + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + + public static class TheoryDataExtensions + { + public static ReadOnlyCollection AsReadOnly(this TheoryData theoryData) => (theoryData as IEnumerable).ToList() + .AsReadOnly(); + } +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs new file mode 100644 index 0000000000..af04252803 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs @@ -0,0 +1,66 @@ +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + public static class CombinationsGenerator + { + public static IEnumerable GenerateCombinationsCounts(int length, int maxValue) + { + if (length <= 0) + { + yield break; + } + + var baseN = maxValue + 1; + var total = 1; + + for (var i = 0; i < length; i++) + { + total *= baseN; + } + + for (var i = 0; i < total; i++) + { + // ReSharper disable once StackAllocInsideLoop + Span currentCombination = stackalloc int[length]; + + var temp = i; + for (var j = length - 1; j >= 0; j--) + { + currentCombination[j] = temp % baseN; + temp /= baseN; + } + + // Copy from Span (stack) to heap-allocated array + var result = new int[length]; + currentCombination.CopyTo(result); + + yield return result; + } + } + + public static IEnumerable CombineArguments(params IEnumerable[] argumentSets) + { + if (argumentSets.Length == 0) + { + yield break; + } + + IEnumerable combinations = new[] { Array.Empty() }; + + foreach (var argumentValues in argumentSets) + { + combinations = combinations.SelectMany(_ => argumentValues.Cast(), (c, v) => c.Concat(new[] { v }) + .ToArray()); + } + + foreach (var combination in combinations) + { + yield return combination; + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs new file mode 100644 index 0000000000..3acb3818cc --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs @@ -0,0 +1,19 @@ +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + internal sealed class FieldOrPropertyDeclarationTheoryData : TheoryData + { + public FieldOrPropertyDeclarationTheoryData() + { + AddRange( +#if NET5_0_OR_GREATER + "Property { get; init; }", +#else + "Property { get; set; }", +#endif + "_field;" + ); + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs new file mode 100644 index 0000000000..76f270f8af --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs @@ -0,0 +1,20 @@ +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + using Xunit; + + internal sealed class NonPublicClassAccessModifiersTheoryData : TheoryData + { + public NonPublicClassAccessModifiersTheoryData() + { + AddRange( + "protected internal ", + "protected ", + "internal ", + "private protected ", + "private ", + "file ", + "" + ); + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs new file mode 100644 index 0000000000..6b3599dc4a --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs @@ -0,0 +1,19 @@ +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + using Xunit; + + internal sealed class NonPublicClassMemberAccessModifiersTheoryData : TheoryData + { + public NonPublicClassMemberAccessModifiersTheoryData() + { + AddRange( + "protected internal ", + "protected ", + "internal ", + "private protected ", + "private ", + "" + ); + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs new file mode 100644 index 0000000000..7138a02507 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs @@ -0,0 +1,18 @@ +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + using Xunit; + + internal sealed class NonPublicPropertySetterAccessModifiersTheoryData : TheoryData + { + public NonPublicPropertySetterAccessModifiersTheoryData() + { + AddRange( + "protected internal", + "protected", + "internal", + "private protected", + "private" + ); + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/BenchmarkDotNet.Analyzers.Vsix.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/BenchmarkDotNet.Analyzers.Vsix.csproj new file mode 100644 index 0000000000..6c4102ba55 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/BenchmarkDotNet.Analyzers.Vsix.csproj @@ -0,0 +1,47 @@ + + + + + + net48 + BenchmarkDotNet.Analyzers.Vsix + BenchmarkDotNet.Analyzers.Vsix + + + + false + false + false + false + false + false + Roslyn + + + + + + + + Program + $(DevEnvDir)devenv.exe + /rootsuffix $(VSSDKTargetPlatformRegRootSuffix) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/source.extension.vsixmanifest b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/source.extension.vsixmanifest new file mode 100644 index 0000000000..8386348009 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/source.extension.vsixmanifest @@ -0,0 +1,24 @@ + + + + + BenchmarkDotNet.Analyzers + Analyzers for BenchmarkDotNet + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs new file mode 100644 index 0000000000..9f56c711b8 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -0,0 +1,73 @@ +namespace BenchmarkDotNet.Analyzers +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using System.Collections.Immutable; + + internal static class AnalyzerHelper + { + public static LocalizableResourceString GetResourceString(string name) => new LocalizableResourceString(name, BenchmarkDotNetAnalyzerResources.ResourceManager, typeof(BenchmarkDotNetAnalyzerResources)); + + public static INamedTypeSymbol GetBenchmarkAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkAttribute"); + + public static bool AttributeListsContainAttribute(string attributeName, Compilation compilation, SyntaxList attributeLists, SemanticModel semanticModel) => AttributeListsContainAttribute(compilation.GetTypeByMetadataName(attributeName), attributeLists, semanticModel); + + public static bool AttributeListsContainAttribute(INamedTypeSymbol attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) + { + if (attributeTypeSymbol == null) + { + return false; + } + + foreach (var attributeListSyntax in attributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null) + { + continue; + } + + if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return true; + } + } + } + + return false; + } + + public static ImmutableArray GetAttributes(string attributeName, Compilation compilation, SyntaxList attributeLists, SemanticModel semanticModel) => GetAttributes(compilation.GetTypeByMetadataName(attributeName), attributeLists, semanticModel); + + public static ImmutableArray GetAttributes(INamedTypeSymbol attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) + { + var attributesBuilder = ImmutableArray.CreateBuilder(); + + if (attributeTypeSymbol == null) + { + return attributesBuilder.ToImmutable(); + } + + foreach (var attributeListSyntax in attributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null) + { + continue; + } + + if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) + { + attributesBuilder.Add(attributeSyntax); + } + } + } + + return attributesBuilder.ToImmutable(); + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Shipped.md b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000000..ab7ee321e4 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,2 @@ +; Shipped analyzer releases +; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000000..b8c5dc41b6 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -0,0 +1,35 @@ +; Unshipped analyzer release +; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + +Rule ID | Category | Severity | Notes +---------|----------|----------|-------------------- +BDN1000 | Usage | Error | BDN1000_BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods +BDN1001 | Usage | Error | BDN1001_General_BenchmarkClass_MethodMustBePublic +BDN1002 | Usage | Error | BDN1002_General_BenchmarkClass_MethodMustBeNonGeneric +BDN1003 | Usage | Error | BDN1003_General_BenchmarkClass_ClassMustBePublic +BDN1004 | Usage | Error | BDN1004_General_BenchmarkClass_ClassMustBeNonStatic +BDN1005 | Usage | Error | BDN1005_General_BenchmarkClass_ClassMustBeNonAbstract +BDN1006 | Usage | Error | BDN1006_General_BenchmarkClass_ClassMustBeNonGeneric +BDN1007 | Usage | Error | BDN1007_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters +BDN1008 | Usage | Error | BDN1008_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount +BDN1009 | Usage | Error | BDN1009_General_BenchmarkClass_ClassMustBeUnsealed +BDN1010 | Usage | Error | BDN1010_General_BenchmarkClass_OnlyOneMethodCanBeBaseline +BDN1011 | Usage | Error | BDN1011_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField +BDN1012 | Usage | Error | BDN1012_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty +BDN1013 | Usage | Error | BDN1013_Attributes_GeneralParameterAttributes_FieldMustBePublic +BDN1014 | Usage | Error | BDN1014_Attributes_GeneralParameterAttributes_PropertyMustBePublic +BDN1015 | Usage | Error | BDN1015_Attributes_GeneralParameterAttributes_NotValidOnReadonlyField +BDN1016 | Usage | Error | BDN1016_Attributes_GeneralParameterAttributes_NotValidOnConstantField +BDN1017 | Usage | Error | BDN1017_Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly +BDN1018 | Usage | Error | BDN1018_Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter +BDN1019 | Usage | Error | BDN1019_Attributes_ParamsAttribute_MustHaveValues +BDN1020 | Usage | Error | BDN1020_Attributes_ParamsAttribute_UnexpectedValueType +BDN1021 | Usage | Warning | BDN1021_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute +BDN1022 | Usage | Error | BDN1022_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType +BDN1023 | Usage | Error | BDN1023_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool +BDN1024 | Usage | Error | BDN1024_Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute +BDN1025 | Usage | Error | BDN1025_Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters +BDN1026 | Usage | Error | BDN1026_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount +BDN1027 | Usage | Error | BDN1027_Attributes_ArgumentsAttribute_MustHaveMatchingValueType diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs new file mode 100644 index 0000000000..ad1277415c --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -0,0 +1,331 @@ +namespace BenchmarkDotNet.Analyzers.Attributes +{ + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + using Microsoft.CodeAnalysis.Text; + + using System; + using System.Collections.Immutable; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor RequiresBenchmarkAttributeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + internal static readonly DiagnosticDescriptor MethodWithoutAttributeMustHaveNoParametersRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_Description); + + internal static readonly DiagnosticDescriptor MustHaveMatchingValueCountRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_MustHaveMatchingValueCount, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_Description))); + + internal static readonly DiagnosticDescriptor MustHaveMatchingValueTypeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_MustHaveMatchingValueType, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueType_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Description))); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + RequiresBenchmarkAttributeRule, + MethodWithoutAttributeMustHaveNoParametersRule, + MustHaveMatchingValueCountRule, + MustHaveMatchingValueTypeRule + ); + + public override void Initialize(AnalysisContext analysisContext) + { + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet.Annotations is referenced + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } + + ctx.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); + }); + } + + private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) + { + if (!(context.Node is MethodDeclarationSyntax methodDeclarationSyntax)) + { + return; + } + + var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation); + if (argumentsAttributeTypeSymbol == null) + { + return; + } + + var hasBenchmarkAttribute = AnalyzerHelper.AttributeListsContainAttribute(AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation), methodDeclarationSyntax.AttributeLists, context.SemanticModel); + + var argumentsAttributes = AnalyzerHelper.GetAttributes(argumentsAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); + if (argumentsAttributes.Length == 0) + { + if (hasBenchmarkAttribute && methodDeclarationSyntax.ParameterList.Parameters.Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create(MethodWithoutAttributeMustHaveNoParametersRule, Location.Create(context.FilterTree, methodDeclarationSyntax.ParameterList.Parameters.Span))); + } + + return; + } + + if (!hasBenchmarkAttribute) + { + foreach (var argumentsAttributeSyntax in argumentsAttributes) + { + context.ReportDiagnostic(Diagnostic.Create(RequiresBenchmarkAttributeRule, argumentsAttributeSyntax.GetLocation())); + } + + return; + } + + var methodParameterTypeSymbolsBuilder = ImmutableArray.CreateBuilder(methodDeclarationSyntax.ParameterList.Parameters.Count); + + foreach (var parameterSyntax in methodDeclarationSyntax.ParameterList.Parameters) + { + if (parameterSyntax.Type != null) + { + var expectedParameterTypeSymbol = context.SemanticModel.GetTypeInfo(parameterSyntax.Type).Type; + if (expectedParameterTypeSymbol != null && expectedParameterTypeSymbol.TypeKind != TypeKind.Error) + { + methodParameterTypeSymbolsBuilder.Add(expectedParameterTypeSymbol); + + continue; + } + + methodParameterTypeSymbolsBuilder.Add(null); + } + } + + var methodParameterTypeSymbols = methodParameterTypeSymbolsBuilder.ToImmutable(); + + foreach (var argumentsAttributeSyntax in argumentsAttributes) + { + if (argumentsAttributeSyntax.ArgumentList == null) + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) + { + ReportMustHaveMatchingValueCountDiagnostic(argumentsAttributeSyntax.GetLocation(), 0); + } + } + else if (!argumentsAttributeSyntax.ArgumentList.Arguments.Any()) + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) + { + ReportMustHaveMatchingValueCountDiagnostic(argumentsAttributeSyntax.ArgumentList.GetLocation(), 0); + } + } + else + { + // Check if this is an explicit params array creation + + var attributeArgumentSyntax = argumentsAttributeSyntax.ArgumentList.Arguments.First(); + if (attributeArgumentSyntax.NameEquals != null) + { + // Ignore named arguments, e.g. Priority + if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) + { + ReportMustHaveMatchingValueCountDiagnostic(attributeArgumentSyntax.GetLocation(), 0); + } + } + + // Collection expression + + else if (attributeArgumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax) + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count != collectionExpressionSyntax.Elements.Count) + { + ReportMustHaveMatchingValueCountDiagnostic(collectionExpressionSyntax.Elements.Count == 0 + ? collectionExpressionSyntax.GetLocation() + : Location.Create(context.FilterTree, collectionExpressionSyntax.Elements.Span), + collectionExpressionSyntax.Elements.Count); + + continue; + } + + ReportIfValueTypeMismatchDiagnostic(i => collectionExpressionSyntax.Elements[i] is ExpressionElementSyntax expressionElementSyntax ? expressionElementSyntax.Expression : null); + } + + // Array creation expression + else + { + var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeArgumentSyntax.Expression).Type; + if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol) + { + if (arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object) + { + if (attributeArgumentSyntax.Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax) + { + if (arrayCreationExpressionSyntax.Initializer == null) + { + var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First(); + if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression)) + { + if (literalExpressionSyntax.Token.Value is int rankSpecifierSize && rankSpecifierSize == 0) + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) + { + ReportMustHaveMatchingValueCountDiagnostic(literalExpressionSyntax.GetLocation(), 0); + } + } + } + } + else + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count != arrayCreationExpressionSyntax.Initializer.Expressions.Count) + { + ReportMustHaveMatchingValueCountDiagnostic(arrayCreationExpressionSyntax.Initializer.Expressions.Count == 0 + ? arrayCreationExpressionSyntax.Initializer.GetLocation() + : Location.Create(context.FilterTree, arrayCreationExpressionSyntax.Initializer.Expressions.Span), + arrayCreationExpressionSyntax.Initializer.Expressions.Count); + + continue; + } + + // ReSharper disable once PossibleNullReferenceException + ReportIfValueTypeMismatchDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]); + } + } + } + } + else + { + // Params values + + var firstNamedArgumentIndex = IndexOfNamedArgument(argumentsAttributeSyntax.ArgumentList.Arguments); + if (firstNamedArgumentIndex > 0) + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count != firstNamedArgumentIndex.Value) + { + ReportMustHaveMatchingValueCountDiagnostic(Location.Create(context.FilterTree, TextSpan.FromBounds(argumentsAttributeSyntax.ArgumentList.Arguments.Span.Start, argumentsAttributeSyntax.ArgumentList.Arguments[firstNamedArgumentIndex.Value - 1].Span.End)), + firstNamedArgumentIndex.Value); + + continue; + } + + // ReSharper disable once PossibleNullReferenceException + ReportIfValueTypeMismatchDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); + } + else + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count != argumentsAttributeSyntax.ArgumentList.Arguments.Count) + { + ReportMustHaveMatchingValueCountDiagnostic(Location.Create(context.FilterTree, argumentsAttributeSyntax.ArgumentList.Arguments.Span), + argumentsAttributeSyntax.ArgumentList.Arguments.Count); + + continue; + } + + // ReSharper disable once PossibleNullReferenceException + ReportIfValueTypeMismatchDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); + } + } + } + } + } + + return; + + void ReportMustHaveMatchingValueCountDiagnostic(Location diagnosticLocation, int valueCount) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueCountRule, diagnosticLocation, + methodDeclarationSyntax.ParameterList.Parameters.Count, + methodDeclarationSyntax.ParameterList.Parameters.Count == 1 ? "" : "s", + methodDeclarationSyntax.Identifier.ToString(), + valueCount)); + } + + void ReportIfValueTypeMismatchDiagnostic(Func valueExpressionSyntaxFunc) + { + for (var i = 0; i < methodParameterTypeSymbols.Length; i++) + { + var methodParameterTypeSymbol = methodParameterTypeSymbols[i]; + if (methodParameterTypeSymbol == null) + { + continue; + } + + var valueExpressionSyntax = valueExpressionSyntaxFunc(i); + if (valueExpressionSyntax == null) + { + continue; + } + + var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntaxFunc(i)).Type; + if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error) + { + var conversionSummary = context.Compilation.ClassifyConversion(actualValueTypeSymbol, methodParameterTypeSymbol); + if (!conversionSummary.IsImplicit) + { + ReportMustHaveMatchingValueTypeDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + methodParameterTypeSymbol.ToString(), + actualValueTypeSymbol.ToString()); + } + } + else + { + ReportMustHaveMatchingValueTypeDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + methodParameterTypeSymbol.ToString()); + } + } + + return; + + void ReportMustHaveMatchingValueTypeDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType = null) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueTypeRule, + diagnosticLocation, + value, + expectedType, + actualType ?? "")); + } + } + } + + private static INamedTypeSymbol GetArgumentsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); + + private static int? IndexOfNamedArgument(SeparatedSyntaxList attributeArguments) + { + var i = 0; + + foreach (var attributeArgumentSyntax in attributeArguments) + { + if (attributeArgumentSyntax.NameEquals != null) + { + return i; + } + + i++; + } + + return null; + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs new file mode 100644 index 0000000000..4967a1e4d3 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs @@ -0,0 +1,329 @@ +namespace BenchmarkDotNet.Analyzers.Attributes +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class GeneralParameterAttributesAnalyzer : DiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor MutuallyExclusiveOnFieldRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_Description))); + + internal static readonly DiagnosticDescriptor MutuallyExclusiveOnPropertyRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_Description))); + + internal static readonly DiagnosticDescriptor FieldMustBePublic = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralParameterAttributes_FieldMustBePublic, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_FieldMustBePublic_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_FieldMustBePublic_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_FieldMustBePublic_Description))); + + internal static readonly DiagnosticDescriptor PropertyMustBePublic = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralParameterAttributes_PropertyMustBePublic, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyMustBePublic_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyMustBePublic_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyMustBePublic_Description))); + + internal static readonly DiagnosticDescriptor NotValidOnReadonlyFieldRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralParameterAttributes_NotValidOnReadonlyField, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_Description))); + + internal static readonly DiagnosticDescriptor NotValidOnConstantFieldRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralParameterAttributes_NotValidOnConstantField, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_NotValidOnConstantField_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_NotValidOnConstantField_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + internal static readonly DiagnosticDescriptor PropertyMustHavePublicSetterRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_Description))); + + internal static readonly DiagnosticDescriptor PropertyCannotBeInitOnlyRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_Description))); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + MutuallyExclusiveOnFieldRule, + MutuallyExclusiveOnPropertyRule, + FieldMustBePublic, + PropertyMustBePublic, + NotValidOnReadonlyFieldRule, + NotValidOnConstantFieldRule, + PropertyCannotBeInitOnlyRule, + PropertyMustHavePublicSetterRule + ); + public override void Initialize(AnalysisContext analysisContext) + { + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet.Annotations is referenced + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } + + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); + }); + } + + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (!(context.Node is AttributeSyntax attributeSyntax)) + { + return; + } + + if (!AllAttributeTypeSymbolsExist(context, out var paramsAttributeTypeSymbol, out var paramsSourceAttributeTypeSymbol, out var paramsAllValuesAttributeTypeSymbol)) + { + return; + } + + var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if ( attributeSyntaxTypeSymbol == null + || !attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default) + && !attributeSyntaxTypeSymbol.Equals(paramsSourceAttributeTypeSymbol, SymbolEqualityComparer.Default) + && !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return; + } + + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax || n is PropertyDeclarationSyntax); + if (attributeTarget == null) + { + return; + } + + ImmutableArray declaredAttributes; + bool fieldOrPropertyIsPublic; + Location fieldConstModifierLocation = null; + Location fieldReadonlyModifierLocation = null; + string fieldOrPropertyIdentifier; + Location propertyInitAccessorKeywordLocation = null; + Location fieldOrPropertyIdentifierLocation; + bool propertyIsMissingAssignableSetter = false; + DiagnosticDescriptor fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule; + DiagnosticDescriptor fieldOrPropertyMustBePublicDiagnosticRule; + + if (attributeTarget is FieldDeclarationSyntax fieldDeclarationSyntax) + { + declaredAttributes = fieldDeclarationSyntax.AttributeLists.SelectMany(als => als.Attributes).ToImmutableArray(); + fieldOrPropertyIsPublic = fieldDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword); + + var fieldConstModifierIndex = fieldDeclarationSyntax.Modifiers.IndexOf(SyntaxKind.ConstKeyword); + fieldConstModifierLocation = fieldConstModifierIndex >= 0 ? fieldDeclarationSyntax.Modifiers[fieldConstModifierIndex].GetLocation() : null; + + var fieldOrPropertyReadonlyModifierIndex = fieldDeclarationSyntax.Modifiers.IndexOf(SyntaxKind.ReadOnlyKeyword); + fieldReadonlyModifierLocation = fieldOrPropertyReadonlyModifierIndex >= 0 ? fieldDeclarationSyntax.Modifiers[fieldOrPropertyReadonlyModifierIndex].GetLocation() : null; + + fieldOrPropertyIdentifier = fieldDeclarationSyntax.Declaration.Variables[0].Identifier.ToString(); + fieldOrPropertyIdentifierLocation = fieldDeclarationSyntax.Declaration.Variables[0].Identifier.GetLocation(); + fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule = MutuallyExclusiveOnFieldRule; + fieldOrPropertyMustBePublicDiagnosticRule = FieldMustBePublic; + } + else if (attributeTarget is PropertyDeclarationSyntax propertyDeclarationSyntax) + { + declaredAttributes = propertyDeclarationSyntax.AttributeLists.SelectMany(als => als.Attributes).ToImmutableArray(); + fieldOrPropertyIsPublic = propertyDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword); + fieldOrPropertyIdentifier = propertyDeclarationSyntax.Identifier.ToString(); + + var propertyInitAccessorIndex = propertyDeclarationSyntax.AccessorList?.Accessors.IndexOf(SyntaxKind.InitAccessorDeclaration); + propertyInitAccessorKeywordLocation = propertyInitAccessorIndex >= 0 ? propertyDeclarationSyntax.AccessorList.Accessors[propertyInitAccessorIndex.Value].Keyword.GetLocation() : null; + + var propertySetAccessorIndex = propertyDeclarationSyntax.AccessorList?.Accessors.IndexOf(SyntaxKind.SetAccessorDeclaration); + propertyIsMissingAssignableSetter = !propertySetAccessorIndex.HasValue || propertySetAccessorIndex.Value < 0 || propertyDeclarationSyntax.AccessorList.Accessors[propertySetAccessorIndex.Value].Modifiers.Any(); + + fieldOrPropertyIdentifierLocation = propertyDeclarationSyntax.Identifier.GetLocation(); + fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule = MutuallyExclusiveOnPropertyRule; + fieldOrPropertyMustBePublicDiagnosticRule = PropertyMustBePublic; + } + else + { + return; + } + + AnalyzeFieldOrPropertySymbol(context, + paramsAttributeTypeSymbol, + paramsSourceAttributeTypeSymbol, + paramsAllValuesAttributeTypeSymbol, + declaredAttributes, + fieldOrPropertyIsPublic, + fieldConstModifierLocation, + fieldReadonlyModifierLocation, + fieldOrPropertyIdentifier, + propertyInitAccessorKeywordLocation, + propertyIsMissingAssignableSetter, + fieldOrPropertyIdentifierLocation, + fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule, + fieldOrPropertyMustBePublicDiagnosticRule, + attributeSyntax); + } + + private static void AnalyzeFieldOrPropertySymbol(SyntaxNodeAnalysisContext context, + INamedTypeSymbol paramsAttributeTypeSymbol, + INamedTypeSymbol paramsSourceAttributeTypeSymbol, + INamedTypeSymbol paramsAllValuesAttributeTypeSymbol, + ImmutableArray declaredAttributes, + bool fieldOrPropertyIsPublic, + Location fieldConstModifierLocation, + Location fieldReadonlyModifierLocation, + string fieldOrPropertyIdentifier, + Location propertyInitAccessorKeywordLocation, + bool propertyIsMissingAssignableSetter, + Location fieldOrPropertyIdentifierLocation, + DiagnosticDescriptor fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule, + DiagnosticDescriptor fieldOrPropertyMustBePublicDiagnosticRule, + AttributeSyntax attributeSyntax) + { + var applicableParameterAttributeTypeSymbols = new[] + { + paramsAttributeTypeSymbol, + paramsSourceAttributeTypeSymbol, + paramsAllValuesAttributeTypeSymbol + }.ToImmutableArray(); + + var parameterAttributeTypeSymbols = new HashSet(SymbolEqualityComparer.Default); + + foreach (var declaredAttributeSyntax in declaredAttributes) + { + var declaredAttributeTypeSymbol = context.SemanticModel.GetTypeInfo(declaredAttributeSyntax).Type; + if (declaredAttributeTypeSymbol != null) + { + foreach (var applicableParameterAttributeTypeSymbol in applicableParameterAttributeTypeSymbols) + { + if (declaredAttributeTypeSymbol.Equals(applicableParameterAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + if (!parameterAttributeTypeSymbols.Add(applicableParameterAttributeTypeSymbol)) + { + return; + } + } + } + } + } + + if (parameterAttributeTypeSymbols.Count == 0) + { + return; + } + + if (parameterAttributeTypeSymbols.Count == 1) + { + if (fieldConstModifierLocation != null) + { + context.ReportDiagnostic(Diagnostic.Create(NotValidOnConstantFieldRule, + fieldConstModifierLocation, + fieldOrPropertyIdentifier, + attributeSyntax.Name.ToString())); + + return; + } + + if (!fieldOrPropertyIsPublic) + { + context.ReportDiagnostic(Diagnostic.Create(fieldOrPropertyMustBePublicDiagnosticRule, + fieldOrPropertyIdentifierLocation, + fieldOrPropertyIdentifier, + attributeSyntax.Name.ToString())); + } + + if (fieldReadonlyModifierLocation != null) + { + context.ReportDiagnostic(Diagnostic.Create(NotValidOnReadonlyFieldRule, + fieldReadonlyModifierLocation, + fieldOrPropertyIdentifier, + attributeSyntax.Name.ToString())); + } + + if (propertyInitAccessorKeywordLocation != null) + { + context.ReportDiagnostic(Diagnostic.Create(PropertyCannotBeInitOnlyRule, + propertyInitAccessorKeywordLocation, + fieldOrPropertyIdentifier, + attributeSyntax.Name.ToString())); + } + else if (propertyIsMissingAssignableSetter) + { + context.ReportDiagnostic(Diagnostic.Create(PropertyMustHavePublicSetterRule, + fieldOrPropertyIdentifierLocation, + fieldOrPropertyIdentifier, + attributeSyntax.Name.ToString())); + } + + return; + } + + context.ReportDiagnostic(Diagnostic.Create(fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule, + attributeSyntax.GetLocation(), + fieldOrPropertyIdentifier)); + } + + private static bool AllAttributeTypeSymbolsExist(in SyntaxNodeAnalysisContext context, + out INamedTypeSymbol paramsAttributeTypeSymbol, + out INamedTypeSymbol paramsSourceAttributeTypeSymbol, + out INamedTypeSymbol paramsAllValuesAttributeTypeSymbol) + { + paramsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute"); + if (paramsAttributeTypeSymbol == null) + { + paramsSourceAttributeTypeSymbol = null; + paramsAllValuesAttributeTypeSymbol = null; + + return false; + } + + paramsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsSourceAttribute"); + if (paramsSourceAttributeTypeSymbol == null) + { + paramsAllValuesAttributeTypeSymbol = null; + + return false; + } + + paramsAllValuesAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAllValuesAttribute"); + if (paramsAllValuesAttributeTypeSymbol == null) + { + return false; + } + + return true; + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs new file mode 100644 index 0000000000..30f3aaadfa --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs @@ -0,0 +1,133 @@ +namespace BenchmarkDotNet.Analyzers.Attributes +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + + using System.Collections.Immutable; + using System.Linq; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ParamsAllValuesAttributeAnalyzer : DiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor NotAllowedOnFlagsEnumPropertyOrFieldTypeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_Description))); + + internal static readonly DiagnosticDescriptor PropertyOrFieldTypeMustBeEnumOrBoolRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + NotAllowedOnFlagsEnumPropertyOrFieldTypeRule, + PropertyOrFieldTypeMustBeEnumOrBoolRule + ); + + public override void Initialize(AnalysisContext analysisContext) + { + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet.Annotations is referenced + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } + + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); + }); + } + + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (!(context.Node is AttributeSyntax attributeSyntax)) + { + return; + } + + var paramsAllValuesAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAllValuesAttribute"); + if (paramsAllValuesAttributeTypeSymbol == null) + { + return; + } + + var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null || !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return; + } + + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax || n is PropertyDeclarationSyntax); + if (attributeTarget == null) + { + return; + } + + TypeSyntax fieldOrPropertyTypeSyntax; + + if (attributeTarget is FieldDeclarationSyntax fieldDeclarationSyntax) + { + fieldOrPropertyTypeSyntax = fieldDeclarationSyntax.Declaration.Type; + + } + else if (attributeTarget is PropertyDeclarationSyntax propertyDeclarationSyntax) + { + fieldOrPropertyTypeSyntax = propertyDeclarationSyntax.Type; + } + else + { + return; + } + + AnalyzeFieldOrPropertyTypeSyntax(context, + fieldOrPropertyTypeSyntax); + } + + private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, + TypeSyntax fieldOrPropertyTypeSyntax) + { + if (fieldOrPropertyTypeSyntax is NullableTypeSyntax fieldOrPropertyNullableTypeSyntax) + { + fieldOrPropertyTypeSyntax = fieldOrPropertyNullableTypeSyntax.ElementType; + } + + var fieldOrPropertyTypeSymbol = context.SemanticModel.GetTypeInfo(fieldOrPropertyTypeSyntax).Type; + if (fieldOrPropertyTypeSymbol == null || fieldOrPropertyTypeSymbol.TypeKind == TypeKind.Error) + { + return; + } + + if (fieldOrPropertyTypeSymbol.TypeKind == TypeKind.Enum) + { + var flagsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("System.FlagsAttribute"); + if (flagsAttributeTypeSymbol == null) + { + return; + } + + if (fieldOrPropertyTypeSymbol.GetAttributes().Any(ad => ad.AttributeClass != null && ad.AttributeClass.Equals(flagsAttributeTypeSymbol, SymbolEqualityComparer.Default))) + { + context.ReportDiagnostic(Diagnostic.Create(NotAllowedOnFlagsEnumPropertyOrFieldTypeRule, fieldOrPropertyTypeSyntax.GetLocation(), fieldOrPropertyTypeSymbol.ToString())); + } + + return; + } + + if (fieldOrPropertyTypeSymbol.SpecialType != SpecialType.System_Boolean) + { + context.ReportDiagnostic(Diagnostic.Create(PropertyOrFieldTypeMustBeEnumOrBoolRule, fieldOrPropertyTypeSyntax.GetLocation())); + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs new file mode 100644 index 0000000000..01177ce208 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs @@ -0,0 +1,283 @@ +namespace BenchmarkDotNet.Analyzers.Attributes +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + + using System.Collections.Immutable; + using System.Linq; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ParamsAttributeAnalyzer : DiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor MustHaveValuesRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_MustHaveValues, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_MustHaveValues_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_MustHaveValues_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + internal static readonly DiagnosticDescriptor UnexpectedValueTypeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_UnexpectedValueType, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnexpectedValueType_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnexpectedValueType_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnexpectedValueType_Description))); + + internal static readonly DiagnosticDescriptor UnnecessarySingleValuePassedToAttributeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_MessageFormat)), + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + MustHaveValuesRule, + UnexpectedValueTypeRule, + UnnecessarySingleValuePassedToAttributeRule + ); + + public override void Initialize(AnalysisContext analysisContext) + { + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet.Annotations is referenced + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } + + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); + }); + } + + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (!(context.Node is AttributeSyntax attributeSyntax)) + { + return; + } + + var paramsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute"); + if (paramsAttributeTypeSymbol == null) + { + return; + } + + var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null || !attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return; + } + + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax || n is PropertyDeclarationSyntax); + if (attributeTarget == null) + { + return; + } + + TypeSyntax fieldOrPropertyTypeSyntax; + + if (attributeTarget is FieldDeclarationSyntax fieldDeclarationSyntax) + { + fieldOrPropertyTypeSyntax = fieldDeclarationSyntax.Declaration.Type; + + } + else if (attributeTarget is PropertyDeclarationSyntax propertyDeclarationSyntax) + { + fieldOrPropertyTypeSyntax = propertyDeclarationSyntax.Type; + } + else + { + return; + } + + AnalyzeFieldOrPropertyTypeSyntax(context, + fieldOrPropertyTypeSyntax, + attributeSyntax); + } + + private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, + TypeSyntax fieldOrPropertyTypeSyntax, + AttributeSyntax attributeSyntax) + { + if (attributeSyntax.ArgumentList == null) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, + attributeSyntax.GetLocation())); + + return; + } + + if (!attributeSyntax.ArgumentList.Arguments.Any()) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, + attributeSyntax.ArgumentList.GetLocation())); + + return; + } + + if (attributeSyntax.ArgumentList.Arguments.All(aas => aas.NameEquals != null)) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, + Location.Create(context.FilterTree, attributeSyntax.ArgumentList.Arguments.Span))); + + return; + } + + var expectedValueTypeSymbol = context.SemanticModel.GetTypeInfo(fieldOrPropertyTypeSyntax).Type; + if (expectedValueTypeSymbol == null || expectedValueTypeSymbol.TypeKind == TypeKind.Error) + { + return; + } + + + // Check if this is an explicit params array creation + + var attributeArgumentSyntax = attributeSyntax.ArgumentList.Arguments.First(); + if (attributeArgumentSyntax.NameEquals != null) + { + // Ignore named arguments, e.g. Priority + return; + } + + // Collection expression + + if (attributeArgumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax) + { + if (!collectionExpressionSyntax.Elements.Any()) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, + collectionExpressionSyntax.GetLocation())); + return; + } + + if (collectionExpressionSyntax.Elements.Count == 1) + { + context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule, + collectionExpressionSyntax.Elements[0].GetLocation())); + } + + foreach (var collectionElementSyntax in collectionExpressionSyntax.Elements) + { + if (collectionElementSyntax is ExpressionElementSyntax expressionElementSyntax) + { + ReportIfUnexpectedValueTypeDiagnostic(expressionElementSyntax.Expression); + } + } + + return; + } + + // Array creation expression + + var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeArgumentSyntax.Expression).Type; + if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol) + { + if (arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object) + { + if (attributeArgumentSyntax.Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax) + { + if (arrayCreationExpressionSyntax.Initializer == null) + { + var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First(); + if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression)) + { + if (literalExpressionSyntax.Token.Value is int rankSpecifierSize && rankSpecifierSize == 0) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, + arrayCreationExpressionSyntax.GetLocation())); + } + } + + return; + } + + if (!arrayCreationExpressionSyntax.Initializer.Expressions.Any()) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, + arrayCreationExpressionSyntax.Initializer.GetLocation())); + + return; + } + + if (arrayCreationExpressionSyntax.Initializer.Expressions.Count == 1) + { + context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule, + arrayCreationExpressionSyntax.Initializer.Expressions[0].GetLocation())); + } + + foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions) + { + ReportIfUnexpectedValueTypeDiagnostic(expressionSyntax); + } + } + } + + return; + } + + + // Params values + + if (attributeSyntax.ArgumentList.Arguments.Count(aas => aas.NameEquals == null) == 1) + { + context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule, + attributeArgumentSyntax.Expression.GetLocation())); + } + + foreach (var parameterValueAttributeArgumentSyntax in attributeSyntax.ArgumentList.Arguments) + { + if (parameterValueAttributeArgumentSyntax.NameEquals != null) + { + // Ignore named arguments, e.g. Priority + continue; + } + + ReportIfUnexpectedValueTypeDiagnostic(parameterValueAttributeArgumentSyntax.Expression); + } + + return; + + void ReportIfUnexpectedValueTypeDiagnostic(ExpressionSyntax valueExpressionSyntax) + { + var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type; + if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error) + { + var conversionSummary = context.Compilation.ClassifyConversion(actualValueTypeSymbol, expectedValueTypeSymbol); + if (!conversionSummary.IsImplicit) + { + ReportUnexpectedValueTypeDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + fieldOrPropertyTypeSyntax.ToString(), + actualValueTypeSymbol.ToString()); + } + } + else + { + ReportUnexpectedValueTypeDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + fieldOrPropertyTypeSyntax.ToString()); + } + + return; + + void ReportUnexpectedValueTypeDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType = null) + { + context.ReportDiagnostic(Diagnostic.Create(UnexpectedValueTypeRule, + diagnosticLocation, + value, + expectedType, + actualType ?? "")); + } + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj new file mode 100644 index 0000000000..47a0dda096 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj @@ -0,0 +1,38 @@ + + + + netstandard2.0 + false + + + *$(MSBuildProjectFile)* + true + + + + + + + + + + + + + + True + True + BenchmarkDotNetAnalyzerResources.resx + + + ResXFileCodeGenerator + BenchmarkDotNetAnalyzerResources.Designer.cs + + + + + + <_Parameter1>BenchmarkDotNet.Analyzers.Tests + + + diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs new file mode 100644 index 0000000000..d75f64d19b --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -0,0 +1,778 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BenchmarkDotNet.Analyzers { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class BenchmarkDotNetAnalyzerResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal BenchmarkDotNetAnalyzerResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BenchmarkDotNet.Analyzers.BenchmarkDotNetAnalyzerResources", typeof(BenchmarkDotNetAnalyzerResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to This method declares one or more parameters but is not annotated with any [Arguments] attributes. To ensure correct argument binding, methods with parameters must explicitly be annotated with one or more [Arguments] attributes. + ///Either add the [Arguments] attribute(s) or remove the parameters.. + /// + internal static string Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_Description { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_Descript" + + "ion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark method without [Arguments] attribute(s) cannot declare parameters. + /// + internal static string Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_MessageF" + + "ormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark methods without [Arguments] attribute(s) cannot declare parameters. + /// + internal static string Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_Title { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of values passed to an [Arguments] attribute must match the number of parameters declared in the targeted benchmark method. + /// + internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_Description { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expected {0} value{1} as declared by the benchmark method '{2}', but found {3}. Update the attribute usage or method to match.. + /// + internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Number of values passed to an [Arguments] attribute must match the number of parameters declared in the targeted benchmark method. + /// + internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_Title { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The values passed to an [Arguments] attribute must match the parameters declared in the targeted benchmark method in both type (or be convertible to) and order. + /// + internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Description { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected type for value '{0}'. Expected '{1}' but found '{2}'.. + /// + internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueType_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MustHaveMatchingValueType_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Values passed to an [Arguments] attribute must match exactly the parameters declared in the targeted benchmark method in both type and order. + /// + internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Title { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The [Arguments] attribute can only be used on methods annotated with the [Benchmark] attribute. + /// + internal static string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [Arguments] attribute can only be used on methods annotated with the [Benchmark] attribute. + /// + internal static string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_Title { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A field annotated with a parameter attribute must be public. + /// + internal static string Attributes_GeneralParameterAttributes_FieldMustBePublic_Description { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_FieldMustBePublic_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Field '{0}' annotated with [{1}] must be public. + /// + internal static string Attributes_GeneralParameterAttributes_FieldMustBePublic_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_FieldMustBePublic_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fields annotated with a parameter attribute must be public. + /// + internal static string Attributes_GeneralParameterAttributes_FieldMustBePublic_Title { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_FieldMustBePublic_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter attributes are mutually exclusive; only one of the attributes [Params], [ParamsSource] or [ParamsAllValues] can be applied to a field at any one time. + /// + internal static string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_Description { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate parameter attribute on field '{0}'. + /// + internal static string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only one parameter attribute can be applied to a field. + /// + internal static string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_Title { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter attributes are mutually exclusive; only one of the attributes [Params], [ParamsSource] or [ParamsAllValues] can be applied to a property at any one time. + /// + internal static string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_Description { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Duplicate parameter attribute on property '{0}'. + /// + internal static string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only one parameter attribute can be applied to a property. + /// + internal static string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_Title { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter attribute [{0}] is not valid on constants. It is only valid on non-constant field declarations.. + /// + internal static string Attributes_GeneralParameterAttributes_NotValidOnConstantField_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_NotValidOnConstantField_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter attributes are not valid on constant field declarations. + /// + internal static string Attributes_GeneralParameterAttributes_NotValidOnConstantField_Title { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_NotValidOnConstantField_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parameter attributes are not valid on fields with a readonly modifier. + /// + internal static string Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_Description { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Modifier 'readonly' is not valid on field '{0}' annotated with parameter attribute [{1}]. + /// + internal static string Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Fields annotated with a parameter attribute cannot be read-only. + /// + internal static string Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_Title { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_NotValidOnReadonlyField_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A property annotated with a parameter attribute must have a public, assignable setter i.e. { set; }. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_Description { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Property '{0}' annotated with [{1}] cannot be init-only. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Properties annotated with a parameter attribute cannot have an init-only setter. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_Title { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A property annotated with a parameter attribute must be public. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyMustBePublic_Description { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyMustBePublic_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Property '{0}' annotated with [{1}] must be public. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyMustBePublic_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyMustBePublic_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Properties annotated with a parameter attribute must be public. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyMustBePublic_Title { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyMustBePublic_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A property annotated with a parameter attribute must have a public setter; make sure that the access modifier of the setter is empty and that the property is not an auto-property or an expression-bodied property.. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_Description { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Property '{0}' annotated with [{1}] must have a public setter. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Properties annotated with a parameter attribute must have a public setter. + /// + internal static string Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_Title { + get { + return ResourceManager.GetString("Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The [ParamsAllValues] attribute cannot be applied to a field or property of an enum type marked with the [Flags] attribute. Use this attribute only with non-flags enum types, as [Flags] enums support bitwise combinations that cannot be exhaustively enumerated.. + /// + internal static string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_Description { + get { + return ResourceManager.GetString("Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_Desc" + + "ription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Field or property enum type '{0}' is marked with [Flags] and cannot be used with this attribute. + /// + internal static string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_Mess" + + "ageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The [ParamsAllValues] attribute cannot be applied to fields or properties of enum types marked with [Flags]. + /// + internal static string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_Title { + get { + return ResourceManager.GetString("Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType_Titl" + + "e", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The [ParamsAllValues] attribute can only be applied to a field or property of enum or bool type (or nullable of these types). + /// + internal static string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool_MessageFo" + + "rmat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The [ParamsAllValues] attribute is only valid on fields or properties of enum or bool type and nullable type for another allowed type. + /// + internal static string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool_Title { + get { + return ResourceManager.GetString("Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The [Params] attribute requires at least one value. No values were provided, or an empty array was specified.. + /// + internal static string Attributes_ParamsAttribute_MustHaveValues_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveValues_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The [Params] attribute must include at least one value. + /// + internal static string Attributes_ParamsAttribute_MustHaveValues_Title { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveValues_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type of each value provided to the [Params] attribute must match the type of the field or property it is applied to. + /// + internal static string Attributes_ParamsAttribute_UnexpectedValueType_Description { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_UnexpectedValueType_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected type for parameter value '{0}'. Expected '{1}' but found '{2}'.. + /// + internal static string Attributes_ParamsAttribute_UnexpectedValueType_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_UnexpectedValueType_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type of all value(s) passed to the [Params] attribute must match the type of the annotated field or property. + /// + internal static string Attributes_ParamsAttribute_UnexpectedValueType_Title { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_UnexpectedValueType_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Providing a single value to the [Params] attribute is unnecessary. This attribute is only useful when provided two or more values.. + /// + internal static string Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unnecessary single value passed to [Params] attribute. + /// + internal static string Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_Title { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The referenced benchmark class must have at least one method annotated with the [Benchmark] attribute. + /// + internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Description { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Intended benchmark class '{0}' has no method(s) annotated with the [Benchmark] attribute. + /// + internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_MessageFormat { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark class has no annotated method(s). + /// + internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Title { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A benchmark class must be non-abstract. + /// + internal static string General_BenchmarkClass_ClassMustBeNonAbstract_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonAbstract_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark class '{0}' cannot be abstract. + /// + internal static string General_BenchmarkClass_ClassMustBeNonAbstract_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonAbstract_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes must be non-abstract. + /// + internal static string General_BenchmarkClass_ClassMustBeNonAbstract_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonAbstract_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A benchmark class not annotated with the [GenericTypeArguments] attribute must be non-generic. + /// + internal static string General_BenchmarkClass_ClassMustBeNonGeneric_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonGeneric_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark class '{0}' cannot be generic. + /// + internal static string General_BenchmarkClass_ClassMustBeNonGeneric_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonGeneric_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes not annotated with the [GenericTypeArguments] attribute must be non-generic. + /// + internal static string General_BenchmarkClass_ClassMustBeNonGeneric_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonGeneric_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A benchmark class must be an instance class. + /// + internal static string General_BenchmarkClass_ClassMustBeNonStatic_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark class '{0}' cannot be static. + /// + internal static string General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes must be non-static. + /// + internal static string General_BenchmarkClass_ClassMustBeNonStatic_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A benchmark class must be public. + /// + internal static string General_BenchmarkClass_ClassMustBePublic_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The benchmark class '{0}' must be public. + /// + internal static string General_BenchmarkClass_ClassMustBePublic_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes must be public. + /// + internal static string General_BenchmarkClass_ClassMustBePublic_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A benchmark class bust be unsealed. + /// + internal static string General_BenchmarkClass_ClassMustBeUnsealed_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeUnsealed_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark class '{0}' cannot be sealed. + /// + internal static string General_BenchmarkClass_ClassMustBeUnsealed_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeUnsealed_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes must be unsealed. + /// + internal static string General_BenchmarkClass_ClassMustBeUnsealed_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeUnsealed_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A benchmark class annotated with the [GenericTypeArguments] attribute must be generic, having between one to three type parameters. + /// + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParamete" + + "rs_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark class '{0}' must be generic. + /// + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParamete" + + "rs_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes annotated with the [GenericTypeArguments] attribute must be generic. + /// + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParamete" + + "rs_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class. + /// + internal static string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameter" + + "Count_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expected {0} type argument{1} as declared on the benchmark class '{2}', but found {3}. Update the attribute usage or the type parameter list of the class declaration to match.. + /// + internal static string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameter" + + "Count_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class. + /// + internal static string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameter" + + "Count_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A method annotated with the [Benchmark] attribute must be non-generic. + /// + internal static string General_BenchmarkClass_MethodMustBeNonGeneric_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_MethodMustBeNonGeneric_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The benchmark method '{0}' must be non-generic. + /// + internal static string General_BenchmarkClass_MethodMustBeNonGeneric_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_MethodMustBeNonGeneric_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark methods must be non-generic. + /// + internal static string General_BenchmarkClass_MethodMustBeNonGeneric_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_MethodMustBeNonGeneric_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A method annotated with the [Benchmark] attribute must be public. + /// + internal static string General_BenchmarkClass_MethodMustBePublic_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_MethodMustBePublic_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The benchmark method '{0}' must be public. + /// + internal static string General_BenchmarkClass_MethodMustBePublic_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_MethodMustBePublic_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark methods must be public. + /// + internal static string General_BenchmarkClass_MethodMustBePublic_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_MethodMustBePublic_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only one benchmark method can be marked as baseline. + /// + internal static string General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only one benchmark method can be baseline. + /// + internal static string General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title", resourceCulture); + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx new file mode 100644 index 0000000000..a4caeab400 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The referenced benchmark class must have at least one method annotated with the [Benchmark] attribute + + + Intended benchmark class '{0}' has no method(s) annotated with the [Benchmark] attribute + + + Benchmark class has no annotated method(s) + + + A benchmark class must be an instance class + + + A benchmark class not annotated with the [GenericTypeArguments] attribute must be non-generic + + + A benchmark class must be non-abstract + + + Benchmark class '{0}' cannot be static + + + Benchmark class '{0}' cannot be abstract + + + Benchmark class '{0}' cannot be generic + + + Benchmark classes must be non-static + + + Benchmark classes must be non-abstract + + + Benchmark classes not annotated with the [GenericTypeArguments] attribute must be non-generic + + + A benchmark class must be public + + + The benchmark class '{0}' must be public + + + Benchmark classes must be public + + + A benchmark class bust be unsealed + + + Benchmark class '{0}' cannot be sealed + + + Benchmark classes must be unsealed + + + A method annotated with the [Benchmark] attribute must be public + + + A method annotated with the [Benchmark] attribute must be non-generic + + + The number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class + + + A benchmark class annotated with the [GenericTypeArguments] attribute must be generic, having between one to three type parameters + + + The benchmark method '{0}' must be public + + + The benchmark method '{0}' must be non-generic + + + Expected {0} type argument{1} as declared on the benchmark class '{2}', but found {3}. Update the attribute usage or the type parameter list of the class declaration to match. + + + Benchmark class '{0}' must be generic + + + Only one benchmark method can be marked as baseline + + + Benchmark methods must be public + + + Benchmark methods must be non-generic + + + Number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class + + + Benchmark classes annotated with the [GenericTypeArguments] attribute must be generic + + + Only one benchmark method can be baseline + + + Parameter attributes are mutually exclusive; only one of the attributes [Params], [ParamsSource] or [ParamsAllValues] can be applied to a field at any one time + + + A field annotated with a parameter attribute must be public + + + A property annotated with a parameter attribute must be public + + + A property annotated with a parameter attribute must have a public setter; make sure that the access modifier of the setter is empty and that the property is not an auto-property or an expression-bodied property. + + + The type of each value provided to the [Params] attribute must match the type of the field or property it is applied to + + + The [ParamsAllValues] attribute cannot be applied to a field or property of an enum type marked with the [Flags] attribute. Use this attribute only with non-flags enum types, as [Flags] enums support bitwise combinations that cannot be exhaustively enumerated. + + + A property annotated with a parameter attribute must have a public, assignable setter i.e. { set; } + + + Parameter attributes are mutually exclusive; only one of the attributes [Params], [ParamsSource] or [ParamsAllValues] can be applied to a property at any one time + + + Duplicate parameter attribute on field '{0}' + + + Field '{0}' annotated with [{1}] must be public + + + Expected {0} value{1} as declared by the benchmark method '{2}', but found {3}. Update the attribute usage or method to match. + + + Benchmark method without [Arguments] attribute(s) cannot declare parameters + + + Unexpected type for value '{0}'. Expected '{1}' but found '{2}'. + + + Property '{0}' annotated with [{1}] must be public + + + Property '{0}' annotated with [{1}] must have a public setter + + + The [Params] attribute requires at least one value. No values were provided, or an empty array was specified. + + + Providing a single value to the [Params] attribute is unnecessary. This attribute is only useful when provided two or more values. + + + Unexpected type for parameter value '{0}'. Expected '{1}' but found '{2}'. + + + Field or property enum type '{0}' is marked with [Flags] and cannot be used with this attribute + + + The [ParamsAllValues] attribute can only be applied to a field or property of enum or bool type (or nullable of these types) + + + Property '{0}' annotated with [{1}] cannot be init-only + + + Duplicate parameter attribute on property '{0}' + + + Only one parameter attribute can be applied to a field + + + Fields annotated with a parameter attribute must be public + + + Number of values passed to an [Arguments] attribute must match the number of parameters declared in the targeted benchmark method + + + Benchmark methods without [Arguments] attribute(s) cannot declare parameters + + + Values passed to an [Arguments] attribute must match exactly the parameters declared in the targeted benchmark method in both type and order + + + Properties annotated with a parameter attribute must be public + + + Properties annotated with a parameter attribute must have a public setter + + + The [Params] attribute must include at least one value + + + Unnecessary single value passed to [Params] attribute + + + Type of all value(s) passed to the [Params] attribute must match the type of the annotated field or property + + + The [ParamsAllValues] attribute cannot be applied to fields or properties of enum types marked with [Flags] + + + The [ParamsAllValues] attribute is only valid on fields or properties of enum or bool type and nullable type for another allowed type + + + Properties annotated with a parameter attribute cannot have an init-only setter + + + Only one parameter attribute can be applied to a property + + + Parameter attributes are not valid on fields with a readonly modifier + + + Fields annotated with a parameter attribute cannot be read-only + + + Parameter attributes are not valid on constant field declarations + + + Modifier 'readonly' is not valid on field '{0}' annotated with parameter attribute [{1}] + + + Parameter attribute [{0}] is not valid on constants. It is only valid on non-constant field declarations. + + + The number of values passed to an [Arguments] attribute must match the number of parameters declared in the targeted benchmark method + + + This method declares one or more parameters but is not annotated with any [Arguments] attributes. To ensure correct argument binding, methods with parameters must explicitly be annotated with one or more [Arguments] attributes. +Either add the [Arguments] attribute(s) or remove the parameters. + + + The values passed to an [Arguments] attribute must match the parameters declared in the targeted benchmark method in both type (or be convertible to) and order + + + [Arguments] attribute can only be used on methods annotated with the [Benchmark] attribute + + + The [Arguments] attribute can only be used on methods annotated with the [Benchmark] attribute + + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs new file mode 100644 index 0000000000..911628840a --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -0,0 +1,128 @@ +namespace BenchmarkDotNet.Analyzers.BenchmarkRunner +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + + using System.Collections.Immutable; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class RunAnalyzer : DiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor TypeArgumentClassMissingBenchmarkMethodsRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Description))); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(TypeArgumentClassMissingBenchmarkMethodsRule); + + public override void Initialize(AnalysisContext analysisContext) + { + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet is referenced + var benchmarkRunnerTypeSymbol = ctx.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"); + if (benchmarkRunnerTypeSymbol == null) + { + return; + } + + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression); + }); + } + + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (!(context.Node is InvocationExpressionSyntax invocationExpression)) + { + return; + } + + if (!(invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression)) + { + return; + } + + if (!(memberAccessExpression.Expression is IdentifierNameSyntax typeIdentifier)) + { + return; + } + + var classMemberAccessSymbol = context.SemanticModel.GetTypeInfo(typeIdentifier).Type; + if (classMemberAccessSymbol is null || !classMemberAccessSymbol.Equals(context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"), SymbolEqualityComparer.Default)) + { + return; + } + + if (!(memberAccessExpression.Name is GenericNameSyntax genericMethod)) + { + return; + } + + if (genericMethod.Identifier.ValueText != "Run") + { + return; + } + + if (genericMethod.TypeArgumentList.Arguments.Count != 1) + { + return; + } + + var benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(genericMethod.TypeArgumentList.Arguments[0]).Type; + if (benchmarkClassTypeSymbol == null || benchmarkClassTypeSymbol.TypeKind == TypeKind.Error) + { + return; + } + + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + ReportDiagnostic(); + + return; + } + + if (!HasBenchmarkAttribute()) + { + ReportDiagnostic(); + } + + return; + + bool HasBenchmarkAttribute() + { + foreach (var member in benchmarkClassTypeSymbol.GetMembers()) + { + if (member is IMethodSymbol) + { + foreach (var attributeData in member.GetAttributes()) + { + if (attributeData.AttributeClass != null) + { + if (attributeData.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return true; + } + } + } + } + } + + return false; + } + + void ReportDiagnostic() + { + context.ReportDiagnostic(Diagnostic.Create(TypeArgumentClassMissingBenchmarkMethodsRule, Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span), benchmarkClassTypeSymbol.ToString())); + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/DiagnosticIds.cs new file mode 100644 index 0000000000..4327845782 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -0,0 +1,34 @@ +namespace BenchmarkDotNet.Analyzers +{ + public static class DiagnosticIds + { + public const string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods = "BDN1000"; + public const string General_BenchmarkClass_MethodMustBePublic = "BDN1001"; + public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1002"; + public const string General_BenchmarkClass_ClassMustBePublic = "BDN1003"; + public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1004"; + public const string General_BenchmarkClass_ClassMustBeNonAbstract = "BDN1005"; + public const string General_BenchmarkClass_ClassMustBeNonGeneric = "BDN1006"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters = "BDN1007"; + public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1008"; + public const string General_BenchmarkClass_ClassMustBeUnsealed = "BDN1009"; + public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1010"; + public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField = "BDN1011"; + public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty = "BDN1012"; + public const string Attributes_GeneralParameterAttributes_FieldMustBePublic = "BDN1013"; + public const string Attributes_GeneralParameterAttributes_PropertyMustBePublic = "BDN1014"; + public const string Attributes_GeneralParameterAttributes_NotValidOnReadonlyField = "BDN1015"; + public const string Attributes_GeneralParameterAttributes_NotValidOnConstantField = "BDN1016"; + public const string Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly = "BDN1017"; + public const string Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter = "BDN1018"; + public const string Attributes_ParamsAttribute_MustHaveValues = "BDN1019"; + public const string Attributes_ParamsAttribute_UnexpectedValueType = "BDN1020"; + public const string Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute = "BDN1021"; + public const string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType = "BDN1022"; + public const string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool = "BDN1023"; + public const string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute = "BDN1024"; + public const string Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters = "BDN1025"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1026"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1027"; + } +} diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs new file mode 100644 index 0000000000..75471585e3 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -0,0 +1,286 @@ +namespace BenchmarkDotNet.Analyzers.General +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + + using System.Collections.Immutable; + using System.Linq; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class BenchmarkClassAnalyzer : DiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor MethodMustBePublicRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_MethodMustBePublic, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_Description))); + + internal static readonly DiagnosticDescriptor MethodMustBeNonGenericRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_MethodMustBeNonGeneric, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_Description))); + + internal static readonly DiagnosticDescriptor ClassMustBePublicRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBePublic, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBePublic_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBePublic_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBePublic_Description))); + + internal static readonly DiagnosticDescriptor ClassMustBeNonStaticRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeNonStatic, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Description))); + + internal static readonly DiagnosticDescriptor ClassMustBeNonAbstractRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeNonAbstract, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonAbstract_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonAbstract_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonAbstract_Description))); + + internal static readonly DiagnosticDescriptor ClassMustBeNonGenericRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeNonGeneric, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonGeneric_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonGeneric_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonGeneric_Description))); + + internal static readonly DiagnosticDescriptor ClassWithGenericTypeArgumentsAttributeMustHaveTypeParametersRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_Description))); + + internal static readonly DiagnosticDescriptor GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Description))); + + internal static readonly DiagnosticDescriptor ClassMustBeUnsealedRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeUnsealed, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeUnsealed_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeUnsealed_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeUnsealed_Description))); + + internal static readonly DiagnosticDescriptor OnlyOneMethodCanBeBaselineRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_OnlyOneMethodCanBeBaseline, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + MethodMustBePublicRule, + MethodMustBeNonGenericRule, + ClassMustBePublicRule, + ClassMustBeNonStaticRule, + ClassMustBeNonAbstractRule, + ClassMustBeNonGenericRule, + ClassWithGenericTypeArgumentsAttributeMustHaveTypeParametersRule, + GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, + ClassMustBeUnsealedRule, + OnlyOneMethodCanBeBaselineRule + ); + + public override void Initialize(AnalysisContext analysisContext) + { + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet.Annotations is referenced + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } + + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ClassDeclaration); + }); + } + + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (!(context.Node is ClassDeclarationSyntax classDeclarationSyntax)) + { + return; + } + + var benchmarkAttributeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); + if (benchmarkAttributeSymbol == null) + { + return; + } + + var benchmarkMethodsBuilder = ImmutableArray.CreateBuilder<(MethodDeclarationSyntax Method, ImmutableArray BaselineLocations)>(); + + foreach (var memberDeclarationSyntax in classDeclarationSyntax.Members) + { + if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax) + { + var benchmarkAttributes = AnalyzerHelper.GetAttributes(benchmarkAttributeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); + if (benchmarkAttributes.Length > 0) + { + benchmarkMethodsBuilder.Add((methodDeclarationSyntax, benchmarkAttributes.SelectMany(a => GetBaselineLocations(a)).ToImmutableArray())); + } + } + } + + var benchmarkMethods = benchmarkMethodsBuilder.ToImmutable(); + if (benchmarkMethods.Length == 0) + { + return; + } + + var genericTypeArgumentsAttributes = AnalyzerHelper.GetAttributes("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, classDeclarationSyntax.AttributeLists, context.SemanticModel); + if (genericTypeArgumentsAttributes.Length == 0) + { + if (classDeclarationSyntax.TypeParameterList != null) + { + context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonGenericRule, classDeclarationSyntax.TypeParameterList.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + } + else if (genericTypeArgumentsAttributes.Length == 1) + { + if (classDeclarationSyntax.TypeParameterList == null) + { + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustHaveTypeParametersRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + else if (classDeclarationSyntax.TypeParameterList.Parameters.Count > 0) + { + var genericTypeArgumentsAttribute = genericTypeArgumentsAttributes[0]; + if (genericTypeArgumentsAttribute.ArgumentList != null && genericTypeArgumentsAttribute.ArgumentList.Arguments.Count > 0) + { + if (genericTypeArgumentsAttribute.ArgumentList.Arguments.Count != classDeclarationSyntax.TypeParameterList.Parameters.Count) + { + context.ReportDiagnostic(Diagnostic.Create(GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, Location.Create(context.FilterTree, genericTypeArgumentsAttribute.ArgumentList.Arguments.Span), + classDeclarationSyntax.TypeParameterList.Parameters.Count, + classDeclarationSyntax.TypeParameterList.Parameters.Count == 1 ? "" : "s", + classDeclarationSyntax.Identifier.ToString(), + genericTypeArgumentsAttribute.ArgumentList.Arguments.Count)); + } + } + } + } + + + var classIsPublic = false; + var classStaticModifier = null as SyntaxToken?; + var classAbstractModifier = null as SyntaxToken?; + var classSealedModifier = null as SyntaxToken?; + + foreach (var modifier in classDeclarationSyntax.Modifiers) + { + if (modifier.IsKind(SyntaxKind.PublicKeyword)) + { + classIsPublic = true; + } + else if (modifier.IsKind(SyntaxKind.StaticKeyword)) + { + classStaticModifier = modifier; + } + else if (modifier.IsKind(SyntaxKind.AbstractKeyword)) + { + classAbstractModifier = modifier; + } + else if (modifier.IsKind(SyntaxKind.SealedKeyword)) + { + classSealedModifier = modifier; + } + } + + if (!classIsPublic) + { + context.ReportDiagnostic(Diagnostic.Create(ClassMustBePublicRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + + if (classAbstractModifier.HasValue) + { + context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + + if (classStaticModifier.HasValue) + { + context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonStaticRule, classStaticModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + + if (classSealedModifier.HasValue) + { + context.ReportDiagnostic(Diagnostic.Create(ClassMustBeUnsealedRule, classSealedModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + + var baselineCount = 0; + foreach (var benchmarkMethod in benchmarkMethods) + { + var methodIsPublic = benchmarkMethod.Method.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)); + if (!methodIsPublic) + { + context.ReportDiagnostic(Diagnostic.Create(MethodMustBePublicRule, benchmarkMethod.Method.Identifier.GetLocation(), benchmarkMethod.Method.Identifier.ToString())); + } + + if (benchmarkMethod.Method.TypeParameterList != null) + { + context.ReportDiagnostic(Diagnostic.Create(MethodMustBeNonGenericRule, benchmarkMethod.Method.TypeParameterList.GetLocation(), benchmarkMethod.Method.Identifier.ToString())); + } + + baselineCount += benchmarkMethod.BaselineLocations.Length; + } + + if (baselineCount > 1) + { + foreach (var benchmarkMethod in benchmarkMethods) + { + foreach (var baselineLocation in benchmarkMethod.BaselineLocations) + { + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselineRule, baselineLocation)); + } + } + } + + return; + + ImmutableArray GetBaselineLocations(AttributeSyntax attributeSyntax) + { + var baselineLocationsBuilder = ImmutableArray.CreateBuilder(); + + if (attributeSyntax.ArgumentList == null || attributeSyntax.ArgumentList.Arguments.Count == 0) + { + return ImmutableArray.Empty; + } + + foreach (var attributeArgumentSyntax in attributeSyntax.ArgumentList.Arguments) + { + if (attributeArgumentSyntax.NameEquals != null && attributeArgumentSyntax.NameEquals.Name.Identifier.ValueText == "Baseline" && attributeArgumentSyntax.Expression.IsKind(SyntaxKind.TrueLiteralExpression)) + { + baselineLocationsBuilder.Add(attributeArgumentSyntax.GetLocation()); + } + } + + return baselineLocationsBuilder.ToImmutable(); + } + } + } +} diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index ccc51bcd9a..8b21e637ad 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -14,4 +14,10 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj b/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj index 2f15efcc13..3bab91dce7 100644 --- a/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj +++ b/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj @@ -1,21 +1,24 @@ - - - - net462 - Exe - BenchmarkDotNet.Disassembler.x64 - BenchmarkDotNet.Disassembler.x64 - win7-x64 - x64 - True - $(DefineConstants);CLRMDV1 - - - ..\BenchmarkDotNet\Disassemblers - BenchmarkDotNet.Disassembler - - - - - - + + + + net462 + Exe + BenchmarkDotNet.Disassembler.x64 + BenchmarkDotNet.Disassembler.x64 + win7-x64 + x64 + True + $(DefineConstants);CLRMDV1 + + + ..\BenchmarkDotNet\Disassemblers + BenchmarkDotNet.Disassembler + + + + + + + + + diff --git a/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj b/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj index 5410f6d77b..fa50112cae 100644 --- a/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj +++ b/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj @@ -1,27 +1,30 @@ - - - - net462 - Exe - BenchmarkDotNet.Disassembler.x86 - BenchmarkDotNet.Disassembler.x86 - win7-x86 - x86 - True - $(DefineConstants);CLRMDV1 - - - ..\BenchmarkDotNet\Disassemblers - BenchmarkDotNet.Disassembler - - - - - - - - - - - - + + + + net462 + Exe + BenchmarkDotNet.Disassembler.x86 + BenchmarkDotNet.Disassembler.x86 + win7-x86 + x86 + True + $(DefineConstants);CLRMDV1 + + + ..\BenchmarkDotNet\Disassemblers + BenchmarkDotNet.Disassembler + + + + + + + + + + + + + + + From a15e0ec2c8b218c1ff01651df99bc86b71039a24 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sat, 11 Oct 2025 00:26:05 +0200 Subject: [PATCH 02/24] Unify C# language version --- .../ArgumentsAttributeAnalyzerTests.cs | 509 ++++++------ ...GeneralParameterAttributesAnalyzerTests.cs | 735 +++++++++-------- .../ParamsAllValuesAttributeAnalyzerTests.cs | 232 +++--- .../ParamsAttributeAnalyzerTests.cs | 291 +++---- .../BenchmarkRunner/RunAnalyzerTests.cs | 146 ++-- .../General/BenchmarkClassAnalyzerTests.cs | 761 +++++++++--------- .../BenchmarkDotNet.Analyzers.Tests.csproj | 20 +- .../Fixtures/AnalyzerTestFixture.cs | 83 +- .../Generators/CombinationsGenerator.cs | 4 +- .../Attributes/ArgumentsAttributeAnalyzer.cs | 2 +- .../GeneralParameterAttributesAnalyzer.cs | 2 +- .../BenchmarkDotNet.Analyzers.csproj | 4 +- 12 files changed, 1378 insertions(+), 1411 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs index a346032365..f3719a0e56 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -26,17 +26,19 @@ public async Task A_method_annotated_with_an_arguments_attribute_with_no_values_ emptyArgumentsAttributeUsages.Add(emptyArgumentsAttributeUsage); } - var testCode = /* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - {string.Join("\n", emptyArgumentsAttributeUsages)} - public void BenchmarkMethod() - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + {{string.Join("\n", emptyArgumentsAttributeUsages)}} + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -65,9 +67,7 @@ public static IEnumerable EmptyArgumentsAttributeUsages() { "[Arguments({0}new object[] {{ }}{1})]", "[Arguments({0}new object[0]{1})]", -#if NET8_0_OR_GREATER "[Arguments({0}[]{1})]", -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) @@ -91,18 +91,19 @@ public RequiresBenchmarkAttribute() : base(ArgumentsAttributeAnalyzer.RequiresBe [MemberData(nameof(ArgumentAttributeUsagesListLength))] public async Task A_method_annotated_with_at_least_one_arguments_attribute_together_with_the_benchmark_attribute_should_not_trigger_diagnostic(int argumentAttributeUsagesListLength) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - [{string.Join("]\n[", ArgumentAttributeUsages.Take(argumentAttributeUsagesListLength))}] - public void BenchmarkMethod() - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{string.Join("]\n[", ArgumentAttributeUsages.Take(argumentAttributeUsagesListLength))}}] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -113,17 +114,18 @@ public void BenchmarkMethod() [MemberData(nameof(ArgumentAttributeUsagesListLength))] public async Task A_method_with_at_least_one_arguments_attribute_but_no_benchmark_attribute_should_trigger_diagnostic(int argumentAttributeUsagesListLength) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - {string.Join("\n", ArgumentAttributeUsages.Take(argumentAttributeUsagesListLength).Select((a, i) => $"[{{|#{i}:{a}|}}]"))} - public void BenchmarkMethod() - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{string.Join("\n", ArgumentAttributeUsages.Take(argumentAttributeUsagesListLength).Select((a, i) => $"[{{|#{i}:{a}|}}]"))}} + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -135,7 +137,7 @@ public void BenchmarkMethod() await RunAsync(); } - public static TheoryData ArgumentAttributeUsagesListLength => new TheoryData(Enumerable.Range(1, ArgumentAttributeUsages.Count)); + public static TheoryData ArgumentAttributeUsagesListLength => new(Enumerable.Range(1, ArgumentAttributeUsages.Count)); private static ReadOnlyCollection ArgumentAttributeUsages => new List { "Arguments", @@ -151,18 +153,19 @@ public MethodWithoutAttributeMustHaveNoParameters() : base(ArgumentsAttributeAna [Fact] public async Task A_method_annotated_with_an_arguments_attribute_and_the_benchmark_attribute_and_having_parameters_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - [Arguments(42, ""test"")] - public void BenchmarkMethod(int a, string b) - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [Arguments(42, "test")] + public void BenchmarkMethod(int a, string b) + { + + } + } + """; TestCode = testCode; @@ -173,16 +176,17 @@ public void BenchmarkMethod(int a, string b) [MemberData(nameof(ParametersListLength))] public async Task A_method_with_parameters_and_no_arguments_or_benchmark_attributes_should_not_trigger_diagnostic(int parametersListLength) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - public void BenchmarkMethod({string.Join(", ", Parameters.Take(parametersListLength))}) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + public void BenchmarkMethod({{string.Join(", ", Parameters.Take(parametersListLength))}}) + { + + } + } + """; TestCode = testCode; @@ -193,17 +197,18 @@ public void BenchmarkMethod({string.Join(", ", Parameters.Take(parametersListLen [MemberData(nameof(ParametersListLength))] public async Task A_method_annotated_with_the_benchmark_attribute_but_no_arguments_attribute_with_parameters_should_trigger_diagnostic(int parametersListLength) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - public void BenchmarkMethod({{|#0:{string.Join(", ", Parameters.Take(parametersListLength))}|}}) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) + { + + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(); @@ -211,7 +216,7 @@ public void BenchmarkMethod({{|#0:{string.Join(", ", Parameters.Take(parametersL await RunAsync(); } - public static TheoryData ParametersListLength => new TheoryData(Enumerable.Range(1, Parameters.Count)); + public static TheoryData ParametersListLength => new(Enumerable.Range(1, Parameters.Count)); private static ReadOnlyCollection Parameters => new List { "int a", @@ -227,17 +232,18 @@ public MustHaveMatchingValueCount() : base(ArgumentsAttributeAnalyzer.MustHaveMa [Fact] public async Task A_method_not_annotated_with_any_arguments_attributes_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -248,18 +254,19 @@ public void BenchmarkMethod() [MemberData(nameof(ArgumentsAttributeUsages))] public async Task Having_a_matching_value_count_should_not_trigger_diagnostic(string argumentsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void BenchmarkMethod(string a, bool b) - {{ + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod(string a, bool b) + { - }} -}}"; + } + } + """; TestCode = testCode; @@ -272,18 +279,19 @@ public async Task Having_a_mismatching_empty_value_count_targeting_a_method_with { const string benchmarkMethodName = "BenchmarkMethod"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void {benchmarkMethodName}(string a) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void {{benchmarkMethodName}}(string a) + { + + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(1, "", benchmarkMethodName, 0); @@ -296,18 +304,19 @@ public async Task Having_a_mismatching_value_count_should_trigger_diagnostic([Co { const string benchmarkMethodName = "BenchmarkMethod"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void {benchmarkMethodName}({parameterData.Parameters}) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void {{benchmarkMethodName}}({{parameterData.Parameters}}) + { + + } + } + """; TestCode = testCode; AddExpectedDiagnostic(0, parameterData.ParameterCount, parameterData.PluralSuffix, benchmarkMethodName, 2); AddExpectedDiagnostic(1, parameterData.ParameterCount, parameterData.PluralSuffix, benchmarkMethodName, 3); @@ -315,17 +324,13 @@ public class BenchmarkClass await RunAsync(); } - public static IEnumerable<(string, int, string)> ParameterLists => new [] { ("string a", 1, ""), ("", 0, "s") }; + public static IEnumerable<(string, int, string)> ParameterLists => [ ("string a", 1, ""), ("", 0, "s") ]; public static TheoryData ArgumentsAttributeUsages() { return new TheoryData(GenerateData()); -#if NET6_0_OR_GREATER static IEnumerable GenerateData() -#else - IEnumerable GenerateData() -#endif { var nameColonUsages = new List { @@ -343,9 +348,7 @@ IEnumerable GenerateData() { "[Arguments({1}{2})]", "[Arguments({0}new object[] {{ {1} }}{2})]", -#if NET8_0_OR_GREATER "[Arguments({0}[ {1} ]{2})]" -#endif }; var valueLists = new List @@ -371,11 +374,7 @@ public static TheoryData EmptyArgumentsAttributeUsagesWithLocationMarker { return new TheoryData(GenerateData()); -#if NET6_0_OR_GREATER static IEnumerable GenerateData() -#else - IEnumerable GenerateData() -#endif { yield return "[{|#0:Arguments|}]"; yield return "[Arguments{|#0:()|}]"; @@ -397,9 +396,7 @@ IEnumerable GenerateData() { "[Arguments({0}new object[] {{|#0:{{ }}|}}{1})]", "[Arguments({0}new object[{{|#0:0|}}]{1})]", -#if NET8_0_OR_GREATER "[Arguments({0}{{|#0:[]|}}{1})]", -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) @@ -433,9 +430,7 @@ public static IEnumerable ArgumentsAttributeUsagesWithLocationMarker() { "[Arguments({{|#{1}:{2}|}}{3})]", "[Arguments({0}new object[] {{ {{|#{1}:{2}|}} }}{3})]", -#if NET8_0_OR_GREATER "[Arguments({0}[ {{|#{1}:{2}|}} ]{3})]" -#endif }; var valueLists = new List @@ -464,17 +459,18 @@ public MustHaveMatchingValueType() : base(ArgumentsAttributeAnalyzer.MustHaveMat [Fact] public async Task A_method_not_annotated_with_any_arguments_attributes_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -485,17 +481,18 @@ public void BenchmarkMethod() [MemberData(nameof(EmptyArgumentsAttributeUsagesWithMismatchingValueCount))] public async Task Having_a_mismatching_value_count_with_empty_argument_attribute_usages_should_not_trigger_diagnostic(string argumentsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void BenchmarkMethod(string a) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod(string a) + { + + } + } + """; TestCode = testCode; await RunAsync(); @@ -505,16 +502,18 @@ public void BenchmarkMethod(string a) public async Task Having_a_mismatching_value_count_with_nonempty_argument_attribute_usages_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ArgumentsAttributeUsagesWithMismatchingValueCount))] string argumentsAttributeUsage, [CombinatorialValues("string a", "")] string parameters) { - var testCode = /* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void BenchmarkMethod({parameters}) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod({{parameters}}) + { + + } + } + """; TestCode = testCode; await RunAsync(); @@ -524,18 +523,19 @@ public void BenchmarkMethod({parameters}) [MemberData(nameof(ArgumentsAttributeUsagesWithMatchingValueTypes))] public async Task Having_matching_value_types_should_not_trigger_diagnostic(string argumentsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void BenchmarkMethod(int a, string b) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod(int a, string b) + { + + } + } + """; TestCode = testCode; @@ -546,18 +546,19 @@ public void BenchmarkMethod(int a, string b) [MemberData(nameof(ArgumentsAttributeUsagesWithConvertibleValueTypes))] public async Task Providing_convertible_value_types_should_not_trigger_diagnostic(string argumentsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void BenchmarkMethod(int a, string b) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod(int a, string b) + { + + } + } + """; TestCode = testCode; @@ -568,18 +569,19 @@ public void BenchmarkMethod(int a, string b) [MemberData(nameof(ArgumentsAttributeUsagesWithMatchingValueTypes))] public async Task Having_unknown_parameter_type_should_not_trigger_diagnostic(string argumentsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void BenchmarkMethod(unkown a, string b) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod(unkown a, string b) + { + + } + } + """; TestCode = testCode; @@ -592,18 +594,19 @@ public void BenchmarkMethod(unkown a, string b) [MemberData(nameof(ArgumentsAttributeUsagesWithMismatchingValueTypesWithLocationMarker))] public async Task Having_mismatching_or_not_convertible_value_types_should_trigger_diagnostic(string argumentsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void BenchmarkMethod(byte a, bool b) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod(byte a, bool b) + { + + } + } + """; TestCode = testCode; @@ -618,18 +621,19 @@ public void BenchmarkMethod(byte a, bool b) [MemberData(nameof(ArgumentsAttributeUsagesWithUnknownValueTypesWithLocationMarker))] public async Task Providing_an_unkown_value_type_should_trigger_diagnostic(string argumentsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [Benchmark] - {argumentsAttributeUsage} - public void BenchmarkMethod(byte a, bool b) - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod(byte a, bool b) + { + + } + } + """; TestCode = testCode; @@ -643,11 +647,7 @@ public static TheoryData EmptyArgumentsAttributeUsagesWithMismatchingVal { return new TheoryData(GenerateData()); -#if NET6_0_OR_GREATER static IEnumerable GenerateData() -#else - IEnumerable GenerateData() -#endif { yield return "[Arguments]"; yield return "[Arguments()]"; @@ -669,9 +669,7 @@ IEnumerable GenerateData() { "[Arguments({0}new object[] {{ }}{1})]", "[Arguments({0}new object[0]{1})]", -#if NET8_0_OR_GREATER "[Arguments({0}[]{1})]", -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) @@ -687,29 +685,34 @@ IEnumerable GenerateData() } } - public static IEnumerable ArgumentsAttributeUsagesWithMismatchingValueCount() => GenerateAttributeUsages(new List { - "42, \"test\"", - "\"value\", 100, true" - }); - - public static TheoryData ArgumentsAttributeUsagesWithMatchingValueTypes() => new TheoryData(GenerateAttributeUsages(new List { - "42, \"test\"", - "43, \"test2\"" - })); - - public static TheoryData ArgumentsAttributeUsagesWithConvertibleValueTypes() => new TheoryData(GenerateAttributeUsages(new List { - "42, \"test\"", - "(byte)5, \"test2\"" - })); - - public static TheoryData ArgumentsAttributeUsagesWithMismatchingValueTypesWithLocationMarker() => new TheoryData(GenerateAttributeUsages(new List { - "{|#0:typeof(string)|}, {|#1:\"test\"|}", - "{|#2:999|}, true" - })); - - public static TheoryData ArgumentsAttributeUsagesWithUnknownValueTypesWithLocationMarker() => new TheoryData(GenerateAttributeUsages(new List { - "{|#0:dummy_literal|}, true" - })); + public static IEnumerable ArgumentsAttributeUsagesWithMismatchingValueCount() => GenerateAttributeUsages( + [ + "42, \"test\"", + "\"value\", 100, true" + ]); + + public static TheoryData ArgumentsAttributeUsagesWithMatchingValueTypes() => new(GenerateAttributeUsages( + [ + "42, \"test\"", + "43, \"test2\"" + ])); + + public static TheoryData ArgumentsAttributeUsagesWithConvertibleValueTypes() => new(GenerateAttributeUsages( + [ + "42, \"test\"", + "(byte)5, \"test2\"" + ])); + + public static TheoryData ArgumentsAttributeUsagesWithMismatchingValueTypesWithLocationMarker() => new(GenerateAttributeUsages( + [ + "{|#0:typeof(string)|}, {|#1:\"test\"|}", + "{|#2:999|}, true" + ])); + + public static TheoryData ArgumentsAttributeUsagesWithUnknownValueTypesWithLocationMarker() => new(GenerateAttributeUsages( + [ + "{|#0:dummy_literal|}, true" + ])); } private static IEnumerable GenerateAttributeUsages(List valueLists) @@ -730,9 +733,7 @@ private static IEnumerable GenerateAttributeUsages(List valueLis { "[Arguments({1}{2})]", "[Arguments({0}new object[] {{ {1} }}{2})]", -#if NET8_0_OR_GREATER "[Arguments({0}[ {1} ]{2})]" -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs index e116912a56..ea30028c12 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs @@ -21,11 +21,12 @@ public MutuallyExclusiveOnField() : base(GeneralParameterAttributesAnalyzer.Mutu [Fact] public async Task A_field_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - public int _field = 0, _field2 = 1; -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + public int _field = 0, _field2 = 1; + } + """; TestCode = testCode; @@ -35,12 +36,13 @@ public async Task A_field_not_annotated_with_any_parameter_attribute_should_not_ [Fact] public async Task A_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - [Dummy] - public int _field = 0, _field2 = 1; -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + public int _field = 0, _field2 = 1; + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -51,13 +53,14 @@ public async Task A_field_annotated_with_a_nonparameter_attribute_should_not_tri [Fact] public async Task A_field_annotated_with_a_duplicate_nonparameter_attribute_should_not_trigger_diagnostic() { - const string testCode -= /* lang=c#-test */ @"public class BenchmarkClass -{ - [Dummy] - [Dummy] - public int _field = 0, _field2 = 1; -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + [Dummy] + public int _field = 0, _field2 = 1; + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -70,14 +73,15 @@ const string testCode [MemberData(nameof(UniqueParameterAttributeUsages))] public async Task A_field_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public int _field = 0, _field2 = 1; -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int _field = 0, _field2 = 1; + } + """; TestCode = testCode; @@ -105,14 +109,15 @@ public async Task A_field_annotated_with_the_same_duplicate_parameter_attribute_ } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public int _field = 0, _field2 = 1; -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int _field = 0, _field2 = 1; + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -139,14 +144,15 @@ public async Task A_field_annotated_with_more_than_one_parameter_attribute_shoul } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public int {fieldIdentifier} = 0, field2 = 1; -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int {{fieldIdentifier}} = 0, field2 = 1; + } + """; TestCode = testCode; @@ -158,11 +164,7 @@ public class BenchmarkClass await RunAsync(); } -#if NET6_0_OR_GREATER public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); -#else - public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); -#endif public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; @@ -176,11 +178,12 @@ public MutuallyExclusiveOnProperty() : base(GeneralParameterAttributesAnalyzer.M [Fact] public async Task A_property_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - public int Property { get; set; } -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + public int Property { get; set; } + } + """; TestCode = testCode; @@ -190,12 +193,13 @@ public async Task A_property_not_annotated_with_any_parameter_attribute_should_n [Fact] public async Task A_property_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - [Dummy] - public int Property { get; set; } -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + public int Property { get; set; } + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -206,13 +210,14 @@ public async Task A_property_annotated_with_a_nonparameter_attribute_should_not_ [Fact] public async Task A_property_annotated_with_a_duplicate_nonparameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - [Dummy] - [Dummy] - public int Property { get; set; } -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + [Dummy] + public int Property { get; set; } + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -225,14 +230,15 @@ public async Task A_property_annotated_with_a_duplicate_nonparameter_attribute_s [MemberData(nameof(UniqueParameterAttributeUsages))] public async Task A_property_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public int Property {{ get; set; }} -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int Property { get; set; } + } + """; TestCode = testCode; @@ -260,14 +266,15 @@ public async Task A_property_annotated_with_the_same_duplicate_parameter_attribu } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public int Property {{ get; set; }} -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int Property { get; set; } + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -294,14 +301,15 @@ public async Task A_property_annotated_with_more_than_one_parameter_attribute_sh } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public int {propertyIdentifier} {{ get; set; }} -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int {{propertyIdentifier}} { get; set; } + } + """; TestCode = testCode; @@ -313,11 +321,7 @@ public class BenchmarkClass await RunAsync(); } -#if NET6_0_OR_GREATER public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); -#else - public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); -#endif public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; @@ -332,11 +336,12 @@ public FieldMustBePublic() : base(GeneralParameterAttributesAnalyzer.FieldMustBe [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] public async Task A_nonpublic_field_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) { - var testCode = -/* lang=c#-test */ $@"public class BenchmarkClass -{{ - {classMemberAccessModifier}int _field = 0, _field2 = 1; -}}"; + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + {{classMemberAccessModifier}}int _field = 0, _field2 = 1; + } + """; TestCode = testCode; @@ -347,12 +352,13 @@ public async Task A_nonpublic_field_not_annotated_with_any_parameter_attribute_s [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] public async Task A_nonpublic_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) { - var testCode = -/* lang=c#-test */ $@"public class BenchmarkClass -{{ - [Dummy] - {classMemberAccessModifier}int _field = 0, _field2 = 1; -}}"; + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + [Dummy] + {{classMemberAccessModifier}}int _field = 0, _field2 = 1; + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -364,14 +370,15 @@ public async Task A_nonpublic_field_annotated_with_a_nonparameter_attribute_shou [MemberData(nameof(UniqueParameterAttributeUsages))] public async Task A_public_field_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public int _field = 0, _field2 = 2; -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int _field = 0, _field2 = 2; + } + """; TestCode = testCode; @@ -399,14 +406,15 @@ public async Task A_nonpublic_field_annotated_with_the_same_duplicate_parameter_ } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - {classMemberAccessModifier}int _field = 0, _field2 = 1; -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + {{classMemberAccessModifier}}int _field = 0, _field2 = 1; + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -430,14 +438,15 @@ public async Task A_nonpublic_field_annotated_with_more_than_one_parameter_attri } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - {classMemberAccessModifier}int _field = 0, _field2 = 1; -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + {{classMemberAccessModifier}}int _field = 0, _field2 = 1; + } + """; TestCode = testCode; @@ -450,14 +459,15 @@ public async Task A_nonpublic_field_annotated_with_a_unique_parameter_attribute_ { const string fieldIdentifier = "_field"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attribute.AttributeUsage}] - {classMemberAccessModifier}int {{|#0:{fieldIdentifier}|}} = 0, field2 = 0; -}}"; + public class BenchmarkClass + { + [{{attribute.AttributeUsage}}] + {{classMemberAccessModifier}}int {|#0:{{fieldIdentifier}}|} = 0, field2 = 0; + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(fieldIdentifier, attribute.AttributeName); @@ -466,24 +476,14 @@ public class BenchmarkClass public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicClassMemberAccessModifiers); -#if NET6_0_OR_GREATER public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); -#else - public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); -#endif -#if NET6_0_OR_GREATER public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); -#else - public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => (tdr[0] as string, tdr[1] as string)); -#endif + public static IEnumerable NonPublicClassMemberAccessModifiers => new NonPublicClassMemberAccessModifiersTheoryData(); -#if NET6_0_OR_GREATER public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); -#else - public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => (tdr[0] as string, (int)tdr[1], tdr[2] as int[])); -#endif + public static IEnumerable DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; } @@ -495,11 +495,12 @@ public PropertyMustBePublic() : base(GeneralParameterAttributesAnalyzer.Property [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] public async Task A_nonpublic_property_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) { - var testCode = -/* lang=c#-test */ $@"public class BenchmarkClass -{{ - {classMemberAccessModifier}int Property {{ get; set; }} -}}"; + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + {{classMemberAccessModifier}}int Property { get; set; } + } + """; TestCode = testCode; @@ -510,12 +511,13 @@ public async Task A_nonpublic_property_not_annotated_with_any_parameter_attribut [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] public async Task A_nonpublic_property_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) { - var testCode = -/* lang=c#-test */ $@"public class BenchmarkClass -{{ - [Dummy] - {classMemberAccessModifier}int Property {{ get; set; }} -}}"; + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + [Dummy] + {{classMemberAccessModifier}}int Property { get; set; } + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -527,14 +529,15 @@ public async Task A_nonpublic_property_annotated_with_a_nonparameter_attribute_s [MemberData(nameof(UniqueParameterAttributeUsages))] public async Task A_public_property_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public int Property {{ get; set; }} -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int Property { get; set; } + } + """; TestCode = testCode; @@ -562,14 +565,15 @@ public async Task A_nonpublic_property_annotated_with_the_same_duplicate_paramet } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - {classMemberAccessModifier}int Property {{ get; set; }} -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + {{classMemberAccessModifier}}int Property { get; set; } + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -593,14 +597,15 @@ public async Task A_nonpublic_property_annotated_with_more_than_one_parameter_at } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - {classMemberAccessModifier}int Property {{ get; set; }} -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + {{classMemberAccessModifier}}int Property { get; set; } + } + """; TestCode = testCode; @@ -613,14 +618,15 @@ public async Task A_nonpublic_property_annotated_with_a_unique_parameter_attribu { const string propertyIdentifier = "Property"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attribute.AttributeUsage}] - {classMemberAccessModifier}int {{|#0:{propertyIdentifier}|}} {{ get; set; }} -}}"; + public class BenchmarkClass + { + [{{attribute.AttributeUsage}}] + {{classMemberAccessModifier}}int {|#0:{{propertyIdentifier}}|} { get; set; } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(propertyIdentifier, attribute.AttributeName); @@ -629,25 +635,13 @@ public class BenchmarkClass public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicClassMemberAccessModifiers); -#if NET6_0_OR_GREATER public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); -#else - public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); -#endif -#if NET6_0_OR_GREATER public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); -#else - public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => (tdr[0] as string, tdr[1] as string)); -#endif public static IEnumerable NonPublicClassMemberAccessModifiers => new NonPublicClassMemberAccessModifiersTheoryData(); -#if NET6_0_OR_GREATER public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); -#else - public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => (tdr[0] as string, (int)tdr[1], tdr[2] as int[])); -#endif public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; } @@ -659,11 +653,12 @@ public NotValidOnReadonlyField() : base(GeneralParameterAttributesAnalyzer.NotVa [Fact] public async Task A_readonly_field_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - public readonly int _field = 0, _field2 = 1; -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + public readonly int _field = 0, _field2 = 1; + } + """; TestCode = testCode; @@ -673,12 +668,13 @@ public async Task A_readonly_field_not_annotated_with_any_parameter_attribute_sh [Fact] public async Task A_readonly_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - [Dummy] - public readonly int _field = 0, _field2 = 1; -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + public readonly int _field = 0, _field2 = 1; + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -690,14 +686,15 @@ public async Task A_readonly_field_annotated_with_a_nonparameter_attribute_shoul [MemberData(nameof(UniqueParameterAttributeUsages))] public async Task A_field_without_a_readonly_modifier_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public int _field = 0, _field2 = 1; -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int _field = 0, _field2 = 1; + } + """; TestCode = testCode; @@ -725,14 +722,15 @@ public async Task A_readonly_field_annotated_with_the_same_duplicate_parameter_a } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public readonly int _field = 0, _field2 = 1; -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public readonly int _field = 0, _field2 = 1; + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -756,14 +754,15 @@ public async Task A_readonly_field_annotated_with_more_than_one_parameter_attrib } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public readonly int _field = 0, _field2 = 1; -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public readonly int _field = 0, _field2 = 1; + } + """; TestCode = testCode; @@ -776,25 +775,22 @@ public async Task A_readonly_field_annotated_with_a_unique_parameter_attribute_s { const string fieldIdentifier = "_field"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public {{|#0:readonly|}} int {fieldIdentifier} = 0, field2 = 1; -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public {|#0:readonly|} int {{fieldIdentifier}} = 0, field2 = 1; + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(fieldIdentifier, attributeName); await RunAsync(); } -#if NET6_0_OR_GREATER public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); -#else - public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); -#endif public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; @@ -810,11 +806,12 @@ public NotValidOnConstantField() : base(GeneralParameterAttributesAnalyzer.NotVa [Fact] public async Task A_constant_field_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - public const int Constant = 0; -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + public const int Constant = 0; + } + """; TestCode = testCode; @@ -824,12 +821,13 @@ public async Task A_constant_field_not_annotated_with_any_parameter_attribute_sh [Fact] public async Task A_constant_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - [Dummy] - public const int Constant = 0; -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + public const int Constant = 0; + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -857,14 +855,15 @@ public async Task A_constant_field_annotated_with_the_same_duplicate_parameter_a } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public const int Constant = 0; -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public const int Constant = 0; + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -888,14 +887,15 @@ public async Task A_constant_field_annotated_with_more_than_one_parameter_attrib } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public const int Constant = 0; -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public const int Constant = 0; + } + """; TestCode = testCode; @@ -908,25 +908,22 @@ public async Task A_constant_field_annotated_with_a_unique_parameter_attribute_s { const string constantIdentifier = "Constant"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public {{|#0:const|}} int {constantIdentifier} = 0; -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public {|#0:const|} int {{constantIdentifier}} = 0; + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(constantIdentifier, attributeName); await RunAsync(); } -#if NET6_0_OR_GREATER public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); -#else - public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); -#endif public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; @@ -942,11 +939,12 @@ public PropertyCannotBeInitOnly() : base(GeneralParameterAttributesAnalyzer.Prop [Fact] public async Task An_initonly_property_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - public int Property { get; init; } -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + public int Property { get; init; } + } + """; TestCode = testCode; @@ -956,12 +954,13 @@ public async Task An_initonly_property_not_annotated_with_any_parameter_attribut [Fact] public async Task An_initonly_property_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"public class BenchmarkClass -{ - [Dummy] - public int Property { get; init; } -}"; + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + public int Property { get; init; } + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -973,14 +972,15 @@ public async Task An_initonly_property_annotated_with_a_nonparameter_attribute_s [MemberData(nameof(UniqueParameterAttributeUsages))] public async Task A_property_with_an_assignable_setter_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public int Property {{ get; set; }} -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int Property { get; set; } + } + """; TestCode = testCode; @@ -1008,14 +1008,15 @@ public async Task An_initonly_property_annotated_with_the_same_duplicate_paramet } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public int Property {{ get; init; }} -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int Property { get; init; } + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -1039,14 +1040,15 @@ public async Task An_initonly_property_annotated_with_more_than_one_parameter_at } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public int Property {{ get; init; }} -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int Property { get; init; } + } + """; TestCode = testCode; @@ -1059,14 +1061,15 @@ public async Task An_initonly_property_annotated_with_a_unique_parameter_attribu { const string propertyIdentifier = "Property"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public int {propertyIdentifier} {{ get; {{|#0:init|}}; }} -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int {{propertyIdentifier}} { get; {|#0:init|}; } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(propertyIdentifier, attributeName); @@ -1074,11 +1077,7 @@ public class BenchmarkClass await RunAsync(); } -#if NET6_0_OR_GREATER public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); -#else - public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); -#endif public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; @@ -1095,11 +1094,12 @@ public PropertyMustHavePublicSetter() : base(GeneralParameterAttributesAnalyzer. [MemberData(nameof(NonPublicPropertySettersTheoryData))] public async Task A_property_with_a_nonpublic_setter_not_annotated_with_any_parameter_attribute_should_not_trigger_diagnostic(string nonPublicPropertySetter) { - var testCode = -/* lang=c#-test */ $@"public class BenchmarkClass -{{ - public int Property {nonPublicPropertySetter} -}}"; + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + public int Property {{nonPublicPropertySetter}} + } + """; TestCode = testCode; @@ -1110,12 +1110,13 @@ public int Property {nonPublicPropertySetter} [MemberData(nameof(NonPublicPropertySettersTheoryData))] public async Task A_property_with_a_nonpublic_setter_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic(string nonPublicPropertySetter) { - var testCode = -/* lang=c#-test */ $@"public class BenchmarkClass -{{ - [Dummy] - public int Property {nonPublicPropertySetter} -}}"; + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + [Dummy] + public int Property {{nonPublicPropertySetter}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -1127,14 +1128,15 @@ public int Property {nonPublicPropertySetter} [MemberData(nameof(UniqueParameterAttributeUsages))] public async Task A_property_with_an_assignable_setter_annotated_with_a_unique_parameter_attribute_should_not_trigger_diagnostic(string attributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attributeUsage}] - public int Property {{ get; set; }} -}}"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int Property { get; set; } + } + """; TestCode = testCode; @@ -1162,14 +1164,15 @@ public async Task A_property_with_a_nonpublic_setter_annotated_with_the_same_dup } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public int Property {nonPublicPropertySetter} -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int Property {{nonPublicPropertySetter}} + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -1193,14 +1196,15 @@ public async Task A_property_with_a_nonpublic_setter_annotated_with_more_than_on } } - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)} - public int Property {nonPublicPropertySetter} -}}"; + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int Property {{nonPublicPropertySetter}} + } + """; TestCode = testCode; @@ -1213,14 +1217,15 @@ public async Task A_property_with_a_nonpublic_setter_annotated_with_a_unique_par { const string propertyIdentifier = "Property"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{attribute.AttributeUsage}] - public int {{|#0:{propertyIdentifier}|}} {nonPublicPropertySetter} -}}"; + public class BenchmarkClass + { + [{{attribute.AttributeUsage}}] + public int {|#0:{{propertyIdentifier}}|} {{nonPublicPropertySetter}} + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(propertyIdentifier, attribute.AttributeName); @@ -1230,35 +1235,23 @@ public class BenchmarkClass public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicPropertySetterCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicPropertySetters()); -#if NET6_0_OR_GREATER public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); -#else - public static TheoryData UniqueParameterAttributeUsages => new TheoryData(UniqueParameterAttributesTheoryData.Select(tdr => tdr[1] as string)); -#endif -#if NET6_0_OR_GREATER public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); -#else - public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => (tdr[0] as string, tdr[1] as string)); -#endif -#if NET6_0_OR_GREATER public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); -#else - public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => (tdr[0] as string, (int)tdr[1], tdr[2] as int[])); -#endif public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; public static IEnumerable NonPublicPropertySetters() => new NonPublicPropertySetterAccessModifiersTheoryData().Select(m => $"{{ get; {m} set; }}") - .Concat(new[] { - "{ get; }", - "=> 0;" - }); - public static TheoryData NonPublicPropertySettersTheoryData() => new TheoryData(NonPublicPropertySetters()); + .Concat([ + "{ get; }", + "=> 0;" + ]); + public static TheoryData NonPublicPropertySettersTheoryData() => new(NonPublicPropertySetters()); } - public static TheoryData UniqueParameterAttributesTheoryData => new TheoryData + public static TheoryData UniqueParameterAttributesTheoryData => new() { { "Params", "Params(3)" }, { "ParamsSource", "ParamsSource(\"test\")" }, @@ -1280,7 +1273,7 @@ public static TheoryData DuplicateSameAttributeUsagesTheoryD } } - public static TheoryData DuplicateAttributeUsageCountsTheoryData => new TheoryData(GenerateDuplicateAttributeUsageCombinations(UniqueParameterAttributesTheoryData)); + public static TheoryData DuplicateAttributeUsageCountsTheoryData => new(GenerateDuplicateAttributeUsageCombinations(UniqueParameterAttributesTheoryData)); private static IEnumerable GenerateDuplicateAttributeUsageCombinations(TheoryData uniqueAttributeUsages) { @@ -1300,15 +1293,9 @@ private static IEnumerable GenerateDuplicateAttributeUsageCombinations(Th private static ReadOnlyCollection<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> GenerateDuplicateSameAttributeUsageCombinations(TheoryData uniqueAttributeUsages) { -#if NET6_0_OR_GREATER var uniqueAttributeUsagesList = uniqueAttributeUsages.Select(tdr => (tdr[1] as string)!) .ToList() .AsReadOnly(); -#else - var uniqueAttributeUsagesList = uniqueAttributeUsages.Select(tdr => tdr[1] as string) - .ToList() - .AsReadOnly(); -#endif var finalCombinationsList = new List<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)>(); diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs index bbfd414254..ed9dc35c87 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs @@ -18,14 +18,15 @@ public async Task A_field_or_property_not_annotated_with_the_paramsallvalues_att [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, [CombinatorialMemberData(nameof(InvalidTypes))] string invalidType) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {missingParamsAttributeUsage} - public {invalidType} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + {{missingParamsAttributeUsage}} + public {{invalidType}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -37,25 +38,25 @@ public class BenchmarkClass public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); public static IEnumerable InvalidTypes => new TheoryData - { - "byte", - "char", - "double", - "float", - "int", - "long", - "sbyte", - "short", - "string", - "uint", - "ulong", - "ushort", - - "DummyEnumWithFlagsAttribute", - - "object", - "System.Type" - }; + { + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "string", + "uint", + "ulong", + "ushort", + + "DummyEnumWithFlagsAttribute", + + "object", + "System.Type" + }; } public class NotAllowedOnFlagsEnumPropertyOrFieldType : AnalyzerTestFixture @@ -65,14 +66,15 @@ public NotAllowedOnFlagsEnumPropertyOrFieldType() : base(ParamsAllValuesAttribut [Theory, CombinatorialData] public async Task A_field_or_property_of_nonnullable_nonenum_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, [CombinatorialMemberData(nameof(NonEnumTypes))] string nonEnumType) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [ParamsAllValues] - public {nonEnumType} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [ParamsAllValues] + public {{nonEnumType}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; @@ -82,14 +84,15 @@ public class BenchmarkClass [Theory, CombinatorialData] public async Task A_field_or_property_of_nullable_nonenum_type_should_not_trigger_diagnostic(bool isNullable, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, [CombinatorialMemberData(nameof(NonEnumStructs))] string nonEnumType) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [ParamsAllValues] - public {nonEnumType}{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [ParamsAllValues] + public {{nonEnumType}}{{(isNullable ? "?" : "")}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; @@ -99,14 +102,15 @@ public class BenchmarkClass [Theory, CombinatorialData] public async Task A_field_or_property_of_enum_type_without_a_flags_attribute_should_not_trigger_diagnostic(bool isNullable, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [ParamsAllValues] - public DummyEnum{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [ParamsAllValues] + public DummyEnum{{(isNullable ? "?" : "")}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyEnum(); @@ -119,14 +123,15 @@ public async Task A_field_or_property_of_enum_type_with_a_flags_attribute_should { const string enumTypeName = "DummyEnumWithFlagsAttribute"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [ParamsAllValues] - public {{|#0:{enumTypeName}|}}{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [ParamsAllValues] + public {|#0:{{enumTypeName}}|}{{(isNullable ? "?" : "")}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyEnumWithFlagsAttribute(); @@ -137,22 +142,28 @@ public class BenchmarkClass public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); - public static IEnumerable NonEnumStructs => new List { - "bool", - "byte", - "char", - "double", - "float", - "int", - "long", - "sbyte", - "short", - "uint", - "ulong", - "ushort", - }.AsReadOnly(); - - public static IEnumerable NonEnumTypes => NonEnumStructs.Concat(new[]{ "string", "object", "System.Type" }); + public static IEnumerable NonEnumStructs => new List + { + "bool", + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort", + }.AsReadOnly(); + + public static IEnumerable NonEnumTypes => NonEnumStructs.Concat( + [ + "string", + "object", + "System.Type" + ]); } public class PropertyOrFieldTypeMustBeEnumOrBool : AnalyzerTestFixture @@ -162,14 +173,15 @@ public PropertyOrFieldTypeMustBeEnumOrBool() : base(ParamsAllValuesAttributeAnal [Theory, CombinatorialData] public async Task A_field_or_property_of_enum_or_bool_type_should_not_trigger_diagnostic(bool isNullable, [CombinatorialValues("DummyEnum", "bool")] string enumOrBoolType, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [ParamsAllValues] - public {enumOrBoolType}{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [ParamsAllValues] + public {{enumOrBoolType}}{{(isNullable ? "?" : "")}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyEnum(); @@ -180,14 +192,15 @@ public class BenchmarkClass [Theory, CombinatorialData] public async Task A_field_or_property_not_of_nonnullable_enum_or_bool_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(NonEnumOrBoolTypes))] string nonEnumOrBoolType, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [ParamsAllValues] - public {{|#0:{nonEnumOrBoolType}|}} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [ParamsAllValues] + public {|#0:{{nonEnumOrBoolType}}|} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyEnum(); @@ -199,14 +212,15 @@ public class BenchmarkClass [Theory, CombinatorialData] public async Task A_field_or_property_not_of_nullable_enum_or_bool_type_should_trigger_diagnostic(bool isNullable, [CombinatorialMemberData(nameof(NonEnumOrBoolStructs))] string nonEnumOrBoolType, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [ParamsAllValues] - public {{|#0:{nonEnumOrBoolType}|}}{(isNullable ? "?" : "")} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [ParamsAllValues] + public {|#0:{{nonEnumOrBoolType}}|}{{(isNullable ? "?" : "")}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyEnum(); @@ -217,21 +231,27 @@ public class BenchmarkClass public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); - public static IEnumerable NonEnumOrBoolStructs => new List { - "byte", - "char", - "double", - "float", - "int", - "long", - "sbyte", - "short", - "uint", - "ulong", - "ushort" - }.AsReadOnly(); - - public static IEnumerable NonEnumOrBoolTypes => NonEnumOrBoolStructs.Concat(new[] { "string", "object", "System.Type" }); + public static IEnumerable NonEnumOrBoolStructs => new List + { + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort" + }.AsReadOnly(); + + public static IEnumerable NonEnumOrBoolTypes => NonEnumOrBoolStructs.Concat( + [ + "string", + "object", + "System.Type" + ]); } } } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs index eb2403012c..63f9fa7af8 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -18,14 +18,15 @@ public class General : AnalyzerTestFixture public async Task A_field_or_property_not_annotated_with_the_params_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, [CombinatorialValues("", "[Dummy]")] string missingParamsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - {missingParamsAttributeUsage} - public string {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + {{missingParamsAttributeUsage}} + public string {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -46,14 +47,15 @@ public async Task Providing_one_or_more_values_should_not_trigger_diagnostic([Co [CombinatorialMemberData(nameof(ScalarValuesListLength))] int scalarValuesListLength, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, string.Join(", ", ScalarValues.Take(scalarValuesListLength)))})] - public string {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, string.Join(", ", ScalarValues.Take(scalarValuesListLength)))}})] + public string {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -68,14 +70,15 @@ public async Task Providing_an_array_with_a_rank_specifier_size_higher_than_zero { Assert.True(rankSpecifierSize > 0); - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params(new object[{rankSpecifierSize}])] - public string {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params(new object[{{rankSpecifierSize}}])] + public string {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -89,14 +92,15 @@ public async Task Providing_no_values_should_trigger_diagnostic([CombinatorialMe [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(EmptyParamsAttributeUsagesWithLocationMarker))] string emptyParamsAttributeUsage) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}{emptyParamsAttributeUsage}] - public string {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}{{emptyParamsAttributeUsage}}] + public string {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -139,9 +143,7 @@ public static IEnumerable EmptyParamsAttributeUsagesWithLocationMarker() { "Params({0}new object[] {{|#0:{{ }}|}}{1})", "Params({0}{{|#0:new object[0]|}}{1})", -#if NET8_0_OR_GREATER "Params({0}{{|#0:[ ]|}}{1})", -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) @@ -166,14 +168,15 @@ public async Task Providing_a_field_or_property_with_an_unknown_type_should_not_ [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, "42, 51, 33")})] - public unknown {fieldOrPropertyDeclaration} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "42, 51, 33")}})] + public unknown {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -187,14 +190,15 @@ public async Task Providing_convertible_value_types_should_not_trigger_diagnosti [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, "(byte)42")})] - public int {fieldOrPropertyDeclaration} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "(byte)42")}})] + public int {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -210,14 +214,15 @@ public async Task Providing_both_expected_and_unexpected_value_types_should_trig const string expectedFieldOrPropertyType = "int"; const string valueWithUnexpectedType = "\"test1\""; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnexpectedType}|}}, 33")})] - public {expectedFieldOrPropertyType} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnexpectedType}|}}, 33")}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); AddDefaultExpectedDiagnostic(valueWithUnexpectedType, expectedFieldOrPropertyType, "string"); @@ -233,14 +238,15 @@ public async Task Providing_an_unknown_value_type_should_trigger_diagnostic([Com const string expectedFieldOrPropertyType = "int"; const string valueWithUnknownType = "dummy_literal"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnknownType}|}}, 33")})] - public {expectedFieldOrPropertyType} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnknownType}|}}, 33")}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -259,14 +265,15 @@ public async Task Providing_an_unexpected_or_not_convertible_value_type_should_t { const string expectedFieldOrPropertyType = "decimal"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType[0]}|}}")})] - public {expectedFieldOrPropertyType} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType[0]}|}}")}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); ReferenceDummyEnum(); @@ -275,12 +282,6 @@ public class BenchmarkClass await RunAsync(); } - //[Theory, CombinatorialData] - //public async Task Providing_an_unexpected_array_value_type_to_params_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - // [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - // [CombinatorialMemberData(nameof(ValuesAndTypes))] string[] valueAndType, - // [CombinatorialMemberData(nameof(ArrayValuesContainerAttributeArgumentWithLocationMarker))] string[] arrayValuesContainerAttributeArgument) - [Theory] [MemberData(nameof(UnexpectedArrayValueTypeCombinations))] public async Task Providing_an_unexpected_array_value_type_to_params_attribute_should_trigger_diagnostic(string fieldOrPropertyDeclaration, @@ -290,14 +291,15 @@ public async Task Providing_an_unexpected_array_value_type_to_params_attribute_s { const string expectedFieldOrPropertyType = "decimal"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(arrayValuesContainerAttributeArgument[0], valueAndType[0], valueAndType[1])})] - public {expectedFieldOrPropertyType} {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(arrayValuesContainerAttributeArgument[0], valueAndType[0], valueAndType[1])}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); ReferenceDummyEnum(); @@ -316,14 +318,15 @@ public async Task Providing_an_empty_array_value_when_type_of_attribute_target_i [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params{emptyValuesAttributeArgument}] - public decimal {fieldOrPropertyDeclaration} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params{{emptyValuesAttributeArgument}}] + public decimal {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -335,14 +338,15 @@ public async Task Providing_an_empty_array_value_when_type_of_attribute_target_i [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params{emptyValuesAttributeArgument}] - public object[] {fieldOrPropertyDeclaration} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params{{emptyValuesAttributeArgument}}] + public object[] {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -375,9 +379,7 @@ public static IEnumerable EmptyValuesAttributeArgument() { "({0}new object[] {{ }}{1})", "({0}new object[0]{1})", -#if NET8_0_OR_GREATER "({0}[ ]{1})" -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) @@ -416,18 +418,12 @@ public static IEnumerable ArrayValuesContainerAttributeArgumentWithLoc { ("{0}new object[] {{{{ {{{{|#0:new[] {{{{ {{0}} }}}}|}}}} }}}}{1}", "new[] {{ {0} }}"), // new object[] { new[] { 42 } } ("{0}new object[] {{{{ {{{{|#0:new {{1}}[] {{{{ {{0}} }}}}|}}}} }}}}{1}", "new {1}[] {{ {0} }}"), // new object[] { new int[] { 42 } } -#if NET8_0_OR_GREATER ("{0}[ {{{{|#0:new[] {{{{ {{0}} }}}}|}}}} ]{1}", "new[] {{ {0} }}"), // [ new[] { 42 } ] ("{0}[ {{{{|#0:new {{1}}[] {{{{ {{0}} }}}}|}}}} ]{1}", "new {1}[] {{ {0} }}"), // [ new int[] { 42 } ] -#endif ("{0}new object[] {{{{ {{{{|#0:new {{1}}[] {{{{ }}}}|}}}} }}}}{1}", "new {1}[] {{ }}"), // new object[] { new int[] { } } -#if NET8_0_OR_GREATER ("{0}[ {{{{|#0:new {{1}}[] {{{{ }}}}|}}}} ]{1}", "new {1}[] {{ }}"), // [ new int[] { } ] -#endif ("{0}new object[] {{{{ {{{{|#0:new {{1}}[0]|}}}} }}}}{1}", "new {1}[0]"), // new object[] { new int[0] } -#if NET8_0_OR_GREATER ("{0}[ {{{{|#0:new {{1}}[0]|}}}} ]{1}", "new {1}[0]"), // [ new int[0] ] -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) @@ -436,24 +432,16 @@ public static IEnumerable ArrayValuesContainerAttributeArgumentWithLoc { foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { -#if NET8_0_OR_GREATER yield return [ string.Format(attributeUsageBase.Item1, nameColonUsage, priorityNamedParameterUsage), attributeUsageBase.Item2 ]; -#else - yield return new[] { - string.Format(attributeUsageBase.Item1, nameColonUsage, priorityNamedParameterUsage), - attributeUsageBase.Item2 - }; -#endif } } } } public static IEnumerable ValuesAndTypes => -#if NET8_0_OR_GREATER [ [ "true", "bool" ], [ "(byte)123", "byte" ], @@ -477,31 +465,8 @@ public static IEnumerable ArrayValuesContainerAttributeArgumentWithLoc [ "typeof(string)", "System.Type" ], [ "DummyEnum.Value1", "DummyEnum" ] ]; -#else - new[] - { - new[] { "true", "bool" }, - new[] { "(byte)123", "byte" }, - new[] { "'A'", "char" }, - new[] { "1.0D", "double" }, - new[] { "1.0F", "float" }, - new[] { "123", "int" }, - new[] { "123L", "long" }, - new[] { "(sbyte)-100", "sbyte" }, - new[] { "(short)-123", "short" }, - new[] { "\"test\"", "string" }, - new[] { "123U", "uint" }, - new[] { "123UL", "ulong" }, - new[] { "(ushort)123", "ushort" }, - - new[] { "(object)\"test_object\"", "object" }, - new[] { "typeof(string)", "System.Type" }, - new[] { "DummyEnum.Value1", "DummyEnum" } - }; -#endif public static IEnumerable NotConvertibleValuesAndTypes => -#if NET8_0_OR_GREATER [ [ "true", "bool" ], [ "1.0D", "double" ], @@ -516,19 +481,6 @@ public static IEnumerable ArrayValuesContainerAttributeArgumentWithLoc [ "typeof(string)", "System.Type" ], [ "DummyEnum.Value1", "DummyEnum" ] ]; -#else - new[] - { - new[] { "true", "bool" }, - new[] { "1.0D", "double" }, - new[] { "1.0F", "float" }, - new[] { "\"test\"", "string" }, - - new[] {"(object)\"test_object\"", "object" }, - new[] { "typeof(string)", "System.Type" }, - new[] { "DummyEnum.Value1", "DummyEnum" } - }; -#endif } public class UnnecessarySingleValuePassedToAttribute : AnalyzerTestFixture @@ -541,14 +493,15 @@ public async Task Providing_two_or_more_values_should_not_trigger_diagnostic([Co [CombinatorialMemberData(nameof(ScalarValuesListLength))] int scalarValuesListLength, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, string.Join(", ", ScalarValues.Take(scalarValuesListLength)))})] - public string {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, string.Join(", ", ScalarValues.Take(scalarValuesListLength)))}})] + public string {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; ReferenceDummyAttribute(); @@ -561,14 +514,15 @@ public async Task Providing_only_a_single_value_should_trigger_diagnostic([Combi [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentWithLocationMarker))] string scalarValuesContainerAttributeArgument) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [{dummyAttributeUsage}Params({string.Format(scalarValuesContainerAttributeArgument, 42)})] - public string {fieldOrPropertyDeclaration} -}}"; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, 42)}})] + public string {{fieldOrPropertyDeclaration}} + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(); @@ -607,9 +561,7 @@ public static IEnumerable ScalarValuesContainerAttributeArgumentWithLoca { "{{{{|#0:{{0}}|}}}}{1}", "{0}new object[] {{{{ {{{{|#0:{{0}}|}}}} }}}}{1}", -#if NET8_0_OR_GREATER "{0}[ {{{{|#0:{{0}}|}}}} ]{1}", -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) @@ -625,21 +577,16 @@ public static IEnumerable ScalarValuesContainerAttributeArgumentWithLoca } } - public static TheoryData DummyAttributeUsageTheoryData => new TheoryData - { + public static TheoryData DummyAttributeUsageTheoryData => [ "", "Dummy, " - }; + ]; public static TheoryData ScalarValuesContainerAttributeArgumentTheoryData() { return new TheoryData(GenerateData()); -#if NET6_0_OR_GREATER static IEnumerable GenerateData() -#else - IEnumerable GenerateData() -#endif { var nameColonUsages = new List { @@ -657,9 +604,7 @@ IEnumerable GenerateData() { "{{0}}{1}", "{0}new object[] {{{{ {{0}} }}}}{1}", -#if NET8_0_OR_GREATER "{0}[ {{0}} ]{1}" -#endif }; foreach (var attributeUsageBase in attributeUsagesBase) diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index f960fab1ac..b7df860b61 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -19,27 +19,29 @@ public async Task Invoking_with_type_argument_class_having_only_one_and_public_m { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Running; - -public class Program -{{ - public static void Main(string[] args) {{ - BenchmarkRunner.Run<{classWithOneBenchmarkMethodName}>(); - }} -}}"; - - var benchmarkClassDocument = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class {classWithOneBenchmarkMethodName} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ - - }} -}}"; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); + } + } + """; + + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{classWithOneBenchmarkMethodName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; AddSource(benchmarkClassDocument); @@ -52,24 +54,26 @@ public async Task Invoking_with_type_argument_class_having_no_public_method_anno { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Running; - -public class Program -{{ - public static void Main(string[] args) {{ - BenchmarkRunner.Run<{{|#0:{classWithOneBenchmarkMethodName}|}}>(); - }} -}}"; - - var benchmarkClassDocument = -/* lang=c#-test */ $@"public class {classWithOneBenchmarkMethodName} -{{ - public void BenchmarkMethod() - {{ - - }} -}}"; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{|#0:{{classWithOneBenchmarkMethodName}}|}>(); + } + } + """; + + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + public class {{classWithOneBenchmarkMethodName}} + { + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; AddSource(benchmarkClassDocument); AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); @@ -82,37 +86,39 @@ public async Task Invoking_with_type_argument_class_having_at_least_one_public_m { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Running; - -public class Program -{{ - public static void Main(string[] args) {{ - BenchmarkRunner.Run<{classWithOneBenchmarkMethodName}>(); - }} -}}"; - - var benchmarkClassDocument = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public class {classWithOneBenchmarkMethodName} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ - - }} - - public void BenchmarkMethod2() - {{ - - }} - - private void BenchmarkMethod3() - {{ - - }} -}}"; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); + } + } + """; + + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{classWithOneBenchmarkMethodName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; TestCode = testCode; AddSource(benchmarkClassDocument); diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 370c31a674..56203d2835 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -18,16 +18,17 @@ public class General : AnalyzerTestFixture [Fact] public async Task Class_with_no_methods_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{ - public void BenchmarkMethod() - { + public class BenchmarkClass + { + public void BenchmarkMethod() + { - } -}"; + } + } + """; TestCode = testCode; @@ -42,22 +43,23 @@ public MethodMustBePublic() : base(BenchmarkClassAnalyzer.MethodMustBePublicRule [Fact] public async Task Public_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -70,17 +72,18 @@ public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_tri { const string benchmarkMethodName = "BenchmarkMethod"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [Benchmark] - {nonPublicClassAccessModifier}void {{|#0:{benchmarkMethodName}|}}() - {{ + public class BenchmarkClass + { + [Benchmark] + {{nonPublicClassAccessModifier}}void {|#0:{{benchmarkMethodName}}|}() + { - }} -}}"; + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkMethodName); @@ -96,17 +99,18 @@ public MethodMustBeNonGeneric() : base(BenchmarkClassAnalyzer.MethodMustBeNonGen [Fact] public async Task Nongeneric_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void NonGenericBenchmarkMethod() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void NonGenericBenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -116,16 +120,17 @@ public void NonGenericBenchmarkMethod() [Fact] public async Task Generic_method_not_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{ - public void GenericMethod() - { + public class BenchmarkClass + { + public void GenericMethod() + { - } -}"; + } + } + """; TestCode = testCode; @@ -138,17 +143,18 @@ public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_tri { const string benchmarkMethodName = "GenericBenchmarkMethod"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{{ - [Benchmark] - public void {benchmarkMethodName}{{|#0:<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}>|}}() - {{ + public class BenchmarkClass + { + [Benchmark] + public void {{benchmarkMethodName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|}() + { - }} -}}"; + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkMethodName); @@ -168,22 +174,23 @@ public ClassMustBePublic() : base(BenchmarkClassAnalyzer.ClassMustBePublicRule) [Fact] public async Task Public_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -196,50 +203,52 @@ public async Task Nonpublic_class_containing_at_least_one_method_annotated_with_ { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class Wrapper {{ - {nonPublicClassAccessModifier}class {{|#0:{benchmarkClassName}|}} - {{ - [Benchmark] - public void BenchmarkMethod() - {{ + public class Wrapper { + {{nonPublicClassAccessModifier}}class {|#0:{{benchmarkClassName}}|} + { + [Benchmark] + public void BenchmarkMethod() + { - }} - }} -}}"; + } + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkClassName); await RunAsync(); } -#if NET7_0_OR_GREATER + [Fact] public async Task File_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -file class {{|#0:{benchmarkClassName}|}} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ - - }} -}}"; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + file class {|#0:{{benchmarkClassName}}|} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkClassName); await RunAsync(); } -#endif - public static TheoryData NonPublicClassAccessModifiersExceptFileTheoryData => new TheoryData(new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file ")); + + public static TheoryData NonPublicClassAccessModifiersExceptFileTheoryData => new(new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file ")); } public class ClassMustBeNonStatic : AnalyzerTestFixture @@ -249,22 +258,23 @@ public ClassMustBeNonStatic() : base(BenchmarkClassAnalyzer.ClassMustBeNonStatic [Fact] public async Task Instance_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -276,17 +286,18 @@ public async Task Static_class_containing_at_least_one_method_annotated_with_ben { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public {{|#0:static|}} class {benchmarkClassName} -{{ - [Benchmark] - public static void BenchmarkMethod() - {{ - - }} -}}"; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {|#0:static|} class {{benchmarkClassName}} + { + [Benchmark] + public static void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkClassName); @@ -302,27 +313,28 @@ public BenchmarkClassMustBeNonAbstract() : base(BenchmarkClassAnalyzer.ClassMust [Fact] public async Task Nonabstract_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } - - private static void NonBenchmarkMethod2() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } + + private static void NonBenchmarkMethod2() + { + + } + } + """; TestCode = testCode; @@ -334,17 +346,18 @@ public async Task Abstract_class_containing_at_least_one_method_annotated_with_b { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public {{|#0:abstract|}} class {benchmarkClassName} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ - - }} -}}"; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {|#0:abstract|} class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkClassName); @@ -360,22 +373,23 @@ public BenchmarkClassMustBeNonGeneric() : base(BenchmarkClassAnalyzer.ClassMustB [Fact] public async Task Nongeneric_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -388,18 +402,19 @@ public async Task Generic_class_annotated_with_the_generictypearguments_attribut { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -[GenericTypeArguments({string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))})] -public class {benchmarkClassName}{{|#0:<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}>|}} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ + [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}})] + public class {{benchmarkClassName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} + { + [Benchmark] + public void BenchmarkMethod() + { - }} -}}"; + } + } + """; TestCode = testCode; @@ -412,17 +427,18 @@ public async Task Generic_class_containing_at_least_one_method_annotated_with_be { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -public class {benchmarkClassName}{{|#0:<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}>|}} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ + public class {{benchmarkClassName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} + { + [Benchmark] + public void BenchmarkMethod() + { - }} -}}"; + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkClassName); @@ -444,17 +460,18 @@ public ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters() : base(Ben [Fact] public async Task Nongeneric_class_not_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { - } -}"; + } + } + """; TestCode = testCode; @@ -465,18 +482,19 @@ public void BenchmarkMethod() [MemberData(nameof(TypeParametersListLength))] public async Task Generic_class_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -[GenericTypeArguments({string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))})] -public class BenchmarkClass<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}> -{{ - [Benchmark] - public void BenchmarkMethod() - {{ + [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}})] + public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + { + [Benchmark] + public void BenchmarkMethod() + { - }} -}}"; + } + } + """; TestCode = testCode; @@ -488,18 +506,19 @@ public async Task Class_annotated_with_the_generictypearguments_attribute_and_co { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -[GenericTypeArguments(typeof(int))] -public class {{|#0:{benchmarkClassName}|}} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ - - }} -}}"; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + [GenericTypeArguments(typeof(int))] + public class {|#0:{{benchmarkClassName}}|} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkClassName); @@ -521,17 +540,18 @@ public GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount() : base( [Fact] public async Task Nongeneric_class_not_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { - } -}"; + } + } + """; TestCode = testCode; @@ -542,18 +562,19 @@ public void BenchmarkMethod() [MemberData(nameof(TypeParametersListLength))] public async Task Generic_class_annotated_with_the_generictypearguments_attribute_having_matching_type_argument_count_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) { - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -[GenericTypeArguments({string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))})] -public class BenchmarkClass<{string.Join(", ", TypeParameters.Take(typeParametersListLength))}> -{{ - [Benchmark] - public void BenchmarkMethod() - {{ + [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}})] + public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + { + [Benchmark] + public void BenchmarkMethod() + { - }} -}}"; + } + } + """; TestCode = testCode; @@ -567,18 +588,19 @@ public async Task Generic_class_annotated_with_the_generictypearguments_attribut { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; -[GenericTypeArguments({{|#0:{typeArguments}|}})] -public class {benchmarkClassName} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ + [GenericTypeArguments({|#0:{{typeArguments}}|})] + public class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { - }} -}}"; + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(1, "", benchmarkClassName, typeArgumentCount); @@ -600,22 +622,23 @@ public BenchmarkClassMustBeUnsealed() : base(BenchmarkClassAnalyzer.ClassMustBeU [Fact] public async Task Unsealed_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void NonBenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -627,17 +650,18 @@ public async Task Sealed_class_containing_at_least_one_method_annotated_with_ben { const string benchmarkClassName = "BenchmarkClass"; - var testCode = -/* lang=c#-test */ $@"using BenchmarkDotNet.Attributes; - -public {{|#0:sealed|}} class {benchmarkClassName} -{{ - [Benchmark] - public void BenchmarkMethod() - {{ - - }} -}}"; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {|#0:sealed|} class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkClassName); @@ -653,29 +677,30 @@ public OnlyOneMethodCanBeBaseline() : base(BenchmarkClassAnalyzer.OnlyOneMethodC [Fact] public async Task Class_with_only_one_benchmark_method_marked_as_baseline_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark(Baseline = true)] - public void BaselineBenchmarkMethod() - { - - } - - [Benchmark] - public void NonBaselineBenchmarkMethod1() - { - - } - - [Benchmark(Baseline = false)] - public void NonBaselineBenchmarkMethod2() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark(Baseline = true)] + public void BaselineBenchmarkMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark(Baseline = false)] + public void NonBaselineBenchmarkMethod2() + { + + } + } + """; TestCode = testCode; @@ -685,29 +710,30 @@ public void NonBaselineBenchmarkMethod2() [Fact] public async Task Class_with_no_benchmark_methods_marked_as_baseline_should_not_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark] - public void NonBaselineBenchmarkMethod1() - { - - } - - [Benchmark] - public void NonBaselineBenchmarkMethod2() - { - - } - - [Benchmark(Baseline = false)] - public void NonBaselineBenchmarkMethod3() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + + [Benchmark(Baseline = false)] + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; TestCode = testCode; @@ -717,43 +743,44 @@ public void NonBaselineBenchmarkMethod3() [Fact] public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_should_trigger_diagnostic() { - const string testCode = -/* lang=c#-test */ @"using BenchmarkDotNet.Attributes; - -public class BenchmarkClass -{ - [Benchmark({|#0:Baseline = true|})] - [Benchmark] - public void BaselineBenchmarkMethod1() - { - - } - - [Benchmark] - public void NonBaselineBenchmarkMethod1() - { - - } - - [Benchmark(Baseline = false)] - public void NonBaselineBenchmarkMethod2() - { - - } - - [Benchmark({|#1:Baseline = true|})] - public void BaselineBenchmarkMethod2() - { - - } - - [Benchmark({|#2:Baseline = true|})] - [Benchmark({|#3:Baseline = true|})] - public void BaselineBenchmarkMethod3() - { - - } -}"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark({|#0:Baseline = true|})] + [Benchmark] + public void BaselineBenchmarkMethod1() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark(Baseline = false)] + public void NonBaselineBenchmarkMethod2() + { + + } + + [Benchmark({|#1:Baseline = true|})] + public void BaselineBenchmarkMethod2() + { + + } + + [Benchmark({|#2:Baseline = true|})] + [Benchmark({|#3:Baseline = true|})] + public void BaselineBenchmarkMethod3() + { + + } + } + """; TestCode = testCode; DisableCompilerDiagnostics(); @@ -766,7 +793,7 @@ public void BaselineBenchmarkMethod3() } } - public static TheoryData TypeParametersListLengthTheoryData => new TheoryData(Enumerable.Range(1, TypeParametersTheoryData.Count)); + public static TheoryData TypeParametersListLengthTheoryData => new(Enumerable.Range(1, TypeParametersTheoryData.Count)); private static ReadOnlyCollection TypeParametersTheoryData => Enumerable.Range(0, 3) .Select(i => $"TParameter{i}") diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj index bc7c74d91b..a671131272 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj @@ -2,11 +2,12 @@ net462;net6.0;net8.0 - enable + enable true true true + true @@ -27,12 +28,12 @@ + + + + + - - - - - @@ -40,14 +41,11 @@ - + - - - - + diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs index 32ce2a871f..beb99fa085 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs @@ -1,7 +1,6 @@ namespace BenchmarkDotNet.Analyzers.Tests.Fixtures { using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; @@ -18,11 +17,7 @@ public abstract class AnalyzerTestFixture { private readonly CSharpAnalyzerTest _analyzerTest; -#if NET6_0_OR_GREATER private readonly DiagnosticDescriptor? _ruleUnderTest; -#else - private readonly DiagnosticDescriptor _ruleUnderTest; -#endif private AnalyzerTestFixture(bool assertUniqueSupportedDiagnostics) { @@ -35,19 +30,6 @@ private AnalyzerTestFixture(bool assertUniqueSupportedDiagnostics) #else ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard20, #endif - SolutionTransforms = - { - (s, pId) => s.WithProjectParseOptions(pId, new CSharpParseOptions( -#if NET8_0_OR_GREATER - LanguageVersion.CSharp12 -#elif NET6_0_OR_GREATER - LanguageVersion.CSharp10 -#else - LanguageVersion.CSharp7_3 -#endif - )) - }, - TestState = { AdditionalReferences = @@ -70,11 +52,8 @@ protected AnalyzerTestFixture(DiagnosticDescriptor diagnosticDescriptor) : this( { var analyzer = AssertUniqueSupportedDiagnostics(); -#if NET6_0_OR_GREATER - if (diagnosticDescriptor == null!) -#else + // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (diagnosticDescriptor == null) -#endif { Assert.Fail("Diagnostic under test cannot be null when using this constructor"); } @@ -97,7 +76,9 @@ void AssertDiagnosticUnderTestIsSupportedByAnalyzer() void DisableAllSupportedDiagnosticsExceptDiagnosticUnderTest() { _analyzerTest.DisabledDiagnostics.Clear(); - _analyzerTest.DisabledDiagnostics.AddRange(analyzer.SupportedDiagnostics.Select(dd => dd.Id).Except(new[] { diagnosticDescriptor.Id })); + _analyzerTest.DisabledDiagnostics.AddRange(analyzer.SupportedDiagnostics.Select(dd => dd.Id).Except([ + diagnosticDescriptor.Id + ])); } } @@ -158,11 +139,8 @@ protected void AddExpectedDiagnostic(int markupKey, params object[] arguments) { AddExpectedDiagnostic(arguments, markupKey); } -#if NET6_0_OR_GREATER + private void AddExpectedDiagnostic(object[]? arguments = null, int markupKey = 0) -#else - private void AddExpectedDiagnostic(object[] arguments = null, int markupKey = 0) -#endif { if (_ruleUnderTest == null) { @@ -192,38 +170,41 @@ protected Task RunAsync() protected void ReferenceDummyAttribute() { - _analyzerTest.TestState.Sources.Add( -@"using System; - -public class DummyAttribute : Attribute -{ - -}"); + _analyzerTest.TestState.Sources.Add(""" + using System; + + public class DummyAttribute : Attribute + { + + } + """); } protected void ReferenceDummyEnum() { - _analyzerTest.TestState.Sources.Add( -@"public enum DummyEnum -{ - Value1, - Value2, - Value3 -}"); + _analyzerTest.TestState.Sources.Add(""" + public enum DummyEnum + { + Value1, + Value2, + Value3 + } + """); } protected void ReferenceDummyEnumWithFlagsAttribute() { - _analyzerTest.TestState.Sources.Add( -@"using System; - -[Flags] -public enum DummyEnumWithFlagsAttribute -{ - Value1, - Value2, - Value3 -}"); + _analyzerTest.TestState.Sources.Add(""" + using System; + + [Flags] + public enum DummyEnumWithFlagsAttribute + { + Value1, + Value2, + Value3 + } + """); } private sealed class InternalAnalyzerTest : CSharpAnalyzerTest diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs index af04252803..55532f3ef5 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs @@ -49,11 +49,11 @@ public static IEnumerable CombineArguments(params IEnumerable[] argume yield break; } - IEnumerable combinations = new[] { Array.Empty() }; + IEnumerable combinations = [[]]; foreach (var argumentValues in argumentSets) { - combinations = combinations.SelectMany(_ => argumentValues.Cast(), (c, v) => c.Concat(new[] { v }) + combinations = combinations.SelectMany(_ => argumentValues.Cast(), (c, v) => c.Concat([v]) .ToArray()); } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index ad1277415c..fc02ce8415 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -34,7 +34,7 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_Description))); - + internal static readonly DiagnosticDescriptor MustHaveMatchingValueTypeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_MustHaveMatchingValueType, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueType_MessageFormat)), diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs index 4967a1e4d3..33c1d02b58 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs @@ -146,7 +146,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) { declaredAttributes = fieldDeclarationSyntax.AttributeLists.SelectMany(als => als.Attributes).ToImmutableArray(); fieldOrPropertyIsPublic = fieldDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword); - + var fieldConstModifierIndex = fieldDeclarationSyntax.Modifiers.IndexOf(SyntaxKind.ConstKeyword); fieldConstModifierLocation = fieldConstModifierIndex >= 0 ? fieldDeclarationSyntax.Modifiers[fieldConstModifierIndex].GetLocation() : null; diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj index 47a0dda096..49e4498e0d 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj @@ -32,7 +32,9 @@ - <_Parameter1>BenchmarkDotNet.Analyzers.Tests + <_Parameter1>BenchmarkDotNet.Analyzers.Tests,PublicKey=00240000048000009400000006020000002400005253413100040000010001002970bbdfca4d129fc74b4845b239973f1b183684f0d7db5e1de7e085917e3656cf94884803cb800d85d5aae5838fb3f8fd1f2829e8208c4f087afcfe970bce44037ba30a66749cd5514b410ca8a35e9c7d6eb86975853c834c9ad25051537f9a05a0c540c5d84f2c7b32ab01619d84367fd424797ba3242f08b0e6ae75f66dad + + From e1822c1f0f2bd584f9da131450976f6878ef202c Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:21:07 +0200 Subject: [PATCH 03/24] Remove Analyzers package projects --- BenchmarkDotNet.Analyzers.sln | 12 - .../BenchmarkDotNet.Analyzers.Package.csproj | 36 --- .../tools/install.ps1 | 250 ----------------- .../tools/uninstall.ps1 | 257 ------------------ .../BenchmarkDotNet.Analyzers.Vsix.csproj | 47 ---- .../source.extension.vsixmanifest | 24 -- 6 files changed, 626 deletions(-) delete mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/BenchmarkDotNet.Analyzers.Package.csproj delete mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/install.ps1 delete mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/uninstall.ps1 delete mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/BenchmarkDotNet.Analyzers.Vsix.csproj delete mode 100644 src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/source.extension.vsixmanifest diff --git a/BenchmarkDotNet.Analyzers.sln b/BenchmarkDotNet.Analyzers.sln index b82b4873d2..3f0956ec9b 100644 --- a/BenchmarkDotNet.Analyzers.sln +++ b/BenchmarkDotNet.Analyzers.sln @@ -5,12 +5,8 @@ VisualStudioVersion = 17.0.31710.8 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.csproj", "{B7664DD5-DCDB-4324-91A9-16D242CC4498}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers.Package", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.Package\BenchmarkDotNet.Analyzers.Package.csproj", "{B7500BDE-4DC7-4858-968F-11889AA4F289}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers.Tests", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.Tests\BenchmarkDotNet.Analyzers.Tests.csproj", "{5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers.Vsix", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.Vsix\BenchmarkDotNet.Analyzers.Vsix.csproj", "{F3163F56-3EC2-44F8-872E-02E3114D7849}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet", "src\BenchmarkDotNet\BenchmarkDotNet.csproj", "{B5F58AA0-88F8-4C8C-B734-E1217E23079E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Annotations", "src\BenchmarkDotNet.Annotations\BenchmarkDotNet.Annotations.csproj", "{F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}" @@ -25,18 +21,10 @@ Global {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Release|Any CPU.Build.0 = Release|Any CPU - {B7500BDE-4DC7-4858-968F-11889AA4F289}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B7500BDE-4DC7-4858-968F-11889AA4F289}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B7500BDE-4DC7-4858-968F-11889AA4F289}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B7500BDE-4DC7-4858-968F-11889AA4F289}.Release|Any CPU.Build.0 = Release|Any CPU {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Release|Any CPU.Build.0 = Release|Any CPU - {F3163F56-3EC2-44F8-872E-02E3114D7849}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F3163F56-3EC2-44F8-872E-02E3114D7849}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F3163F56-3EC2-44F8-872E-02E3114D7849}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F3163F56-3EC2-44F8-872E-02E3114D7849}.Release|Any CPU.Build.0 = Release|Any CPU {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/BenchmarkDotNet.Analyzers.Package.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/BenchmarkDotNet.Analyzers.Package.csproj deleted file mode 100644 index cfd362d45a..0000000000 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/BenchmarkDotNet.Analyzers.Package.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - netstandard2.0 - false - true - true - - - - BenchmarkDotNet.Analyzers - 1.0.0.0 - - Analyzers for the BenchmarkDotNet package. - - true - true - - $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput - - - - - - - - - - - - - - - - - diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/install.ps1 b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/install.ps1 deleted file mode 100644 index be2f74c118..0000000000 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/install.ps1 +++ /dev/null @@ -1,250 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -if($project.Object.SupportsPackageDependencyResolution) -{ - if($project.Object.SupportsPackageDependencyResolution()) - { - # Do not install analyzers via install.ps1, instead let the project system handle it. - return - } -} - -$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve - -foreach($analyzersPath in $analyzersPaths) -{ - if (Test-Path $analyzersPath) - { - # Install the language agnostic analyzers. - foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($analyzersPath in $analyzersPaths) -{ - # Install language specific analyzers. - $languageAnalyzersPath = join-path $analyzersPath $languageFolder - if (Test-Path $languageAnalyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) - } - } - } -} -# SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA/i+qRUHsWzI0s -# FVk99zLgt/HOEQ33uvkFsWtHTHZgf6CCDYEwggX/MIID56ADAgECAhMzAAAB32vw -# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn -# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw -# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS -# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG -# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh -# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH -# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS -# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp -# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok -# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 -# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao -# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD -# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt -# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G -# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ -# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 -# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgRjg7DcI6 -# uhYfXWwAQ6hK0mPW7iyr2tzHR0DHSDJkscIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQCt+qfRFudb8jpGDEFmJnD3uqC44a6SmTO+nFx0s2tf -# o+OTFNnRhwUMuTz/KVCfSwTzD/p2mD6JmuOuNmtTKyanjVNbYvwzO7LWedJh3d+T -# WEKZ6KYFbm6rPM+9DIINZNuK5Vb15vvNBUYI4PgFrSLGXdmRIB5xGiLRYWM/UET/ -# Sb4T6edTQKYx4vkDX9UcM4cYCx1u59hR6FgdCCHzU9/ZHYqN0AhBrHrTWGuqxx3E -# Oo0wdYJMRLH8zPFbzRNcG4qVlq95yDtWqzNcYMWybejIZenDg6am3ZldQFMoGU38 -# 76WP/a5unw8DKpkL/4ZO686G9Boh5Jc6U8mMGlLctW43oYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIITxzR9P1o4UBFnvUGa4yCqvmQhov1ZeA/XM1qBB -# 5/5xAgZgieV7UmkYEzIwMjEwNTEzMTkwNDA3LjM3MVowBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjpEOURFLUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABYfWiM16gKiRpAAAA -# AAFhMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTIxMDExNDE5MDIyMVoXDTIyMDQxMTE5MDIyMVowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpEOURF -# LUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJeInahBrU//GzTqhxUy -# AC8UXct6UJCkb2xEZKV3gjggmLAheBrxJk7tH+Pw2tTcyarLRfmV2xo5oBk5pW/O -# cDc/n/TcTeQU6JIN5PlTcn0C9RlKQ6t9OuU/WAyAxGTjKE4ENnUjXtxiNlD/K2ZG -# MLvjpROBKh7TtkUJK6ZGWw/uTRabNBxRg13TvjkGHXEUEDJ8imacw9BCeR9L6und -# r32tj4duOFIHD8m1es3SNN98Zq4IDBP3Ccb+HQgxpbeHIUlK0y6zmzIkvfN73Zxw -# fGvFv0/Max79WJY0cD8poCnZFijciWrf0eD1T2/+7HgewzrdxPdSFockUQ8QovID -# IYkCAwEAAaOCARswggEXMB0GA1UdDgQWBBRWHpqd1hv71SVj5LAdPfNE7PhLLzAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAQTA9bqVBmx5TTMhzj+Q8zWkPQXgCc -# SQiqy2YYWF0hWr5GEiN2LtA+EWdu1y8oysZau4CP7SzM8VTSq31CLJiOy39Z4RvE -# q2mr0EftFvmX2CxQ7ZyzrkhWMZaZQLkYbH5oabIFwndW34nh980BOY395tfnNS/Y -# 6N0f+jXdoFn7fI2c43TFYsUqIPWjOHJloMektlD6/uS6Zn4xse/lItFm+fWOcB2A -# xyXEB3ZREeSg9j7+GoEl1xT/iJuV/So7TlWdwyacQu4lv3MBsvxzRIbKhZwrDYog -# moyJ+rwgQB8mKS4/M1SDRtIptamoTFJ56Tk6DuUXx1JudToelgjEZPa5MIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpE -# OURFLUUzOUEtNDNGRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUAFW5ShAw5ekTEXvL/4V1s0rbDz3mggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AORHgWMwIhgPMjAyMTA1MTMxNDQzNDdaGA8yMDIxMDUxNDE0NDM0N1owdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA5EeBYwIBADAKAgEAAgIYbAIB/zAHAgEAAgIRJjAK -# AgUA5EjS4wIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJ7JAjVdIqigM77q -# X+WQlagNMRjqaFC99qAjVz3kuffmZJoaEZAUFl+ynf6+HFfOhtbygpb3Inb1ewPz -# sZH0SoRd1eGUpvXk0rzjFl8jKiV/FWTV/xJDdRyKf4I6Pl4hzA1gpsB0sNO3Qqr3 -# u8dTOzbh3DWucOQgfLBWoq3e/UuUMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAFh9aIzXqAqJGkAAAAAAWEwDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQgTf39WlLL62P8pSZibw+7Xw+a3N3OO0YRek+60a+WpNswgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCBhz4un6mkSLd/zA+0N5YLDGp4vW/VBtNW/lpmh -# tAk4bzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# YfWiM16gKiRpAAAAAAFhMCIEIIqi7Un+0eNrtRg58qtA+fKclBz8FTjtf7MCNv7U -# MEInMA0GCSqGSIb3DQEBCwUABIIBADPu+hGKCfhBayhOOzglyKPK9RBeNCsRzMKp -# Uymhnad7xNyu+78hq0nWjS/DWgiCYRSF4g0Hl2ls9AfHbz7vT0GLZelJOfaTW+M7 -# gA6HGctwkDhfQg1tG0+pJ5D+pdCrvlMzp4K0EF5pE8FSib3BWOIBu5Ja4D4IbmE4 -# 3kkbHw/FWQLJDEhPFLIpQg45p4dsMLlR39QaPXQpX3hu2Tp+LgzQYA+meIpt95W0 -# gKA/jb0H26x7TncDwyi5bgMt7cKDhkiLSm6y1yHDnd9yJa3XkbcU9Ez7MjEBvG35 -# RXImHA84+QsRDHzx8MlAjy8f3ln/JwUt/U/OtGl43qbTKt/ZX78= -# SIG # End signature block diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/uninstall.ps1 b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/uninstall.ps1 deleted file mode 100644 index af3d04f297..0000000000 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Package/tools/uninstall.ps1 +++ /dev/null @@ -1,257 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -if($project.Object.SupportsPackageDependencyResolution) -{ - if($project.Object.SupportsPackageDependencyResolution()) - { - # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. - return - } -} - -$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve - -foreach($analyzersPath in $analyzersPaths) -{ - # Uninstall the language agnostic analyzers. - if (Test-Path $analyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) - } - } - } -} - -# $project.Type gives the language name like (C# or VB.NET) -$languageFolder = "" -if($project.Type -eq "C#") -{ - $languageFolder = "cs" -} -if($project.Type -eq "VB.NET") -{ - $languageFolder = "vb" -} -if($languageFolder -eq "") -{ - return -} - -foreach($analyzersPath in $analyzersPaths) -{ - # Uninstall language specific analyzers. - $languageAnalyzersPath = join-path $analyzersPath $languageFolder - if (Test-Path $languageAnalyzersPath) - { - foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) - { - if($project.Object.AnalyzerReferences) - { - try - { - $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) - } - catch - { - - } - } - } - } -} -# SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDC68wb97fg0QGL -# yXrxJhYfmibzcOh8caqC0uZprfczDaCCDYEwggX/MIID56ADAgECAhMzAAAB32vw -# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn -# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw -# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS -# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG -# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh -# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH -# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS -# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp -# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok -# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 -# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao -# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD -# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt -# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G -# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ -# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 -# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgF1ypFyzl -# AvvWGVCeXczrfpXmJNm9vpyjcwd4y4ivfqowQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQBtnC8PmVgmkVoKR15F39ljf7fpP29Vlo9dnBmOcDAw -# lHnt37zK9UIQSoyqiMhY/lt8iltz49NEj3D3ddXTXdIOXlfjUYvoIIrNUahgDF/Z -# 3iQVhBW6Me8FEF4ImSntGkkvf86Hp5dlLCrpdDDWVVJkCxRGvCFXC0aNPHlFHRF1 -# Hbqqstm9xbYliNTk3BhJoo56j8XO61JkNEjzva3gemuG4dVhFSz9OF5HsjPTpTiJ -# pg5//GDE5xeho5kwxk8Algyfac3vseJXLr6388cIP556sruynQumo0+K0cxyxhVI -# cyvYlZAi4WzRpNNnWP8VXFb0ITFbgr0SLBIYUrQGFr2QoYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIJp8zyESzF9BGcJWXqSm1vrCJ/LFDEjr5Yc3y0OW -# 46OzAgZgieX0wY0YEzIwMjEwNTEzMTkwNDA4Ljg1NlowBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjo0RDJGLUUzREQtQkVFRjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABX8OuZVblU1jsAAAA -# AAFfMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTIxMDExNDE5MDIxOVoXDTIyMDQxMTE5MDIxOVowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0RDJG -# LUUzREQtQkVFRjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALw9efmC2WQ9uaaw7k4g -# xHSSCEoJLk22FTAaF8jYbAMkQC6DQF0WPnIheLM1ERTuQ9FWbglf0mXbDd2KjezR -# Nlz53ycJIReiGUQOnw5vd4TgjLUxL17g3K0MP2nNhY/LyP98Ml/40X905egDbiIn -# dZdtHiDb1xfY17a7v1j9o3muc+MCgFt9fO+U4CDNUpMMMQJFr/9QlU4YdJawjbyK -# fK3Ltvqfq3lvgK0/HphiDtX5ch3beGNBKowKSTXhft8pwuXQProutWgB5PZmAN8X -# ZhACo4jWi/a0zgAJJcBqoXvS6InrWcH/Eqi/qVaj8Vs56/Z/6kaYZZu/1mSzLn5E -# ALMCAwEAAaOCARswggEXMB0GA1UdDgQWBBQl7OnTlc0rgZ7Fd7qlDFguYTU49TAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAOgtfZLJYSbsE3W73nd0hLnqQqHSFl -# 2spHxzeXxM4uJT2uAVk/SLVzzjvZemUDBeOedKeXG8hctprpoQMpU3gbsNUnUaDe -# sDcmR+eELCwYa+VBkUCqsIGJmQlDwuDwNa67kyCEPyPW59Yu2w/djNrwNWSjtuRw -# fUFoDkjYyDjnXD0josi67qxJgW8rRqjl9a62hGzlzgE+aVLTT5IhK5z2X62Lph8j -# 9f4XjtCPnyeFKFmgBWHPY1HbbjUHfg91StCLxueH2LjZoQETWOJ+pxElicXwVP5B -# 0wlWkiauwug3rTKnDb5WKUb2llsnQgaegV+MQjMI7K6v+spvsMgRjPlhMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0 -# RDJGLUUzREQtQkVFRjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUA+gfSqjdAndOFEaXOQyBCdupmQoeggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AORHga4wIhgPMjAyMTA1MTMxNDQ1MDJaGA8yMDIxMDUxNDE0NDUwMlowdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA5EeBrgIBADAKAgEAAgIVQwIB/zAHAgEAAgIRLTAK -# AgUA5EjTLgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAHcp665K7IGKPBn3 -# +FtC8KgxAbGQbXp2oOCS8f+3Pu54iO/Ek4BR2F/YsanLXnr6nM/J1Qd9KVu8C6UJ -# a41UfaEkEwbkLWBdEbr7bTbFReOfVlhObtYW2IrLXREmyeEgnce+7cGZZ0QLERSu -# iQTNffmseSvEiARxDVXSpPsO3WsaMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAFfw65lVuVTWOwAAAAAAV8wDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQggG5jZDP+yZVGKxHthsrzCc0tL7YUokx1zgz/FJhsf0gwgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCDQzXq1KxGsLuj0szktrnlhIRqmbwp5bVGc6Bu6 -# hglMXDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# X8OuZVblU1jsAAAAAAFfMCIEIPpiRGFlqZgftkQa9jNO7zYg4bdv1ZAveCzZoH8k -# BxL1MA0GCSqGSIb3DQEBCwUABIIBAKgTDD8eJkOyyk9VEeHVmR8wisdcPHgu4vJa -# yZx4290MVwWAZ8aFYnsrmocb1csIEFlKelbIeB2gJKrdFQwoLiTyN1suVC8mOToz -# FICo6csyu9i5UTNYvidCZXaOZDom6cqamlCjA83npO+UERYvcldkiS3sjK8ejk01 -# OKwCMT8qxws2Csa3D/lm46ig5D4I0a5HccUiaoVMXk3RJtypvyutoH27pBAu+PhW -# jwY0yW4YgS+ZaNgmlCSNkywUKzM3GHpVZd9hAmiIehr52FXIjtGHg6t5VOWlUVXT -# CP5QCuqwUB4RxJUNJ1+yYuLCryZ0eYurv3Kw2yuTOvqyvVAyu9c= -# SIG # End signature block diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/BenchmarkDotNet.Analyzers.Vsix.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/BenchmarkDotNet.Analyzers.Vsix.csproj deleted file mode 100644 index 6c4102ba55..0000000000 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/BenchmarkDotNet.Analyzers.Vsix.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - net48 - BenchmarkDotNet.Analyzers.Vsix - BenchmarkDotNet.Analyzers.Vsix - - - - false - false - false - false - false - false - Roslyn - - - - - - - - Program - $(DevEnvDir)devenv.exe - /rootsuffix $(VSSDKTargetPlatformRegRootSuffix) - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/source.extension.vsixmanifest b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/source.extension.vsixmanifest deleted file mode 100644 index 8386348009..0000000000 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Vsix/source.extension.vsixmanifest +++ /dev/null @@ -1,24 +0,0 @@ - - - - - BenchmarkDotNet.Analyzers - Analyzers for BenchmarkDotNet - - - - - - - - - - - - - - - - - \ No newline at end of file From 3eab33d8356ec331a438ab67cb8edfcbc4daae6b Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:22:54 +0200 Subject: [PATCH 04/24] Revert BenchmarkDotNet.Disassembler changes --- .../BenchmarkDotNet.Disassembler.x64.csproj | 45 +++++++-------- .../BenchmarkDotNet.Disassembler.x86.csproj | 57 +++++++++---------- 2 files changed, 48 insertions(+), 54 deletions(-) diff --git a/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj b/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj index 3bab91dce7..2f15efcc13 100644 --- a/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj +++ b/src/BenchmarkDotNet.Disassembler.x64/BenchmarkDotNet.Disassembler.x64.csproj @@ -1,24 +1,21 @@ - - - - net462 - Exe - BenchmarkDotNet.Disassembler.x64 - BenchmarkDotNet.Disassembler.x64 - win7-x64 - x64 - True - $(DefineConstants);CLRMDV1 - - - ..\BenchmarkDotNet\Disassemblers - BenchmarkDotNet.Disassembler - - - - - - - - - + + + + net462 + Exe + BenchmarkDotNet.Disassembler.x64 + BenchmarkDotNet.Disassembler.x64 + win7-x64 + x64 + True + $(DefineConstants);CLRMDV1 + + + ..\BenchmarkDotNet\Disassemblers + BenchmarkDotNet.Disassembler + + + + + + diff --git a/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj b/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj index fa50112cae..5410f6d77b 100644 --- a/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj +++ b/src/BenchmarkDotNet.Disassembler.x86/BenchmarkDotNet.Disassembler.x86.csproj @@ -1,30 +1,27 @@ - - - - net462 - Exe - BenchmarkDotNet.Disassembler.x86 - BenchmarkDotNet.Disassembler.x86 - win7-x86 - x86 - True - $(DefineConstants);CLRMDV1 - - - ..\BenchmarkDotNet\Disassemblers - BenchmarkDotNet.Disassembler - - - - - - - - - - - - - - - + + + + net462 + Exe + BenchmarkDotNet.Disassembler.x86 + BenchmarkDotNet.Disassembler.x86 + win7-x86 + x86 + True + $(DefineConstants);CLRMDV1 + + + ..\BenchmarkDotNet\Disassemblers + BenchmarkDotNet.Disassembler + + + + + + + + + + + + From 6aa546f87007b77ea6b5f4e42e5c49217c18623a Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:29:00 +0200 Subject: [PATCH 05/24] Reference Analyzers project from Annotations --- .../BenchmarkDotNet.Annotations.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index 8b21e637ad..fd871dc167 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -20,4 +20,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file From bafb873135a50616e6e7151ecbffc8ecb3450414 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:37:17 +0200 Subject: [PATCH 06/24] Move Benchmark.Analyzers and Benchmark.Analyzers.Tests to correct directories --- BenchmarkDotNet.Analyzers.sln | 24 +++++++++---------- .../AnalyzerHelper.cs | 0 .../AnalyzerReleases.Shipped.md | 0 .../AnalyzerReleases.Unshipped.md | 0 .../Attributes/ArgumentsAttributeAnalyzer.cs | 0 .../GeneralParameterAttributesAnalyzer.cs | 0 .../ParamsAllValuesAttributeAnalyzer.cs | 0 .../Attributes/ParamsAttributeAnalyzer.cs | 0 .../BenchmarkDotNet.Analyzers.csproj | 2 +- ...nchmarkDotNetAnalyzerResources.Designer.cs | 0 .../BenchmarkDotNetAnalyzerResources.resx | 0 .../BenchmarkRunner/RunAnalyzer.cs | 0 .../DiagnosticIds.cs | 0 .../General/BenchmarkClassAnalyzer.cs | 0 .../BenchmarkDotNet.Annotations.csproj | 2 +- .../ArgumentsAttributeAnalyzerTests.cs | 0 ...GeneralParameterAttributesAnalyzerTests.cs | 0 .../ParamsAllValuesAttributeAnalyzerTests.cs | 0 .../ParamsAttributeAnalyzerTests.cs | 0 .../BenchmarkRunner/RunAnalyzerTests.cs | 0 .../General/BenchmarkClassAnalyzerTests.cs | 0 .../BenchmarkDotNet.Analyzers.Tests.csproj | 6 ++--- ...kDotNet.Analyzers.Tests.csproj.DotSettings | 0 .../Fixtures/AnalyzerTestFixture.cs | 0 .../Extensions/TheoryDataExtensions.cs | 0 .../Generators/CombinationsGenerator.cs | 0 .../FieldOrPropertyDeclarationTheoryData.cs | 0 ...NonPublicClassAccessModifiersTheoryData.cs | 0 ...licClassMemberAccessModifiersTheoryData.cs | 0 ...PropertySetterAccessModifiersTheoryData.cs | 0 30 files changed, 17 insertions(+), 17 deletions(-) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/AnalyzerHelper.cs (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/AnalyzerReleases.Shipped.md (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/AnalyzerReleases.Unshipped.md (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/Attributes/ArgumentsAttributeAnalyzer.cs (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/Attributes/GeneralParameterAttributesAnalyzer.cs (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/Attributes/ParamsAllValuesAttributeAnalyzer.cs (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/Attributes/ParamsAttributeAnalyzer.cs (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/BenchmarkDotNet.Analyzers.csproj (94%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/BenchmarkDotNetAnalyzerResources.Designer.cs (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/BenchmarkDotNetAnalyzerResources.resx (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/BenchmarkRunner/RunAnalyzer.cs (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/DiagnosticIds.cs (100%) rename src/BenchmarkDotNet.Analyzers/{BenchmarkDotNet.Analyzers => }/General/BenchmarkClassAnalyzer.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj (86%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs (100%) rename {src/BenchmarkDotNet.Analyzers => tests}/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs (100%) diff --git a/BenchmarkDotNet.Analyzers.sln b/BenchmarkDotNet.Analyzers.sln index 3f0956ec9b..2398ad7d66 100644 --- a/BenchmarkDotNet.Analyzers.sln +++ b/BenchmarkDotNet.Analyzers.sln @@ -3,28 +3,20 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31710.8 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.csproj", "{B7664DD5-DCDB-4324-91A9-16D242CC4498}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Analyzers.Tests", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.Tests\BenchmarkDotNet.Analyzers.Tests.csproj", "{5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet", "src\BenchmarkDotNet\BenchmarkDotNet.csproj", "{B5F58AA0-88F8-4C8C-B734-E1217E23079E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BenchmarkDotNet.Annotations", "src\BenchmarkDotNet.Annotations\BenchmarkDotNet.Annotations.csproj", "{F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Analyzers", "src\BenchmarkDotNet.Analyzers\BenchmarkDotNet.Analyzers.csproj", "{AA4DDCA0-C1D8-ADA8-69FE-2F67C4CA96B1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Analyzers.Tests", "tests\BenchmarkDotNet.Analyzers.Tests\BenchmarkDotNet.Analyzers.Tests.csproj", "{7DE89F16-2160-42E3-004E-1F5064732121}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B7664DD5-DCDB-4324-91A9-16D242CC4498}.Release|Any CPU.Build.0 = Release|Any CPU - {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5D1F1A9E-681D-456B-A838-2EAAAD24BC7D}.Release|Any CPU.Build.0 = Release|Any CPU {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B5F58AA0-88F8-4C8C-B734-E1217E23079E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -33,6 +25,14 @@ Global {F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}.Debug|Any CPU.Build.0 = Debug|Any CPU {F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}.Release|Any CPU.ActiveCfg = Release|Any CPU {F07A7F74-15B6-4DC6-8617-A3A9C11C71EF}.Release|Any CPU.Build.0 = Release|Any CPU + {AA4DDCA0-C1D8-ADA8-69FE-2F67C4CA96B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AA4DDCA0-C1D8-ADA8-69FE-2F67C4CA96B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AA4DDCA0-C1D8-ADA8-69FE-2F67C4CA96B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AA4DDCA0-C1D8-ADA8-69FE-2F67C4CA96B1}.Release|Any CPU.Build.0 = Release|Any CPU + {7DE89F16-2160-42E3-004E-1F5064732121}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DE89F16-2160-42E3-004E-1F5064732121}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DE89F16-2160-42E3-004E-1F5064732121}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DE89F16-2160-42E3-004E-1F5064732121}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs rename to src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Shipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Shipped.md similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Shipped.md rename to src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Shipped.md diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md rename to src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs rename to src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs rename to src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs rename to src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs rename to src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj similarity index 94% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj rename to src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj index 49e4498e0d..c1751c49c0 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj @@ -36,5 +36,5 @@ - + diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs rename to src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx rename to src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs rename to src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/DiagnosticIds.cs rename to src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs rename to src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index fd871dc167..40aa069a2c 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -21,6 +21,6 @@ - + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj similarity index 86% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj rename to tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj index a671131272..b7dced86f3 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj +++ b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj @@ -29,8 +29,8 @@ - - + + @@ -46,6 +46,6 @@ - + diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings rename to tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs similarity index 100% rename from src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs From 067574e5a611d21d311c4e3aca3f5fcb3f20d9c4 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sat, 11 Oct 2025 22:19:42 +0200 Subject: [PATCH 07/24] Remove accidentally added package Microsoft.CodeAnalysis.NetAnalyzers from BenchmarkDotNet.Annotations --- .../BenchmarkDotNet.Annotations.csproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index 40aa069a2c..c1d3c66209 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -14,12 +14,6 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - From 0416c10ebd1eb46e75d5a547505675fe09b09efc Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sun, 12 Oct 2025 23:28:17 +0200 Subject: [PATCH 08/24] * Benchmark classes annotated with a [GenericTypeArguments] attribute must be non-abstract and generic * Benchmark classes are allowed to be generic if they are either abstract or annotated with at least one [GenericTypeArguments] attribute * Assume that a class can be annotated with more than one [GenericTypeArguments] attribute --- .../AnalyzerHelper.cs | 7 +- .../AnalyzerReleases.Unshipped.md | 2 +- ...nchmarkDotNetAnalyzerResources.Designer.cs | 121 ++++++------ .../BenchmarkDotNetAnalyzerResources.resx | 27 ++- .../BenchmarkRunner/RunAnalyzer.cs | 8 +- .../DiagnosticIds.cs | 6 +- .../General/BenchmarkClassAnalyzer.cs | 143 ++++++++------- .../BenchmarkRunner/RunAnalyzerTests.cs | 20 +- .../General/BenchmarkClassAnalyzerTests.cs | 172 ++++++++++++++---- 9 files changed, 298 insertions(+), 208 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index 9f56c711b8..0b885bf1bd 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -2,17 +2,18 @@ { using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; + using System.Collections.Immutable; internal static class AnalyzerHelper { public static LocalizableResourceString GetResourceString(string name) => new LocalizableResourceString(name, BenchmarkDotNetAnalyzerResources.ResourceManager, typeof(BenchmarkDotNetAnalyzerResources)); - public static INamedTypeSymbol GetBenchmarkAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkAttribute"); + public static INamedTypeSymbol? GetBenchmarkAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkAttribute"); public static bool AttributeListsContainAttribute(string attributeName, Compilation compilation, SyntaxList attributeLists, SemanticModel semanticModel) => AttributeListsContainAttribute(compilation.GetTypeByMetadataName(attributeName), attributeLists, semanticModel); - public static bool AttributeListsContainAttribute(INamedTypeSymbol attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) + public static bool AttributeListsContainAttribute(INamedTypeSymbol? attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) { if (attributeTypeSymbol == null) { @@ -41,7 +42,7 @@ public static bool AttributeListsContainAttribute(INamedTypeSymbol attributeType public static ImmutableArray GetAttributes(string attributeName, Compilation compilation, SyntaxList attributeLists, SemanticModel semanticModel) => GetAttributes(compilation.GetTypeByMetadataName(attributeName), attributeLists, semanticModel); - public static ImmutableArray GetAttributes(INamedTypeSymbol attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) + public static ImmutableArray GetAttributes(INamedTypeSymbol? attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) { var attributesBuilder = ImmutableArray.CreateBuilder(); diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index b8c5dc41b6..6a4133cfd2 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -12,7 +12,7 @@ BDN1003 | Usage | Error | BDN1003_General_BenchmarkClass_ClassMustBePubli BDN1004 | Usage | Error | BDN1004_General_BenchmarkClass_ClassMustBeNonStatic BDN1005 | Usage | Error | BDN1005_General_BenchmarkClass_ClassMustBeNonAbstract BDN1006 | Usage | Error | BDN1006_General_BenchmarkClass_ClassMustBeNonGeneric -BDN1007 | Usage | Error | BDN1007_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters +BDN1007 | Usage | Error | BDN1007_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric BDN1008 | Usage | Error | BDN1008_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount BDN1009 | Usage | Error | BDN1009_General_BenchmarkClass_ClassMustBeUnsealed BDN1010 | Usage | Error | BDN1010_General_BenchmarkClass_OnlyOneMethodCanBeBaseline diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index d75f64d19b..3c1f5c55e3 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -508,60 +508,6 @@ internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMeth } } - /// - /// Looks up a localized string similar to A benchmark class must be non-abstract. - /// - internal static string General_BenchmarkClass_ClassMustBeNonAbstract_Description { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonAbstract_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Benchmark class '{0}' cannot be abstract. - /// - internal static string General_BenchmarkClass_ClassMustBeNonAbstract_MessageFormat { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonAbstract_MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Benchmark classes must be non-abstract. - /// - internal static string General_BenchmarkClass_ClassMustBeNonAbstract_Title { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonAbstract_Title", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A benchmark class not annotated with the [GenericTypeArguments] attribute must be non-generic. - /// - internal static string General_BenchmarkClass_ClassMustBeNonGeneric_Description { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonGeneric_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Benchmark class '{0}' cannot be generic. - /// - internal static string General_BenchmarkClass_ClassMustBeNonGeneric_MessageFormat { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonGeneric_MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Benchmark classes not annotated with the [GenericTypeArguments] attribute must be non-generic. - /// - internal static string General_BenchmarkClass_ClassMustBeNonGeneric_Title { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonGeneric_Title", resourceCulture); - } - } - /// /// Looks up a localized string similar to A benchmark class must be an instance class. /// @@ -646,30 +592,79 @@ internal static string General_BenchmarkClass_ClassMustBeUnsealed_Title { /// /// Looks up a localized string similar to A benchmark class annotated with the [GenericTypeArguments] attribute must be generic, having between one to three type parameters. /// - internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_Description { + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Description { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParamete" + - "rs_Description", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Descri" + + "ption", resourceCulture); } } /// /// Looks up a localized string similar to Benchmark class '{0}' must be generic. /// - internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_MessageFormat { + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_MessageFormat { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParamete" + - "rs_MessageFormat", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Messag" + + "eFormat", resourceCulture); } } /// /// Looks up a localized string similar to Benchmark classes annotated with the [GenericTypeArguments] attribute must be generic. /// - internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_Title { + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A benchmark class annotated with the [GenericTypeArguments] attribute must be non-abstract. + /// + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Description { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_De" + + "scription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark class '{0}' cannot be abstract. + /// + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Me" + + "ssageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes annotated with the [GenericTypeArguments] attribute must be non-abstract. + /// + internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Ti" + + "tle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark class '{0}' cannot be generic unless declared as abstract or annotated with a [GenericTypeArguments] attribute. + /// + internal static string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgum" + + "entsAttribute_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes can only be generic if they're either abstract or annotated with a [GenericTypeArguments] attribute. + /// + internal static string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute_Title { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParamete" + - "rs_Title", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgum" + + "entsAttribute_Title", resourceCulture); } } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index a4caeab400..be52fe0f6e 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -129,29 +129,26 @@ A benchmark class must be an instance class - - A benchmark class not annotated with the [GenericTypeArguments] attribute must be non-generic - - - A benchmark class must be non-abstract + + A benchmark class annotated with the [GenericTypeArguments] attribute must be non-abstract Benchmark class '{0}' cannot be static - + Benchmark class '{0}' cannot be abstract - - Benchmark class '{0}' cannot be generic + + Benchmark class '{0}' cannot be generic unless declared as abstract or annotated with a [GenericTypeArguments] attribute Benchmark classes must be non-static - - Benchmark classes must be non-abstract + + Benchmark classes annotated with the [GenericTypeArguments] attribute must be non-abstract - - Benchmark classes not annotated with the [GenericTypeArguments] attribute must be non-generic + + Benchmark classes can only be generic if they're either abstract or annotated with a [GenericTypeArguments] attribute A benchmark class must be public @@ -180,7 +177,7 @@ The number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class - + A benchmark class annotated with the [GenericTypeArguments] attribute must be generic, having between one to three type parameters @@ -192,7 +189,7 @@ Expected {0} type argument{1} as declared on the benchmark class '{2}', but found {3}. Update the attribute usage or the type parameter list of the class declaration to match. - + Benchmark class '{0}' must be generic @@ -207,7 +204,7 @@ Number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class - + Benchmark classes annotated with the [GenericTypeArguments] attribute must be generic diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index 911628840a..f77600a565 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -40,17 +40,17 @@ public override void Initialize(AnalysisContext analysisContext) private static void Analyze(SyntaxNodeAnalysisContext context) { - if (!(context.Node is InvocationExpressionSyntax invocationExpression)) + if (context.Node is not InvocationExpressionSyntax invocationExpression) { return; } - if (!(invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression)) + if (invocationExpression.Expression is not MemberAccessExpressionSyntax memberAccessExpression) { return; } - if (!(memberAccessExpression.Expression is IdentifierNameSyntax typeIdentifier)) + if (memberAccessExpression.Expression is not IdentifierNameSyntax typeIdentifier) { return; } @@ -61,7 +61,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - if (!(memberAccessExpression.Name is GenericNameSyntax genericMethod)) + if (memberAccessExpression.Name is not GenericNameSyntax genericMethod) { return; } diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index 4327845782..8ed7c4ce52 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -7,9 +7,9 @@ public static class DiagnosticIds public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1002"; public const string General_BenchmarkClass_ClassMustBePublic = "BDN1003"; public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1004"; - public const string General_BenchmarkClass_ClassMustBeNonAbstract = "BDN1005"; - public const string General_BenchmarkClass_ClassMustBeNonGeneric = "BDN1006"; - public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters = "BDN1007"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract = "BDN1005"; + public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1006"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1007"; public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1008"; public const string General_BenchmarkClass_ClassMustBeUnsealed = "BDN1009"; public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1010"; diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 75471585e3..677d954cb7 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -43,29 +43,28 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Description))); - internal static readonly DiagnosticDescriptor ClassMustBeNonAbstractRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeNonAbstract, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonAbstract_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonAbstract_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonAbstract_Description))); - - internal static readonly DiagnosticDescriptor ClassMustBeNonGenericRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeNonGeneric, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonGeneric_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonGeneric_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonGeneric_Description))); - - internal static readonly DiagnosticDescriptor ClassWithGenericTypeArgumentsAttributeMustHaveTypeParametersRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters_Description))); + internal static readonly DiagnosticDescriptor ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Description))); + + internal static readonly DiagnosticDescriptor GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + internal static readonly DiagnosticDescriptor ClassWithGenericTypeArgumentsAttributeMustBeGenericRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Description))); internal static readonly DiagnosticDescriptor GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Title)), @@ -90,18 +89,19 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - MethodMustBePublicRule, - MethodMustBeNonGenericRule, - ClassMustBePublicRule, - ClassMustBeNonStaticRule, - ClassMustBeNonAbstractRule, - ClassMustBeNonGenericRule, - ClassWithGenericTypeArgumentsAttributeMustHaveTypeParametersRule, - GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, - ClassMustBeUnsealedRule, - OnlyOneMethodCanBeBaselineRule - ); + public override ImmutableArray SupportedDiagnostics => + [ + MethodMustBePublicRule, + MethodMustBeNonGenericRule, + ClassMustBePublicRule, + ClassMustBeNonStaticRule, + ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, + GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, + ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, + GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, + ClassMustBeUnsealedRule, + OnlyOneMethodCanBeBaselineRule + ]; public override void Initialize(AnalysisContext analysisContext) { @@ -124,11 +124,17 @@ public override void Initialize(AnalysisContext analysisContext) private static void Analyze(SyntaxNodeAnalysisContext context) { - if (!(context.Node is ClassDeclarationSyntax classDeclarationSyntax)) + if (context.Node is not ClassDeclarationSyntax classDeclarationSyntax) { return; } + var genericTypeArgumentsAttributes = AnalyzerHelper.GetAttributes("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, classDeclarationSyntax.AttributeLists, context.SemanticModel); + if (genericTypeArgumentsAttributes.Length > 0 && classDeclarationSyntax.TypeParameterList == null) + { + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + var benchmarkAttributeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); if (benchmarkAttributeSymbol == null) { @@ -155,38 +161,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - var genericTypeArgumentsAttributes = AnalyzerHelper.GetAttributes("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, classDeclarationSyntax.AttributeLists, context.SemanticModel); - if (genericTypeArgumentsAttributes.Length == 0) - { - if (classDeclarationSyntax.TypeParameterList != null) - { - context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonGenericRule, classDeclarationSyntax.TypeParameterList.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } - } - else if (genericTypeArgumentsAttributes.Length == 1) - { - if (classDeclarationSyntax.TypeParameterList == null) - { - context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustHaveTypeParametersRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } - else if (classDeclarationSyntax.TypeParameterList.Parameters.Count > 0) - { - var genericTypeArgumentsAttribute = genericTypeArgumentsAttributes[0]; - if (genericTypeArgumentsAttribute.ArgumentList != null && genericTypeArgumentsAttribute.ArgumentList.Arguments.Count > 0) - { - if (genericTypeArgumentsAttribute.ArgumentList.Arguments.Count != classDeclarationSyntax.TypeParameterList.Parameters.Count) - { - context.ReportDiagnostic(Diagnostic.Create(GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, Location.Create(context.FilterTree, genericTypeArgumentsAttribute.ArgumentList.Arguments.Span), - classDeclarationSyntax.TypeParameterList.Parameters.Count, - classDeclarationSyntax.TypeParameterList.Parameters.Count == 1 ? "" : "s", - classDeclarationSyntax.Identifier.ToString(), - genericTypeArgumentsAttribute.ArgumentList.Arguments.Count)); - } - } - } - } - - var classIsPublic = false; var classStaticModifier = null as SyntaxToken?; var classAbstractModifier = null as SyntaxToken?; @@ -212,14 +186,43 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } } + if (genericTypeArgumentsAttributes.Length == 0) + { + if (classDeclarationSyntax.TypeParameterList != null && !classAbstractModifier.HasValue) + { + context.ReportDiagnostic(Diagnostic.Create(GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, classDeclarationSyntax.TypeParameterList.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + } + else + { + if (classDeclarationSyntax.TypeParameterList is { Parameters.Count: > 0 }) + { + foreach (var genericTypeArgumentsAttribute in genericTypeArgumentsAttributes) + { + if (genericTypeArgumentsAttribute.ArgumentList is { Arguments.Count: > 0 }) + { + if (genericTypeArgumentsAttribute.ArgumentList.Arguments.Count != classDeclarationSyntax.TypeParameterList.Parameters.Count) + { + context.ReportDiagnostic(Diagnostic.Create(GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, Location.Create(context.FilterTree, genericTypeArgumentsAttribute.ArgumentList.Arguments.Span), + classDeclarationSyntax.TypeParameterList.Parameters.Count, + classDeclarationSyntax.TypeParameterList.Parameters.Count == 1 ? "" : "s", + classDeclarationSyntax.Identifier.ToString(), + genericTypeArgumentsAttribute.ArgumentList.Arguments.Count)); + } + } + } + + } + } + if (!classIsPublic) { context.ReportDiagnostic(Diagnostic.Create(ClassMustBePublicRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); } - if (classAbstractModifier.HasValue) + if (classAbstractModifier.HasValue && genericTypeArgumentsAttributes.Length > 0) { - context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); } if (classStaticModifier.HasValue) diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index b7df860b61..659d6b3fc5 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -19,16 +19,16 @@ public async Task Invoking_with_type_argument_class_having_only_one_and_public_m { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Running; - - public class Program - { - public static void Main(string[] args) { - BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); - } - } - """; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); + } + } + """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 56203d2835..74458e7652 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -306,12 +306,12 @@ public static void BenchmarkMethod() } } - public class BenchmarkClassMustBeNonAbstract : AnalyzerTestFixture + public class ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract : AnalyzerTestFixture { - public BenchmarkClassMustBeNonAbstract() : base(BenchmarkClassAnalyzer.ClassMustBeNonAbstractRule) { } + public ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule) { } [Fact] - public async Task Nonabstract_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + public async Task Nonabstract_class_not_annotated_with_any_generictypearguments_attributes_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { const string testCode = /* lang=c#-test */ """ using BenchmarkDotNet.Attributes; @@ -328,8 +328,29 @@ public void NonBenchmarkMethod() { } + } + """; + + TestCode = testCode; + + await RunAsync(); + } + + [Fact] + public async Task Abstract_class_not_annotated_with_any_generictypearguments_attributes_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public abstract class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { + + } - private static void NonBenchmarkMethod2() + public void NonBenchmarkMethod() { } @@ -341,23 +362,28 @@ private static void NonBenchmarkMethod2() await RunAsync(); } - [Fact] - public async Task Abstract_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + [Theory] + [InlineData(1)] + [InlineData(2)] + public async Task Abstract_class_annotated_with_at_least_one_generictypearguments_attribute_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int attributeUsageCount) { const string benchmarkClassName = "BenchmarkClass"; - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public {|#0:abstract|} class {{benchmarkClassName}} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; + var attributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", attributeUsageCount); + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + {{string.Join("\n", attributeUsages)}} + public {|#0:abstract|} class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; AddDefaultExpectedDiagnostic(benchmarkClassName); @@ -366,9 +392,9 @@ public void BenchmarkMethod() } } - public class BenchmarkClassMustBeNonGeneric : AnalyzerTestFixture + public class GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute : AnalyzerTestFixture { - public BenchmarkClassMustBeNonGeneric() : base(BenchmarkClassAnalyzer.ClassMustBeNonGenericRule) { } + public GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute() : base(BenchmarkClassAnalyzer.GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule) { } [Fact] public async Task Nongeneric_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() @@ -396,17 +422,18 @@ public void NonBenchmarkMethod() await RunAsync(); } - [Theory] - [MemberData(nameof(TypeParametersListLength))] - public async Task Generic_class_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) + [Theory, CombinatorialData] + public async Task Generic_class_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic([CombinatorialRange(1, 3)] int attributeUsageCount, + [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength) { - const string benchmarkClassName = "BenchmarkClass"; + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); + var attributeUsages = string.Join("\n", Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", attributeUsageCount)); var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}})] - public class {{benchmarkClassName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} + {{attributeUsages}} + public class BenchmarkClass{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} { [Benchmark] public void BenchmarkMethod() @@ -423,7 +450,29 @@ public void BenchmarkMethod() [Theory] [MemberData(nameof(TypeParametersListLength))] - public async Task Generic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) + public async Task Abstract_generic_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(TypeParametersListLength))] + public async Task Nonabstract_generic_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) { const string benchmarkClassName = "BenchmarkClass"; @@ -446,6 +495,8 @@ public void BenchmarkMethod() await RunAsync(); } + public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; @@ -453,12 +504,12 @@ public void BenchmarkMethod() private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; } - public class ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters : AnalyzerTestFixture + public class ClassWithGenericTypeArgumentsAttributeMustBeGeneric : AnalyzerTestFixture { - public ClassWithGenericTypeArgumentsAttributeMustHaveTypeParameters() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustHaveTypeParametersRule) { } + public ClassWithGenericTypeArgumentsAttributeMustBeGeneric() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeGenericRule) { } [Fact] - public async Task Nongeneric_class_not_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + public async Task Nongeneric_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { const string testCode = /* lang=c#-test */ """ using BenchmarkDotNet.Attributes; @@ -480,12 +531,12 @@ public void BenchmarkMethod() [Theory] [MemberData(nameof(TypeParametersListLength))] - public async Task Generic_class_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) + public async Task Generic_class_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}})] + [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength))}})] public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> { [Benchmark] @@ -502,7 +553,7 @@ public void BenchmarkMethod() } [Fact] - public async Task Class_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_having_no_type_parameters_should_trigger_diagnostic() + public async Task Class_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_having_no_type_parameters_should_trigger_diagnostic() { const string benchmarkClassName = "BenchmarkClass"; @@ -526,6 +577,47 @@ public void BenchmarkMethod() await RunAsync(); } + [Theory, CombinatorialData] + public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_inheriting_from_an_abstract_generic_class_should_trigger_diagnostic([CombinatorialRange(1, 3)] int attributeUsageCount, + [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength) + { + const string benchmarkClassName = "BenchmarkClass"; + + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); + var attributeUsages = string.Join("\n", Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", attributeUsageCount)); + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + {{attributeUsages}} + public class {|#0:{{benchmarkClassName}}|} : BenchmarkClassBase<{{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}}> + { + } + """; + + var benchmarkBaseClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkBaseClassDocument); + + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + + public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; @@ -565,7 +657,7 @@ public async Task Generic_class_annotated_with_the_generictypearguments_attribut var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}})] + [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength))}})] public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> { [Benchmark] @@ -615,9 +707,9 @@ public void BenchmarkMethod() private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; } - public class BenchmarkClassMustBeUnsealed : AnalyzerTestFixture + public class ClassMustBeUnsealed : AnalyzerTestFixture { - public BenchmarkClassMustBeUnsealed() : base(BenchmarkClassAnalyzer.ClassMustBeUnsealedRule) { } + public ClassMustBeUnsealed() : base(BenchmarkClassAnalyzer.ClassMustBeUnsealedRule) { } [Fact] public async Task Unsealed_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() @@ -793,12 +885,14 @@ public void BaselineBenchmarkMethod3() } } - public static TheoryData TypeParametersListLengthTheoryData => new(Enumerable.Range(1, TypeParametersTheoryData.Count)); + public static TheoryData TypeParametersListLengthTheoryData => new(TypeParametersListLengthEnumerable); + + public static IEnumerable TypeParametersListLengthEnumerable => Enumerable.Range(1, TypeParametersTheoryData.Count); - private static ReadOnlyCollection TypeParametersTheoryData => Enumerable.Range(0, 3) + private static ReadOnlyCollection TypeParametersTheoryData => Enumerable.Range(1, 3) .Select(i => $"TParameter{i}") .ToList() .AsReadOnly(); - private static ReadOnlyCollection GenericTypeArgumentsTheoryData => new List { "typeof(int)", "typeof(string)", "typeof(bool)" }.AsReadOnly(); + private static ReadOnlyCollection GenericTypeArgumentsTheoryData => new List { "int", "string", "bool" }.AsReadOnly(); } } From 9bdda82a20ee1f93ebd0542381173e208ec67d83 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 13 Oct 2025 11:19:16 +0200 Subject: [PATCH 09/24] * Change diagnostic ID increment ordering * Add a rule that the benchmark class referenced in the type argument of the BenchmarkRunner.Run method cannot be abstract --- .../AnalyzerReleases.Unshipped.md | 55 +++++++------- ...nchmarkDotNetAnalyzerResources.Designer.cs | 37 +++++++-- .../BenchmarkDotNetAnalyzerResources.resx | 19 +++-- .../BenchmarkRunner/RunAnalyzer.cs | 27 +++++-- .../DiagnosticIds.cs | 55 +++++++------- .../BenchmarkRunner/RunAnalyzerTests.cs | 76 ++++++++++++++----- 6 files changed, 182 insertions(+), 87 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index 6a4133cfd2..4ec76b3810 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -6,30 +6,31 @@ Rule ID | Category | Severity | Notes ---------|----------|----------|-------------------- BDN1000 | Usage | Error | BDN1000_BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods -BDN1001 | Usage | Error | BDN1001_General_BenchmarkClass_MethodMustBePublic -BDN1002 | Usage | Error | BDN1002_General_BenchmarkClass_MethodMustBeNonGeneric -BDN1003 | Usage | Error | BDN1003_General_BenchmarkClass_ClassMustBePublic -BDN1004 | Usage | Error | BDN1004_General_BenchmarkClass_ClassMustBeNonStatic -BDN1005 | Usage | Error | BDN1005_General_BenchmarkClass_ClassMustBeNonAbstract -BDN1006 | Usage | Error | BDN1006_General_BenchmarkClass_ClassMustBeNonGeneric -BDN1007 | Usage | Error | BDN1007_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric -BDN1008 | Usage | Error | BDN1008_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount -BDN1009 | Usage | Error | BDN1009_General_BenchmarkClass_ClassMustBeUnsealed -BDN1010 | Usage | Error | BDN1010_General_BenchmarkClass_OnlyOneMethodCanBeBaseline -BDN1011 | Usage | Error | BDN1011_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField -BDN1012 | Usage | Error | BDN1012_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty -BDN1013 | Usage | Error | BDN1013_Attributes_GeneralParameterAttributes_FieldMustBePublic -BDN1014 | Usage | Error | BDN1014_Attributes_GeneralParameterAttributes_PropertyMustBePublic -BDN1015 | Usage | Error | BDN1015_Attributes_GeneralParameterAttributes_NotValidOnReadonlyField -BDN1016 | Usage | Error | BDN1016_Attributes_GeneralParameterAttributes_NotValidOnConstantField -BDN1017 | Usage | Error | BDN1017_Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly -BDN1018 | Usage | Error | BDN1018_Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter -BDN1019 | Usage | Error | BDN1019_Attributes_ParamsAttribute_MustHaveValues -BDN1020 | Usage | Error | BDN1020_Attributes_ParamsAttribute_UnexpectedValueType -BDN1021 | Usage | Warning | BDN1021_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute -BDN1022 | Usage | Error | BDN1022_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType -BDN1023 | Usage | Error | BDN1023_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool -BDN1024 | Usage | Error | BDN1024_Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute -BDN1025 | Usage | Error | BDN1025_Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters -BDN1026 | Usage | Error | BDN1026_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount -BDN1027 | Usage | Error | BDN1027_Attributes_ArgumentsAttribute_MustHaveMatchingValueType +BDN1001 | Usage | Error | BDN1001_BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract +BDN1100 | Usage | Error | BDN1100_General_BenchmarkClass_MethodMustBePublic +BDN1101 | Usage | Error | BDN1101_General_BenchmarkClass_MethodMustBeNonGeneric +BDN1102 | Usage | Error | BDN1102_General_BenchmarkClass_ClassMustBePublic +BDN1103 | Usage | Error | BDN1103_General_BenchmarkClass_ClassMustBeNonStatic +BDN1104 | Usage | Error | BDN1104_General_BenchmarkClass_ClassMustBeNonAbstract +BDN1105 | Usage | Error | BDN1105_General_BenchmarkClass_ClassMustBeNonGeneric +BDN1106 | Usage | Error | BDN1106_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric +BDN1107 | Usage | Error | BDN1107_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount +BDN1108 | Usage | Error | BDN1108_General_BenchmarkClass_ClassMustBeUnsealed +BDN1109 | Usage | Error | BDN1109_General_BenchmarkClass_OnlyOneMethodCanBeBaseline +BDN1200 | Usage | Error | BDN1200_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField +BDN1201 | Usage | Error | BDN1201_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty +BDN1202 | Usage | Error | BDN1202_Attributes_GeneralParameterAttributes_FieldMustBePublic +BDN1203 | Usage | Error | BDN1203_Attributes_GeneralParameterAttributes_PropertyMustBePublic +BDN1204 | Usage | Error | BDN1204_Attributes_GeneralParameterAttributes_NotValidOnReadonlyField +BDN1205 | Usage | Error | BDN1205_Attributes_GeneralParameterAttributes_NotValidOnConstantField +BDN1206 | Usage | Error | BDN1206_Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly +BDN1207 | Usage | Error | BDN1207_Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter +BDN1300 | Usage | Error | BDN1300_Attributes_ParamsAttribute_MustHaveValues +BDN1301 | Usage | Error | BDN1301_Attributes_ParamsAttribute_UnexpectedValueType +BDN1302 | Usage | Warning | BDN1302_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute +BDN1303 | Usage | Error | BDN1303_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType +BDN1304 | Usage | Error | BDN1304_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool +BDN1400 | Usage | Error | BDN1400_Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute +BDN1401 | Usage | Error | BDN1401_Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters +BDN1402 | Usage | Error | BDN1402_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount +BDN1403 | Usage | Error | BDN1403_Attributes_ArgumentsAttribute_MustHaveMatchingValueType diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index 3c1f5c55e3..5a5d04b631 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -508,6 +508,33 @@ internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMeth } } + /// + /// Looks up a localized string similar to A benchmark class referenced in the BenchmarkRunner.Run method must be non-abstract. + /// + internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Description { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Referenced benchmark class '{0}' cannot be abstract. + /// + internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_MessageFormat { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes must be non-abstract. + /// + internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Title { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to A benchmark class must be an instance class. /// @@ -590,7 +617,7 @@ internal static string General_BenchmarkClass_ClassMustBeUnsealed_Title { } /// - /// Looks up a localized string similar to A benchmark class annotated with the [GenericTypeArguments] attribute must be generic, having between one to three type parameters. + /// Looks up a localized string similar to A benchmark class annotated with a [GenericTypeArguments] attribute must be generic, having between one to three type parameters. /// internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Description { get { @@ -610,7 +637,7 @@ internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttri } /// - /// Looks up a localized string similar to Benchmark classes annotated with the [GenericTypeArguments] attribute must be generic. + /// Looks up a localized string similar to Benchmark classes annotated with a [GenericTypeArguments] attribute must be generic. /// internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Title { get { @@ -619,7 +646,7 @@ internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttri } /// - /// Looks up a localized string similar to A benchmark class annotated with the [GenericTypeArguments] attribute must be non-abstract. + /// Looks up a localized string similar to A benchmark class annotated with a [GenericTypeArguments] attribute must be non-abstract. /// internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Description { get { @@ -669,7 +696,7 @@ internal static string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnota } /// - /// Looks up a localized string similar to The number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class. + /// Looks up a localized string similar to The number of type arguments passed to a [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class. /// internal static string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Description { get { @@ -689,7 +716,7 @@ internal static string General_BenchmarkClass_GenericTypeArgumentsAttributeMustH } /// - /// Looks up a localized string similar to Number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class. + /// Looks up a localized string similar to Number of type arguments passed to a [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class. /// internal static string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Title { get { diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index be52fe0f6e..7184904c93 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -123,14 +123,20 @@ Intended benchmark class '{0}' has no method(s) annotated with the [Benchmark] attribute + + Referenced benchmark class '{0}' cannot be abstract + Benchmark class has no annotated method(s) + + Benchmark classes must be non-abstract + A benchmark class must be an instance class - A benchmark class annotated with the [GenericTypeArguments] attribute must be non-abstract + A benchmark class annotated with a [GenericTypeArguments] attribute must be non-abstract Benchmark class '{0}' cannot be static @@ -175,10 +181,10 @@ A method annotated with the [Benchmark] attribute must be non-generic - The number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class + The number of type arguments passed to a [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class - A benchmark class annotated with the [GenericTypeArguments] attribute must be generic, having between one to three type parameters + A benchmark class annotated with a [GenericTypeArguments] attribute must be generic, having between one to three type parameters The benchmark method '{0}' must be public @@ -202,10 +208,10 @@ Benchmark methods must be non-generic - Number of type arguments passed to the [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class + Number of type arguments passed to a [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class - Benchmark classes annotated with the [GenericTypeArguments] attribute must be generic + Benchmark classes annotated with a [GenericTypeArguments] attribute must be generic Only one benchmark method can be baseline @@ -349,4 +355,7 @@ Either add the [Arguments] attribute(s) or remove the parameters. The [Arguments] attribute can only be used on methods annotated with the [Benchmark] attribute + + A benchmark class referenced in the BenchmarkRunner.Run method must be non-abstract + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index f77600a565..240722d6be 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -18,7 +18,19 @@ public class RunAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Description))); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(TypeArgumentClassMissingBenchmarkMethodsRule); + internal static readonly DiagnosticDescriptor TypeArgumentClassMustBeNonAbstractRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Description))); + + public override ImmutableArray SupportedDiagnostics => + [ + TypeArgumentClassMissingBenchmarkMethodsRule, + TypeArgumentClassMustBeNonAbstractRule + ]; public override void Initialize(AnalysisContext analysisContext) { @@ -85,14 +97,19 @@ private static void Analyze(SyntaxNodeAnalysisContext context) var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); if (benchmarkAttributeTypeSymbol == null) { - ReportDiagnostic(); + ReportDiagnostic(TypeArgumentClassMissingBenchmarkMethodsRule); return; } if (!HasBenchmarkAttribute()) { - ReportDiagnostic(); + ReportDiagnostic(TypeArgumentClassMissingBenchmarkMethodsRule); + } + + if (benchmarkClassTypeSymbol.IsAbstract) + { + ReportDiagnostic(TypeArgumentClassMustBeNonAbstractRule); } return; @@ -119,9 +136,9 @@ bool HasBenchmarkAttribute() return false; } - void ReportDiagnostic() + void ReportDiagnostic(DiagnosticDescriptor diagnosticDescriptor) { - context.ReportDiagnostic(Diagnostic.Create(TypeArgumentClassMissingBenchmarkMethodsRule, Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span), benchmarkClassTypeSymbol.ToString())); + context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span), benchmarkClassTypeSymbol.ToString())); } } } diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index 8ed7c4ce52..d7eca1bc4c 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -3,32 +3,33 @@ public static class DiagnosticIds { public const string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods = "BDN1000"; - public const string General_BenchmarkClass_MethodMustBePublic = "BDN1001"; - public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1002"; - public const string General_BenchmarkClass_ClassMustBePublic = "BDN1003"; - public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1004"; - public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract = "BDN1005"; - public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1006"; - public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1007"; - public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1008"; - public const string General_BenchmarkClass_ClassMustBeUnsealed = "BDN1009"; - public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1010"; - public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField = "BDN1011"; - public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty = "BDN1012"; - public const string Attributes_GeneralParameterAttributes_FieldMustBePublic = "BDN1013"; - public const string Attributes_GeneralParameterAttributes_PropertyMustBePublic = "BDN1014"; - public const string Attributes_GeneralParameterAttributes_NotValidOnReadonlyField = "BDN1015"; - public const string Attributes_GeneralParameterAttributes_NotValidOnConstantField = "BDN1016"; - public const string Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly = "BDN1017"; - public const string Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter = "BDN1018"; - public const string Attributes_ParamsAttribute_MustHaveValues = "BDN1019"; - public const string Attributes_ParamsAttribute_UnexpectedValueType = "BDN1020"; - public const string Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute = "BDN1021"; - public const string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType = "BDN1022"; - public const string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool = "BDN1023"; - public const string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute = "BDN1024"; - public const string Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters = "BDN1025"; - public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1026"; - public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1027"; + public const string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract = "BDN1001"; + public const string General_BenchmarkClass_MethodMustBePublic = "BDN1100"; + public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1101"; + public const string General_BenchmarkClass_ClassMustBePublic = "BDN1102"; + public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1103"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract = "BDN1104"; + public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1105"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1106"; + public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1107"; + public const string General_BenchmarkClass_ClassMustBeUnsealed = "BDN1108"; + public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1109"; + public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField = "BDN1200"; + public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty = "BDN1201"; + public const string Attributes_GeneralParameterAttributes_FieldMustBePublic = "BDN1202"; + public const string Attributes_GeneralParameterAttributes_PropertyMustBePublic = "BDN1203"; + public const string Attributes_GeneralParameterAttributes_NotValidOnReadonlyField = "BDN1204"; + public const string Attributes_GeneralParameterAttributes_NotValidOnConstantField = "BDN1205"; + public const string Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly = "BDN1206"; + public const string Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter = "BDN1207"; + public const string Attributes_ParamsAttribute_MustHaveValues = "BDN1300"; + public const string Attributes_ParamsAttribute_UnexpectedValueType = "BDN1301"; + public const string Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute = "BDN1302"; + public const string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType = "BDN1303"; + public const string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool = "BDN1304"; + public const string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute = "BDN1400"; + public const string Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters = "BDN1401"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1402"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1403"; } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index 659d6b3fc5..877e4c6e62 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -10,12 +10,50 @@ public class RunAnalyzerTests { + public class General : AnalyzerTestFixture + { + [Fact] + public async Task Invoking_with_a_nonabstract_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() + { + const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); + } + } + """; + + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{classWithOneBenchmarkMethodName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + + await RunAsync(); + } + } + public class TypeArgumentClassMissingBenchmarkMethods : AnalyzerTestFixture { public TypeArgumentClassMissingBenchmarkMethods() : base(RunAnalyzer.TypeArgumentClassMissingBenchmarkMethodsRule) { } [Fact] - public async Task Invoking_with_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() + public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; @@ -40,6 +78,16 @@ public void BenchmarkMethod() { } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } } """; @@ -80,9 +128,14 @@ public void BenchmarkMethod() await RunAsync(); } + } + + public class TypeArgumentClassMustBeNonAbstract : AnalyzerTestFixture + { + public TypeArgumentClassMustBeNonAbstract() : base(RunAnalyzer.TypeArgumentClassMustBeNonAbstractRule) { } [Fact] - public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() + public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagnostic() { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; @@ -92,36 +145,23 @@ public async Task Invoking_with_type_argument_class_having_at_least_one_public_m public class Program { public static void Main(string[] args) { - BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); + BenchmarkRunner.Run<{|#0:{{classWithOneBenchmarkMethodName}}|}>(); } } """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class {{classWithOneBenchmarkMethodName}} + public abstract class {{classWithOneBenchmarkMethodName}} { - [Benchmark] public void BenchmarkMethod() { } - - public void BenchmarkMethod2() - { - - } - - private void BenchmarkMethod3() - { - - } } """; - TestCode = testCode; AddSource(benchmarkClassDocument); + AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); await RunAsync(); } From cbf5d8818ef9a9f88ce57853caec5bf2fd4280cf Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:11:09 +0200 Subject: [PATCH 10/24] When determining whether a class has any benchmark methods, iterate through its ancestors --- ...nchmarkDotNetAnalyzerResources.Designer.cs | 6 +- .../BenchmarkDotNetAnalyzerResources.resx | 6 +- .../BenchmarkRunner/RunAnalyzer.cs | 19 +++-- .../BenchmarkRunner/RunAnalyzerTests.cs | 72 +++++++++++++++++++ 4 files changed, 91 insertions(+), 12 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index 5a5d04b631..db67b499b8 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -482,7 +482,7 @@ internal static string Attributes_ParamsAttribute_UnnecessarySingleValuePassedTo } /// - /// Looks up a localized string similar to The referenced benchmark class must have at least one method annotated with the [Benchmark] attribute. + /// Looks up a localized string similar to The referenced benchmark class (or any of its inherited classes) must have at least one method annotated with the [Benchmark] attribute. /// internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Description { get { @@ -491,7 +491,7 @@ internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMeth } /// - /// Looks up a localized string similar to Intended benchmark class '{0}' has no method(s) annotated with the [Benchmark] attribute. + /// Looks up a localized string similar to Intended benchmark class '{0}' (or any of its ancestors) has no method(s) annotated with the [Benchmark] attribute. /// internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_MessageFormat { get { @@ -500,7 +500,7 @@ internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMeth } /// - /// Looks up a localized string similar to Benchmark class has no annotated method(s). + /// Looks up a localized string similar to Benchmark class (or any of its ancestors) has no annotated method(s). /// internal static string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Title { get { diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index 7184904c93..5933d50040 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -118,16 +118,16 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - The referenced benchmark class must have at least one method annotated with the [Benchmark] attribute + The referenced benchmark class (or any of its inherited classes) must have at least one method annotated with the [Benchmark] attribute - Intended benchmark class '{0}' has no method(s) annotated with the [Benchmark] attribute + Intended benchmark class '{0}' (or any of its ancestors) has no method(s) annotated with the [Benchmark] attribute Referenced benchmark class '{0}' cannot be abstract - Benchmark class has no annotated method(s) + Benchmark class (or any of its ancestors) has no annotated method(s) Benchmark classes must be non-abstract diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index 240722d6be..dd9a9043a1 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -116,21 +116,28 @@ private static void Analyze(SyntaxNodeAnalysisContext context) bool HasBenchmarkAttribute() { - foreach (var member in benchmarkClassTypeSymbol.GetMembers()) + var baseType = benchmarkClassTypeSymbol; + + while (baseType != null && baseType.SpecialType != SpecialType.System_Object) { - if (member is IMethodSymbol) + foreach (var member in baseType.GetMembers()) { - foreach (var attributeData in member.GetAttributes()) + if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary }) { - if (attributeData.AttributeClass != null) + foreach (var attributeData in member.GetAttributes()) { - if (attributeData.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) + if (attributeData.AttributeClass != null) { - return true; + if (attributeData.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return true; + } } } } } + + baseType = baseType.BaseType; } return false; diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index 877e4c6e62..904a241996 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -97,6 +97,78 @@ private void BenchmarkMethod3() await RunAsync(); } + [Theory, CombinatorialData] + public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic([CombinatorialValues("", "abstract ")] string abstractModifier) + { + const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; + + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); + } + } + """; + + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1 + { + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; + + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + + await RunAsync(); + } + [Fact] public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_should_trigger_diagnostic() { From 957fc560a2a6d2464e23f9bb142b9fe8f6da69df Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:21:53 +0200 Subject: [PATCH 11/24] Move "Benchmark class cannot be sealed" to Run analyzer --- .../AnalyzerReleases.Unshipped.md | 4 +- ...nchmarkDotNetAnalyzerResources.Designer.cs | 54 +++++++++--------- .../BenchmarkDotNetAnalyzerResources.resx | 10 ++-- .../BenchmarkRunner/RunAnalyzer.cs | 16 +++++- .../DiagnosticIds.cs | 4 +- .../General/BenchmarkClassAnalyzer.cs | 19 ------- .../BenchmarkRunner/RunAnalyzerTests.cs | 45 +++++++++++++-- .../General/BenchmarkClassAnalyzerTests.cs | 55 ------------------- 8 files changed, 92 insertions(+), 115 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index 4ec76b3810..9f4a3a102d 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -7,6 +7,7 @@ Rule ID | Category | Severity | Notes ---------|----------|----------|-------------------- BDN1000 | Usage | Error | BDN1000_BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods BDN1001 | Usage | Error | BDN1001_BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract +BDN1002 | Usage | Error | BDN1002_BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed BDN1100 | Usage | Error | BDN1100_General_BenchmarkClass_MethodMustBePublic BDN1101 | Usage | Error | BDN1101_General_BenchmarkClass_MethodMustBeNonGeneric BDN1102 | Usage | Error | BDN1102_General_BenchmarkClass_ClassMustBePublic @@ -15,8 +16,7 @@ BDN1104 | Usage | Error | BDN1104_General_BenchmarkClass_ClassMustBeNonAb BDN1105 | Usage | Error | BDN1105_General_BenchmarkClass_ClassMustBeNonGeneric BDN1106 | Usage | Error | BDN1106_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric BDN1107 | Usage | Error | BDN1107_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount -BDN1108 | Usage | Error | BDN1108_General_BenchmarkClass_ClassMustBeUnsealed -BDN1109 | Usage | Error | BDN1109_General_BenchmarkClass_OnlyOneMethodCanBeBaseline +BDN1108 | Usage | Error | BDN1108_General_BenchmarkClass_OnlyOneMethodCanBeBaseline BDN1200 | Usage | Error | BDN1200_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField BDN1201 | Usage | Error | BDN1201_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty BDN1202 | Usage | Error | BDN1202_Attributes_GeneralParameterAttributes_FieldMustBePublic diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index db67b499b8..7a05fea0e2 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -536,83 +536,83 @@ internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Ti } /// - /// Looks up a localized string similar to A benchmark class must be an instance class. + /// Looks up a localized string similar to A benchmark class referenced in the BenchmarkRunner.Run method must be unsealed. /// - internal static string General_BenchmarkClass_ClassMustBeNonStatic_Description { + internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Description { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_Description", resourceCulture); + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Description", resourceCulture); } } /// - /// Looks up a localized string similar to Benchmark class '{0}' cannot be static. + /// Looks up a localized string similar to Referenced benchmark class '{0}' cannot be sealed. /// - internal static string General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat { + internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_MessageFormat { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat", resourceCulture); + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Benchmark classes must be non-static. + /// Looks up a localized string similar to Benchmark classes must be unsealed. /// - internal static string General_BenchmarkClass_ClassMustBeNonStatic_Title { + internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Title { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_Title", resourceCulture); + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Title", resourceCulture); } } /// - /// Looks up a localized string similar to A benchmark class must be public. + /// Looks up a localized string similar to A benchmark class must be an instance class. /// - internal static string General_BenchmarkClass_ClassMustBePublic_Description { + internal static string General_BenchmarkClass_ClassMustBeNonStatic_Description { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_Description", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_Description", resourceCulture); } } /// - /// Looks up a localized string similar to The benchmark class '{0}' must be public. + /// Looks up a localized string similar to Benchmark class '{0}' cannot be static. /// - internal static string General_BenchmarkClass_ClassMustBePublic_MessageFormat { + internal static string General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_MessageFormat", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Benchmark classes must be public. + /// Looks up a localized string similar to Benchmark classes must be non-static. /// - internal static string General_BenchmarkClass_ClassMustBePublic_Title { + internal static string General_BenchmarkClass_ClassMustBeNonStatic_Title { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_Title", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeNonStatic_Title", resourceCulture); } } /// - /// Looks up a localized string similar to A benchmark class bust be unsealed. + /// Looks up a localized string similar to A benchmark class must be public. /// - internal static string General_BenchmarkClass_ClassMustBeUnsealed_Description { + internal static string General_BenchmarkClass_ClassMustBePublic_Description { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeUnsealed_Description", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_Description", resourceCulture); } } /// - /// Looks up a localized string similar to Benchmark class '{0}' cannot be sealed. + /// Looks up a localized string similar to The benchmark class '{0}' must be public. /// - internal static string General_BenchmarkClass_ClassMustBeUnsealed_MessageFormat { + internal static string General_BenchmarkClass_ClassMustBePublic_MessageFormat { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeUnsealed_MessageFormat", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Benchmark classes must be unsealed. + /// Looks up a localized string similar to Benchmark classes must be public. /// - internal static string General_BenchmarkClass_ClassMustBeUnsealed_Title { + internal static string General_BenchmarkClass_ClassMustBePublic_Title { get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBeUnsealed_Title", resourceCulture); + return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_Title", resourceCulture); } } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index 5933d50040..91856acca2 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -165,13 +165,13 @@ Benchmark classes must be public - - A benchmark class bust be unsealed + + A benchmark class referenced in the BenchmarkRunner.Run method must be unsealed - - Benchmark class '{0}' cannot be sealed + + Referenced benchmark class '{0}' cannot be sealed - + Benchmark classes must be unsealed diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index dd9a9043a1..4d3c5f3bf0 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -26,10 +26,19 @@ public class RunAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Description))); + internal static readonly DiagnosticDescriptor TypeArgumentClassMustBeUnsealedRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Description))); + public override ImmutableArray SupportedDiagnostics => [ TypeArgumentClassMissingBenchmarkMethodsRule, - TypeArgumentClassMustBeNonAbstractRule + TypeArgumentClassMustBeNonAbstractRule, + TypeArgumentClassMustBeUnsealedRule ]; public override void Initialize(AnalysisContext analysisContext) @@ -112,6 +121,11 @@ private static void Analyze(SyntaxNodeAnalysisContext context) ReportDiagnostic(TypeArgumentClassMustBeNonAbstractRule); } + if (benchmarkClassTypeSymbol.IsSealed) + { + ReportDiagnostic(TypeArgumentClassMustBeUnsealedRule); + } + return; bool HasBenchmarkAttribute() diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index d7eca1bc4c..6cb3070c61 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -4,6 +4,7 @@ public static class DiagnosticIds { public const string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods = "BDN1000"; public const string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract = "BDN1001"; + public const string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed = "BDN1002"; public const string General_BenchmarkClass_MethodMustBePublic = "BDN1100"; public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1101"; public const string General_BenchmarkClass_ClassMustBePublic = "BDN1102"; @@ -12,8 +13,7 @@ public static class DiagnosticIds public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1105"; public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1106"; public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1107"; - public const string General_BenchmarkClass_ClassMustBeUnsealed = "BDN1108"; - public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1109"; + public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1108"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField = "BDN1200"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty = "BDN1201"; public const string Attributes_GeneralParameterAttributes_FieldMustBePublic = "BDN1202"; diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 677d954cb7..32587db5dd 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -74,14 +74,6 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Description))); - internal static readonly DiagnosticDescriptor ClassMustBeUnsealedRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeUnsealed, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeUnsealed_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeUnsealed_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeUnsealed_Description))); - internal static readonly DiagnosticDescriptor OnlyOneMethodCanBeBaselineRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_OnlyOneMethodCanBeBaseline, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat)), @@ -99,7 +91,6 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, - ClassMustBeUnsealedRule, OnlyOneMethodCanBeBaselineRule ]; @@ -164,7 +155,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context) var classIsPublic = false; var classStaticModifier = null as SyntaxToken?; var classAbstractModifier = null as SyntaxToken?; - var classSealedModifier = null as SyntaxToken?; foreach (var modifier in classDeclarationSyntax.Modifiers) { @@ -180,10 +170,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context) { classAbstractModifier = modifier; } - else if (modifier.IsKind(SyntaxKind.SealedKeyword)) - { - classSealedModifier = modifier; - } } if (genericTypeArgumentsAttributes.Length == 0) @@ -230,11 +216,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonStaticRule, classStaticModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); } - if (classSealedModifier.HasValue) - { - context.ReportDiagnostic(Diagnostic.Create(ClassMustBeUnsealedRule, classSealedModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } - var baselineCount = 0; foreach (var benchmarkMethod in benchmarkMethods) { diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index 904a241996..255ee4f2fa 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -209,7 +209,7 @@ public TypeArgumentClassMustBeNonAbstract() : base(RunAnalyzer.TypeArgumentClass [Fact] public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagnostic() { - const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + const string benchmarkClassName = "BenchmarkClass"; const string testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -217,13 +217,13 @@ public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagn public class Program { public static void Main(string[] args) { - BenchmarkRunner.Run<{|#0:{{classWithOneBenchmarkMethodName}}|}>(); + BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); } } """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" - public abstract class {{classWithOneBenchmarkMethodName}} + public abstract class {{benchmarkClassName}} { public void BenchmarkMethod() { @@ -233,7 +233,44 @@ public void BenchmarkMethod() """; TestCode = testCode; AddSource(benchmarkClassDocument); - AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + } + + public class TypeArgumentClassMustBeUnsealed : AnalyzerTestFixture + { + public TypeArgumentClassMustBeUnsealed() : base(RunAnalyzer.TypeArgumentClassMustBeUnsealedRule) { } + + [Fact] + public async Task Invoking_with_a_sealed_benchmark_class_should_trigger_diagnostic() + { + const string benchmarkClassName = "BenchmarkClass"; + + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); + } + } + """; + + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + public sealed class {{benchmarkClassName}} + { + public void BenchmarkMethod() + { + + } + } + """; + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddDefaultExpectedDiagnostic(benchmarkClassName); await RunAsync(); } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 74458e7652..c59ba4a342 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -707,61 +707,6 @@ public void BenchmarkMethod() private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; } - public class ClassMustBeUnsealed : AnalyzerTestFixture - { - public ClassMustBeUnsealed() : base(BenchmarkClassAnalyzer.ClassMustBeUnsealedRule) { } - - [Fact] - public async Task Unsealed_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() - { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Fact] - public async Task Sealed_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() - { - const string benchmarkClassName = "BenchmarkClass"; - - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public {|#0:sealed|} class {{benchmarkClassName}} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); - - await RunAsync(); - } - } - public class OnlyOneMethodCanBeBaseline : AnalyzerTestFixture { public OnlyOneMethodCanBeBaseline() : base(BenchmarkClassAnalyzer.OnlyOneMethodCanBeBaselineRule) { } From d7c5e4876d1f3dd81217330519dbdeac6df7bd4e Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 13 Oct 2025 16:17:47 +0200 Subject: [PATCH 12/24] Move "Benchmark class must be public" to Run analyzer --- .../AnalyzerReleases.Unshipped.md | 18 +-- ...nchmarkDotNetAnalyzerResources.Designer.cs | 45 +++--- .../BenchmarkDotNetAnalyzerResources.resx | 11 +- .../BenchmarkRunner/RunAnalyzer.cs | 17 ++- .../DiagnosticIds.cs | 18 +-- .../General/BenchmarkClassAnalyzer.cs | 21 +-- .../BenchmarkRunner/RunAnalyzerTests.cs | 131 +++++++++++++++++- .../General/BenchmarkClassAnalyzerTests.cs | 84 ----------- 8 files changed, 187 insertions(+), 158 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index 9f4a3a102d..82af856750 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -6,17 +6,17 @@ Rule ID | Category | Severity | Notes ---------|----------|----------|-------------------- BDN1000 | Usage | Error | BDN1000_BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods -BDN1001 | Usage | Error | BDN1001_BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract -BDN1002 | Usage | Error | BDN1002_BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed +BDN1001 | Usage | Error | BDN1001_BenchmarkRunner_Run_TypeArgumentClassMustBePublic +BDN1002 | Usage | Error | BDN1002_BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract +BDN1003 | Usage | Error | BDN1003_BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed BDN1100 | Usage | Error | BDN1100_General_BenchmarkClass_MethodMustBePublic BDN1101 | Usage | Error | BDN1101_General_BenchmarkClass_MethodMustBeNonGeneric -BDN1102 | Usage | Error | BDN1102_General_BenchmarkClass_ClassMustBePublic -BDN1103 | Usage | Error | BDN1103_General_BenchmarkClass_ClassMustBeNonStatic -BDN1104 | Usage | Error | BDN1104_General_BenchmarkClass_ClassMustBeNonAbstract -BDN1105 | Usage | Error | BDN1105_General_BenchmarkClass_ClassMustBeNonGeneric -BDN1106 | Usage | Error | BDN1106_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric -BDN1107 | Usage | Error | BDN1107_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount -BDN1108 | Usage | Error | BDN1108_General_BenchmarkClass_OnlyOneMethodCanBeBaseline +BDN1102 | Usage | Error | BDN1102_General_BenchmarkClass_ClassMustBeNonStatic +BDN1103 | Usage | Error | BDN1103_General_BenchmarkClass_ClassMustBeNonAbstract +BDN1104 | Usage | Error | BDN1104_General_BenchmarkClass_ClassMustBeNonGeneric +BDN1105 | Usage | Error | BDN1105_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric +BDN1106 | Usage | Error | BDN1106_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount +BDN1107 | Usage | Error | BDN1107_General_BenchmarkClass_OnlyOneMethodCanBeBaseline BDN1200 | Usage | Error | BDN1200_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField BDN1201 | Usage | Error | BDN1201_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty BDN1202 | Usage | Error | BDN1202_Attributes_GeneralParameterAttributes_FieldMustBePublic diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index 7a05fea0e2..7f608a2256 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -535,6 +535,24 @@ internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Ti } } + /// + /// Looks up a localized string similar to Referenced benchmark class '{0}' must be public. + /// + internal static string BenchmarkRunner_Run_TypeArgumentClassMustBePublic_MessageFormat { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMustBePublic_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark classes must be public. + /// + internal static string BenchmarkRunner_Run_TypeArgumentClassMustBePublic_Title { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_TypeArgumentClassMustBePublic_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to A benchmark class referenced in the BenchmarkRunner.Run method must be unsealed. /// @@ -589,33 +607,6 @@ internal static string General_BenchmarkClass_ClassMustBeNonStatic_Title { } } - /// - /// Looks up a localized string similar to A benchmark class must be public. - /// - internal static string General_BenchmarkClass_ClassMustBePublic_Description { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_Description", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The benchmark class '{0}' must be public. - /// - internal static string General_BenchmarkClass_ClassMustBePublic_MessageFormat { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Benchmark classes must be public. - /// - internal static string General_BenchmarkClass_ClassMustBePublic_Title { - get { - return ResourceManager.GetString("General_BenchmarkClass_ClassMustBePublic_Title", resourceCulture); - } - } - /// /// Looks up a localized string similar to A benchmark class annotated with a [GenericTypeArguments] attribute must be generic, having between one to three type parameters. /// diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index 91856acca2..c9248de0c4 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -156,13 +156,7 @@ Benchmark classes can only be generic if they're either abstract or annotated with a [GenericTypeArguments] attribute - - A benchmark class must be public - - - The benchmark class '{0}' must be public - - + Benchmark classes must be public @@ -358,4 +352,7 @@ Either add the [Arguments] attribute(s) or remove the parameters. A benchmark class referenced in the BenchmarkRunner.Run method must be non-abstract + + Referenced benchmark class '{0}' must be public + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index 4d3c5f3bf0..41cb9da3ec 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -18,6 +18,13 @@ public class RunAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods_Description))); + internal static readonly DiagnosticDescriptor TypeArgumentClassMustBePublicRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_TypeArgumentClassMustBePublic, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBePublic_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBePublic_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + internal static readonly DiagnosticDescriptor TypeArgumentClassMustBeNonAbstractRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_MessageFormat)), @@ -37,6 +44,7 @@ public class RunAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => [ TypeArgumentClassMissingBenchmarkMethodsRule, + TypeArgumentClassMustBePublicRule, TypeArgumentClassMustBeNonAbstractRule, TypeArgumentClassMustBeUnsealedRule ]; @@ -82,6 +90,8 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } + // TODO: Support Type overloads that use the typeof() expression + if (memberAccessExpression.Name is not GenericNameSyntax genericMethod) { return; @@ -116,6 +126,11 @@ private static void Analyze(SyntaxNodeAnalysisContext context) ReportDiagnostic(TypeArgumentClassMissingBenchmarkMethodsRule); } + if (benchmarkClassTypeSymbol.DeclaredAccessibility != Accessibility.Public) + { + ReportDiagnostic(TypeArgumentClassMustBePublicRule); + } + if (benchmarkClassTypeSymbol.IsAbstract) { ReportDiagnostic(TypeArgumentClassMustBeNonAbstractRule); @@ -159,7 +174,7 @@ bool HasBenchmarkAttribute() void ReportDiagnostic(DiagnosticDescriptor diagnosticDescriptor) { - context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span), benchmarkClassTypeSymbol.ToString())); + context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span), benchmarkClassTypeSymbol.Name)); } } } diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index 6cb3070c61..29878b8624 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -3,17 +3,17 @@ public static class DiagnosticIds { public const string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods = "BDN1000"; - public const string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract = "BDN1001"; - public const string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed = "BDN1002"; + public const string BenchmarkRunner_Run_TypeArgumentClassMustBePublic = "BDN1001"; + public const string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract = "BDN1002"; + public const string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed = "BDN1003"; public const string General_BenchmarkClass_MethodMustBePublic = "BDN1100"; public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1101"; - public const string General_BenchmarkClass_ClassMustBePublic = "BDN1102"; - public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1103"; - public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract = "BDN1104"; - public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1105"; - public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1106"; - public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1107"; - public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1108"; + public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1102"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract = "BDN1103"; + public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1104"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1105"; + public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1106"; + public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1107"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField = "BDN1200"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty = "BDN1201"; public const string Attributes_GeneralParameterAttributes_FieldMustBePublic = "BDN1202"; diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 32587db5dd..040a11a990 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -27,14 +27,6 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_Description))); - internal static readonly DiagnosticDescriptor ClassMustBePublicRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBePublic, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBePublic_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBePublic_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBePublic_Description))); - internal static readonly DiagnosticDescriptor ClassMustBeNonStaticRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeNonStatic, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat)), @@ -85,7 +77,6 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer [ MethodMustBePublicRule, MethodMustBeNonGenericRule, - ClassMustBePublicRule, ClassMustBeNonStaticRule, ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, @@ -152,17 +143,12 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - var classIsPublic = false; var classStaticModifier = null as SyntaxToken?; var classAbstractModifier = null as SyntaxToken?; foreach (var modifier in classDeclarationSyntax.Modifiers) { - if (modifier.IsKind(SyntaxKind.PublicKeyword)) - { - classIsPublic = true; - } - else if (modifier.IsKind(SyntaxKind.StaticKeyword)) + if (modifier.IsKind(SyntaxKind.StaticKeyword)) { classStaticModifier = modifier; } @@ -201,11 +187,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } } - if (!classIsPublic) - { - context.ReportDiagnostic(Diagnostic.Create(ClassMustBePublicRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } - if (classAbstractModifier.HasValue && genericTypeArgumentsAttributes.Length > 0) { context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index 255ee4f2fa..f4657fc5f5 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -6,6 +6,7 @@ using Xunit; + using System.Linq; using System.Threading.Tasks; public class RunAnalyzerTests @@ -13,7 +14,7 @@ public class RunAnalyzerTests public class General : AnalyzerTestFixture { [Fact] - public async Task Invoking_with_a_nonabstract_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() + public async Task Invoking_with_a_public_nonabstract_unsealed_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; @@ -202,6 +203,128 @@ public void BenchmarkMethod() } } + public class TypeArgumentClassMustBePublic : AnalyzerTestFixture + { + public TypeArgumentClassMustBePublic() : base(RunAnalyzer.TypeArgumentClassMustBePublicRule) { } + + [Fact] + public async Task Invoking_with_a_nonpublic_class_with_multiple_inheritance_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + { + const string benchmarkClassName = "BenchmarkClass"; + + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); + } + + private class {{benchmarkClassName}} : BenchmarkClassAncestor1 + { + } + + private class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + } + """; + + const string benchmarkClassAncestor2Document = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClassAncestor2 + { + } + """; + + + + TestCode = testCode; + AddSource(benchmarkClassAncestor2Document); + + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(NonPublicClassAccessModifiersExceptFileTheoryData))] + public async Task Invoking_with_a_nonpublic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string nonPublicClassAccessModifier) + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); + } + + {{nonPublicClassAccessModifier}}class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + } + """; + + TestCode = testCode; + + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + + [Fact] + public async Task Invoking_with_a_file_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + { + const string benchmarkClassName = "BenchmarkClass"; + + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); + } + } + + file class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } + + public static TheoryData NonPublicClassAccessModifiersExceptFileTheoryData => new(new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file ")); + } + public class TypeArgumentClassMustBeNonAbstract : AnalyzerTestFixture { public TypeArgumentClassMustBeNonAbstract() : base(RunAnalyzer.TypeArgumentClassMustBeNonAbstractRule) { } @@ -223,8 +346,11 @@ public static void Main(string[] args) { """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + public abstract class {{benchmarkClassName}} { + [Benchmark] public void BenchmarkMethod() { @@ -260,8 +386,11 @@ public static void Main(string[] args) { """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + public sealed class {{benchmarkClassName}} { + [Benchmark] public void BenchmarkMethod() { diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index c59ba4a342..95fc417985 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -167,90 +167,6 @@ public class BenchmarkClass private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; } - public class ClassMustBePublic : AnalyzerTestFixture - { - public ClassMustBePublic() : base(BenchmarkClassAnalyzer.ClassMustBePublicRule) { } - - [Fact] - public async Task Public_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() - { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Theory] - [MemberData(nameof(NonPublicClassAccessModifiersExceptFileTheoryData))] - public async Task Nonpublic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string nonPublicClassAccessModifier) - { - const string benchmarkClassName = "BenchmarkClass"; - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class Wrapper { - {{nonPublicClassAccessModifier}}class {|#0:{{benchmarkClassName}}|} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - } - """; - - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); - - await RunAsync(); - } - - [Fact] - public async Task File_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() - { - const string benchmarkClassName = "BenchmarkClass"; - - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - file class {|#0:{{benchmarkClassName}}|} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); - - await RunAsync(); - } - - public static TheoryData NonPublicClassAccessModifiersExceptFileTheoryData => new(new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file ")); - } - public class ClassMustBeNonStatic : AnalyzerTestFixture { public ClassMustBeNonStatic() : base(BenchmarkClassAnalyzer.ClassMustBeNonStaticRule) { } From a1b6e459085f36fe6515803180c24eea6b89d494 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Wed, 15 Oct 2025 18:20:22 +0200 Subject: [PATCH 13/24] Support analyzing overload of BenchmarkRunner.Run that takes a Type parameter created by using a typeof expression --- build/common.props | 2 +- .../AnalyzerHelper.cs | 20 + ...nchmarkDotNetAnalyzerResources.Designer.cs | 2 +- .../BenchmarkDotNetAnalyzerResources.resx | 2 +- .../BenchmarkRunner/RunAnalyzer.cs | 50 ++- .../BenchmarkRunner/RunAnalyzerTests.cs | 405 ++++++++++++++---- 6 files changed, 379 insertions(+), 102 deletions(-) diff --git a/build/common.props b/build/common.props index bb8b1ad253..c22f8f841c 100644 --- a/build/common.props +++ b/build/common.props @@ -21,7 +21,7 @@ false $(MSBuildThisFileDirectory)CodingStyle.ruleset true - + NU1900 annotations true diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index 0b885bf1bd..0347c41d26 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -70,5 +70,25 @@ public static ImmutableArray GetAttributes(INamedTypeSymbol? at return attributesBuilder.ToImmutable(); } + + public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol) + { + string typeName; + + if (namedTypeSymbol.SpecialType != SpecialType.None) + { + typeName = namedTypeSymbol.ToString(); + } + else if (namedTypeSymbol.IsUnboundGenericType) + { + typeName = $"{namedTypeSymbol.Name}<{new string(',', namedTypeSymbol.TypeArguments.Length - 1)}>"; + } + else + { + typeName = namedTypeSymbol.Name; + } + + return typeName; + } } } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index 7f608a2256..fb2ab0d4ca 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -563,7 +563,7 @@ internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Descr } /// - /// Looks up a localized string similar to Referenced benchmark class '{0}' cannot be sealed. + /// Looks up a localized string similar to Referenced benchmark class '{0}' is sealed. /// internal static string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_MessageFormat { get { diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index c9248de0c4..4fa05e7379 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -163,7 +163,7 @@ A benchmark class referenced in the BenchmarkRunner.Run method must be unsealed - Referenced benchmark class '{0}' cannot be sealed + Referenced benchmark class '{0}' is sealed Benchmark classes must be unsealed diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index 41cb9da3ec..3c02436030 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -79,36 +79,56 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - if (memberAccessExpression.Expression is not IdentifierNameSyntax typeIdentifier) + if (memberAccessExpression.Expression is not IdentifierNameSyntax identifierNameSyntax) { return; } - var classMemberAccessSymbol = context.SemanticModel.GetTypeInfo(typeIdentifier).Type; - if (classMemberAccessSymbol is null || !classMemberAccessSymbol.Equals(context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"), SymbolEqualityComparer.Default)) + var classMemberAccessTypeSymbol = context.SemanticModel.GetTypeInfo(identifierNameSyntax).Type; + if ( classMemberAccessTypeSymbol is null + || classMemberAccessTypeSymbol.TypeKind == TypeKind.Error + || !classMemberAccessTypeSymbol.Equals(context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"), SymbolEqualityComparer.Default)) { return; } - // TODO: Support Type overloads that use the typeof() expression - - if (memberAccessExpression.Name is not GenericNameSyntax genericMethod) + if (memberAccessExpression.Name.Identifier.ValueText != "Run") { return; } - if (genericMethod.Identifier.ValueText != "Run") + INamedTypeSymbol? benchmarkClassTypeSymbol; + Location? diagnosticLocation; + + if (memberAccessExpression.Name is GenericNameSyntax genericMethod) { - return; - } + if (genericMethod.TypeArgumentList.Arguments.Count != 1) + { + return; + } - if (genericMethod.TypeArgumentList.Arguments.Count != 1) + diagnosticLocation = Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span); + benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(genericMethod.TypeArgumentList.Arguments[0]).Type as INamedTypeSymbol; + } + else { - return; + if (invocationExpression.ArgumentList.Arguments.Count == 0) + { + return; + } + + // TODO: Support analyzing an array of typeof() expressions + if (invocationExpression.ArgumentList.Arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpression) + { + return; + } + + diagnosticLocation = typeOfExpression.Type.GetLocation(); + benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(typeOfExpression.Type).Type as INamedTypeSymbol; + } - var benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(genericMethod.TypeArgumentList.Arguments[0]).Type; - if (benchmarkClassTypeSymbol == null || benchmarkClassTypeSymbol.TypeKind == TypeKind.Error) + if (benchmarkClassTypeSymbol == null || benchmarkClassTypeSymbol.TypeKind == TypeKind.Error || (benchmarkClassTypeSymbol.IsGenericType && !benchmarkClassTypeSymbol.IsUnboundGenericType)) { return; } @@ -166,7 +186,7 @@ bool HasBenchmarkAttribute() } } - baseType = baseType.BaseType; + baseType = baseType.OriginalDefinition.BaseType; } return false; @@ -174,7 +194,7 @@ bool HasBenchmarkAttribute() void ReportDiagnostic(DiagnosticDescriptor diagnosticDescriptor) { - context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span), benchmarkClassTypeSymbol.Name)); + context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, diagnosticLocation, AnalyzerHelper.NormalizeTypeName(benchmarkClassTypeSymbol))); } } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index f4657fc5f5..2ce43a6fa7 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -3,7 +3,7 @@ using Fixtures; using Analyzers.BenchmarkRunner; - + using System.Collections.Generic; using Xunit; using System.Linq; @@ -13,21 +13,23 @@ public class RunAnalyzerTests { public class General : AnalyzerTestFixture { - [Fact] - public async Task Invoking_with_a_public_nonabstract_unsealed_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Invoking_with_a_public_nonabstract_unsealed_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic(bool isGeneric) { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Running; + var invocationExpression = isGeneric ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; - public class Program - { - public static void Main(string[] args) { - BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); - } - } - """; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -53,21 +55,23 @@ public class TypeArgumentClassMissingBenchmarkMethods : AnalyzerTestFixture(); - } - } - """; + var invocationExpression = isGeneric ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -99,7 +103,83 @@ private void BenchmarkMethod3() } [Theory, CombinatorialData] - public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic([CombinatorialValues("", "abstract ")] string abstractModifier) + public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic(bool isGeneric, [CombinatorialValues("", "abstract ")] string abstractModifier) + { + const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; + + var invocationExpression = isGeneric ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; + + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1 + { + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; + + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + + await RunAsync(); + } + + [Theory] + [InlineData("")] + [InlineData("abstract ")] + public async Task Invoking_with_a_generic_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic(string abstractModifier) { const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; @@ -109,11 +189,85 @@ public async Task Invoking_with_type_argument_class_having_at_least_one_public_m public class Program { public static void Main(string[] args) { - BenchmarkRunner.Run<{{classWithOneBenchmarkMethodName}}>(); + BenchmarkRunner.Run(typeof({{classWithOneBenchmarkMethodName}}<,>)); } } """; + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1 + { + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; + + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_trigger_diagnostic(bool isGeneric, [CombinatorialValues("", "abstract ")] string abstractModifier) + { + const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; + + var invocationExpression = isGeneric ? $"<{{|#0:{classWithOneBenchmarkMethodName}|}}>()" : $"(typeof({{|#0:{classWithOneBenchmarkMethodName}|}}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; + const string benchmarkClassDocument = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -143,7 +297,6 @@ public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1 public {{abstractModifier}}class BenchmarkClassAncestor3 { - [Benchmark] public void BenchmarkMethod() { @@ -167,13 +320,17 @@ private void BenchmarkMethod3() AddSource(benchmarkClassAncestor2Document); AddSource(benchmarkClassAncestor3Document); + AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); + await RunAsync(); } - [Fact] - public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_should_trigger_diagnostic() + [Theory] + [InlineData("")] + [InlineData("abstract ")] + public async Task Invoking_with_a_generic_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_trigger_diagnostic(string abstractModifier) { - const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; const string testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -181,11 +338,81 @@ public async Task Invoking_with_type_argument_class_having_no_public_method_anno public class Program { public static void Main(string[] args) { - BenchmarkRunner.Run<{|#0:{{classWithOneBenchmarkMethodName}}|}>(); + BenchmarkRunner.Run(typeof({|#0:{{classWithOneBenchmarkMethodName}}<,>|})); } } """; + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1 + { + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; + + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + public void BenchmarkMethod() + { + + } + + private void BenchmarkMethod2() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + + AddDefaultExpectedDiagnostic($"{classWithOneBenchmarkMethodName}<,>"); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_should_trigger_diagnostic(bool isGeneric) + { + const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + + var invocationExpression = isGeneric ? $"<{{|#0:{classWithOneBenchmarkMethodName}|}}>()" : $"(typeof({{|#0:{classWithOneBenchmarkMethodName}|}}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; + const string benchmarkClassDocument = /* lang=c#-test */ $$""" public class {{classWithOneBenchmarkMethodName}} { @@ -197,6 +424,7 @@ public void BenchmarkMethod() """; TestCode = testCode; AddSource(benchmarkClassDocument); + AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); await RunAsync(); @@ -207,19 +435,21 @@ public class TypeArgumentClassMustBePublic : AnalyzerTestFixture { public TypeArgumentClassMustBePublic() : base(RunAnalyzer.TypeArgumentClassMustBePublicRule) { } - [Fact] - public async Task Invoking_with_a_nonpublic_class_with_multiple_inheritance_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Invoking_with_a_nonpublic_class_with_multiple_inheritance_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(bool isGeneric) { const string benchmarkClassName = "BenchmarkClass"; - const string testCode = /* lang=c#-test */ $$""" + var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + + var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; public class Program { public static void Main(string[] args) { - BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); + BenchmarkRunner.Run{{invocationExpression}}; } private class {{benchmarkClassName}} : BenchmarkClassAncestor1 @@ -255,12 +485,13 @@ public class BenchmarkClassAncestor2 await RunAsync(); } - [Theory] - [MemberData(nameof(NonPublicClassAccessModifiersExceptFileTheoryData))] - public async Task Invoking_with_a_nonpublic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string nonPublicClassAccessModifier) + [Theory, CombinatorialData] + public async Task Invoking_with_a_nonpublic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(NonPublicClassAccessModifiersExceptFile))] string nonPublicClassAccessModifier, bool isGeneric) { const string benchmarkClassName = "BenchmarkClass"; + var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; @@ -268,7 +499,7 @@ public async Task Invoking_with_a_nonpublic_class_containing_at_least_one_method public class Program { public static void Main(string[] args) { - BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); + BenchmarkRunner.Run{{invocationExpression}}; } {{nonPublicClassAccessModifier}}class {{benchmarkClassName}} @@ -289,31 +520,33 @@ public void BenchmarkMethod() await RunAsync(); } - [Fact] - public async Task Invoking_with_a_file_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Invoking_with_a_file_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(bool isGeneric) { const string benchmarkClassName = "BenchmarkClass"; - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - using BenchmarkDotNet.Running; + var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; - public class Program - { - public static void Main(string[] args) { - BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); - } - } - - file class {{benchmarkClassName}} - { - [Benchmark] - public void BenchmarkMethod() - { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Running; - } - } - """; + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + + file class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; @@ -322,28 +555,30 @@ public void BenchmarkMethod() await RunAsync(); } - public static TheoryData NonPublicClassAccessModifiersExceptFileTheoryData => new(new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file ")); + public static IEnumerable NonPublicClassAccessModifiersExceptFile => new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file "); } public class TypeArgumentClassMustBeNonAbstract : AnalyzerTestFixture { public TypeArgumentClassMustBeNonAbstract() : base(RunAnalyzer.TypeArgumentClassMustBeNonAbstractRule) { } - [Fact] - public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagnostic(bool isGeneric) { const string benchmarkClassName = "BenchmarkClass"; - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Running; - - public class Program - { - public static void Main(string[] args) { - BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); - } - } - """; + var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -369,21 +604,23 @@ public class TypeArgumentClassMustBeUnsealed : AnalyzerTestFixture { public TypeArgumentClassMustBeUnsealed() : base(RunAnalyzer.TypeArgumentClassMustBeUnsealedRule) { } - [Fact] - public async Task Invoking_with_a_sealed_benchmark_class_should_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Invoking_with_a_sealed_benchmark_class_should_trigger_diagnostic(bool isGeneric) { const string benchmarkClassName = "BenchmarkClass"; - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Running; - - public class Program - { - public static void Main(string[] args) { - BenchmarkRunner.Run<{|#0:{{benchmarkClassName}}|}>(); - } - } - """; + var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; const string benchmarkClassDocument = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; From 7d3dfc0f65fee59da83032d6dcfd5629fad61028 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Wed, 15 Oct 2025 21:46:16 +0200 Subject: [PATCH 14/24] Remove requirement that a class must have at least one method annotated with the Benchmark attribute when analyzing GenericTypeArguments attribute rules --- ...nchmarkDotNetAnalyzerResources.Designer.cs | 8 +- .../BenchmarkDotNetAnalyzerResources.resx | 8 +- .../DiagnosticIds.cs | 14 +- .../General/BenchmarkClassAnalyzer.cs | 78 +-- .../General/BenchmarkClassAnalyzerTests.cs | 589 +++++++----------- 5 files changed, 296 insertions(+), 401 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index fb2ab0d4ca..bcb78f4ba2 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -118,7 +118,7 @@ internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount_ } /// - /// Looks up a localized string similar to The values passed to an [Arguments] attribute must match the parameters declared in the targeted benchmark method in both type (or be convertible to) and order. + /// Looks up a localized string similar to The values passed to an [Arguments] attribute must match the parameters declared in the targeted benchmark method in both type (or be implicitly convertible to) and order. /// internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Description { get { @@ -136,7 +136,7 @@ internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueType_M } /// - /// Looks up a localized string similar to Values passed to an [Arguments] attribute must match exactly the parameters declared in the targeted benchmark method in both type and order. + /// Looks up a localized string similar to Values passed to an [Arguments] attribute must match exactly the parameters declared in the targeted benchmark method in both type (or be implicitly convertible to) and order. /// internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Title { get { @@ -437,7 +437,7 @@ internal static string Attributes_ParamsAttribute_MustHaveValues_Title { } /// - /// Looks up a localized string similar to The type of each value provided to the [Params] attribute must match the type of the field or property it is applied to. + /// Looks up a localized string similar to The type of each value provided to the [Params] attribute must match the type of (or be implicitly convertible to) the field or property it is applied to. /// internal static string Attributes_ParamsAttribute_UnexpectedValueType_Description { get { @@ -455,7 +455,7 @@ internal static string Attributes_ParamsAttribute_UnexpectedValueType_MessageFor } /// - /// Looks up a localized string similar to Type of all value(s) passed to the [Params] attribute must match the type of the annotated field or property. + /// Looks up a localized string similar to Type of all value(s) passed to the [Params] attribute must match the type of (or be implicitly convertible to) the annotated field or property. /// internal static string Attributes_ParamsAttribute_UnexpectedValueType_Title { get { diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index 4fa05e7379..80393a20f9 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -223,7 +223,7 @@ A property annotated with a parameter attribute must have a public setter; make sure that the access modifier of the setter is empty and that the property is not an auto-property or an expression-bodied property. - The type of each value provided to the [Params] attribute must match the type of the field or property it is applied to + The type of each value provided to the [Params] attribute must match the type of (or be implicitly convertible to) the field or property it is applied to The [ParamsAllValues] attribute cannot be applied to a field or property of an enum type marked with the [Flags] attribute. Use this attribute only with non-flags enum types, as [Flags] enums support bitwise combinations that cannot be exhaustively enumerated. @@ -289,7 +289,7 @@ Benchmark methods without [Arguments] attribute(s) cannot declare parameters - Values passed to an [Arguments] attribute must match exactly the parameters declared in the targeted benchmark method in both type and order + Values passed to an [Arguments] attribute must match exactly the parameters declared in the targeted benchmark method in both type (or be implicitly convertible to) and order Properties annotated with a parameter attribute must be public @@ -304,7 +304,7 @@ Unnecessary single value passed to [Params] attribute - Type of all value(s) passed to the [Params] attribute must match the type of the annotated field or property + Type of all value(s) passed to the [Params] attribute must match the type of (or be implicitly convertible to) the annotated field or property The [ParamsAllValues] attribute cannot be applied to fields or properties of enum types marked with [Flags] @@ -341,7 +341,7 @@ Either add the [Arguments] attribute(s) or remove the parameters. - The values passed to an [Arguments] attribute must match the parameters declared in the targeted benchmark method in both type (or be convertible to) and order + The values passed to an [Arguments] attribute must match the parameters declared in the targeted benchmark method in both type (or be implicitly convertible to) and order [Arguments] attribute can only be used on methods annotated with the [Benchmark] attribute diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index 29878b8624..4f84e85668 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -6,13 +6,13 @@ public static class DiagnosticIds public const string BenchmarkRunner_Run_TypeArgumentClassMustBePublic = "BDN1001"; public const string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract = "BDN1002"; public const string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed = "BDN1003"; - public const string General_BenchmarkClass_MethodMustBePublic = "BDN1100"; - public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1101"; - public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1102"; - public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract = "BDN1103"; - public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1104"; - public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1105"; - public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1106"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract = "BDN1100"; + public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1101"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1102"; + public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1103"; + public const string General_BenchmarkClass_MethodMustBePublic = "BDN1104"; + public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1105"; + public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1106"; public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1107"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField = "BDN1200"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty = "BDN1201"; diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 040a11a990..008597e0df 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -11,30 +11,6 @@ [DiagnosticAnalyzer(LanguageNames.CSharp)] public class BenchmarkClassAnalyzer : DiagnosticAnalyzer { - internal static readonly DiagnosticDescriptor MethodMustBePublicRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_MethodMustBePublic, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_Description))); - - internal static readonly DiagnosticDescriptor MethodMustBeNonGenericRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_MethodMustBeNonGeneric, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_Description))); - - internal static readonly DiagnosticDescriptor ClassMustBeNonStaticRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeNonStatic, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Description))); - internal static readonly DiagnosticDescriptor ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_MessageFormat)), @@ -66,6 +42,31 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount_Description))); + internal static readonly DiagnosticDescriptor MethodMustBePublicRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_MethodMustBePublic, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBePublic_Description))); + + internal static readonly DiagnosticDescriptor MethodMustBeNonGenericRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_MethodMustBeNonGeneric, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_MethodMustBeNonGeneric_Description))); + + internal static readonly DiagnosticDescriptor ClassMustBeNonStaticRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassMustBeNonStatic, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Description))); + + internal static readonly DiagnosticDescriptor OnlyOneMethodCanBeBaselineRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_OnlyOneMethodCanBeBaseline, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat)), @@ -75,13 +76,13 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => [ - MethodMustBePublicRule, - MethodMustBeNonGenericRule, - ClassMustBeNonStaticRule, ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, + MethodMustBePublicRule, + MethodMustBeNonGenericRule, + ClassMustBeNonStaticRule, OnlyOneMethodCanBeBaselineRule ]; @@ -138,10 +139,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } var benchmarkMethods = benchmarkMethodsBuilder.ToImmutable(); - if (benchmarkMethods.Length == 0) - { - return; - } var classStaticModifier = null as SyntaxToken?; var classAbstractModifier = null as SyntaxToken?; @@ -192,26 +189,31 @@ private static void Analyze(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); } + if (benchmarkMethods.Length == 0) + { + return; + } + if (classStaticModifier.HasValue) { context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonStaticRule, classStaticModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); } var baselineCount = 0; - foreach (var benchmarkMethod in benchmarkMethods) + foreach (var (benchmarkMethod, baselineLocations) in benchmarkMethods) { - var methodIsPublic = benchmarkMethod.Method.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)); + var methodIsPublic = benchmarkMethod.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)); if (!methodIsPublic) { - context.ReportDiagnostic(Diagnostic.Create(MethodMustBePublicRule, benchmarkMethod.Method.Identifier.GetLocation(), benchmarkMethod.Method.Identifier.ToString())); + context.ReportDiagnostic(Diagnostic.Create(MethodMustBePublicRule, benchmarkMethod.Identifier.GetLocation(), benchmarkMethod.Identifier.ToString())); } - if (benchmarkMethod.Method.TypeParameterList != null) + if (benchmarkMethod.TypeParameterList != null) { - context.ReportDiagnostic(Diagnostic.Create(MethodMustBeNonGenericRule, benchmarkMethod.Method.TypeParameterList.GetLocation(), benchmarkMethod.Method.Identifier.ToString())); + context.ReportDiagnostic(Diagnostic.Create(MethodMustBeNonGenericRule, benchmarkMethod.TypeParameterList.GetLocation(), benchmarkMethod.Identifier.ToString())); } - baselineCount += benchmarkMethod.BaselineLocations.Length; + baselineCount += baselineLocations.Length; } if (baselineCount > 1) @@ -227,7 +229,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; - ImmutableArray GetBaselineLocations(AttributeSyntax attributeSyntax) + static ImmutableArray GetBaselineLocations(AttributeSyntax attributeSyntax) { var baselineLocationsBuilder = ImmutableArray.CreateBuilder(); diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 95fc417985..2d6acb0967 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -15,20 +15,22 @@ public class BenchmarkClassAnalyzerTests { public class General : AnalyzerTestFixture { - [Fact] - public async Task Class_with_no_methods_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + [Theory] + [InlineData("")] + [InlineData(" abstract")] + public async Task Class_not_annotated_with_any_generictypearguments_attributes_and_with_no_methods_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(string abstractModifier) { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - public class BenchmarkClass - { - public void BenchmarkMethod() - { + public{{abstractModifier}} class BenchmarkClass + { + public void BenchmarkMethod() + { - } - } - """; + } + } + """; TestCode = testCode; @@ -36,49 +38,25 @@ public void BenchmarkMethod() } } - public class MethodMustBePublic : AnalyzerTestFixture + public class ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract : AnalyzerTestFixture { - public MethodMustBePublic() : base(BenchmarkClassAnalyzer.MethodMustBePublicRule) { } + public ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule) { } - [Fact] - public async Task Public_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Abstract_class_annotated_with_at_least_one_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } + const string benchmarkClassName = "BenchmarkClass"; - [Theory] - [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] - public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string nonPublicClassAccessModifier) - { - const string benchmarkMethodName = "BenchmarkMethod"; + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", genericTypeArgumentsAttributeUsageCount); var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - public class BenchmarkClass + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public {|#0:abstract|} class {{benchmarkClassName}} { - [Benchmark] - {{nonPublicClassAccessModifier}}void {|#0:{{benchmarkMethodName}}|}() + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() { } @@ -86,217 +64,144 @@ public class BenchmarkClass """; TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkMethodName); + AddDefaultExpectedDiagnostic(benchmarkClassName); await RunAsync(); } } - public class MethodMustBeNonGeneric : AnalyzerTestFixture + public class GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute : AnalyzerTestFixture { - public MethodMustBeNonGeneric() : base(BenchmarkClassAnalyzer.MethodMustBeNonGenericRule) { } + public GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute() : base(BenchmarkClassAnalyzer.GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule) { } - [Fact] - public async Task Nongeneric_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Generic_class_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic([CombinatorialRange(1, 3)] int attributeUsageCount, + [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - public void NonGenericBenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); + var attributeUsages = string.Join("\n", Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", attributeUsageCount)); - [Fact] - public async Task Generic_method_not_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() - { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - public class BenchmarkClass - { - public void GenericMethod() - { + {{attributeUsages}} + public class BenchmarkClass{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { - } - } - """; + } + } + """; TestCode = testCode; await RunAsync(); } - [Theory] - [MemberData(nameof(TypeParametersListLength))] - public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) + [Theory, CombinatorialData] + public async Task Abstract_generic_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { - const string benchmarkMethodName = "GenericBenchmarkMethod"; - var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - - public class BenchmarkClass + + public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> { - [Benchmark] - public void {{benchmarkMethodName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|}() + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() { - + } } """; TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkMethodName); await RunAsync(); } - public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; - - private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; - } - - public class ClassMustBeNonStatic : AnalyzerTestFixture - { - public ClassMustBeNonStatic() : base(BenchmarkClassAnalyzer.ClassMustBeNonStaticRule) { } - - [Fact] - public async Task Instance_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Nonabstract_generic_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; + const string benchmarkClassName = "BenchmarkClass"; - public class BenchmarkClass - { - [Benchmark] - public void BenchmarkMethod() - { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - } - - public void NonBenchmarkMethod() - { - - } - } - """; + public class {{benchmarkClassName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); await RunAsync(); } - [Fact] - public async Task Static_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() - { - const string benchmarkClassName = "BenchmarkClass"; - - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public {|#0:static|} class {{benchmarkClassName}} - { - [Benchmark] - public static void BenchmarkMethod() - { - - } - } - """; + public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); + private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; - await RunAsync(); - } + private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; } - public class ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract : AnalyzerTestFixture + public class ClassWithGenericTypeArgumentsAttributeMustBeGeneric : AnalyzerTestFixture { - public ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule) { } + public ClassWithGenericTypeArgumentsAttributeMustBeGeneric() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeGenericRule) { } - [Fact] - public async Task Nonabstract_class_not_annotated_with_any_generictypearguments_attributes_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Generic_class_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - public void BenchmarkMethod() - { + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", genericTypeArgumentsAttributeUsageCount); - } - - public void NonBenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Fact] - public async Task Abstract_class_not_annotated_with_any_generictypearguments_attributes_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() - { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - public abstract class BenchmarkClass - { - [Benchmark] - public void BenchmarkMethod() - { + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public class BenchmarkClass + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { - } - - public void NonBenchmarkMethod() - { - - } - } - """; + } + } + """; TestCode = testCode; await RunAsync(); } - [Theory] - [InlineData(1)] - [InlineData(2)] - public async Task Abstract_class_annotated_with_at_least_one_generictypearguments_attribute_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int attributeUsageCount) + [Theory, CombinatorialData] + public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { const string benchmarkClassName = "BenchmarkClass"; - var attributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", attributeUsageCount); + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", genericTypeArgumentsAttributeUsageCount); var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - - {{string.Join("\n", attributeUsages)}} - public {|#0:abstract|} class {{benchmarkClassName}} + + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public class {|#0:{{benchmarkClassName}}|} { - [Benchmark] + {{benchmarkAttributeUsage}} public void BenchmarkMethod() { - + } } """; @@ -306,77 +211,76 @@ public void BenchmarkMethod() await RunAsync(); } - } - - public class GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute : AnalyzerTestFixture - { - public GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute() : base(BenchmarkClassAnalyzer.GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule) { } - - [Fact] - public async Task Nongeneric_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() - { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void NonBenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } [Theory, CombinatorialData] - public async Task Generic_class_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic([CombinatorialRange(1, 3)] int attributeUsageCount, - [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength) + public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_inheriting_from_an_abstract_generic_class_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { + const string benchmarkClassName = "BenchmarkClass"; + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); - var attributeUsages = string.Join("\n", Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", attributeUsageCount)); + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", genericTypeArgumentsAttributeUsageCount); var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - {{attributeUsages}} - public class BenchmarkClass{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public class {|#0:{{benchmarkClassName}}|} : BenchmarkClassBase<{{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}}> { - [Benchmark] - public void BenchmarkMethod() - { - - } } """; + var benchmarkBaseClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + TestCode = testCode; + AddSource(benchmarkBaseClassDocument); + + AddDefaultExpectedDiagnostic(benchmarkClassName); await RunAsync(); } - [Theory] - [MemberData(nameof(TypeParametersListLength))] - public async Task Abstract_generic_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) + public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + + private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; + + private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; + } + + public class GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount : AnalyzerTestFixture + { + public GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount() : base(BenchmarkClassAnalyzer.GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule) { } + + [Theory, CombinatorialData] + public async Task Generic_class_annotated_with_a_generictypearguments_attribute_having_matching_type_argument_count_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", genericTypeArgumentsAttributeUsageCount); + var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - - public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> { - [Benchmark] + {{benchmarkAttributeUsage}} public void BenchmarkMethod() { - + } } """; @@ -386,18 +290,20 @@ public void BenchmarkMethod() await RunAsync(); } - [Theory] - [MemberData(nameof(TypeParametersListLength))] - public async Task Nonabstract_generic_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) + [Theory, CombinatorialData] + public async Task Generic_class_annotated_with_a_generictypearguments_attribute_having_mismatching_type_argument_count_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeArgumentsData))] (string TypeArguments, int TypeArgumentCount) typeArgumentsData, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { const string benchmarkClassName = "BenchmarkClass"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - public class {{benchmarkClassName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} + [GenericTypeArguments({|#0:{{typeArgumentsData.TypeArguments}}|})] + [GenericTypeArguments(typeof(int))] + public class {{benchmarkClassName}} { - [Benchmark] + {{benchmarkAttributeUsage}} public void BenchmarkMethod() { @@ -406,26 +312,30 @@ public void BenchmarkMethod() """; TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); + AddDefaultExpectedDiagnostic(1, "", benchmarkClassName, typeArgumentsData.TypeArgumentCount); await RunAsync(); } - public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + public static IEnumerable<(string TypeArguments, int TypeArgumentCount)> TypeArgumentsData => + [ + ("typeof(int), typeof(string)", 2), + ("typeof(int), typeof(string), typeof(bool)", 3) + ]; - public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; + public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; } - public class ClassWithGenericTypeArgumentsAttributeMustBeGeneric : AnalyzerTestFixture + public class MethodMustBePublic : AnalyzerTestFixture { - public ClassWithGenericTypeArgumentsAttributeMustBeGeneric() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeGenericRule) { } + public MethodMustBePublic() : base(BenchmarkClassAnalyzer.MethodMustBePublicRule) { } [Fact] - public async Task Nongeneric_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + public async Task Public_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { const string testCode = /* lang=c#-test */ """ using BenchmarkDotNet.Attributes; @@ -437,6 +347,11 @@ public void BenchmarkMethod() { } + + public void NonBenchmarkMethod() + { + + } } """; @@ -446,17 +361,18 @@ public void BenchmarkMethod() } [Theory] - [MemberData(nameof(TypeParametersListLength))] - public async Task Generic_class_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string nonPublicClassAccessModifier) { + const string benchmarkMethodName = "BenchmarkMethod"; + var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength))}})] - public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + public class BenchmarkClass { [Benchmark] - public void BenchmarkMethod() + {{nonPublicClassAccessModifier}}void {|#0:{{benchmarkMethodName}}|}() { } @@ -464,89 +380,93 @@ public void BenchmarkMethod() """; TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkMethodName); await RunAsync(); } + } + + public class MethodMustBeNonGeneric : AnalyzerTestFixture + { + public MethodMustBeNonGeneric() : base(BenchmarkClassAnalyzer.MethodMustBeNonGenericRule) { } [Fact] - public async Task Class_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_having_no_type_parameters_should_trigger_diagnostic() + public async Task Nongeneric_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string benchmarkClassName = "BenchmarkClass"; - - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; - [GenericTypeArguments(typeof(int))] - public class {|#0:{{benchmarkClassName}}|} - { - [Benchmark] - public void BenchmarkMethod() - { + public class BenchmarkClass + { + [Benchmark] + public void NonGenericBenchmarkMethod() + { - } - } - """; + } + } + """; TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); await RunAsync(); } - [Theory, CombinatorialData] - public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_inheriting_from_an_abstract_generic_class_should_trigger_diagnostic([CombinatorialRange(1, 3)] int attributeUsageCount, - [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength) + [Fact] + public async Task Generic_method_not_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - const string benchmarkClassName = "BenchmarkClass"; + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; - var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); - var attributeUsages = string.Join("\n", Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", attributeUsageCount)); + public class BenchmarkClass + { + public void GenericMethod() + { + + } + } + """; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(TypeParametersListLength))] + public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) + { + const string benchmarkMethodName = "GenericBenchmarkMethod"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - {{attributeUsages}} - public class {|#0:{{benchmarkClassName}}|} : BenchmarkClassBase<{{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}}> + public class BenchmarkClass { + [Benchmark] + public void {{benchmarkMethodName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|}() + { + + } } """; - var benchmarkBaseClassDocument = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; - TestCode = testCode; - AddSource(benchmarkBaseClassDocument); - - AddDefaultExpectedDiagnostic(benchmarkClassName); + AddDefaultExpectedDiagnostic(benchmarkMethodName); await RunAsync(); } - public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; - public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; - - private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; } - public class GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount : AnalyzerTestFixture + public class ClassMustBeNonStatic : AnalyzerTestFixture { - public GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount() : base(BenchmarkClassAnalyzer.GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule) { } + public ClassMustBeNonStatic() : base(BenchmarkClassAnalyzer.ClassMustBeNonStaticRule) { } [Fact] - public async Task Nongeneric_class_not_annotated_with_the_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + public async Task Instance_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { const string testCode = /* lang=c#-test */ """ using BenchmarkDotNet.Attributes; @@ -558,6 +478,11 @@ public void BenchmarkMethod() { } + + public void NonBenchmarkMethod() + { + + } } """; @@ -566,61 +491,29 @@ public void BenchmarkMethod() await RunAsync(); } - [Theory] - [MemberData(nameof(TypeParametersListLength))] - public async Task Generic_class_annotated_with_the_generictypearguments_attribute_having_matching_type_argument_count_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic(int typeParametersListLength) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - [GenericTypeArguments({{string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength))}})] - public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Theory] - [InlineData("typeof(int), typeof(string)", 2)] - [InlineData("typeof(int), typeof(string), typeof(bool)", 3)] - public async Task Generic_class_annotated_with_the_generictypearguments_attribute_having_mismatching_type_argument_count_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string typeArguments, int typeArgumentCount) + [Fact] + public async Task Static_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() { const string benchmarkClassName = "BenchmarkClass"; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - [GenericTypeArguments({|#0:{{typeArguments}}|})] - public class {{benchmarkClassName}} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {|#0:static|} class {{benchmarkClassName}} + { + [Benchmark] + public static void BenchmarkMethod() + { + + } + } + """; TestCode = testCode; - AddDefaultExpectedDiagnostic(1, "", benchmarkClassName, typeArgumentCount); + AddDefaultExpectedDiagnostic(benchmarkClassName); await RunAsync(); } - - public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; - - private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; - - private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; } public class OnlyOneMethodCanBeBaseline : AnalyzerTestFixture From 947e3bd9f1298b80aa9623303e51f5429eece14c Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Thu, 16 Oct 2025 21:53:59 +0200 Subject: [PATCH 15/24] * Integer attribute values that fit within target type range should not trigger mismatching type diagnostics * Test all valid attribute value types when performing type matching --- .../AnalyzerHelper.cs | 88 +++++- .../Attributes/ArgumentsAttributeAnalyzer.cs | 59 ++-- .../Attributes/ParamsAttributeAnalyzer.cs | 42 ++- .../BenchmarkRunner/RunAnalyzer.cs | 11 +- .../General/BenchmarkClassAnalyzer.cs | 8 +- .../ArgumentsAttributeAnalyzerTests.cs | 289 ++++++++++++++---- .../ParamsAllValuesAttributeAnalyzerTests.cs | 6 +- .../ParamsAttributeAnalyzerTests.cs | 288 ++++++++++++----- .../General/BenchmarkClassAnalyzerTests.cs | 18 +- .../BenchmarkDotNet.Analyzers.Tests.csproj | 2 +- ...kDotNet.Analyzers.Tests.csproj.DotSettings | 1 + .../Fixtures/Serializable/ValueTupleDouble.cs | 27 ++ .../Fixtures/Serializable/ValueTupleTriple.cs | 31 ++ ... FieldOrPropertyDeclarationsTheoryData.cs} | 4 +- 14 files changed, 688 insertions(+), 186 deletions(-) create mode 100644 tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleDouble.cs create mode 100644 tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleTriple.cs rename tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/{FieldOrPropertyDeclarationTheoryData.cs => FieldOrPropertyDeclarationsTheoryData.cs} (66%) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index 0347c41d26..68fe2555aa 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -3,11 +3,12 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; + using System; using System.Collections.Immutable; internal static class AnalyzerHelper { - public static LocalizableResourceString GetResourceString(string name) => new LocalizableResourceString(name, BenchmarkDotNetAnalyzerResources.ResourceManager, typeof(BenchmarkDotNetAnalyzerResources)); + public static LocalizableResourceString GetResourceString(string name) => new(name, BenchmarkDotNetAnalyzerResources.ResourceManager, typeof(BenchmarkDotNetAnalyzerResources)); public static INamedTypeSymbol? GetBenchmarkAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkAttribute"); @@ -90,5 +91,90 @@ public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol) return typeName; } + + public static bool ValueFitsInType(object value, ITypeSymbol targetType) + { + + + try + { + switch (targetType.SpecialType) + { + case SpecialType.System_Byte: + var byteVal = Convert.ToInt64(value); + + return byteVal is >= byte.MinValue and <= byte.MaxValue; + + case SpecialType.System_SByte: + var sbyteVal = Convert.ToInt64(value); + + return sbyteVal is >= sbyte.MinValue and <= sbyte.MaxValue; + + case SpecialType.System_Int16: + var int16Val = Convert.ToInt64(value); + + return int16Val is >= short.MinValue and <= short.MaxValue; + + case SpecialType.System_UInt16: + var uint16Val = Convert.ToInt64(value); + + return uint16Val is >= ushort.MinValue and <= ushort.MaxValue; + + case SpecialType.System_Int32: + var int32Val = Convert.ToInt64(value); + + return int32Val is >= int.MinValue and <= int.MaxValue; + + case SpecialType.System_UInt32: + var uint32Val = Convert.ToInt64(value); + + return uint32Val is >= uint.MinValue and <= uint.MaxValue; + + case SpecialType.System_Int64: + { + _ = Convert.ToInt64(value); + } + + return true; + + case SpecialType.System_UInt64: + var val = Convert.ToInt64(value); + + return val >= 0; + + case SpecialType.System_Single: + if (value is double) + { + return false; + } + + var floatVal = Convert.ToSingle(value); + + return !float.IsInfinity(floatVal); + + case SpecialType.System_Double: + var doubleVal = Convert.ToDouble(value); + + return !double.IsInfinity(doubleVal); + + case SpecialType.System_Decimal: + if (value is double or float) + { + return false; + } + + _ = Convert.ToDecimal(value); + + return true; + + default: + return false; + } + } + catch (Exception) + { + return false; + } + } } } diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index fc02ce8415..fec0559c08 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -43,12 +43,13 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_MustHaveMatchingValueType_Description))); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - RequiresBenchmarkAttributeRule, - MethodWithoutAttributeMustHaveNoParametersRule, - MustHaveMatchingValueCountRule, - MustHaveMatchingValueTypeRule - ); + public override ImmutableArray SupportedDiagnostics => + [ + RequiresBenchmarkAttributeRule, + MethodWithoutAttributeMustHaveNoParametersRule, + MustHaveMatchingValueCountRule, + MustHaveMatchingValueTypeRule + ]; public override void Initialize(AnalysisContext analysisContext) { @@ -70,12 +71,12 @@ public override void Initialize(AnalysisContext analysisContext) private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) { - if (!(context.Node is MethodDeclarationSyntax methodDeclarationSyntax)) + if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax) { return; } - var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation); + var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); if (argumentsAttributeTypeSymbol == null) { return; @@ -104,7 +105,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) return; } - var methodParameterTypeSymbolsBuilder = ImmutableArray.CreateBuilder(methodDeclarationSyntax.ParameterList.Parameters.Count); + var methodParameterTypeSymbolsBuilder = ImmutableArray.CreateBuilder(methodDeclarationSyntax.ParameterList.Parameters.Count); foreach (var parameterSyntax in methodDeclarationSyntax.ParameterList.Parameters) { @@ -168,7 +169,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) continue; } - ReportIfValueTypeMismatchDiagnostic(i => collectionExpressionSyntax.Elements[i] is ExpressionElementSyntax expressionElementSyntax ? expressionElementSyntax.Expression : null); + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => collectionExpressionSyntax.Elements[i] is ExpressionElementSyntax expressionElementSyntax ? expressionElementSyntax.Expression : null); } // Array creation expression @@ -208,7 +209,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) } // ReSharper disable once PossibleNullReferenceException - ReportIfValueTypeMismatchDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]); + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]); } } } @@ -229,7 +230,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) } // ReSharper disable once PossibleNullReferenceException - ReportIfValueTypeMismatchDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); } else { @@ -242,7 +243,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) } // ReSharper disable once PossibleNullReferenceException - ReportIfValueTypeMismatchDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); } } } @@ -260,7 +261,7 @@ void ReportMustHaveMatchingValueCountDiagnostic(Location diagnosticLocation, int valueCount)); } - void ReportIfValueTypeMismatchDiagnostic(Func valueExpressionSyntaxFunc) + void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func valueExpressionSyntaxFunc) { for (var i = 0; i < methodParameterTypeSymbols.Length; i++) { @@ -282,23 +283,35 @@ void ReportIfValueTypeMismatchDiagnostic(Func valueExpres var conversionSummary = context.Compilation.ClassifyConversion(actualValueTypeSymbol, methodParameterTypeSymbol); if (!conversionSummary.IsImplicit) { - ReportMustHaveMatchingValueTypeDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionSyntax.ToString(), - methodParameterTypeSymbol.ToString(), - actualValueTypeSymbol.ToString()); + if (conversionSummary is { IsExplicit: true, IsEnumeration: false }) + { + var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax is CastExpressionSyntax castExpressionSyntax ? castExpressionSyntax.Expression : valueExpressionSyntax); + if (constantValue is { HasValue: true, Value: not null }) + { + if (AnalyzerHelper.ValueFitsInType(constantValue.Value, methodParameterTypeSymbol)) + { + return; + } + } + } + + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + methodParameterTypeSymbol.ToString(), + actualValueTypeSymbol.ToString()); } } else { - ReportMustHaveMatchingValueTypeDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionSyntax.ToString(), - methodParameterTypeSymbol.ToString()); + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + methodParameterTypeSymbol.ToString()); } } return; - void ReportMustHaveMatchingValueTypeDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType = null) + void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string? actualType = null) { context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueTypeRule, diagnosticLocation, @@ -309,8 +322,6 @@ void ReportMustHaveMatchingValueTypeDiagnostic(Location diagnosticLocation, stri } } - private static INamedTypeSymbol GetArgumentsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); - private static int? IndexOfNamedArgument(SeparatedSyntaxList attributeArguments) { var i = 0; diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs index 01177ce208..fc2a6f2f69 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs @@ -60,7 +60,7 @@ public override void Initialize(AnalysisContext analysisContext) private static void Analyze(SyntaxNodeAnalysisContext context) { - if (!(context.Node is AttributeSyntax attributeSyntax)) + if (context.Node is not AttributeSyntax attributeSyntax) { return; } @@ -77,7 +77,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax || n is PropertyDeclarationSyntax); + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); if (attributeTarget == null) { return; @@ -169,7 +169,7 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c { if (collectionElementSyntax is ExpressionElementSyntax expressionElementSyntax) { - ReportIfUnexpectedValueTypeDiagnostic(expressionElementSyntax.Expression); + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(expressionElementSyntax.Expression); } } @@ -190,7 +190,7 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First(); if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression)) { - if (literalExpressionSyntax.Token.Value is int rankSpecifierSize && rankSpecifierSize == 0) + if (literalExpressionSyntax.Token.Value is 0) { context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, arrayCreationExpressionSyntax.GetLocation())); @@ -216,7 +216,7 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions) { - ReportIfUnexpectedValueTypeDiagnostic(expressionSyntax); + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(expressionSyntax); } } } @@ -241,12 +241,12 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c continue; } - ReportIfUnexpectedValueTypeDiagnostic(parameterValueAttributeArgumentSyntax.Expression); + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(parameterValueAttributeArgumentSyntax.Expression); } return; - void ReportIfUnexpectedValueTypeDiagnostic(ExpressionSyntax valueExpressionSyntax) + void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(ExpressionSyntax valueExpressionSyntax) { var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type; if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error) @@ -254,22 +254,34 @@ void ReportIfUnexpectedValueTypeDiagnostic(ExpressionSyntax valueExpressionSynta var conversionSummary = context.Compilation.ClassifyConversion(actualValueTypeSymbol, expectedValueTypeSymbol); if (!conversionSummary.IsImplicit) { - ReportUnexpectedValueTypeDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionSyntax.ToString(), - fieldOrPropertyTypeSyntax.ToString(), - actualValueTypeSymbol.ToString()); + if (conversionSummary is { IsExplicit: true, IsEnumeration: false }) + { + var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax is CastExpressionSyntax castExpressionSyntax ? castExpressionSyntax.Expression : valueExpressionSyntax); + if (constantValue is { HasValue: true, Value: not null }) + { + if (AnalyzerHelper.ValueFitsInType(constantValue.Value, expectedValueTypeSymbol)) + { + return; + } + } + } + + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + fieldOrPropertyTypeSyntax.ToString(), + actualValueTypeSymbol.ToString()); } } else { - ReportUnexpectedValueTypeDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionSyntax.ToString(), - fieldOrPropertyTypeSyntax.ToString()); + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + fieldOrPropertyTypeSyntax.ToString()); } return; - void ReportUnexpectedValueTypeDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType = null) + void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string? actualType = null) { context.ReportDiagnostic(Diagnostic.Create(UnexpectedValueTypeRule, diagnosticLocation, diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index 3c02436030..92e8e83d5e 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -84,10 +84,17 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } + var benchmarkRunnerTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"); + if ( benchmarkRunnerTypeSymbol == null + || benchmarkRunnerTypeSymbol.TypeKind == TypeKind.Error) + { + return; + } + var classMemberAccessTypeSymbol = context.SemanticModel.GetTypeInfo(identifierNameSyntax).Type; if ( classMemberAccessTypeSymbol is null || classMemberAccessTypeSymbol.TypeKind == TypeKind.Error - || !classMemberAccessTypeSymbol.Equals(context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"), SymbolEqualityComparer.Default)) + || !classMemberAccessTypeSymbol.Equals(benchmarkRunnerTypeSymbol, SymbolEqualityComparer.Default)) { return; } @@ -117,7 +124,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - // TODO: Support analyzing an array of typeof() expressions + // TODO: Support analyzing a collection of typeof() expressions if (invocationExpression.ArgumentList.Arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpression) { return; diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 008597e0df..0cc9588b8f 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -157,10 +157,10 @@ private static void Analyze(SyntaxNodeAnalysisContext context) if (genericTypeArgumentsAttributes.Length == 0) { - if (classDeclarationSyntax.TypeParameterList != null && !classAbstractModifier.HasValue) - { - context.ReportDiagnostic(Diagnostic.Create(GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, classDeclarationSyntax.TypeParameterList.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } + //if (classDeclarationSyntax.TypeParameterList != null && !classAbstractModifier.HasValue) + //{ + // context.ReportDiagnostic(Diagnostic.Create(GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, classDeclarationSyntax.TypeParameterList.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + //} } else { diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs index f3719a0e56..31cdf4cb59 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -499,7 +499,7 @@ public void BenchmarkMethod(string a) } [Theory, CombinatorialData] - public async Task Having_a_mismatching_value_count_with_nonempty_argument_attribute_usages_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ArgumentsAttributeUsagesWithMismatchingValueCount))] string argumentsAttributeUsage, + public async Task Having_a_mismatching_value_count_with_nonempty_argument_attribute_usages_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, [CombinatorialValues("string a", "")] string parameters) { var testCode = /* lang=c#-test */ $$""" @@ -507,7 +507,12 @@ public async Task Having_a_mismatching_value_count_with_nonempty_argument_attrib public class BenchmarkClass { [Benchmark] - {{argumentsAttributeUsage}} + [{{string.Format(scalarValuesContainerAttributeArgument, """ + 42, "test" + """)}}] + [{{string.Format(scalarValuesContainerAttributeArgument, """ + "value", 100, true + """)}}] public void BenchmarkMethod({{parameters}}) { @@ -519,9 +524,10 @@ public void BenchmarkMethod({{parameters}}) await RunAsync(); } - [Theory] - [MemberData(nameof(ArgumentsAttributeUsagesWithMatchingValueTypes))] - public async Task Having_matching_value_types_should_not_trigger_diagnostic(string argumentsAttributeUsage) + [Theory, CombinatorialData] + public async Task Providing_expected_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -529,8 +535,8 @@ public async Task Having_matching_value_types_should_not_trigger_diagnostic(stri public class BenchmarkClass { [Benchmark] - {{argumentsAttributeUsage}} - public void BenchmarkMethod(int a, string b) + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, valueAndType.Value1)}}] + public void BenchmarkMethod({{valueAndType.Value2}} a) { } @@ -538,13 +544,17 @@ public void BenchmarkMethod(int a, string b) """; TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); await RunAsync(); } - [Theory] - [MemberData(nameof(ArgumentsAttributeUsagesWithConvertibleValueTypes))] - public async Task Providing_convertible_value_types_should_not_trigger_diagnostic(string argumentsAttributeUsage) + [Theory, CombinatorialData] + public async Task Providing_integer_value_types_within_target_type_range_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(IntegerValuesAndTypesWithinTargetTypeRange))] ValueTupleDouble integerValueAndType, + bool explicitCast) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -552,8 +562,8 @@ public async Task Providing_convertible_value_types_should_not_trigger_diagnosti public class BenchmarkClass { [Benchmark] - {{argumentsAttributeUsage}} - public void BenchmarkMethod(int a, string b) + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{(explicitCast ? $"({integerValueAndType.Value2})" : "")}{integerValueAndType.Value1}")}}] + public void BenchmarkMethod({{integerValueAndType.Value2}} a) { } @@ -561,13 +571,15 @@ public void BenchmarkMethod(int a, string b) """; TestCode = testCode; + ReferenceDummyAttribute(); await RunAsync(); } - [Theory] - [MemberData(nameof(ArgumentsAttributeUsagesWithMatchingValueTypes))] - public async Task Having_unknown_parameter_type_should_not_trigger_diagnostic(string argumentsAttributeUsage) + [Theory, CombinatorialData] + public async Task Providing_implicitly_convertible_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, + [CombinatorialValues("(byte)42", "'c'")] string value) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -575,7 +587,32 @@ public async Task Having_unknown_parameter_type_should_not_trigger_diagnostic(st public class BenchmarkClass { [Benchmark] - {{argumentsAttributeUsage}} + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, value)}}] + public void BenchmarkMethod(int a) + { + + } + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Having_unknown_parameter_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "42, \"test\"")}}] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "43, \"test2\"")}}] public void BenchmarkMethod(unkown a, string b) { @@ -584,24 +621,58 @@ public void BenchmarkMethod(unkown a, string b) """; TestCode = testCode; + ReferenceDummyAttribute(); DisableCompilerDiagnostics(); await RunAsync(); } - [Theory] - [MemberData(nameof(ArgumentsAttributeUsagesWithMismatchingValueTypesWithLocationMarker))] - public async Task Having_mismatching_or_not_convertible_value_types_should_trigger_diagnostic(string argumentsAttributeUsage) + [Theory, CombinatorialData] + public async Task Providing_an_unexpected_or_not_implicitly_convertible_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NotConvertibleValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + { + const string expectedArgumentType = "decimal"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType.Value1}|}}")}}] + public void BenchmarkMethod({{expectedArgumentType}} a) + { + + } + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + AddDefaultExpectedDiagnostic(valueAndType.Value1!, expectedArgumentType, valueAndType.Value2!); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_integer_value_types_not_within_target_type_range_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NotConvertibleValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) { + const string expectedArgumentType = "decimal"; + var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; public class BenchmarkClass { [Benchmark] - {{argumentsAttributeUsage}} - public void BenchmarkMethod(byte a, bool b) + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType.Value1}|}}")}}] + public void BenchmarkMethod({{expectedArgumentType}} a) { } @@ -609,17 +680,17 @@ public void BenchmarkMethod(byte a, bool b) """; TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); - AddExpectedDiagnostic(0, "typeof(string)", "byte", "System.Type"); - AddExpectedDiagnostic(1, "\"test\"", "bool", "string"); - AddExpectedDiagnostic(2, "999", "byte", "int"); + AddDefaultExpectedDiagnostic(valueAndType.Value1!, expectedArgumentType, valueAndType.Value2!); await RunAsync(); } - [Theory] - [MemberData(nameof(ArgumentsAttributeUsagesWithUnknownValueTypesWithLocationMarker))] - public async Task Providing_an_unkown_value_type_should_trigger_diagnostic(string argumentsAttributeUsage) + [Theory, CombinatorialData] + public async Task Providing_an_unkown_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -627,7 +698,7 @@ public async Task Providing_an_unkown_value_type_should_trigger_diagnostic(strin public class BenchmarkClass { [Benchmark] - {{argumentsAttributeUsage}} + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:dummy_literal|}, true")}}] public void BenchmarkMethod(byte a, bool b) { @@ -636,6 +707,7 @@ public void BenchmarkMethod(byte a, bool b) """; TestCode = testCode; + ReferenceDummyAttribute(); DisableCompilerDiagnostics(); AddDefaultExpectedDiagnostic("dummy_literal", "byte", ""); @@ -643,6 +715,8 @@ public void BenchmarkMethod(byte a, bool b) await RunAsync(); } + public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + public static TheoryData EmptyArgumentsAttributeUsagesWithMismatchingValueCount() { return new TheoryData(GenerateData()); @@ -685,37 +759,140 @@ static IEnumerable GenerateData() } } - public static IEnumerable ArgumentsAttributeUsagesWithMismatchingValueCount() => GenerateAttributeUsages( - [ - "42, \"test\"", - "\"value\", 100, true" - ]); + public static IEnumerable ScalarValuesContainerAttributeArgumentEnumerableLocal => ScalarValuesContainerAttributeArgumentEnumerable(); - public static TheoryData ArgumentsAttributeUsagesWithMatchingValueTypes() => new(GenerateAttributeUsages( + public static IEnumerable> IntegerValuesAndTypesWithinTargetTypeRange => [ - "42, \"test\"", - "43, \"test2\"" - ])); - - public static TheoryData ArgumentsAttributeUsagesWithConvertibleValueTypes() => new(GenerateAttributeUsages( + // byte (0 to 255) + ("0", "byte"), + ("100", "byte"), + ("255", "byte"), + + // sbyte (-128 to 127) + ("-128", "sbyte"), + ("0", "sbyte"), + ("127", "sbyte"), + + // short (-32,768 to 32,767) + ("-32768", "short"), + ("0", "short"), + ("32767", "short"), + + // ushort (0 to 65,535) + ("0", "ushort"), + ("1000", "ushort"), + ("65535", "ushort"), + + // int (-2,147,483,648 to 2,147,483,647) + ("-2147483648", "int"), + ("0", "int"), + ("2147483647", "int"), + + // uint (0 to 4,294,967,295) + ("0", "uint"), + ("1000000", "uint"), + ("4294967295", "uint"), + + // long (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807) + ("-9223372036854775808", "long"), + ("0", "long"), + ("9223372036854775807", "long"), + + // ulong (0 to 18,446,744,073,709,551,615) + ("0", "ulong"), + ("1000000", "ulong"), + ("18446744073709551615", "ulong"), + ]; + + public static IEnumerable> IntegerValuesAndTypesNotWithinTargetTypeRange => [ - "42, \"test\"", - "(byte)5, \"test2\"" - ])); - - public static TheoryData ArgumentsAttributeUsagesWithMismatchingValueTypesWithLocationMarker() => new(GenerateAttributeUsages( + // byte (0 to 255) - out of range values + ("-1", "byte", "int"), + ("256", "byte", "int"), + ("1000", "byte", "int"), + + // sbyte (-128 to 127) - out of range values + ("-129", "sbyte", "int"), + ("128", "sbyte", "int"), + ("500", "sbyte", "int"), + + // short (-32,768 to 32,767) - out of range values + ("-32769", "short", "int"), + ("32768", "short", "int"), + ("100000", "short", "int"), + + // ushort (0 to 65,535) - out of range values + ("-1", "ushort", "int"), + ("65536", "ushort", "int"), + ("100000", "ushort", "int"), + + // int (-2,147,483,648 to 2,147,483,647) - out of range values + ("-2147483649", "int", "long"), + ("2147483648", "int", "uint"), + ("5000000000", "int", "long"), + + // uint (0 to 4,294,967,295) - out of range values + ("-1", "uint", "int"), + ("4294967296", "uint", "long"), + ("5000000000", "uint", "long"), + + // long - out of range values (exceeding long range) + ("9223372036854775808", "long", "ulong"), + + // ulong - negative values + ("-1", "ulong", "int"), + ("-100", "ulong", "int"), + ("-9223372036854775808", "ulong", "long"), + ]; + + public static IEnumerable> ValuesAndTypes => [ - "{|#0:typeof(string)|}, {|#1:\"test\"|}", - "{|#2:999|}, true" - ])); - - public static TheoryData ArgumentsAttributeUsagesWithUnknownValueTypesWithLocationMarker() => new(GenerateAttributeUsages( + ( "true", "bool" ), + ( "(byte)123", "byte" ), + ( "'A'", "char" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( "123", "int" ), + ( "123L", "long" ), + ( "(sbyte)-100", "sbyte" ), + ( "(short)-123", "short" ), + ( """ + "test" + """, "string" ), + ( "123U", "uint" ), + ( "123UL", "ulong" ), + ( "(ushort)123", "ushort" ), + + ( """ + (object)"test_object" + """, "object" ), + ( "typeof(string)", "System.Type" ), + ( "DummyEnum.Value1", "DummyEnum" ) + ]; + + public static IEnumerable> NotConvertibleValuesAndTypes => [ - "{|#0:dummy_literal|}, true" - ])); + ( "true", "bool" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( """ + "test" + """, "string" ), + + ( """ + (object)"test_object" + """, "object" ), + ( "typeof(string)", "System.Type" ), + ( "DummyEnum.Value1", "DummyEnum" ) + ]; } - private static IEnumerable GenerateAttributeUsages(List valueLists) + public static TheoryData DummyAttributeUsageTheoryData => [ + "", + "Dummy, " + ]; + + private static IEnumerable ScalarValuesContainerAttributeArgumentEnumerable() { var nameColonUsages = new List { @@ -731,9 +908,9 @@ private static IEnumerable GenerateAttributeUsages(List valueLis var attributeUsagesBase = new List { - "[Arguments({1}{2})]", - "[Arguments({0}new object[] {{ {1} }}{2})]", - "[Arguments({0}[ {1} ]{2})]" + "Arguments({{0}}{1})", + "Arguments({0}new object[] {{{{ {{0}} }}}}{1})", + "Arguments({0}[ {{0}} ]{1})" }; foreach (var attributeUsageBase in attributeUsagesBase) @@ -742,7 +919,7 @@ private static IEnumerable GenerateAttributeUsages(List valueLis { foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { - yield return string.Join("\n ", valueLists.Select(vv => string.Format(attributeUsageBase, nameColonUsage, vv, priorityNamedParameterUsage))); + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); } } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs index ed9dc35c87..701d5a2a06 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs @@ -35,7 +35,7 @@ public class BenchmarkClass await RunAsync(); } - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); public static IEnumerable InvalidTypes => new TheoryData { @@ -140,7 +140,7 @@ public class BenchmarkClass await RunAsync(); } - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); public static IEnumerable NonEnumStructs => new List { @@ -229,7 +229,7 @@ public class BenchmarkClass await RunAsync(); } - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); public static IEnumerable NonEnumOrBoolStructs => new List { diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs index 63f9fa7af8..fcbb63b8ca 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -3,6 +3,7 @@ using Fixtures; using BenchmarkDotNet.Analyzers.Attributes; + using Xunit; using System.Collections.Generic; @@ -34,7 +35,7 @@ public string {{fieldOrPropertyDeclaration}} await RunAsync(); } - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); } public class MustHaveValues : AnalyzerTestFixture @@ -109,7 +110,7 @@ public string {{fieldOrPropertyDeclaration}} await RunAsync(); } - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; @@ -178,7 +179,6 @@ public unknown {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; - ReferenceDummyAttribute(); DisableCompilerDiagnostics(); @@ -186,21 +186,65 @@ public unknown {{fieldOrPropertyDeclaration}} } [Theory, CombinatorialData] - public async Task Providing_convertible_value_types_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + public async Task Providing_expected_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, valueAndType.Value1)}})] + public {{valueAndType.Value2}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_implicitly_convertible_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument, + [CombinatorialValues("(byte)42", "'c'")] string value) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "(byte)42")}})] + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, value)}})] public int {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + [Theory, CombinatorialData] + public async Task Providing_integer_value_types_within_target_type_range_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(IntegerValuesAndTypesWithinTargetTypeRange))] ValueTupleDouble integerValueAndType, + bool explicitCast) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{(explicitCast ? $"({integerValueAndType.Value2})" : "")}{integerValueAndType.Value1}")}})] + public {{integerValueAndType.Value2}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; ReferenceDummyAttribute(); await RunAsync(); @@ -225,6 +269,7 @@ public class BenchmarkClass """; TestCode = testCode; ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(valueWithUnexpectedType, expectedFieldOrPropertyType, "string"); await RunAsync(); @@ -248,20 +293,19 @@ public class BenchmarkClass } """; TestCode = testCode; - ReferenceDummyAttribute(); + DisableCompilerDiagnostics(); AddDefaultExpectedDiagnostic(valueWithUnknownType, expectedFieldOrPropertyType, ""); await RunAsync(); } - [Theory] - [MemberData(nameof(NotConvertibleValueTypeCombinations))] - public async Task Providing_an_unexpected_or_not_convertible_value_type_should_trigger_diagnostic(string fieldOrPropertyDeclaration, - string dummyAttributeUsage, - string[] valueAndType, - string scalarValuesContainerAttributeArgument) + [Theory, CombinatorialData] + public async Task Providing_an_unexpected_or_not_implicitly_convertible_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NotConvertibleValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) { const string expectedFieldOrPropertyType = "decimal"; @@ -270,24 +314,48 @@ public async Task Providing_an_unexpected_or_not_convertible_value_type_should_t public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType[0]}|}}")}})] + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType.Value1}|}}")}})] public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; ReferenceDummyAttribute(); ReferenceDummyEnum(); - AddDefaultExpectedDiagnostic(valueAndType[0], expectedFieldOrPropertyType, valueAndType[1]); + + AddDefaultExpectedDiagnostic(valueAndType.Value1!, expectedFieldOrPropertyType, valueAndType.Value2!); await RunAsync(); } - [Theory] - [MemberData(nameof(UnexpectedArrayValueTypeCombinations))] - public async Task Providing_an_unexpected_array_value_type_to_params_attribute_should_trigger_diagnostic(string fieldOrPropertyDeclaration, - string dummyAttributeUsage, - string[] valueAndType, - string[] arrayValuesContainerAttributeArgument) + [Theory, CombinatorialData] + public async Task Providing_integer_value_types_not_within_target_type_range_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(IntegerValuesAndTypesNotWithinTargetTypeRange))] ValueTupleTriple integerValueAndType) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{integerValueAndType.Value1}|}}")}})] + public {{integerValueAndType.Value2}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(integerValueAndType.Value1!, integerValueAndType.Value2!, integerValueAndType.Value3!); + + await RunAsync(); + } + + + + [Theory, CombinatorialData] + public async Task Providing_an_unexpected_array_value_type_to_params_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ArrayValuesContainerAttributeArgumentWithLocationMarker))] ValueTupleDouble arrayValuesContainerAttributeArgument) { const string expectedFieldOrPropertyType = "decimal"; @@ -296,19 +364,19 @@ public async Task Providing_an_unexpected_array_value_type_to_params_attribute_s public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(arrayValuesContainerAttributeArgument[0], valueAndType[0], valueAndType[1])}})] + [{{dummyAttributeUsage}}Params({{string.Format(arrayValuesContainerAttributeArgument.Value1, valueAndType.Value1, valueAndType.Value2)}})] public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; ReferenceDummyAttribute(); ReferenceDummyEnum(); - AddDefaultExpectedDiagnostic( - string.Format(arrayValuesContainerAttributeArgument[1], - valueAndType[0], - valueAndType[1]), + + AddDefaultExpectedDiagnostic(string.Format(arrayValuesContainerAttributeArgument.Value2, + valueAndType.Value1, + valueAndType.Value2), expectedFieldOrPropertyType, - $"{valueAndType[1]}[]"); + $"{valueAndType.Value2}[]"); await RunAsync(); } @@ -353,10 +421,94 @@ public object[] {{fieldOrPropertyDeclaration}} await RunAsync(); } - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + public static IEnumerable> IntegerValuesAndTypesWithinTargetTypeRange => + [ + // byte (0 to 255) + ("0", "byte"), + ("100", "byte"), + ("255", "byte"), + + // sbyte (-128 to 127) + ("-128", "sbyte"), + ("0", "sbyte"), + ("127", "sbyte"), + + // short (-32,768 to 32,767) + ("-32768", "short"), + ("0", "short"), + ("32767", "short"), + + // ushort (0 to 65,535) + ("0", "ushort"), + ("1000", "ushort"), + ("65535", "ushort"), + + // int (-2,147,483,648 to 2,147,483,647) + ("-2147483648", "int"), + ("0", "int"), + ("2147483647", "int"), + + // uint (0 to 4,294,967,295) + ("0", "uint"), + ("1000000", "uint"), + ("4294967295", "uint"), + + // long (-9,223,372,036,854,775,808 to 9,223,372,036,854,775,807) + ("-9223372036854775808", "long"), + ("0", "long"), + ("9223372036854775807", "long"), + + // ulong (0 to 18,446,744,073,709,551,615) + ("0", "ulong"), + ("1000000", "ulong"), + ("18446744073709551615", "ulong"), + ]; + + public static IEnumerable> IntegerValuesAndTypesNotWithinTargetTypeRange => + [ + // byte (0 to 255) - out of range values + ("-1", "byte", "int"), + ("256", "byte", "int"), + ("1000", "byte", "int"), + + // sbyte (-128 to 127) - out of range values + ("-129", "sbyte", "int"), + ("128", "sbyte", "int"), + ("500", "sbyte", "int"), + + // short (-32,768 to 32,767) - out of range values + ("-32769", "short", "int"), + ("32768", "short", "int"), + ("100000", "short", "int"), + + // ushort (0 to 65,535) - out of range values + ("-1", "ushort", "int"), + ("65536", "ushort", "int"), + ("100000", "ushort", "int"), + + // int (-2,147,483,648 to 2,147,483,647) - out of range values + ("-2147483649", "int", "long"), + ("2147483648", "int", "uint"), + ("5000000000", "int", "long"), + + // uint (0 to 4,294,967,295) - out of range values + ("-1", "uint", "int"), + ("4294967296", "uint", "long"), + ("5000000000", "uint", "long"), + + // long - out of range values (exceeding long range) + ("9223372036854775808", "long", "ulong"), + + // ulong - negative values + ("-1", "ulong", "int"), + ("-100", "ulong", "int"), + ("-9223372036854775808", "ulong", "long"), + ]; + public static IEnumerable EmptyValuesAttributeArgument() { yield return ""; @@ -394,13 +546,9 @@ public static IEnumerable EmptyValuesAttributeArgument() } } - public static IEnumerable UnexpectedArrayValueTypeCombinations => CombinationsGenerator.CombineArguments(FieldOrPropertyDeclarations, DummyAttributeUsage, ValuesAndTypes, ArrayValuesContainerAttributeArgumentWithLocationMarker()); - - public static IEnumerable NotConvertibleValueTypeCombinations => CombinationsGenerator.CombineArguments(FieldOrPropertyDeclarations, DummyAttributeUsage, NotConvertibleValuesAndTypes, ScalarValuesContainerAttributeArgument); - public static IEnumerable ScalarValuesContainerAttributeArgument => ScalarValuesContainerAttributeArgumentTheoryData(); - public static IEnumerable ArrayValuesContainerAttributeArgumentWithLocationMarker() + public static IEnumerable> ArrayValuesContainerAttributeArgumentWithLocationMarker() { var nameColonUsages = new List { @@ -432,54 +580,55 @@ public static IEnumerable ArrayValuesContainerAttributeArgumentWithLoc { foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { - yield return [ - string.Format(attributeUsageBase.Item1, nameColonUsage, priorityNamedParameterUsage), - attributeUsageBase.Item2 - ]; + yield return new ValueTupleDouble + { + Value1 = string.Format(attributeUsageBase.Item1, nameColonUsage, priorityNamedParameterUsage), + Value2 = attributeUsageBase.Item2 + }; } } } } - public static IEnumerable ValuesAndTypes => + public static IEnumerable> ValuesAndTypes => [ - [ "true", "bool" ], - [ "(byte)123", "byte" ], - [ "'A'", "char" ], - [ "1.0D", "double" ], - [ "1.0F", "float" ], - [ "123", "int" ], - [ "123L", "long" ], - [ "(sbyte)-100", "sbyte" ], - [ "(short)-123", "short" ], - [ """ + ( "true", "bool" ), + ( "(byte)123", "byte" ), + ( "'A'", "char" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( "123", "int" ), + ( "123L", "long" ), + ( "(sbyte)-100", "sbyte" ), + ( "(short)-123", "short" ), + ( """ "test" - """, "string" ], - [ "123U", "uint" ], - [ "123UL", "ulong" ], - [ "(ushort)123", "ushort" ], + """, "string" ), + ( "123U", "uint" ), + ( "123UL", "ulong" ), + ( "(ushort)123", "ushort" ), - [ """ + ( """ (object)"test_object" - """, "object" ], - [ "typeof(string)", "System.Type" ], - [ "DummyEnum.Value1", "DummyEnum" ] + """, "object" ), + ( "typeof(string)", "System.Type" ), + ( "DummyEnum.Value1", "DummyEnum" ) ]; - public static IEnumerable NotConvertibleValuesAndTypes => + public static IEnumerable> NotConvertibleValuesAndTypes => [ - [ "true", "bool" ], - [ "1.0D", "double" ], - [ "1.0F", "float" ], - [ """ + ( "true", "bool" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( """ "test" - """, "string" ], + """, "string" ), - [ """ + ( """ (object)"test_object" - """, "object" ], - [ "typeof(string)", "System.Type" ], - [ "DummyEnum.Value1", "DummyEnum" ] + """, "object" ), + ( "typeof(string)", "System.Type" ), + ( "DummyEnum.Value1", "DummyEnum" ) ]; } @@ -525,13 +674,14 @@ public string {{fieldOrPropertyDeclaration}} """; TestCode = testCode; - AddDefaultExpectedDiagnostic(); ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(); + await RunAsync(); } - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationTheoryData(); + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 2d6acb0967..264837166f 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -102,8 +102,8 @@ public void BenchmarkMethod() } [Theory, CombinatorialData] - public async Task Abstract_generic_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) + public async Task Abstract_generic_class_not_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -124,8 +124,8 @@ public void BenchmarkMethod() } [Theory, CombinatorialData] - public async Task Nonabstract_generic_class_not_annotated_with_a_generictypearguments_attribute_and_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) + public async Task Nonabstract_generic_class_not_annotated_with_a_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { const string benchmarkClassName = "BenchmarkClass"; @@ -291,7 +291,7 @@ public void BenchmarkMethod() } [Theory, CombinatorialData] - public async Task Generic_class_annotated_with_a_generictypearguments_attribute_having_mismatching_type_argument_count_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeArgumentsData))] (string TypeArguments, int TypeArgumentCount) typeArgumentsData, + public async Task Generic_class_annotated_with_a_generictypearguments_attribute_having_mismatching_type_argument_count_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeArgumentsData))] ValueTupleDouble typeArgumentsData, [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) { const string benchmarkClassName = "BenchmarkClass"; @@ -299,7 +299,7 @@ public async Task Generic_class_annotated_with_a_generictypearguments_attribute_ var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - [GenericTypeArguments({|#0:{{typeArgumentsData.TypeArguments}}|})] + [GenericTypeArguments({|#0:{{typeArgumentsData.Value1}}|})] [GenericTypeArguments(typeof(int))] public class {{benchmarkClassName}} { @@ -312,12 +312,12 @@ public void BenchmarkMethod() """; TestCode = testCode; - AddDefaultExpectedDiagnostic(1, "", benchmarkClassName, typeArgumentsData.TypeArgumentCount); + AddDefaultExpectedDiagnostic(1, "", benchmarkClassName, typeArgumentsData.Value2); await RunAsync(); } - public static IEnumerable<(string TypeArguments, int TypeArgumentCount)> TypeArgumentsData => + public static IEnumerable> TypeArgumentsData => [ ("typeof(int), typeof(string)", 2), ("typeof(int), typeof(string), typeof(bool)", 3) @@ -433,7 +433,7 @@ public void GenericMethod() [Theory] [MemberData(nameof(TypeParametersListLength))] - public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) + public async Task Nongeneric_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) { const string benchmarkMethodName = "GenericBenchmarkMethod"; diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj index b7dced86f3..ee1e876743 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj +++ b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings index b8e08e1b17..4ee741cd7e 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings +++ b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj.DotSettings @@ -1,5 +1,6 @@  True True + True True True \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleDouble.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleDouble.cs new file mode 100644 index 0000000000..c7b6309cea --- /dev/null +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleDouble.cs @@ -0,0 +1,27 @@ +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + using Xunit.Abstractions; + + public class ValueTupleDouble : IXunitSerializable + { + public T1? Value1 { get; set; } + + public T2? Value2 { get; set; } + + public void Deserialize(IXunitSerializationInfo info) + { + Value1 = info.GetValue(nameof(Value1)); + Value2 = info.GetValue(nameof(Value2)); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(Value1), Value1); + info.AddValue(nameof(Value2), Value2); + } + + public static implicit operator ValueTupleDouble((T1, T2) valueTupleDouble) => new() { Value1 = valueTupleDouble.Item1, Value2 = valueTupleDouble.Item2 }; + + public override string ToString() => Value1 == null || Value2 == null ? "" : $"{Value1} · {Value2}"; + } +} diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleTriple.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleTriple.cs new file mode 100644 index 0000000000..ae3cd9614b --- /dev/null +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleTriple.cs @@ -0,0 +1,31 @@ +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +{ + using Xunit.Abstractions; + + public class ValueTupleTriple : IXunitSerializable + { + public T1? Value1 { get; set; } + + public T2? Value2 { get; set; } + + public T3? Value3 { get; set; } + + public void Deserialize(IXunitSerializationInfo info) + { + Value1 = info.GetValue(nameof(Value1)); + Value2 = info.GetValue(nameof(Value2)); + Value3 = info.GetValue(nameof(Value3)); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue(nameof(Value1), Value1); + info.AddValue(nameof(Value2), Value2); + info.AddValue(nameof(Value3), Value3); + } + + public static implicit operator ValueTupleTriple((T1, T2, T3) valueTupleTriple) => new() { Value1 = valueTupleTriple.Item1, Value2 = valueTupleTriple.Item2, Value3 = valueTupleTriple.Item3 }; + + public override string ToString() => Value1 == null || Value2 == null || Value3 == null ? "" : $"{Value1} · {Value2} · {Value3}"; + } +} diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationsTheoryData.cs similarity index 66% rename from tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs rename to tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationsTheoryData.cs index 3acb3818cc..9ddaacf203 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationTheoryData.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationsTheoryData.cs @@ -2,9 +2,9 @@ namespace BenchmarkDotNet.Analyzers.Tests.Fixtures { - internal sealed class FieldOrPropertyDeclarationTheoryData : TheoryData + internal sealed class FieldOrPropertyDeclarationsTheoryData : TheoryData { - public FieldOrPropertyDeclarationTheoryData() + public FieldOrPropertyDeclarationsTheoryData() { AddRange( #if NET5_0_OR_GREATER From 2869a552b97335bc9e84a3e86b75fba0d1cd962d Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:44:33 +0200 Subject: [PATCH 16/24] Use a dummy syntax tree to test whether types are implicitly convertible --- .../AnalyzerHelper.cs | 90 +++---------------- .../Attributes/ArgumentsAttributeAnalyzer.cs | 19 +--- .../Attributes/ParamsAttributeAnalyzer.cs | 21 ++--- 3 files changed, 19 insertions(+), 111 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index 68fe2555aa..c150bb61a1 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -1,9 +1,9 @@ namespace BenchmarkDotNet.Analyzers { using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; - using System; using System.Collections.Immutable; internal static class AnalyzerHelper @@ -92,89 +92,19 @@ public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol) return typeName; } - public static bool ValueFitsInType(object value, ITypeSymbol targetType) + public static bool IsConstantAssignableToType(Compilation compilation, ITypeSymbol targetType, string valueExpression) { + var code = $$""" + file class Internal { + {{targetType}} x = {{valueExpression}}; + } + """; + var syntaxTree = CSharpSyntaxTree.ParseText(code); - try - { - switch (targetType.SpecialType) - { - case SpecialType.System_Byte: - var byteVal = Convert.ToInt64(value); - - return byteVal is >= byte.MinValue and <= byte.MaxValue; - - case SpecialType.System_SByte: - var sbyteVal = Convert.ToInt64(value); - - return sbyteVal is >= sbyte.MinValue and <= sbyte.MaxValue; - - case SpecialType.System_Int16: - var int16Val = Convert.ToInt64(value); - - return int16Val is >= short.MinValue and <= short.MaxValue; - - case SpecialType.System_UInt16: - var uint16Val = Convert.ToInt64(value); - - return uint16Val is >= ushort.MinValue and <= ushort.MaxValue; - - case SpecialType.System_Int32: - var int32Val = Convert.ToInt64(value); - - return int32Val is >= int.MinValue and <= int.MaxValue; - - case SpecialType.System_UInt32: - var uint32Val = Convert.ToInt64(value); - - return uint32Val is >= uint.MinValue and <= uint.MaxValue; - - case SpecialType.System_Int64: - { - _ = Convert.ToInt64(value); - } - - return true; + var diagnostics = compilation.AddSyntaxTrees(syntaxTree).GetSemanticModel(syntaxTree).GetMethodBodyDiagnostics(); - case SpecialType.System_UInt64: - var val = Convert.ToInt64(value); - - return val >= 0; - - case SpecialType.System_Single: - if (value is double) - { - return false; - } - - var floatVal = Convert.ToSingle(value); - - return !float.IsInfinity(floatVal); - - case SpecialType.System_Double: - var doubleVal = Convert.ToDouble(value); - - return !double.IsInfinity(doubleVal); - - case SpecialType.System_Decimal: - if (value is double or float) - { - return false; - } - - _ = Convert.ToDecimal(value); - - return true; - - default: - return false; - } - } - catch (Exception) - { - return false; - } + return diagnostics.Length == 0; } } } diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index fec0559c08..6877207f9a 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -277,24 +277,13 @@ void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func Date: Fri, 17 Oct 2025 16:12:56 +0200 Subject: [PATCH 17/24] Move "Generic class must be abstract or annotated with a [GenericTypeArgumentsAttribute]" to Run analyzer and remove abstract modifier requirement --- .../AnalyzerHelper.cs | 17 +- .../AnalyzerReleases.Unshipped.md | 20 +- ...nchmarkDotNetAnalyzerResources.Designer.cs | 50 +- .../BenchmarkDotNetAnalyzerResources.resx | 11 +- .../BenchmarkRunner/RunAnalyzer.cs | 34 +- .../DiagnosticIds.cs | 18 +- .../General/BenchmarkClassAnalyzer.cs | 8 - .../BenchmarkRunner/RunAnalyzerTests.cs | 436 ++++++++++++++---- .../General/BenchmarkClassAnalyzerTests.cs | 85 ---- 9 files changed, 442 insertions(+), 237 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index c150bb61a1..e3694f134c 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -3,8 +3,9 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; - + using System.Collections.Generic; using System.Collections.Immutable; + using System.Linq; internal static class AnalyzerHelper { @@ -16,7 +17,7 @@ internal static class AnalyzerHelper public static bool AttributeListsContainAttribute(INamedTypeSymbol? attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) { - if (attributeTypeSymbol == null) + if (attributeTypeSymbol == null || attributeTypeSymbol.TypeKind == TypeKind.Error) { return false; } @@ -41,6 +42,18 @@ public static bool AttributeListsContainAttribute(INamedTypeSymbol? attributeTyp return false; } + public static bool AttributeListContainsAttribute(string attributeName, Compilation compilation, ImmutableArray attributeList) => AttributeListContainsAttribute(compilation.GetTypeByMetadataName(attributeName), attributeList); + + public static bool AttributeListContainsAttribute(INamedTypeSymbol? attributeTypeSymbol, ImmutableArray attributeList) + { + if (attributeTypeSymbol == null || attributeTypeSymbol.TypeKind == TypeKind.Error) + { + return false; + } + + return attributeList.Any(ad => ad.AttributeClass != null && ad.AttributeClass.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)); + } + public static ImmutableArray GetAttributes(string attributeName, Compilation compilation, SyntaxList attributeLists, SemanticModel semanticModel) => GetAttributes(compilation.GetTypeByMetadataName(attributeName), attributeLists, semanticModel); public static ImmutableArray GetAttributes(INamedTypeSymbol? attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index 82af856750..b66b3e91b2 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -7,16 +7,16 @@ Rule ID | Category | Severity | Notes ---------|----------|----------|-------------------- BDN1000 | Usage | Error | BDN1000_BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods BDN1001 | Usage | Error | BDN1001_BenchmarkRunner_Run_TypeArgumentClassMustBePublic -BDN1002 | Usage | Error | BDN1002_BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract -BDN1003 | Usage | Error | BDN1003_BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed -BDN1100 | Usage | Error | BDN1100_General_BenchmarkClass_MethodMustBePublic -BDN1101 | Usage | Error | BDN1101_General_BenchmarkClass_MethodMustBeNonGeneric -BDN1102 | Usage | Error | BDN1102_General_BenchmarkClass_ClassMustBeNonStatic -BDN1103 | Usage | Error | BDN1103_General_BenchmarkClass_ClassMustBeNonAbstract -BDN1104 | Usage | Error | BDN1104_General_BenchmarkClass_ClassMustBeNonGeneric -BDN1105 | Usage | Error | BDN1105_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric -BDN1106 | Usage | Error | BDN1106_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount -BDN1107 | Usage | Error | BDN1107_General_BenchmarkClass_OnlyOneMethodCanBeBaseline +BDN1002 | Usage | Error | BDN1002_BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed +BDN1003 | Usage | Error | BDN1003_BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract +BDN1004 | Usage | Error | BDN1004_BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute +BDN1100 | Usage | Error | BDN1100_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract +BDN1101 | Usage | Error | BDN1101_General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric +BDN1102 | Usage | Error | BDN1102_General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount +BDN1103 | Usage | Error | BDN1103_General_BenchmarkClass_MethodMustBePublic +BDN1104 | Usage | Error | BDN1104_General_BenchmarkClass_MethodMustBeNonGeneric +BDN1105 | Usage | Error | BDN1105_General_BenchmarkClass_ClassMustBeNonStatic +BDN1106 | Usage | Error | BDN1106_General_BenchmarkClass_OnlyOneMethodCanBeBaseline BDN1200 | Usage | Error | BDN1200_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField BDN1201 | Usage | Error | BDN1201_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty BDN1202 | Usage | Error | BDN1202_Attributes_GeneralParameterAttributes_FieldMustBePublic diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index bcb78f4ba2..c8a29a9377 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -481,6 +481,36 @@ internal static string Attributes_ParamsAttribute_UnnecessarySingleValuePassedTo } } + /// + /// Looks up a localized string similar to A generic benchmark class referenced in the BenchmarkRunner.Run method must be must be annotated with at least one [GenericTypeArguments] attribute. + /// + internal static string BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute_Description { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgume" + + "ntsAttribute_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Referenced generic benchmark class '{0}' has no [GenericTypeArguments] attribute(s). + /// + internal static string BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute_MessageFormat { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgume" + + "ntsAttribute_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Generic benchmark classes must be annotated with at least one [GenericTypeArguments] attribute. + /// + internal static string BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute_Title { + get { + return ResourceManager.GetString("BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgume" + + "ntsAttribute_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to The referenced benchmark class (or any of its inherited classes) must have at least one method annotated with the [Benchmark] attribute. /// @@ -666,26 +696,6 @@ internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttri } } - /// - /// Looks up a localized string similar to Benchmark class '{0}' cannot be generic unless declared as abstract or annotated with a [GenericTypeArguments] attribute. - /// - internal static string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute_MessageFormat { - get { - return ResourceManager.GetString("General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgum" + - "entsAttribute_MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Benchmark classes can only be generic if they're either abstract or annotated with a [GenericTypeArguments] attribute. - /// - internal static string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute_Title { - get { - return ResourceManager.GetString("General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgum" + - "entsAttribute_Title", resourceCulture); - } - } - /// /// Looks up a localized string similar to The number of type arguments passed to a [GenericTypeArguments] attribute must match the number of type parameters on the targeted benchmark class. /// diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index 80393a20f9..b45f7cea91 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -144,8 +144,11 @@ Benchmark class '{0}' cannot be abstract - - Benchmark class '{0}' cannot be generic unless declared as abstract or annotated with a [GenericTypeArguments] attribute + + Referenced generic benchmark class '{0}' has no [GenericTypeArguments] attribute(s) + + + A generic benchmark class referenced in the BenchmarkRunner.Run method must be must be annotated with at least one [GenericTypeArguments] attribute Benchmark classes must be non-static @@ -153,8 +156,8 @@ Benchmark classes annotated with the [GenericTypeArguments] attribute must be non-abstract - - Benchmark classes can only be generic if they're either abstract or annotated with a [GenericTypeArguments] attribute + + Generic benchmark classes must be annotated with at least one [GenericTypeArguments] attribute Benchmark classes must be public diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index 92e8e83d5e..87dbaa65c9 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; + using System.Linq; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class RunAnalyzer : DiagnosticAnalyzer @@ -25,6 +26,14 @@ public class RunAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); + internal static readonly DiagnosticDescriptor TypeArgumentClassMustBeUnsealedRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Description))); + internal static readonly DiagnosticDescriptor TypeArgumentClassMustBeNonAbstractRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_MessageFormat)), @@ -33,20 +42,22 @@ public class RunAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_Description))); - internal static readonly DiagnosticDescriptor TypeArgumentClassMustBeUnsealedRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed_Description))); + internal static readonly DiagnosticDescriptor GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule = new DiagnosticDescriptor(DiagnosticIds.BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute_Description))); + public override ImmutableArray SupportedDiagnostics => [ TypeArgumentClassMissingBenchmarkMethodsRule, TypeArgumentClassMustBePublicRule, + TypeArgumentClassMustBeUnsealedRule, TypeArgumentClassMustBeNonAbstractRule, - TypeArgumentClassMustBeUnsealedRule + GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule, ]; public override void Initialize(AnalysisContext analysisContext) @@ -143,8 +154,6 @@ private static void Analyze(SyntaxNodeAnalysisContext context) var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); if (benchmarkAttributeTypeSymbol == null) { - ReportDiagnostic(TypeArgumentClassMissingBenchmarkMethodsRule); - return; } @@ -168,6 +177,11 @@ private static void Analyze(SyntaxNodeAnalysisContext context) ReportDiagnostic(TypeArgumentClassMustBeUnsealedRule); } + if (benchmarkClassTypeSymbol.IsUnboundGenericType && !AnalyzerHelper.AttributeListContainsAttribute("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, benchmarkClassTypeSymbol.GetAttributes())) + { + ReportDiagnostic(GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule); + } + return; bool HasBenchmarkAttribute() diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index 4f84e85668..09221fd3c4 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -4,16 +4,16 @@ public static class DiagnosticIds { public const string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods = "BDN1000"; public const string BenchmarkRunner_Run_TypeArgumentClassMustBePublic = "BDN1001"; - public const string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract = "BDN1002"; - public const string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed = "BDN1003"; + public const string BenchmarkRunner_Run_TypeArgumentClassMustBeUnsealed = "BDN1002"; + public const string BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract = "BDN1003"; + public const string BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1004"; public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract = "BDN1100"; - public const string General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute = "BDN1101"; - public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1102"; - public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1103"; - public const string General_BenchmarkClass_MethodMustBePublic = "BDN1104"; - public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1105"; - public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1106"; - public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1107"; + public const string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric = "BDN1101"; + public const string General_BenchmarkClass_GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount = "BDN1102"; + public const string General_BenchmarkClass_MethodMustBePublic = "BDN1103"; + public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1104"; + public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1105"; + public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1106"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField = "BDN1200"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty = "BDN1201"; public const string Attributes_GeneralParameterAttributes_FieldMustBePublic = "BDN1202"; diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 0cc9588b8f..68876fac01 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -19,13 +19,6 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_Description))); - internal static readonly DiagnosticDescriptor GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - internal static readonly DiagnosticDescriptor ClassWithGenericTypeArgumentsAttributeMustBeGenericRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_MessageFormat)), @@ -77,7 +70,6 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => [ ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, - GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, MethodMustBePublicRule, diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index 2ce43a6fa7..9f21ecd550 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -3,9 +3,11 @@ using Fixtures; using Analyzers.BenchmarkRunner; - using System.Collections.Generic; + using Xunit; + using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; @@ -14,11 +16,11 @@ public class RunAnalyzerTests public class General : AnalyzerTestFixture { [Theory, CombinatorialData] - public async Task Invoking_with_a_public_nonabstract_unsealed_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic(bool isGeneric) + public async Task Invoking_with_a_public_nonabstract_unsealed_nongeneric_type_argument_class_having_only_one_and_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic(bool isGenericInvocation) { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - var invocationExpression = isGeneric ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; + var invocationExpression = isGenericInvocation ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -56,11 +58,11 @@ public class TypeArgumentClassMissingBenchmarkMethods : AnalyzerTestFixture()" : $"(typeof({classWithOneBenchmarkMethodName}))"; + var invocationExpression = isGenericInvocation ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -103,11 +105,11 @@ private void BenchmarkMethod3() } [Theory, CombinatorialData] - public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic(bool isGeneric, [CombinatorialValues("", "abstract ")] string abstractModifier) + public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic(bool isGenericInvocation, [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier) { const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; - var invocationExpression = isGeneric ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; + var invocationExpression = isGenericInvocation ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -129,16 +131,12 @@ public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1 """; var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 { } """; var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 { } @@ -176,44 +174,37 @@ private void BenchmarkMethod3() await RunAsync(); } - [Theory] - [InlineData("")] - [InlineData("abstract ")] - public async Task Invoking_with_a_generic_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic(string abstractModifier) + [Theory, CombinatorialData] + public async Task Invoking_with_a_generic_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier) { - const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; - - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Running; - - public class Program - { - public static void Main(string[] args) { - BenchmarkRunner.Run(typeof({{classWithOneBenchmarkMethodName}}<,>)); - } - } - """; + var typeParameters = string.Join(", ", TypeParameters.Take(typeParametersListLength)); - const string benchmarkClassDocument = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1 - { - } - """; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run(typeof(BenchmarkClass<{{new string(',', typeParametersListLength - 1)}}>)); + } + } + """; + + var benchmarkClassDocument = /* lang=c#-test */ $$""" + public class BenchmarkClass<{{typeParameters}}> : BenchmarkClassAncestor1<{{typeParameters}}> + { + } + """; var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + public {{abstractModifier}}class BenchmarkClassAncestor1<{{typeParameters}}> : BenchmarkClassAncestor2<{{typeParameters}}> { } """; var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + public {{abstractModifier}}class BenchmarkClassAncestor2<{{typeParameters}}> : BenchmarkClassAncestor3 { } """; @@ -251,11 +242,11 @@ private void BenchmarkMethod3() } [Theory, CombinatorialData] - public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_trigger_diagnostic(bool isGeneric, [CombinatorialValues("", "abstract ")] string abstractModifier) + public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_trigger_diagnostic(bool isGenericInvocation, [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier) { const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; - var invocationExpression = isGeneric ? $"<{{|#0:{classWithOneBenchmarkMethodName}|}}>()" : $"(typeof({{|#0:{classWithOneBenchmarkMethodName}|}}))"; + var invocationExpression = isGenericInvocation ? $"<{{|#0:{classWithOneBenchmarkMethodName}|}}>()" : $"(typeof({{|#0:{classWithOneBenchmarkMethodName}|}}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -325,44 +316,40 @@ private void BenchmarkMethod3() await RunAsync(); } - [Theory] - [InlineData("")] - [InlineData("abstract ")] - public async Task Invoking_with_a_generic_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_trigger_diagnostic(string abstractModifier) + [Theory, CombinatorialData] + public async Task Invoking_with_a_generic_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_in_one_of_its_ancestor_classes_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier) { - const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; + const string classWithOneBenchmarkMethodName = "BenchmarkClass"; - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Running; - - public class Program - { - public static void Main(string[] args) { - BenchmarkRunner.Run(typeof({|#0:{{classWithOneBenchmarkMethodName}}<,>|})); - } - } - """; + var unboundGenericTypeParameterList = new string(',', typeParametersListLength - 1); + var typeParameters = string.Join(", ", TypeParameters.Take(typeParametersListLength)); - const string benchmarkClassDocument = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class {{classWithOneBenchmarkMethodName}} : BenchmarkClassAncestor1 - { - } - """; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run(typeof({|#0:{{classWithOneBenchmarkMethodName}}<{{unboundGenericTypeParameterList}}>|})); + } + } + """; - var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + var benchmarkClassDocument = /* lang=c#-test */ $$""" + public class {{classWithOneBenchmarkMethodName}}<{{typeParameters}}> : BenchmarkClassAncestor1<{{typeParameters}}> + { + } + """; - public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1<{{typeParameters}}> : BenchmarkClassAncestor2<{{typeParameters}}> { } """; var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + public {{abstractModifier}}class BenchmarkClassAncestor2<{{typeParameters}}> : BenchmarkClassAncestor3 { } """; @@ -390,17 +377,17 @@ private void BenchmarkMethod2() AddSource(benchmarkClassAncestor2Document); AddSource(benchmarkClassAncestor3Document); - AddDefaultExpectedDiagnostic($"{classWithOneBenchmarkMethodName}<,>"); + AddDefaultExpectedDiagnostic($"{classWithOneBenchmarkMethodName}<{unboundGenericTypeParameterList}>"); await RunAsync(); } [Theory, CombinatorialData] - public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_should_trigger_diagnostic(bool isGeneric) + public async Task Invoking_with_type_argument_class_having_no_public_method_annotated_with_the_benchmark_attribute_should_trigger_diagnostic(bool isGenericInvocation) { const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - var invocationExpression = isGeneric ? $"<{{|#0:{classWithOneBenchmarkMethodName}|}}>()" : $"(typeof({{|#0:{classWithOneBenchmarkMethodName}|}}))"; + var invocationExpression = isGenericInvocation ? $"<{{|#0:{classWithOneBenchmarkMethodName}|}}>()" : $"(typeof({{|#0:{classWithOneBenchmarkMethodName}|}}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -429,6 +416,10 @@ public void BenchmarkMethod() await RunAsync(); } + + public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + + public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; } public class TypeArgumentClassMustBePublic : AnalyzerTestFixture @@ -436,11 +427,11 @@ public class TypeArgumentClassMustBePublic : AnalyzerTestFixture public TypeArgumentClassMustBePublic() : base(RunAnalyzer.TypeArgumentClassMustBePublicRule) { } [Theory, CombinatorialData] - public async Task Invoking_with_a_nonpublic_class_with_multiple_inheritance_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(bool isGeneric) + public async Task Invoking_with_a_nonpublic_class_with_multiple_inheritance_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(bool isGenericInvocation) { const string benchmarkClassName = "BenchmarkClass"; - var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + var invocationExpression = isGenericInvocation ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -486,11 +477,12 @@ public class BenchmarkClassAncestor2 } [Theory, CombinatorialData] - public async Task Invoking_with_a_nonpublic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(NonPublicClassAccessModifiersExceptFile))] string nonPublicClassAccessModifier, bool isGeneric) + public async Task Invoking_with_a_nonpublic_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(NonPublicClassAccessModifiersExceptFile))] string nonPublicClassAccessModifier, + bool isGenericInvocation) { const string benchmarkClassName = "BenchmarkClass"; - var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + var invocationExpression = isGenericInvocation ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -521,11 +513,11 @@ public void BenchmarkMethod() } [Theory, CombinatorialData] - public async Task Invoking_with_a_file_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(bool isGeneric) + public async Task Invoking_with_a_file_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(bool isGenericInvocation) { const string benchmarkClassName = "BenchmarkClass"; - var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + var invocationExpression = isGenericInvocation ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -558,16 +550,16 @@ public void BenchmarkMethod() public static IEnumerable NonPublicClassAccessModifiersExceptFile => new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file "); } - public class TypeArgumentClassMustBeNonAbstract : AnalyzerTestFixture + public class TypeArgumentClassMustBeUnsealed : AnalyzerTestFixture { - public TypeArgumentClassMustBeNonAbstract() : base(RunAnalyzer.TypeArgumentClassMustBeNonAbstractRule) { } + public TypeArgumentClassMustBeUnsealed() : base(RunAnalyzer.TypeArgumentClassMustBeUnsealedRule) { } [Theory, CombinatorialData] - public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagnostic(bool isGeneric) + public async Task Invoking_with_a_sealed_benchmark_class_should_trigger_diagnostic(bool isGenericInvocation) { const string benchmarkClassName = "BenchmarkClass"; - var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + var invocationExpression = isGenericInvocation ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -583,7 +575,7 @@ public static void Main(string[] args) { const string benchmarkClassDocument = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - public abstract class {{benchmarkClassName}} + public sealed class {{benchmarkClassName}} { [Benchmark] public void BenchmarkMethod() @@ -600,16 +592,16 @@ public void BenchmarkMethod() } } - public class TypeArgumentClassMustBeUnsealed : AnalyzerTestFixture + public class TypeArgumentClassMustBeNonAbstract : AnalyzerTestFixture { - public TypeArgumentClassMustBeUnsealed() : base(RunAnalyzer.TypeArgumentClassMustBeUnsealedRule) { } + public TypeArgumentClassMustBeNonAbstract() : base(RunAnalyzer.TypeArgumentClassMustBeNonAbstractRule) { } [Theory, CombinatorialData] - public async Task Invoking_with_a_sealed_benchmark_class_should_trigger_diagnostic(bool isGeneric) + public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagnostic(bool isGenericInvocation) { const string benchmarkClassName = "BenchmarkClass"; - var invocationExpression = isGeneric ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + var invocationExpression = isGenericInvocation ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Running; @@ -625,7 +617,7 @@ public static void Main(string[] args) { const string benchmarkClassDocument = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; - public sealed class {{benchmarkClassName}} + public abstract class {{benchmarkClassName}} { [Benchmark] public void BenchmarkMethod() @@ -641,5 +633,271 @@ public void BenchmarkMethod() await RunAsync(); } } + + public class GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute : AnalyzerTestFixture + { + public GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute() : base(RunAnalyzer.GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule) { } + + [Theory, CombinatorialData] + public async Task Invoking_with_a_generic_class_annotated_with_at_least_one_generictypearguments_attribute_should_not_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + const string benchmarkClassName = "BenchmarkClass"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run(typeof({{benchmarkClassName}}<{{new string(',', typeParametersListLength - 1)}}>)); + } + } + """; + + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); + var genericTypeArgumentsAttributeUsages = string.Join("\n", Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", genericTypeArgumentsAttributeUsageCount)); + var typeParameters = string.Join(", ", TypeParameters.Take(typeParametersListLength)); + + var benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + {{genericTypeArgumentsAttributeUsages}} + public class BenchmarkClass<{{typeParameters}}> : BenchmarkClassAncestor1<{{typeParameters}}> + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1<{{typeParameters}}> : BenchmarkClassAncestor2<{{typeParameters}}> + { + + } + """; + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2<{{typeParameters}}> + { + + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Invoking_with_a_nongeneric_class_that_inherits_from_a_generic_class_not_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic(bool isGenericInvocation, + [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + const string benchmarkClassName = "BenchmarkClass"; + + var invocationExpression = isGenericInvocation ? $"<{benchmarkClassName}>()" : $"(typeof({benchmarkClassName}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; + + var benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{benchmarkClassName}} : BenchmarkClassAncestor<{{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}}> + { + + } + """; + var benchmarkClassAncestorDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + { + + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestorDocument); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task A_generic_class_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + var typeParameters = string.Join(", ", TypeParameters.Take(typeParametersListLength)); + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> : BenchmarkClassAncestor1<{{typeParameters}}> + { + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1<{{typeParameters}}> : BenchmarkClassAncestor2<{{typeParameters}}> + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor2<{{typeParameters}}> : BenchmarkClassAncestor3 + { + } + """; + + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Invoking_with_a_generic_class_not_annotated_with_a_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialRange(0, 3)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + const string benchmarkClassName = "BenchmarkClass"; + + var unboundGenericTypeParameterList = new string(',', typeParametersListLength - 1); + var typeParameters = string.Join(", ", TypeParameters.Take(typeParametersListLength)); + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); + var genericTypeArgumentsAttributeUsages = string.Join("\n", Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", genericTypeArgumentsAttributeUsageCount)); + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run(typeof({|#0:{{benchmarkClassName}}<{{unboundGenericTypeParameterList}}>|})); + } + } + """; + + var benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class {{benchmarkClassName}}<{{typeParameters}}> : BenchmarkClassAncestor1<{{typeParameters}}> + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1<{{typeParameters}}> : BenchmarkClassAncestor2<{{typeParameters}}> + { + + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + {{genericTypeArgumentsAttributeUsages}} + public {{abstractModifier}}class BenchmarkClassAncestor2<{{typeParameters}}> + { + + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + AddDefaultExpectedDiagnostic($"{benchmarkClassName}<{unboundGenericTypeParameterList}>"); + + await RunAsync(); + } + + public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; + + public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + } + + public static IEnumerable ClassAbstractModifiersEnumerable => [ "", "abstract " ]; + + public static IEnumerable BenchmarkAttributeUsagesEnumerable => [ "", "[Benchmark]" ]; + + public static IEnumerable TypeParametersListLengthEnumerable => Enumerable.Range(1, TypeParameters.Count); + + private static ReadOnlyCollection TypeParameters => Enumerable.Range(1, 3) + .Select(i => $"TParameter{i}") + .ToList() + .AsReadOnly(); + + private static ReadOnlyCollection GenericTypeArguments => new List { "int", "string", "bool" }.AsReadOnly(); } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 264837166f..51736dcde2 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -70,91 +70,6 @@ public void BenchmarkMethod() } } - public class GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute : AnalyzerTestFixture - { - public GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttribute() : base(BenchmarkClassAnalyzer.GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule) { } - - [Theory, CombinatorialData] - public async Task Generic_class_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic([CombinatorialRange(1, 3)] int attributeUsageCount, - [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) - { - var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); - var attributeUsages = string.Join("\n", Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", attributeUsageCount)); - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - {{attributeUsages}} - public class BenchmarkClass{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Theory, CombinatorialData] - public async Task Abstract_generic_class_not_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Theory, CombinatorialData] - public async Task Nonabstract_generic_class_not_annotated_with_a_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) - { - const string benchmarkClassName = "BenchmarkClass"; - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class {{benchmarkClassName}}{|#0:<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}>|} - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); - - await RunAsync(); - } - - public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; - - private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; - - private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; - } - public class ClassWithGenericTypeArgumentsAttributeMustBeGeneric : AnalyzerTestFixture { public ClassWithGenericTypeArgumentsAttributeMustBeGeneric() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeGenericRule) { } From c1780591035d8b0b87df87801ac4a81802a9e0a1 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sat, 18 Oct 2025 00:36:53 +0200 Subject: [PATCH 18/24] Add support to analyze implicit conversion from an array to a Span of said array for [Arguments] attribute values --- .../AnalyzerHelper.cs | 37 ++++++- .../Attributes/ArgumentsAttributeAnalyzer.cs | 45 ++++---- .../Attributes/ParamsAttributeAnalyzer.cs | 55 +++++----- ...nchmarkDotNetAnalyzerResources.Designer.cs | 4 +- .../BenchmarkDotNetAnalyzerResources.resx | 4 +- .../ArgumentsAttributeAnalyzerTests.cs | 103 ++++++++++++++---- .../ParamsAttributeAnalyzerTests.cs | 49 +++++---- .../BenchmarkRunner/RunAnalyzerTests.cs | 24 ++-- .../Fixtures/AnalyzerTestFixture.cs | 7 +- 9 files changed, 207 insertions(+), 121 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index e3694f134c..a501b93e45 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; - using System.Collections.Generic; + using System.Collections.Immutable; using System.Linq; @@ -105,19 +105,44 @@ public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol) return typeName; } - public static bool IsConstantAssignableToType(Compilation compilation, ITypeSymbol targetType, string valueExpression) + public static bool IsAssignableToField(Compilation compilation, ITypeSymbol targetType, string valueExpression) + { + var code = $$""" + file static class Internal { + static readonly {{targetType}} x = {{valueExpression}}; + } + """; + + var syntaxTree = CSharpSyntaxTree.ParseText(code); + + var compilationErrors = compilation.AddSyntaxTrees(syntaxTree) + .GetSemanticModel(syntaxTree) + .GetMethodBodyDiagnostics() + .Where(d => d.DefaultSeverity == DiagnosticSeverity.Error) + .ToList(); + + return compilationErrors.Count == 0; + } + + public static bool IsAssignableToLocal(Compilation compilation, ITypeSymbol targetType, string valueExpression) { var code = $$""" - file class Internal { - {{targetType}} x = {{valueExpression}}; + file static class Internal { + static Internal() { + {{targetType}} x = {{valueExpression}}; + } } """; var syntaxTree = CSharpSyntaxTree.ParseText(code); - var diagnostics = compilation.AddSyntaxTrees(syntaxTree).GetSemanticModel(syntaxTree).GetMethodBodyDiagnostics(); + var compilationErrors = compilation.AddSyntaxTrees(syntaxTree) + .GetSemanticModel(syntaxTree) + .GetMethodBodyDiagnostics() + .Where(d => d.DefaultSeverity == DiagnosticSeverity.Error) + .ToList(); - return diagnostics.Length == 0; + return compilationErrors.Count == 0; } } } diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index 6877207f9a..2b4c174e58 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -176,41 +176,38 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) else { var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeArgumentSyntax.Expression).Type; - if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol) + if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol && arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object) { - if (arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object) + if (attributeArgumentSyntax.Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax) { - if (attributeArgumentSyntax.Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax) + if (arrayCreationExpressionSyntax.Initializer == null) { - if (arrayCreationExpressionSyntax.Initializer == null) + var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First(); + if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression)) { - var rankSpecifierSizeSyntax = arrayCreationExpressionSyntax.Type.RankSpecifiers.First().Sizes.First(); - if (rankSpecifierSizeSyntax is LiteralExpressionSyntax literalExpressionSyntax && literalExpressionSyntax.IsKind(SyntaxKind.NumericLiteralExpression)) + if (literalExpressionSyntax.Token.Value is 0) { - if (literalExpressionSyntax.Token.Value is int rankSpecifierSize && rankSpecifierSize == 0) + if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) { - if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) - { - ReportMustHaveMatchingValueCountDiagnostic(literalExpressionSyntax.GetLocation(), 0); - } + ReportMustHaveMatchingValueCountDiagnostic(literalExpressionSyntax.GetLocation(), 0); } } } - else + } + else + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count != arrayCreationExpressionSyntax.Initializer.Expressions.Count) { - if (methodDeclarationSyntax.ParameterList.Parameters.Count != arrayCreationExpressionSyntax.Initializer.Expressions.Count) - { - ReportMustHaveMatchingValueCountDiagnostic(arrayCreationExpressionSyntax.Initializer.Expressions.Count == 0 - ? arrayCreationExpressionSyntax.Initializer.GetLocation() - : Location.Create(context.FilterTree, arrayCreationExpressionSyntax.Initializer.Expressions.Span), - arrayCreationExpressionSyntax.Initializer.Expressions.Count); + ReportMustHaveMatchingValueCountDiagnostic(arrayCreationExpressionSyntax.Initializer.Expressions.Count == 0 + ? arrayCreationExpressionSyntax.Initializer.GetLocation() + : Location.Create(context.FilterTree, arrayCreationExpressionSyntax.Initializer.Expressions.Span), + arrayCreationExpressionSyntax.Initializer.Expressions.Count); - continue; - } - - // ReSharper disable once PossibleNullReferenceException - ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]); + continue; } + + // ReSharper disable once PossibleNullReferenceException + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]); } } } @@ -282,7 +279,7 @@ void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func - /// Looks up a localized string similar to Unexpected type for value '{0}'. Expected '{1}' but found '{2}'.. + /// Looks up a localized string similar to Unexpected type for argument value '{0}'. Expected '{1}' but found '{2}'.. /// internal static string Attributes_ArgumentsAttribute_MustHaveMatchingValueType_MessageFormat { get { @@ -482,7 +482,7 @@ internal static string Attributes_ParamsAttribute_UnnecessarySingleValuePassedTo } /// - /// Looks up a localized string similar to A generic benchmark class referenced in the BenchmarkRunner.Run method must be must be annotated with at least one [GenericTypeArguments] attribute. + /// Looks up a localized string similar to A generic benchmark class referenced in the BenchmarkRunner.Run method must be annotated with at least one [GenericTypeArguments] attribute. /// internal static string BenchmarkRunner_Run_GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute_Description { get { diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index b45f7cea91..9f14762ad0 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -148,7 +148,7 @@ Referenced generic benchmark class '{0}' has no [GenericTypeArguments] attribute(s) - A generic benchmark class referenced in the BenchmarkRunner.Run method must be must be annotated with at least one [GenericTypeArguments] attribute + A generic benchmark class referenced in the BenchmarkRunner.Run method must be annotated with at least one [GenericTypeArguments] attribute Benchmark classes must be non-static @@ -250,7 +250,7 @@ Benchmark method without [Arguments] attribute(s) cannot declare parameters - Unexpected type for value '{0}'. Expected '{1}' but found '{2}'. + Unexpected type for argument value '{0}'. Expected '{1}' but found '{2}'. Property '{0}' annotated with [{1}] must be public diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs index 31cdf4cb59..2f54662f63 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -601,6 +601,30 @@ public void BenchmarkMethod(int a) await RunAsync(); } + [Theory, CombinatorialData] + public async Task Providing_an_implicitly_convertible_array_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "new[] { 0, 1, 2 }")}}] + public void BenchmarkMethod(System.Span a) + { + + } + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } + [Theory, CombinatorialData] public async Task Having_unknown_parameter_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) @@ -658,6 +682,33 @@ public void BenchmarkMethod({{expectedArgumentType}} a) await RunAsync(); } + [Theory, CombinatorialData] + public async Task Providing_a_not_implicitly_convertible_array_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, + [CombinatorialValues("System.Span", "string[]")] string expectedArgumentType) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:new[] { 0, 1, 2 }|}")}}] + public void BenchmarkMethod({{expectedArgumentType}} a) + { + + } + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + + AddDefaultExpectedDiagnostic("new[] { 0, 1, 2 }", expectedArgumentType, "int[]"); + + await RunAsync(); + } + [Theory, CombinatorialData] public async Task Providing_integer_value_types_not_within_target_type_range_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(NotConvertibleValuesAndTypes))] ValueTupleDouble valueAndType, @@ -867,7 +918,9 @@ static IEnumerable GenerateData() (object)"test_object" """, "object" ), ( "typeof(string)", "System.Type" ), - ( "DummyEnum.Value1", "DummyEnum" ) + ( "DummyEnum.Value1", "DummyEnum" ), + + ( "new[] { 0, 1, 2 }", "int[]" ), ]; public static IEnumerable> NotConvertibleValuesAndTypes => @@ -894,35 +947,41 @@ static IEnumerable GenerateData() private static IEnumerable ScalarValuesContainerAttributeArgumentEnumerable() { - var nameColonUsages = new List - { - "", - "values: " - }; - - var priorityNamedParameterUsages = new List - { - "", - ", Priority = 1" - }; - - var attributeUsagesBase = new List + return GenerateData().Distinct(); + + static IEnumerable GenerateData() + { + var nameColonUsages = new List { - "Arguments({{0}}{1})", - "Arguments({0}new object[] {{{{ {{0}} }}}}{1})", - "Arguments({0}[ {{0}} ]{1})" + "", + "values: " }; - foreach (var attributeUsageBase in attributeUsagesBase) - { - foreach (var nameColonUsage in nameColonUsages) + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "Arguments({{0}}{1})", + "Arguments({0}new object[] {{{{ {{0}} }}}}{1})", + "Arguments({0}[ {{0}} ]{1})" + }; + + foreach (var attributeUsageBase in attributeUsagesBase) { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + foreach (var nameColonUsage in nameColonUsages) { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } } } } + } } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs index fcbb63b8ca..906188001b 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -612,7 +612,7 @@ public static IEnumerable> ArrayValuesContainer (object)"test_object" """, "object" ), ( "typeof(string)", "System.Type" ), - ( "DummyEnum.Value1", "DummyEnum" ) + ( "DummyEnum.Value1", "DummyEnum" ), ]; public static IEnumerable> NotConvertibleValuesAndTypes => @@ -695,32 +695,37 @@ public string {{fieldOrPropertyDeclaration}} public static IEnumerable ScalarValuesContainerAttributeArgumentWithLocationMarker() { - var nameColonUsages = new List - { - "", - "values: " - }; + return GenerateData().Distinct(); - var priorityNamedParameterUsages = new List - { - "", - ", Priority = 1" - }; - - var attributeUsagesBase = new List + static IEnumerable GenerateData() + { + var nameColonUsages = new List { - "{{{{|#0:{{0}}|}}}}{1}", - "{0}new object[] {{{{ {{{{|#0:{{0}}|}}}} }}}}{1}", - "{0}[ {{{{|#0:{{0}}|}}}} ]{1}", + "", + "values: " }; - foreach (var attributeUsageBase in attributeUsagesBase) - { - foreach (var nameColonUsage in nameColonUsages) + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + var attributeUsagesBase = new List + { + "{{{{|#0:{{0}}|}}}}{1}", + "{0}new object[] {{{{ {{{{|#0:{{0}}|}}}} }}}}{1}", + "{0}[ {{{{|#0:{{0}}|}}}} ]{1}", + }; + + foreach (var attributeUsageBase in attributeUsagesBase) { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + foreach (var nameColonUsage in nameColonUsages) { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } } } } @@ -734,7 +739,7 @@ public static IEnumerable ScalarValuesContainerAttributeArgumentWithLoca public static TheoryData ScalarValuesContainerAttributeArgumentTheoryData() { - return new TheoryData(GenerateData()); + return new TheoryData(GenerateData().Distinct()); static IEnumerable GenerateData() { diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index 9f21ecd550..34f49bfbeb 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -181,15 +181,15 @@ public async Task Invoking_with_a_generic_type_argument_class_having_at_least_on var typeParameters = string.Join(", ", TypeParameters.Take(typeParametersListLength)); var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Running; - - public class Program - { - public static void Main(string[] args) { - BenchmarkRunner.Run(typeof(BenchmarkClass<{{new string(',', typeParametersListLength - 1)}}>)); - } - } - """; + using BenchmarkDotNet.Running; + + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run(typeof(BenchmarkClass<{{new string(',', typeParametersListLength - 1)}}>)); + } + } + """; var benchmarkClassDocument = /* lang=c#-test */ $$""" public class BenchmarkClass<{{typeParameters}}> : BenchmarkClassAncestor1<{{typeParameters}}> @@ -754,9 +754,9 @@ public void BenchmarkMethod() } [Theory, CombinatorialData] - public async Task A_generic_class_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, - [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + public async Task A_generic_class_not_referenced_in_run_method_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + [CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { var typeParameters = string.Join(", ", TypeParameters.Take(typeParametersListLength)); diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs index beb99fa085..7879f1a442 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs @@ -35,8 +35,11 @@ private AnalyzerTestFixture(bool assertUniqueSupportedDiagnostics) AdditionalReferences = { "BenchmarkDotNet.dll", - "BenchmarkDotNet.Annotations.dll" - } + "BenchmarkDotNet.Annotations.dll", +#if !NET6_0_OR_GREATER + "System.Memory.dll" +#endif + } } }; From 41942fa2f773aedafb900ef75f88d4ff192ceca8 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 20 Oct 2025 14:16:37 +0200 Subject: [PATCH 19/24] Add support to analyze implicit conversion when using constant values for [Params] and [Arguments] attribute values --- .../AnalyzerHelper.cs | 101 ++++-- .../AnalyzerReleases.Unshipped.md | 2 +- .../Attributes/ArgumentsAttributeAnalyzer.cs | 31 +- .../GeneralParameterAttributesAnalyzer.cs | 52 ++-- .../ParamsAllValuesAttributeAnalyzer.cs | 30 +- .../Attributes/ParamsAttributeAnalyzer.cs | 54 ++-- ...nchmarkDotNetAnalyzerResources.Designer.cs | 32 +- .../BenchmarkDotNetAnalyzerResources.resx | 8 +- .../DiagnosticIds.cs | 2 +- .../General/BenchmarkClassAnalyzer.cs | 6 +- .../ArgumentsAttributeAnalyzerTests.cs | 265 ++++++++++++++-- .../ParamsAllValuesAttributeAnalyzerTests.cs | 1 + .../ParamsAttributeAnalyzerTests.cs | 291 +++++++++++++++--- .../Fixtures/AnalyzerTestFixture.cs | 12 + 14 files changed, 696 insertions(+), 191 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index a501b93e45..597181715a 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; + using System.Globalization; using System.Linq; internal static class AnalyzerHelper @@ -105,35 +106,68 @@ public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol) return typeName; } - public static bool IsAssignableToField(Compilation compilation, ITypeSymbol targetType, string valueExpression) + public static bool IsAssignableToField(Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) { - var code = $$""" - file static class Internal { - static readonly {{targetType}} x = {{valueExpression}}; - } - """; + const string codeTemplate1 = """ + file static class Internal {{ + static readonly {0} x = {1}; + }} + """; + + const string codeTemplate2 = """ + file static class Internal {{ + static readonly {0} x = ({1}){2}; + }} + """; + + return IsAssignableTo(codeTemplate1, codeTemplate2, compilation, targetType, valueExpression, constantValue, valueType); + } - var syntaxTree = CSharpSyntaxTree.ParseText(code); + public static bool IsAssignableToLocal(Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) + { + const string codeTemplate1 = """ + file static class Internal {{ + static void Method() {{ + {0} x = {1}; + }} + }} + """; + + const string codeTemplate2 = """ + file static class Internal {{ + static void Method() {{ + {0} x = ({1}){2}; + }} + }} + """; + + return IsAssignableTo(codeTemplate1, codeTemplate2, compilation, targetType, valueExpression, constantValue, valueType); + } - var compilationErrors = compilation.AddSyntaxTrees(syntaxTree) - .GetSemanticModel(syntaxTree) - .GetMethodBodyDiagnostics() - .Where(d => d.DefaultSeverity == DiagnosticSeverity.Error) - .ToList(); + private static bool IsAssignableTo(string codeTemplate1, string codeTemplate2, Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) + { + var hasNoCompilationErrors = HasNoCompilationErrors(string.Format(codeTemplate1, targetType, valueExpression), compilation); + if (hasNoCompilationErrors) + { + return true; + } - return compilationErrors.Count == 0; + if (!constantValue.HasValue || valueType == null) + { + return false; + } + + var constantLiteral = FormatLiteral(constantValue.Value); + if (constantLiteral == null) + { + return false; + } + + return HasNoCompilationErrors(string.Format(codeTemplate2, targetType, valueType, constantLiteral), compilation); } - public static bool IsAssignableToLocal(Compilation compilation, ITypeSymbol targetType, string valueExpression) + private static bool HasNoCompilationErrors(string code, Compilation compilation) { - var code = $$""" - file static class Internal { - static Internal() { - {{targetType}} x = {{valueExpression}}; - } - } - """; - var syntaxTree = CSharpSyntaxTree.ParseText(code); var compilationErrors = compilation.AddSyntaxTrees(syntaxTree) @@ -144,5 +178,28 @@ static Internal() { return compilationErrors.Count == 0; } + + private static string? FormatLiteral(object? value) + { + return value switch + { + byte b => b.ToString(), + sbyte sb => sb.ToString(), + short s => s.ToString(), + ushort us => us.ToString(), + int i => i.ToString(), + uint ui => $"{ui}U", + long l => $"{l}L", + ulong ul => $"{ul}UL", + float f => $"{f.ToString(CultureInfo.InvariantCulture)}F", + double d => $"{d.ToString(CultureInfo.InvariantCulture)}D", + decimal m => $"{m.ToString(CultureInfo.InvariantCulture)}M", + char c => $"'{c}'", + bool b => b ? "true" : "false", + string s => $"\"{s}\"", + null => "null", + _ => null + }; + } } } diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index b66b3e91b2..947dbb391c 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -26,7 +26,7 @@ BDN1205 | Usage | Error | BDN1205_Attributes_GeneralParameterAttributes_N BDN1206 | Usage | Error | BDN1206_Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly BDN1207 | Usage | Error | BDN1207_Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter BDN1300 | Usage | Error | BDN1300_Attributes_ParamsAttribute_MustHaveValues -BDN1301 | Usage | Error | BDN1301_Attributes_ParamsAttribute_UnexpectedValueType +BDN1301 | Usage | Error | BDN1301_Attributes_ParamsAttribute_MustHaveMatchingValueType BDN1302 | Usage | Warning | BDN1302_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute BDN1303 | Usage | Error | BDN1303_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType BDN1304 | Usage | Error | BDN1304_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index 2b4c174e58..9a6d9dfe05 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -89,7 +89,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) { if (hasBenchmarkAttribute && methodDeclarationSyntax.ParameterList.Parameters.Count > 0) { - context.ReportDiagnostic(Diagnostic.Create(MethodWithoutAttributeMustHaveNoParametersRule, Location.Create(context.FilterTree, methodDeclarationSyntax.ParameterList.Parameters.Span))); + context.ReportDiagnostic(Diagnostic.Create(MethodWithoutAttributeMustHaveNoParametersRule, Location.Create(context.FilterTree, methodDeclarationSyntax.ParameterList.Parameters.Span), methodDeclarationSyntax.Identifier.ToString())); } return; @@ -276,10 +276,16 @@ void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func")); + actualType)); } } } diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs index 33c1d02b58..c174280c20 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs @@ -75,16 +75,18 @@ public class GeneralParameterAttributesAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly_Description))); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - MutuallyExclusiveOnFieldRule, - MutuallyExclusiveOnPropertyRule, - FieldMustBePublic, - PropertyMustBePublic, - NotValidOnReadonlyFieldRule, - NotValidOnConstantFieldRule, - PropertyCannotBeInitOnlyRule, - PropertyMustHavePublicSetterRule - ); + public override ImmutableArray SupportedDiagnostics => + [ + MutuallyExclusiveOnFieldRule, + MutuallyExclusiveOnPropertyRule, + FieldMustBePublic, + PropertyMustBePublic, + NotValidOnReadonlyFieldRule, + NotValidOnConstantFieldRule, + PropertyCannotBeInitOnlyRule, + PropertyMustHavePublicSetterRule + ]; + public override void Initialize(AnalysisContext analysisContext) { analysisContext.EnableConcurrentExecution(); @@ -106,7 +108,7 @@ public override void Initialize(AnalysisContext analysisContext) private static void Analyze(SyntaxNodeAnalysisContext context) { - if (!(context.Node is AttributeSyntax attributeSyntax)) + if (context.Node is not AttributeSyntax attributeSyntax) { return; } @@ -118,14 +120,16 @@ private static void Analyze(SyntaxNodeAnalysisContext context) var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; if ( attributeSyntaxTypeSymbol == null - || !attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default) - && !attributeSyntaxTypeSymbol.Equals(paramsSourceAttributeTypeSymbol, SymbolEqualityComparer.Default) - && !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default)) + || attributeSyntaxTypeSymbol.TypeKind == TypeKind.Error + || + (!attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default) + && !attributeSyntaxTypeSymbol.Equals(paramsSourceAttributeTypeSymbol, SymbolEqualityComparer.Default) + && !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default))) { return; } - var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax || n is PropertyDeclarationSyntax); + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); if (attributeTarget == null) { return; @@ -197,15 +201,15 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } private static void AnalyzeFieldOrPropertySymbol(SyntaxNodeAnalysisContext context, - INamedTypeSymbol paramsAttributeTypeSymbol, - INamedTypeSymbol paramsSourceAttributeTypeSymbol, - INamedTypeSymbol paramsAllValuesAttributeTypeSymbol, + INamedTypeSymbol? paramsAttributeTypeSymbol, + INamedTypeSymbol? paramsSourceAttributeTypeSymbol, + INamedTypeSymbol? paramsAllValuesAttributeTypeSymbol, ImmutableArray declaredAttributes, bool fieldOrPropertyIsPublic, - Location fieldConstModifierLocation, - Location fieldReadonlyModifierLocation, + Location? fieldConstModifierLocation, + Location? fieldReadonlyModifierLocation, string fieldOrPropertyIdentifier, - Location propertyInitAccessorKeywordLocation, + Location? propertyInitAccessorKeywordLocation, bool propertyIsMissingAssignableSetter, Location fieldOrPropertyIdentifierLocation, DiagnosticDescriptor fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule, @@ -296,9 +300,9 @@ private static void AnalyzeFieldOrPropertySymbol(SyntaxNodeAnalysisContext conte } private static bool AllAttributeTypeSymbolsExist(in SyntaxNodeAnalysisContext context, - out INamedTypeSymbol paramsAttributeTypeSymbol, - out INamedTypeSymbol paramsSourceAttributeTypeSymbol, - out INamedTypeSymbol paramsAllValuesAttributeTypeSymbol) + out INamedTypeSymbol? paramsAttributeTypeSymbol, + out INamedTypeSymbol? paramsSourceAttributeTypeSymbol, + out INamedTypeSymbol? paramsAllValuesAttributeTypeSymbol) { paramsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute"); if (paramsAttributeTypeSymbol == null) diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs index 30f3aaadfa..30a8a7b1f3 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs @@ -26,10 +26,11 @@ public class ParamsAllValuesAttributeAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - NotAllowedOnFlagsEnumPropertyOrFieldTypeRule, - PropertyOrFieldTypeMustBeEnumOrBoolRule - ); + public override ImmutableArray SupportedDiagnostics => + [ + NotAllowedOnFlagsEnumPropertyOrFieldTypeRule, + PropertyOrFieldTypeMustBeEnumOrBoolRule + ]; public override void Initialize(AnalysisContext analysisContext) { @@ -39,8 +40,7 @@ public override void Initialize(AnalysisContext analysisContext) analysisContext.RegisterCompilationStartAction(ctx => { // Only run if BenchmarkDotNet.Annotations is referenced - var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); - if (benchmarkAttributeTypeSymbol == null) + if (GetParamsAllValuesAttributeTypeSymbol(ctx.Compilation) == null) { return; } @@ -51,16 +51,12 @@ public override void Initialize(AnalysisContext analysisContext) private static void Analyze(SyntaxNodeAnalysisContext context) { - if (!(context.Node is AttributeSyntax attributeSyntax)) + if (context.Node is not AttributeSyntax attributeSyntax) { return; } - var paramsAllValuesAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAllValuesAttribute"); - if (paramsAllValuesAttributeTypeSymbol == null) - { - return; - } + var paramsAllValuesAttributeTypeSymbol = GetParamsAllValuesAttributeTypeSymbol(context.Compilation); var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; if (attributeSyntaxTypeSymbol == null || !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default)) @@ -68,7 +64,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax || n is PropertyDeclarationSyntax); + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); if (attributeTarget == null) { return; @@ -90,12 +86,10 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - AnalyzeFieldOrPropertyTypeSyntax(context, - fieldOrPropertyTypeSyntax); + AnalyzeFieldOrPropertyTypeSyntax(context, fieldOrPropertyTypeSyntax); } - private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, - TypeSyntax fieldOrPropertyTypeSyntax) + private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, TypeSyntax fieldOrPropertyTypeSyntax) { if (fieldOrPropertyTypeSyntax is NullableTypeSyntax fieldOrPropertyNullableTypeSyntax) { @@ -129,5 +123,7 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c context.ReportDiagnostic(Diagnostic.Create(PropertyOrFieldTypeMustBeEnumOrBoolRule, fieldOrPropertyTypeSyntax.GetLocation())); } } + + private static INamedTypeSymbol? GetParamsAllValuesAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAllValuesAttribute"); } } diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs index 34f29274d1..126d2daf1f 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs @@ -18,13 +18,13 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); - internal static readonly DiagnosticDescriptor UnexpectedValueTypeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_UnexpectedValueType, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnexpectedValueType_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnexpectedValueType_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnexpectedValueType_Description))); + internal static readonly DiagnosticDescriptor MustHaveMatchingValueTypeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_MustHaveMatchingValueType, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_MustHaveMatchingValueType_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_MustHaveMatchingValueType_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_MustHaveMatchingValueType_Description))); internal static readonly DiagnosticDescriptor UnnecessarySingleValuePassedToAttributeRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_Title)), @@ -35,7 +35,7 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( MustHaveValuesRule, - UnexpectedValueTypeRule, + MustHaveMatchingValueTypeRule, UnnecessarySingleValuePassedToAttributeRule ); @@ -48,8 +48,7 @@ public override void Initialize(AnalysisContext analysisContext) analysisContext.RegisterCompilationStartAction(ctx => { // Only run if BenchmarkDotNet.Annotations is referenced - var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); - if (benchmarkAttributeTypeSymbol == null) + if (GetParamsAttributeTypeSymbol(ctx.Compilation) == null) { return; } @@ -65,11 +64,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) return; } - var paramsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute"); - if (paramsAttributeTypeSymbol == null) - { - return; - } + var paramsAttributeTypeSymbol = GetParamsAttributeTypeSymbol(context.Compilation); var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; if (attributeSyntaxTypeSymbol == null || !attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default)) @@ -245,12 +240,18 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(ExpressionSyntax valueExpressionSyntax) { + var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax); + var valueExpressionString = valueExpressionSyntax.ToString(); var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type; if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error) { - if (!AnalyzerHelper.IsAssignableToField(context.Compilation, expectedValueTypeSymbol, valueExpressionString)) + if (!AnalyzerHelper.IsAssignableToField(context.Compilation, + expectedValueTypeSymbol, + valueExpressionString, + constantValue, + actualValueTypeSymbol.ToString())) { ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), valueExpressionString, @@ -260,22 +261,31 @@ void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(ExpressionSyntax valueE } else { - ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionString, - fieldOrPropertyTypeSyntax.ToString()); + if (constantValue is { HasValue: true, Value: null }) + { + if (!AnalyzerHelper.IsAssignableToField(context.Compilation, expectedValueTypeSymbol, valueExpressionString, constantValue, null)) + { + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), + valueExpressionString, + fieldOrPropertyTypeSyntax.ToString(), + "null"); + } + } } return; - void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string? actualType = null) + void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType) { - context.ReportDiagnostic(Diagnostic.Create(UnexpectedValueTypeRule, + context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueTypeRule, diagnosticLocation, value, expectedType, - actualType ?? "")); + actualType)); } } } + + private static INamedTypeSymbol? GetParamsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute"); } } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index 61a5e20c8b..e5ffd7c897 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -72,7 +72,7 @@ internal static string Attributes_ArgumentsAttribute_MethodWithoutAttributeMustH } /// - /// Looks up a localized string similar to Benchmark method without [Arguments] attribute(s) cannot declare parameters. + /// Looks up a localized string similar to Benchmark method '{0}' without [Arguments] attribute(s) cannot declare parameters. /// internal static string Attributes_ArgumentsAttribute_MethodWithoutAttributeMustHaveNoParameters_MessageFormat { get { @@ -419,47 +419,47 @@ internal static string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMu } /// - /// Looks up a localized string similar to The [Params] attribute requires at least one value. No values were provided, or an empty array was specified.. + /// Looks up a localized string similar to The type of each value provided to the [Params] attribute must match the type of (or be implicitly convertible to) the field or property it is applied to. /// - internal static string Attributes_ParamsAttribute_MustHaveValues_MessageFormat { + internal static string Attributes_ParamsAttribute_MustHaveMatchingValueType_Description { get { - return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveValues_MessageFormat", resourceCulture); + return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveMatchingValueType_Description", resourceCulture); } } /// - /// Looks up a localized string similar to The [Params] attribute must include at least one value. + /// Looks up a localized string similar to Unexpected type for parameter value '{0}'. Expected '{1}' but found '{2}'.. /// - internal static string Attributes_ParamsAttribute_MustHaveValues_Title { + internal static string Attributes_ParamsAttribute_MustHaveMatchingValueType_MessageFormat { get { - return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveValues_Title", resourceCulture); + return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveMatchingValueType_MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to The type of each value provided to the [Params] attribute must match the type of (or be implicitly convertible to) the field or property it is applied to. + /// Looks up a localized string similar to Type of all value(s) passed to the [Params] attribute must match the type of (or be implicitly convertible to) the annotated field or property. /// - internal static string Attributes_ParamsAttribute_UnexpectedValueType_Description { + internal static string Attributes_ParamsAttribute_MustHaveMatchingValueType_Title { get { - return ResourceManager.GetString("Attributes_ParamsAttribute_UnexpectedValueType_Description", resourceCulture); + return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveMatchingValueType_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Unexpected type for parameter value '{0}'. Expected '{1}' but found '{2}'.. + /// Looks up a localized string similar to The [Params] attribute requires at least one value. No values were provided, or an empty array was specified.. /// - internal static string Attributes_ParamsAttribute_UnexpectedValueType_MessageFormat { + internal static string Attributes_ParamsAttribute_MustHaveValues_MessageFormat { get { - return ResourceManager.GetString("Attributes_ParamsAttribute_UnexpectedValueType_MessageFormat", resourceCulture); + return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveValues_MessageFormat", resourceCulture); } } /// - /// Looks up a localized string similar to Type of all value(s) passed to the [Params] attribute must match the type of (or be implicitly convertible to) the annotated field or property. + /// Looks up a localized string similar to The [Params] attribute must include at least one value. /// - internal static string Attributes_ParamsAttribute_UnexpectedValueType_Title { + internal static string Attributes_ParamsAttribute_MustHaveValues_Title { get { - return ResourceManager.GetString("Attributes_ParamsAttribute_UnexpectedValueType_Title", resourceCulture); + return ResourceManager.GetString("Attributes_ParamsAttribute_MustHaveValues_Title", resourceCulture); } } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index 9f14762ad0..6fc4189093 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -225,7 +225,7 @@ A property annotated with a parameter attribute must have a public setter; make sure that the access modifier of the setter is empty and that the property is not an auto-property or an expression-bodied property. - + The type of each value provided to the [Params] attribute must match the type of (or be implicitly convertible to) the field or property it is applied to @@ -247,7 +247,7 @@ Expected {0} value{1} as declared by the benchmark method '{2}', but found {3}. Update the attribute usage or method to match. - Benchmark method without [Arguments] attribute(s) cannot declare parameters + Benchmark method '{0}' without [Arguments] attribute(s) cannot declare parameters Unexpected type for argument value '{0}'. Expected '{1}' but found '{2}'. @@ -264,7 +264,7 @@ Providing a single value to the [Params] attribute is unnecessary. This attribute is only useful when provided two or more values. - + Unexpected type for parameter value '{0}'. Expected '{1}' but found '{2}'. @@ -306,7 +306,7 @@ Unnecessary single value passed to [Params] attribute - + Type of all value(s) passed to the [Params] attribute must match the type of (or be implicitly convertible to) the annotated field or property diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index 09221fd3c4..3afdee3e17 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -23,7 +23,7 @@ public static class DiagnosticIds public const string Attributes_GeneralParameterAttributes_PropertyCannotBeInitOnly = "BDN1206"; public const string Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter = "BDN1207"; public const string Attributes_ParamsAttribute_MustHaveValues = "BDN1300"; - public const string Attributes_ParamsAttribute_UnexpectedValueType = "BDN1301"; + public const string Attributes_ParamsAttribute_MustHaveMatchingValueType = "BDN1301"; public const string Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute = "BDN1302"; public const string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType = "BDN1303"; public const string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool = "BDN1304"; diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 68876fac01..37cd95c9b5 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -110,8 +110,8 @@ private static void Analyze(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); } - var benchmarkAttributeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); - if (benchmarkAttributeSymbol == null) + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); + if (benchmarkAttributeTypeSymbol == null) { return; } @@ -122,7 +122,7 @@ private static void Analyze(SyntaxNodeAnalysisContext context) { if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax) { - var benchmarkAttributes = AnalyzerHelper.GetAttributes(benchmarkAttributeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); + var benchmarkAttributes = AnalyzerHelper.GetAttributes(benchmarkAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); if (benchmarkAttributes.Length > 0) { benchmarkMethodsBuilder.Add((methodDeclarationSyntax, benchmarkAttributes.SelectMany(a => GetBaselineLocations(a)).ToImmutableArray())); diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs index 2f54662f63..ab88d0ec73 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -197,13 +197,15 @@ public void BenchmarkMethod({{string.Join(", ", Parameters.Take(parametersListLe [MemberData(nameof(ParametersListLength))] public async Task A_method_annotated_with_the_benchmark_attribute_but_no_arguments_attribute_with_parameters_should_trigger_diagnostic(int parametersListLength) { + const string benchmarkMethodName = "BenchmarkMethod"; + var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; public class BenchmarkClass { [Benchmark] - public void BenchmarkMethod({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) + public void {{benchmarkMethodName}}({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) { } @@ -211,7 +213,7 @@ public void BenchmarkMethod({|#0:{{string.Join(", ", Parameters.Take(parametersL """; TestCode = testCode; - AddDefaultExpectedDiagnostic(); + AddDefaultExpectedDiagnostic(benchmarkMethodName); await RunAsync(); } @@ -576,6 +578,99 @@ public void BenchmarkMethod({{integerValueAndType.Value2}} a) await RunAsync(); } + [Theory, CombinatorialData] + public async Task Providing_null_to_nullable_struct_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NullableStructTypes))] string type, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "null")}}] + public void BenchmarkMethod({{type}}? a) + { + + } + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_expected_constant_value_type_should_not_trigger_diagnostic(bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ConstantValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const {valueAndType.Value2} _x = {(useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!)};" : "")}} + + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!)}}] + public void BenchmarkMethod({{valueAndType.Value2}} a) + { + + } + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + if (useConstantFromOtherClass) + { + ReferenceConstants($"{valueAndType.Value2!}", valueAndType.Value1!); + } + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_expected_null_reference_constant_value_type_should_not_trigger_diagnostic(bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NullReferenceConstantTypes))] string type, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const {type}? _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}] + public void BenchmarkMethod({{type}} a) + { + + } + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + if (useConstantFromOtherClass) + { + ReferenceConstants($"{type}?", "null"); + } + + await RunAsync(); + } + [Theory, CombinatorialData] public async Task Providing_implicitly_convertible_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, @@ -602,8 +697,8 @@ public void BenchmarkMethod(int a) } [Theory, CombinatorialData] - public async Task Providing_an_implicitly_convertible_array_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + public async Task Providing_an_implicitly_convertible_array_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -652,6 +747,32 @@ public void BenchmarkMethod(unkown a, string b) await RunAsync(); } + [Theory, CombinatorialData] + public async Task Providing_an_unkown_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:dummy_literal|}, true")}}] + public void BenchmarkMethod(byte a, bool b) + { + + } + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + + DisableCompilerDiagnostics(); + + await RunAsync(); + } + [Theory, CombinatorialData] public async Task Providing_an_unexpected_or_not_implicitly_convertible_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(NotConvertibleValuesAndTypes))] ValueTupleDouble valueAndType, @@ -683,65 +804,74 @@ public void BenchmarkMethod({{expectedArgumentType}} a) } [Theory, CombinatorialData] - public async Task Providing_a_not_implicitly_convertible_array_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, - [CombinatorialValues("System.Span", "string[]")] string expectedArgumentType) + public async Task Providing_an_unexpected_or_not_implicitly_convertible_constant_value_type_should_trigger_diagnostic(bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NotConvertibleConstantValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) { + const string expectedArgumentType = "decimal"; + var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; public class BenchmarkClass { + {{(useLocalConstant ? $"private const {valueAndType.Value2} _x = {(useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1)};" : "")}} + [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:new[] { 0, 1, 2 }|}")}}] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1)}|}}")}}] public void BenchmarkMethod({{expectedArgumentType}} a) { - + } } """; - TestCode = testCode; ReferenceDummyAttribute(); + ReferenceDummyEnum(); - AddDefaultExpectedDiagnostic("new[] { 0, 1, 2 }", expectedArgumentType, "int[]"); + if (useConstantFromOtherClass) + { + ReferenceConstants(valueAndType.Value2!, valueAndType.Value1!); + } + + AddDefaultExpectedDiagnostic(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!, expectedArgumentType, valueAndType.Value2!); await RunAsync(); } [Theory, CombinatorialData] - public async Task Providing_integer_value_types_not_within_target_type_range_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(NotConvertibleValuesAndTypes))] ValueTupleDouble valueAndType, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + public async Task Providing_null_to_nonnullable_struct_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NullableStructTypes))] string type, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) { - const string expectedArgumentType = "decimal"; - var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; public class BenchmarkClass { [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType.Value1}|}}")}}] - public void BenchmarkMethod({{expectedArgumentType}} a) + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:null|}")}}] + public void BenchmarkMethod({{type}} a) { - + } } """; - TestCode = testCode; ReferenceDummyAttribute(); ReferenceDummyEnum(); - AddDefaultExpectedDiagnostic(valueAndType.Value1!, expectedArgumentType, valueAndType.Value2!); + AddDefaultExpectedDiagnostic("null", type, "null"); await RunAsync(); } [Theory, CombinatorialData] - public async Task Providing_an_unkown_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + public async Task Providing_a_not_implicitly_convertible_array_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, + [CombinatorialValues("System.Span", "string[]")] string expectedArgumentType) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -749,8 +879,8 @@ public async Task Providing_an_unkown_value_type_should_trigger_diagnostic([Comb public class BenchmarkClass { [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:dummy_literal|}, true")}}] - public void BenchmarkMethod(byte a, bool b) + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:new[] { 0, 1, 2 }|}")}}] + public void BenchmarkMethod({{expectedArgumentType}} a) { } @@ -760,8 +890,32 @@ public void BenchmarkMethod(byte a, bool b) TestCode = testCode; ReferenceDummyAttribute(); - DisableCompilerDiagnostics(); - AddDefaultExpectedDiagnostic("dummy_literal", "byte", ""); + AddDefaultExpectedDiagnostic("new[] { 0, 1, 2 }", expectedArgumentType, "int[]"); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_integer_value_types_not_within_target_type_range_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(IntegerValuesAndTypesNotWithinTargetTypeRange))] ValueTupleTriple integerValueAndType) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{integerValueAndType.Value1}|}}")}}] + public void BenchmarkMethod({{integerValueAndType.Value2}} a) + { + + } + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(integerValueAndType.Value1!, integerValueAndType.Value2!, integerValueAndType.Value3!); await RunAsync(); } @@ -938,6 +1092,63 @@ static IEnumerable GenerateData() ( "typeof(string)", "System.Type" ), ( "DummyEnum.Value1", "DummyEnum" ) ]; + + public static IEnumerable> ConstantValuesAndTypes => + [ + ( "true", "bool" ), + ( "(byte)123", "byte" ), + ( "'A'", "char" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( "123", "int" ), + ( "123L", "long" ), + ( "(sbyte)-100", "sbyte" ), + ( "(short)-123", "short" ), + ( """ + "test" + """, "string" ), + ( "123U", "uint" ), + ( "123UL", "ulong" ), + ( "(ushort)123", "ushort" ), + + ( "DummyEnum.Value1", "DummyEnum" ), + ]; + + public static IEnumerable NullableStructTypes => + [ + "bool", + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort", + "DummyEnum", + ]; + + public static IEnumerable NullReferenceConstantTypes => + [ + "object", + "string", + "System.Type", + ]; + + public static IEnumerable> NotConvertibleConstantValuesAndTypes => + [ + ( "true", "bool" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( """ + "test" + """, "string" ), + + ( "DummyEnum.Value1", "DummyEnum" ) + ]; } public static TheoryData DummyAttributeUsageTheoryData => [ diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs index 701d5a2a06..7c97129107 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs @@ -3,6 +3,7 @@ using Fixtures; using BenchmarkDotNet.Analyzers.Attributes; + using Xunit; using System.Collections.Generic; diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs index 906188001b..149ecfa04d 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -160,14 +160,14 @@ public static IEnumerable EmptyParamsAttributeUsagesWithLocationMarker() } } - public class UnexpectedValueType : AnalyzerTestFixture + public class MustHaveMatchingValueType : AnalyzerTestFixture { - public UnexpectedValueType() : base(ParamsAttributeAnalyzer.UnexpectedValueTypeRule) { } + public MustHaveMatchingValueType() : base(ParamsAttributeAnalyzer.MustHaveMatchingValueTypeRule) { } [Theory, CombinatorialData] public async Task Providing_a_field_or_property_with_an_unknown_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -186,10 +186,10 @@ public unknown {{fieldOrPropertyDeclaration}} } [Theory, CombinatorialData] - public async Task Providing_expected_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + public async Task Providing_expected_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(ValuesAndTypes))] ValueTupleDouble valueAndType, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -208,10 +208,94 @@ public class BenchmarkClass } [Theory, CombinatorialData] - public async Task Providing_implicitly_convertible_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument, - [CombinatorialValues("(byte)42", "'c'")] string value) + public async Task Providing_null_to_nullable_struct_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NullableStructTypes))] string type, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "null")}})] + public {{type}}? {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_expected_constant_value_type_should_not_trigger_diagnostic(bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ConstantValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const {valueAndType.Value2} _x = {(useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!)};" : "")}} + + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!)}})] + public {{valueAndType.Value2}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + if (useConstantFromOtherClass) + { + ReferenceConstants($"{valueAndType.Value2!}", valueAndType.Value1!); + } + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_expected_null_reference_constant_value_type_should_not_trigger_diagnostic(bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NullReferenceConstantTypes))] string type, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const {type}? _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}})] + public {{type}}? {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + if (useConstantFromOtherClass) + { + ReferenceConstants($"{type}?", "null"); + } + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_implicitly_convertible_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialValues("(byte)42", "'c'")] string value, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -229,11 +313,11 @@ public int {{fieldOrPropertyDeclaration}} } [Theory, CombinatorialData] - public async Task Providing_integer_value_types_within_target_type_range_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument, + public async Task Providing_integer_value_types_within_target_type_range_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, [CombinatorialMemberData(nameof(IntegerValuesAndTypesWithinTargetTypeRange))] ValueTupleDouble integerValueAndType, - bool explicitCast) + bool explicitCast, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -251,61 +335,60 @@ public class BenchmarkClass } [Theory, CombinatorialData] - public async Task Providing_both_expected_and_unexpected_value_types_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + public async Task Providing_an_unknown_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { const string expectedFieldOrPropertyType = "int"; - const string valueWithUnexpectedType = "\"test1\""; + const string valueWithUnknownType = "dummy_literal"; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnexpectedType}|}}, 33")}})] + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnknownType}|}}, 33")}})] public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; ReferenceDummyAttribute(); - AddDefaultExpectedDiagnostic(valueWithUnexpectedType, expectedFieldOrPropertyType, "string"); + DisableCompilerDiagnostics(); await RunAsync(); } [Theory, CombinatorialData] - public async Task Providing_an_unknown_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + public async Task Providing_both_expected_and_unexpected_value_types_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { const string expectedFieldOrPropertyType = "int"; - const string valueWithUnknownType = "dummy_literal"; + const string valueWithUnexpectedType = "\"test1\""; var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnknownType}|}}, 33")}})] + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnexpectedType}|}}, 33")}})] public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; ReferenceDummyAttribute(); - DisableCompilerDiagnostics(); - AddDefaultExpectedDiagnostic(valueWithUnknownType, expectedFieldOrPropertyType, ""); + AddDefaultExpectedDiagnostic(valueWithUnexpectedType, expectedFieldOrPropertyType, "string"); await RunAsync(); } [Theory, CombinatorialData] - public async Task Providing_an_unexpected_or_not_implicitly_convertible_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + public async Task Providing_an_unexpected_or_not_implicitly_convertible_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(NotConvertibleValuesAndTypes))] ValueTupleDouble valueAndType, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument) + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { const string expectedFieldOrPropertyType = "decimal"; @@ -328,10 +411,69 @@ public class BenchmarkClass } [Theory, CombinatorialData] - public async Task Providing_integer_value_types_not_within_target_type_range_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgument))] string scalarValuesContainerAttributeArgument, - [CombinatorialMemberData(nameof(IntegerValuesAndTypesNotWithinTargetTypeRange))] ValueTupleTriple integerValueAndType) + public async Task Providing_an_unexpected_or_not_implicitly_convertible_constant_value_type_should_trigger_diagnostic(bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NotConvertibleConstantValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + const string expectedFieldOrPropertyType = "decimal"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const {valueAndType.Value2} _x = {(useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1)};" : "")}} + + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1)}|}}")}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + if (useConstantFromOtherClass) + { + ReferenceConstants(valueAndType.Value2!, valueAndType.Value1!); + } + + AddDefaultExpectedDiagnostic(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!, expectedFieldOrPropertyType, valueAndType.Value2!); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_null_to_nonnullable_struct_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(NullableStructTypes))] string type, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "{|#0:null|}")}})] + public {{type}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + AddDefaultExpectedDiagnostic("null", type, "null"); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_integer_value_types_not_within_target_type_range_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(IntegerValuesAndTypesNotWithinTargetTypeRange))] ValueTupleTriple integerValueAndType, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -349,13 +491,11 @@ public class BenchmarkClass await RunAsync(); } - - [Theory, CombinatorialData] - public async Task Providing_an_unexpected_array_value_type_to_params_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + public async Task Providing_an_unexpected_array_value_type_to_params_attribute_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, [CombinatorialMemberData(nameof(ValuesAndTypes))] ValueTupleDouble valueAndType, - [CombinatorialMemberData(nameof(ArrayValuesContainerAttributeArgumentWithLocationMarker))] ValueTupleDouble arrayValuesContainerAttributeArgument) + [CombinatorialMemberData(nameof(ArrayValuesContainerAttributeArgumentWithLocationMarkerEnumerable))] ValueTupleDouble arrayValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { const string expectedFieldOrPropertyType = "decimal"; @@ -382,9 +522,9 @@ public class BenchmarkClass } [Theory, CombinatorialData] - public async Task Providing_an_empty_array_value_when_type_of_attribute_target_is_not_object_array_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument) + public async Task Providing_an_empty_array_value_when_type_of_attribute_target_is_not_object_array_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -402,9 +542,9 @@ public decimal {{fieldOrPropertyDeclaration}} } [Theory, CombinatorialData] - public async Task Providing_an_empty_array_value_when_type_of_attribute_target_is_object_array_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument) + public async Task Providing_an_empty_array_value_when_type_of_attribute_target_is_object_array_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; @@ -546,9 +686,9 @@ public static IEnumerable EmptyValuesAttributeArgument() } } - public static IEnumerable ScalarValuesContainerAttributeArgument => ScalarValuesContainerAttributeArgumentTheoryData(); + public static IEnumerable ScalarValuesContainerAttributeArgumentEnumerable => ScalarValuesContainerAttributeArgumentTheoryData(); - public static IEnumerable> ArrayValuesContainerAttributeArgumentWithLocationMarker() + public static IEnumerable> ArrayValuesContainerAttributeArgumentWithLocationMarkerEnumerable() { var nameColonUsages = new List { @@ -630,6 +770,63 @@ public static IEnumerable> ArrayValuesContainer ( "typeof(string)", "System.Type" ), ( "DummyEnum.Value1", "DummyEnum" ) ]; + + public static IEnumerable> ConstantValuesAndTypes => + [ + ( "true", "bool" ), + ( "(byte)123", "byte" ), + ( "'A'", "char" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( "123", "int" ), + ( "123L", "long" ), + ( "(sbyte)-100", "sbyte" ), + ( "(short)-123", "short" ), + ( """ + "test" + """, "string" ), + ( "123U", "uint" ), + ( "123UL", "ulong" ), + ( "(ushort)123", "ushort" ), + + ( "DummyEnum.Value1", "DummyEnum" ), + ]; + + public static IEnumerable NullableStructTypes => + [ + "bool", + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort", + "DummyEnum", + ]; + + public static IEnumerable NullReferenceConstantTypes => + [ + "object", + "string", + "System.Type", + ]; + + public static IEnumerable> NotConvertibleConstantValuesAndTypes => + [ + ( "true", "bool" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( """ + "test" + """, "string" ), + + ( "DummyEnum.Value1", "DummyEnum" ) + ]; } public class UnnecessarySingleValuePassedToAttribute : AnalyzerTestFixture diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs index 7879f1a442..e92db1a01f 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs @@ -210,6 +210,18 @@ public enum DummyEnumWithFlagsAttribute """); } + protected void ReferenceConstants(string type, string value) + { + _analyzerTest.TestState.Sources.Add($$""" + using System; + + public static class Constants + { + public const {{type}} Value = {{value}}; + } + """); + } + private sealed class InternalAnalyzerTest : CSharpAnalyzerTest { protected override string DefaultTestProjectName => "BenchmarksAssemblyUnderAnalysis"; From f6b41a9c78824f46d48450bb134c2d20b0fe2ffb Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 27 Oct 2025 10:52:27 +0100 Subject: [PATCH 20/24] * Add support to analyze a boolean constant value for the Baseline property on BenchmarkAttribute * Split logic for baseline method analyzer into two rules, also verifying whether they're unique per category --- .../AnalyzerHelper.cs | 58 +- .../AnalyzerReleases.Unshipped.md | 4 +- ...nchmarkDotNetAnalyzerResources.Designer.cs | 42 +- .../BenchmarkDotNetAnalyzerResources.resx | 16 +- .../BenchmarkRunner/RunAnalyzer.cs | 1 - .../DiagnosticIds.cs | 4 +- .../General/BenchmarkClassAnalyzer.cs | 474 +++++- .../General/BenchmarkClassAnalyzerTests.cs | 1406 +++++++++++++++-- .../Fixtures/AnalyzerTestFixture.cs | 39 +- 9 files changed, 1859 insertions(+), 185 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index 597181715a..447f3809d9 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; + using System.Collections.Generic; using System.Collections.Immutable; using System.Globalization; using System.Linq; @@ -86,6 +87,37 @@ public static ImmutableArray GetAttributes(INamedTypeSymbol? at return attributesBuilder.ToImmutable(); } + public static int GetAttributeUsageCount(string attributeName, Compilation compilation, SyntaxList attributeLists, SemanticModel semanticModel) => GetAttributeUsageCount(compilation.GetTypeByMetadataName(attributeName), attributeLists, semanticModel); + + public static int GetAttributeUsageCount(INamedTypeSymbol? attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) + { + var attributeUsageCount = 0; + + if (attributeTypeSymbol == null) + { + return 0; + } + + foreach (var attributeListSyntax in attributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null) + { + continue; + } + + if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) + { + attributeUsageCount++; + } + } + } + + return attributeUsageCount; + } + public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol) { string typeName; @@ -146,8 +178,8 @@ static void Method() {{ private static bool IsAssignableTo(string codeTemplate1, string codeTemplate2, Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) { - var hasNoCompilationErrors = HasNoCompilationErrors(string.Format(codeTemplate1, targetType, valueExpression), compilation); - if (hasNoCompilationErrors) + var hasCompilerDiagnostics = HasNoCompilerDiagnostics(string.Format(codeTemplate1, targetType, valueExpression), compilation); + if (hasCompilerDiagnostics) { return true; } @@ -163,20 +195,20 @@ private static bool IsAssignableTo(string codeTemplate1, string codeTemplate2, C return false; } - return HasNoCompilationErrors(string.Format(codeTemplate2, targetType, valueType, constantLiteral), compilation); + return HasNoCompilerDiagnostics(string.Format(codeTemplate2, targetType, valueType, constantLiteral), compilation); } - private static bool HasNoCompilationErrors(string code, Compilation compilation) + private static bool HasNoCompilerDiagnostics(string code, Compilation compilation) { var syntaxTree = CSharpSyntaxTree.ParseText(code); - var compilationErrors = compilation.AddSyntaxTrees(syntaxTree) - .GetSemanticModel(syntaxTree) - .GetMethodBodyDiagnostics() - .Where(d => d.DefaultSeverity == DiagnosticSeverity.Error) - .ToList(); + var compilerDiagnostics = compilation.AddSyntaxTrees(syntaxTree) + .GetSemanticModel(syntaxTree) + .GetMethodBodyDiagnostics() + .Where(d => d.DefaultSeverity == DiagnosticSeverity.Error) + .ToList(); - return compilationErrors.Count == 0; + return compilerDiagnostics.Count == 0; } private static string? FormatLiteral(object? value) @@ -201,5 +233,11 @@ private static bool HasNoCompilationErrors(string code, Compilation compilation) _ => null }; } + + public static void Deconstruct(this KeyValuePair tuple, out T1 key, out T2 value) + { + key = tuple.Key; + value = tuple.Value; + } } } diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index 947dbb391c..50a7a43055 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -16,7 +16,9 @@ BDN1102 | Usage | Error | BDN1102_General_BenchmarkClass_GenericTypeArgum BDN1103 | Usage | Error | BDN1103_General_BenchmarkClass_MethodMustBePublic BDN1104 | Usage | Error | BDN1104_General_BenchmarkClass_MethodMustBeNonGeneric BDN1105 | Usage | Error | BDN1105_General_BenchmarkClass_ClassMustBeNonStatic -BDN1106 | Usage | Error | BDN1106_General_BenchmarkClass_OnlyOneMethodCanBeBaseline +BDN1106 | Usage | Error | BDN1106_General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed +BDN1107 | Usage | Error | BDN1107_General_BenchmarkClass_OnlyOneMethodCanBeBaseline +BDN1108 | Usage | Warning | BDN1108_General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory BDN1200 | Usage | Error | BDN1200_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField BDN1201 | Usage | Error | BDN1201_Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty BDN1202 | Usage | Error | BDN1202_Attributes_GeneralParameterAttributes_FieldMustBePublic diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index e5ffd7c897..fe8a5aea7e 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -781,7 +781,7 @@ internal static string General_BenchmarkClass_MethodMustBePublic_Title { } /// - /// Looks up a localized string similar to Only one benchmark method can be marked as baseline. + /// Looks up a localized string similar to Only one benchmark method can be marked as baseline per class. /// internal static string General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat { get { @@ -790,12 +790,50 @@ internal static string General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Message } /// - /// Looks up a localized string similar to Only one benchmark method can be baseline. + /// Looks up a localized string similar to Only one benchmark method can be baseline per class. /// internal static string General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title { get { return ResourceManager.GetString("General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title", resourceCulture); } } + + /// + /// Looks up a localized string similar to Only one benchmark method can be marked as baseline per class and category. + /// + internal static string General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Only one benchmark method can be baseline per class and category. + /// + internal static string General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "SomeCategory", ...) or a non-null value instead.. + /// + internal static string General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_MessageFormat { + get { + return ResourceManager.GetString("General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_M" + + "essageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Single null argument to the [BenchmarkCategory] attribute results in unintended null array. + /// + internal static string General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_Title { + get { + return ResourceManager.GetString("General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_T" + + "itle", resourceCulture); + } + } } } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index 6fc4189093..2e15ea7b88 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -196,7 +196,13 @@ Benchmark class '{0}' must be generic - Only one benchmark method can be marked as baseline + Only one benchmark method can be marked as baseline per class + + + Only one benchmark method can be marked as baseline per class and category + + + Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "SomeCategory", ...) or a non-null value instead. Benchmark methods must be public @@ -211,7 +217,13 @@ Benchmark classes annotated with a [GenericTypeArguments] attribute must be generic - Only one benchmark method can be baseline + Only one benchmark method can be baseline per class + + + Only one benchmark method can be baseline per class and category + + + Single null argument to the [BenchmarkCategory] attribute results in unintended null array Parameter attributes are mutually exclusive; only one of the attributes [Params], [ParamsSource] or [ParamsAllValues] can be applied to a field at any one time diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index 87dbaa65c9..590693ed53 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -6,7 +6,6 @@ using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; - using System.Linq; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class RunAnalyzer : DiagnosticAnalyzer diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index 3afdee3e17..bc18fc00c8 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -13,7 +13,9 @@ public static class DiagnosticIds public const string General_BenchmarkClass_MethodMustBePublic = "BDN1103"; public const string General_BenchmarkClass_MethodMustBeNonGeneric = "BDN1104"; public const string General_BenchmarkClass_ClassMustBeNonStatic = "BDN1105"; - public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1106"; + public const string General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed = "BDN1106"; + public const string General_BenchmarkClass_OnlyOneMethodCanBeBaseline = "BDN1107"; + public const string General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory = "BDN1108"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnField = "BDN1200"; public const string Attributes_GeneralParameterAttributes_MutuallyExclusiveOnProperty = "BDN1201"; public const string Attributes_GeneralParameterAttributes_FieldMustBePublic = "BDN1202"; diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 37cd95c9b5..8088957e2a 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -5,6 +5,8 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; + using System; + using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -59,6 +61,12 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true, description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_ClassMustBeNonStatic_Description))); + internal static readonly DiagnosticDescriptor SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); internal static readonly DiagnosticDescriptor OnlyOneMethodCanBeBaselineRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_OnlyOneMethodCanBeBaseline, AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title)), @@ -67,6 +75,13 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); + internal static readonly DiagnosticDescriptor OnlyOneMethodCanBeBaselinePerCategoryRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat)), + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + public override ImmutableArray SupportedDiagnostics => [ ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, @@ -75,7 +90,9 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer MethodMustBePublicRule, MethodMustBeNonGenericRule, ClassMustBeNonStaticRule, - OnlyOneMethodCanBeBaselineRule + SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule, + OnlyOneMethodCanBeBaselineRule, + OnlyOneMethodCanBeBaselinePerCategoryRule ]; public override void Initialize(AnalysisContext analysisContext) @@ -93,45 +110,18 @@ public override void Initialize(AnalysisContext analysisContext) return; } - ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.ClassDeclaration); + ctx.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration); + ctx.RegisterSyntaxNodeAction(AnalyzeAttributeSyntax, SyntaxKind.Attribute); }); } - private static void Analyze(SyntaxNodeAnalysisContext context) + private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) { if (context.Node is not ClassDeclarationSyntax classDeclarationSyntax) { return; } - var genericTypeArgumentsAttributes = AnalyzerHelper.GetAttributes("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, classDeclarationSyntax.AttributeLists, context.SemanticModel); - if (genericTypeArgumentsAttributes.Length > 0 && classDeclarationSyntax.TypeParameterList == null) - { - context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } - - var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); - if (benchmarkAttributeTypeSymbol == null) - { - return; - } - - var benchmarkMethodsBuilder = ImmutableArray.CreateBuilder<(MethodDeclarationSyntax Method, ImmutableArray BaselineLocations)>(); - - foreach (var memberDeclarationSyntax in classDeclarationSyntax.Members) - { - if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax) - { - var benchmarkAttributes = AnalyzerHelper.GetAttributes(benchmarkAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); - if (benchmarkAttributes.Length > 0) - { - benchmarkMethodsBuilder.Add((methodDeclarationSyntax, benchmarkAttributes.SelectMany(a => GetBaselineLocations(a)).ToImmutableArray())); - } - } - } - - var benchmarkMethods = benchmarkMethodsBuilder.ToImmutable(); - var classStaticModifier = null as SyntaxToken?; var classAbstractModifier = null as SyntaxToken?; @@ -147,16 +137,19 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } } - if (genericTypeArgumentsAttributes.Length == 0) - { - //if (classDeclarationSyntax.TypeParameterList != null && !classAbstractModifier.HasValue) - //{ - // context.ReportDiagnostic(Diagnostic.Create(GenericClassMustBeAbstractOrAnnotatedWithAGenericTypeArgumentsAttributeRule, classDeclarationSyntax.TypeParameterList.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - //} - } - else + var genericTypeArgumentsAttributes = AnalyzerHelper.GetAttributes("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, classDeclarationSyntax.AttributeLists, context.SemanticModel); + if (genericTypeArgumentsAttributes.Length > 0 ) { - if (classDeclarationSyntax.TypeParameterList is { Parameters.Count: > 0 }) + if (classAbstractModifier.HasValue) + { + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + + if (classDeclarationSyntax.TypeParameterList == null || classDeclarationSyntax.TypeParameterList.Parameters.Count == 0) + { + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } + else { foreach (var genericTypeArgumentsAttribute in genericTypeArgumentsAttributes) { @@ -172,74 +165,411 @@ private static void Analyze(SyntaxNodeAnalysisContext context) } } } - } } - if (classAbstractModifier.HasValue && genericTypeArgumentsAttributes.Length > 0) + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); + if (benchmarkAttributeTypeSymbol == null) { - context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + return; } - if (benchmarkMethods.Length == 0) + var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryTypeSymbol(context.Compilation); + if (benchmarkCategoryAttributeTypeSymbol == null) { return; } - if (classStaticModifier.HasValue) + var hasBenchmarkMethods = false; + var nullBenchmarkCategoryBenchmarkAttributeBaselineLocations = new List(); + var benchmarkCategoryBenchmarkAttributeBaselineLocations = new Dictionary>(); + + foreach (var memberDeclarationSyntax in classDeclarationSyntax.Members) { - context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonStaticRule, classStaticModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + var hasBenchmarkCategoryCompilerDiagnostics = false; + var benchmarkCategories = new List(); + var benchmarkAttributeUsages = new List(); + + if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax) + { + foreach (var attributeListSyntax in methodDeclarationSyntax.AttributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) + { + var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null) + { + continue; + } + + if (attributeSyntaxTypeSymbol.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + benchmarkAttributeUsages.Add(attributeSyntax); + } + else if (attributeSyntaxTypeSymbol.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) + { + // Check if this is an explicit params array creation + + Optional constantValue; + + // Collection expression + + if (attributeSyntax.ArgumentList.Arguments[0].Expression is CollectionExpressionSyntax collectionExpressionSyntax) + { + foreach (var collectionElementSyntax in collectionExpressionSyntax.Elements) + { + if (collectionElementSyntax is ExpressionElementSyntax expressionElementSyntax) + { + constantValue = context.SemanticModel.GetConstantValue(expressionElementSyntax.Expression); + if (constantValue.HasValue) + { + if (constantValue.Value is string benchmarkCategoryValue) + { + benchmarkCategories.Add(benchmarkCategoryValue); + } + else if (constantValue.Value is null) + { + benchmarkCategories.Add(null); + } + } + else + { + hasBenchmarkCategoryCompilerDiagnostics = true; + + break; + } + } + } + + continue; + } + + // Array creation expression + + var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeSyntax.ArgumentList.Arguments[0].Expression).Type; + if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol) + { + if (arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_String) + { + if (attributeSyntax.ArgumentList.Arguments[0].Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax) + { + if (arrayCreationExpressionSyntax.Initializer != null) + { + foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions) + { + constantValue = context.SemanticModel.GetConstantValue(expressionSyntax); + if (constantValue.HasValue) + { + if (constantValue.Value is string benchmarkCategoryValue) + { + benchmarkCategories.Add(benchmarkCategoryValue); + } + else if (constantValue.Value is null) + { + benchmarkCategories.Add(null); + } + } + else + { + hasBenchmarkCategoryCompilerDiagnostics = true; + + break; + } + } + } + } + } + + continue; + } + + // Params value + + constantValue = context.SemanticModel.GetConstantValue(attributeSyntax.ArgumentList.Arguments[0].Expression); + if (constantValue.HasValue) + { + if (constantValue.Value is null) + { + hasBenchmarkCategoryCompilerDiagnostics = true; + } + else if (constantValue.Value is string benchmarkCategoryValue) + { + benchmarkCategories.Add(benchmarkCategoryValue); + } + } + } + else if (attributeSyntax.ArgumentList is { Arguments.Count: > 1 }) + { + // Params values + + foreach (var parameterValueAttributeArgumentSyntax in attributeSyntax.ArgumentList.Arguments) + { + var constantValue = context.SemanticModel.GetConstantValue(parameterValueAttributeArgumentSyntax.Expression); + if (constantValue.HasValue) + { + if (constantValue.Value is string benchmarkCategoryValue) + { + benchmarkCategories.Add(benchmarkCategoryValue); + } + else if (constantValue.Value is null) + { + benchmarkCategories.Add(null); + } + } + else + { + hasBenchmarkCategoryCompilerDiagnostics = true; + + break; + } + } + } + } + } + } + + if (benchmarkAttributeUsages.Count == 1) + { + hasBenchmarkMethods = true; + + if (!methodDeclarationSyntax.Modifiers.Any(SyntaxKind.PublicKeyword)) + { + context.ReportDiagnostic(Diagnostic.Create(MethodMustBePublicRule, methodDeclarationSyntax.Identifier.GetLocation(), methodDeclarationSyntax.Identifier.ToString())); + } + + if (methodDeclarationSyntax.TypeParameterList != null) + { + context.ReportDiagnostic(Diagnostic.Create(MethodMustBeNonGenericRule, methodDeclarationSyntax.TypeParameterList.GetLocation(), methodDeclarationSyntax.Identifier.ToString())); + } + + if (!hasBenchmarkCategoryCompilerDiagnostics) + { + if (benchmarkCategories.Count > 0 && benchmarkAttributeUsages[0].ArgumentList != null) + { + foreach (var attributeArgumentSyntax in benchmarkAttributeUsages[0].ArgumentList.Arguments) + { + if (attributeArgumentSyntax.NameEquals != null && attributeArgumentSyntax.NameEquals.Name.Identifier.ValueText == "Baseline") + { + var constantValue = context.SemanticModel.GetConstantValue(attributeArgumentSyntax.Expression); + if (constantValue is { HasValue: true, Value: true }) + { + var benchmarkCategoryFormatted = FormatBenchmarkCategory(benchmarkCategories); + var baselineLocation = attributeArgumentSyntax.GetLocation(); + + if (benchmarkCategoryBenchmarkAttributeBaselineLocations.TryGetValue(benchmarkCategoryFormatted, out var baselineLocationsPerUniqueBenchmarkCategory)) + { + baselineLocationsPerUniqueBenchmarkCategory.Add(baselineLocation); + } + else + { + benchmarkCategoryBenchmarkAttributeBaselineLocations[benchmarkCategoryFormatted] = [ baselineLocation ]; + } + } + } + } + } + else + { + if (benchmarkAttributeUsages[0].ArgumentList != null) + { + foreach (var attributeArgumentSyntax in benchmarkAttributeUsages[0].ArgumentList.Arguments) + { + if (attributeArgumentSyntax.NameEquals != null && attributeArgumentSyntax.NameEquals.Name.Identifier.ValueText == "Baseline") + { + var constantValue = context.SemanticModel.GetConstantValue(attributeArgumentSyntax.Expression); + if (constantValue is { HasValue: true, Value: true }) + { + nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Add(attributeArgumentSyntax.GetLocation()); + } + } + } + } + } + } + } + } } - var baselineCount = 0; - foreach (var (benchmarkMethod, baselineLocations) in benchmarkMethods) + if (hasBenchmarkMethods) { - var methodIsPublic = benchmarkMethod.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)); - if (!methodIsPublic) + if (classStaticModifier.HasValue) { - context.ReportDiagnostic(Diagnostic.Create(MethodMustBePublicRule, benchmarkMethod.Identifier.GetLocation(), benchmarkMethod.Identifier.ToString())); + context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonStaticRule, classStaticModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); } - if (benchmarkMethod.TypeParameterList != null) + if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count >= 2) { - context.ReportDiagnostic(Diagnostic.Create(MethodMustBeNonGenericRule, benchmarkMethod.TypeParameterList.GetLocation(), benchmarkMethod.Identifier.ToString())); + foreach (var baselineLocation in nullBenchmarkCategoryBenchmarkAttributeBaselineLocations) + { + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselineRule, baselineLocation)); + } } - baselineCount += baselineLocations.Length; - } + var singularBenchmarkCategoryBenchmarkAttributeBaselineLocations = new Dictionary>(benchmarkCategoryBenchmarkAttributeBaselineLocations); - if (baselineCount > 1) - { - foreach (var benchmarkMethod in benchmarkMethods) + foreach (var (benchmarkCategory, baselineLocations) in benchmarkCategoryBenchmarkAttributeBaselineLocations) { - foreach (var baselineLocation in benchmarkMethod.BaselineLocations) + if (baselineLocations.Count > 1) { - context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselineRule, baselineLocation)); + foreach (var baselineLocation in baselineLocations) + { + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselinePerCategoryRule, baselineLocation)); + } + + singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.Remove(benchmarkCategory); + } + } + + if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 || singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count > 0) + { + var hasDuplicateBaselineBenchmarkMethodNullCategories = false; + var duplicateBaselineBenchmarkMethodCategories = new HashSet(); + + var benchmarkClassTypeSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax); + if (benchmarkClassTypeSymbol is { TypeKind: TypeKind.Class }) + { + var baseType = benchmarkClassTypeSymbol.OriginalDefinition.BaseType; + + while (baseType != null && baseType.SpecialType != SpecialType.System_Object) + { + foreach (var member in baseType.GetMembers()) + { + var hasBenchmarkCategoryCompilerDiagnostics = false; + var benchmarkCategories = new List(); + var benchmarkAttributeUsages = new List(); + + if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary } methodSymbol) + { + var methodAttributes = methodSymbol.GetAttributes(); + + foreach (var attribute in methodAttributes) + { + if (attribute.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + benchmarkAttributeUsages.Add(attribute); + } + else if (attribute.AttributeClass.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + foreach (var benchmarkCategoriesArray in attribute.ConstructorArguments) + { + if (!benchmarkCategoriesArray.IsNull) + { + foreach (var benchmarkCategory in benchmarkCategoriesArray.Values) + { + // TODO: Check if this is necessary + if (benchmarkCategory.Kind != TypedConstantKind.Error) + { + if (benchmarkCategory.Value == null) + { + benchmarkCategories.Add(null); + } + else if (benchmarkCategory.Value is string benchmarkCategoryValue) + { + benchmarkCategories.Add(benchmarkCategoryValue); + } + } + else + { + hasBenchmarkCategoryCompilerDiagnostics = true; + + break; + } + } + } + } + } + } + } + + if (benchmarkAttributeUsages.Count == 1) + { + if (!hasBenchmarkCategoryCompilerDiagnostics) + { + if (benchmarkCategories.Count > 0) + { + var benchmarkCategoryFormatted = FormatBenchmarkCategory(benchmarkCategories); + if (singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.ContainsKey(benchmarkCategoryFormatted)) + { + if (!duplicateBaselineBenchmarkMethodCategories.Contains(benchmarkCategoryFormatted)) + { + if (benchmarkAttributeUsages[0].NamedArguments.Any(na => na is { Key: "Baseline", Value.Value: true })) + { + duplicateBaselineBenchmarkMethodCategories.Add(benchmarkCategoryFormatted); + } + } + } + } + else + { + if ( nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 + && !hasDuplicateBaselineBenchmarkMethodNullCategories + && benchmarkAttributeUsages[0].NamedArguments.Any(na => na is { Key: "Baseline", Value.Value: true })) + { + hasDuplicateBaselineBenchmarkMethodNullCategories = true; + } + } + } + } + } + + baseType = baseType.OriginalDefinition.BaseType; + } + } + + if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 && hasDuplicateBaselineBenchmarkMethodNullCategories) + { + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselineRule, nullBenchmarkCategoryBenchmarkAttributeBaselineLocations[0])); + } + + foreach (var duplicateBaselineBenchmarkMethodCategory in duplicateBaselineBenchmarkMethodCategories) + { + if (singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.TryGetValue(duplicateBaselineBenchmarkMethodCategory, out var baselineLocations)) + { + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselinePerCategoryRule, baselineLocations[0])); + } } } } + } - return; + private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) + { + if (context.Node is not AttributeSyntax attributeSyntax) + { + return; + } - static ImmutableArray GetBaselineLocations(AttributeSyntax attributeSyntax) + var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryTypeSymbol(context.Compilation); + if (benchmarkCategoryAttributeTypeSymbol == null) { - var baselineLocationsBuilder = ImmutableArray.CreateBuilder(); + return; + } - if (attributeSyntax.ArgumentList == null || attributeSyntax.ArgumentList.Arguments.Count == 0) + var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) { - return ImmutableArray.Empty; - } + var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; - foreach (var attributeArgumentSyntax in attributeSyntax.ArgumentList.Arguments) - { - if (attributeArgumentSyntax.NameEquals != null && attributeArgumentSyntax.NameEquals.Name.Identifier.ValueText == "Baseline" && attributeArgumentSyntax.Expression.IsKind(SyntaxKind.TrueLiteralExpression)) + var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); + if (constantValue is { HasValue: true, Value: null }) { - baselineLocationsBuilder.Add(attributeArgumentSyntax.GetLocation()); + context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule, argumentSyntax.GetLocation())); } } - - return baselineLocationsBuilder.ToImmutable(); } } + + private static INamedTypeSymbol? GetBenchmarkCategoryTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkCategoryAttribute"); + + private static string FormatBenchmarkCategory(List benchmarkCategories) + { + // Default ICategoryDiscoverer implementation: DefaultCategoryDiscoverer + + return string.Join(",", benchmarkCategories.Distinct(StringComparer.OrdinalIgnoreCase)); + } } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 51736dcde2..f1a593ca18 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -11,6 +11,8 @@ using System.Linq; using System.Threading.Tasks; + // TODO: Verify which diagnostics rely on presence of [Benchmark] attribute on methods and test with 0, 2, or 3 attribute usages + public class BenchmarkClassAnalyzerTests { public class General : AnalyzerTestFixture @@ -43,7 +45,8 @@ public class ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract : AnalyzerT public ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule) { } [Theory, CombinatorialData] - public async Task Abstract_class_annotated_with_at_least_one_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) + public async Task Abstract_class_annotated_with_at_least_one_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { const string benchmarkClassName = "BenchmarkClass"; @@ -68,6 +71,8 @@ public void BenchmarkMethod() await RunAsync(); } + + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; } public class ClassWithGenericTypeArgumentsAttributeMustBeGeneric : AnalyzerTestFixture @@ -76,7 +81,7 @@ public ClassWithGenericTypeArgumentsAttributeMustBeGeneric() : base(BenchmarkCla [Theory, CombinatorialData] public async Task Generic_class_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", genericTypeArgumentsAttributeUsageCount); @@ -101,7 +106,7 @@ public void BenchmarkMethod() [Theory, CombinatorialData] public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { const string benchmarkClassName = "BenchmarkClass"; @@ -130,7 +135,7 @@ public void BenchmarkMethod() [Theory, CombinatorialData] public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_inheriting_from_an_abstract_generic_class_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, [CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { const string benchmarkClassName = "BenchmarkClass"; @@ -172,6 +177,8 @@ public void BenchmarkMethod() private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; + + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; } public class GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount : AnalyzerTestFixture @@ -181,7 +188,7 @@ public GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount() : base( [Theory, CombinatorialData] public async Task Generic_class_annotated_with_a_generictypearguments_attribute_having_matching_type_argument_count_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(TypeParametersListLengthEnumerableLocal))] int typeParametersListLength, [CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", genericTypeArgumentsAttributeUsageCount); @@ -207,7 +214,7 @@ public void BenchmarkMethod() [Theory, CombinatorialData] public async Task Generic_class_annotated_with_a_generictypearguments_attribute_having_mismatching_type_argument_count_should_trigger_diagnostic([CombinatorialMemberData(nameof(TypeArgumentsData))] ValueTupleDouble typeArgumentsData, - [CombinatorialValues("", "[Benchmark]")] string benchmarkAttributeUsage) + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { const string benchmarkClassName = "BenchmarkClass"; @@ -243,6 +250,8 @@ public void BenchmarkMethod() private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; + + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; } public class MethodMustBePublic : AnalyzerTestFixture @@ -431,127 +440,1246 @@ public static void BenchmarkMethod() } } + public class SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed : AnalyzerTestFixture + { + public SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed() : base(BenchmarkClassAnalyzer.SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule) + { + } + + [Theory, CombinatorialData] + public async Task Providing_a_non_null_single_argument_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + [assembly: BenchmarkDotNet.Attributes.BenchmarkCategory({{(useConstantFromOtherClass ? "Constants.Value" : "\"test\"")}})] + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + } + """; + + var benchmarkCategoryAttributeUsage = $"[BenchmarkCategory({(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "\"test\"")})]"; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + {{benchmarkCategoryAttributeUsage}} + public {{abstractModifier}}class BenchmarkClassAncestor1 + { + {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "\"test\"")};" : "")}} + + {{benchmarkCategoryAttributeUsage}} + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + ReferenceConstants("string", "\"test\""); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_empty_array_argument_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + [CombinatorialMemberData(nameof(EmptyBenchmarkCategoryAttributeArgumentEnumerableLocal))] string emptyBenchmarkCategoryAttributeArgument, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + [assembly: BenchmarkDotNet.Attributes.BenchmarkCategory{{emptyBenchmarkCategoryAttributeArgument}}] + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + [BenchmarkCategory{{emptyBenchmarkCategoryAttributeArgument}}] + public {{abstractModifier}}class BenchmarkClassAncestor1 + { + [BenchmarkCategory{{emptyBenchmarkCategoryAttributeArgument}}] + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_array_argument_containing_one_or_more_null_values_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialValues("{0}", "{0}, {1}", "{1}, {0}", "{0}, {1}, {0}", "{1}, {0}, {1}")] string valuesTemplate, + [CombinatorialMemberData(nameof(BenchmarkCategoryAttributeValuesContainerEnumerableLocal), false)] string valuesContainer, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + var assemblyLevelAttributeValues = string.Format(valuesContainer, string.Format(valuesTemplate, + useConstantsFromOtherClass ? "Constants.Value1" : "null", + useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")); + + var testCode = /* lang=c#-test */ $$""" + [assembly: BenchmarkDotNet.Attributes.BenchmarkCategory({{assemblyLevelAttributeValues}})] + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + } + """; + + var classAndMethodAttributeLevelValues = string.Format(valuesContainer, string.Format(valuesTemplate, + useLocalConstants ? "_xNull" : useConstantsFromOtherClass ? "Constants.Value1" : "null", + useLocalConstants ? "_xValue" : useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")); + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + [BenchmarkCategory({{classAndMethodAttributeLevelValues}})] + public {{abstractModifier}}class BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const string _xNull = {(useConstantsFromOtherClass ? "Constants.Value1" : "null")}; + private const string _xValue = {(useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")}; + """ : "")}} + + [BenchmarkCategory({{classAndMethodAttributeLevelValues}})] + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + ReferenceConstants(("string", "null"), ("string", "\"test\"")); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_a_null_single_argument_should_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + [assembly: BenchmarkDotNet.Attributes.BenchmarkCategory({|#0:{{(useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + [BenchmarkCategory({|#1:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] + public {{abstractModifier}}class BenchmarkClassAncestor1 + { + {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + + [BenchmarkCategory({|#2:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + ReferenceConstants("string", "null"); + + AddExpectedDiagnostic(0); + AddExpectedDiagnostic(1); + AddExpectedDiagnostic(2); + + await RunAsync(); + } + + public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; + + public static IEnumerable EmptyBenchmarkCategoryAttributeArgumentEnumerableLocal => EmptyBenchmarkCategoryAttributeArgumentEnumerable(); + + public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerableLocal(bool useParamsValues) => BenchmarkCategoryAttributeValuesContainerEnumerable(useParamsValues); + } + public class OnlyOneMethodCanBeBaseline : AnalyzerTestFixture { public OnlyOneMethodCanBeBaseline() : base(BenchmarkClassAnalyzer.OnlyOneMethodCanBeBaselineRule) { } - [Fact] - public async Task Class_with_only_one_benchmark_method_marked_as_baseline_should_not_trigger_diagnostic() + // TODO: Test with duplicate [Benchmark] attribute usage on same method (should not trigger diagnostic) + // Category can contain multiple values separated by comma + // Test with all types of array containers (see Parameter attribute tests) + + [Theory, CombinatorialData] + public async Task Class_with_only_one_benchmark_method_marked_as_baseline_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + bool useInvalidFalseValue) { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")}; + """ : "")}} + + [Benchmark(Baseline = {{(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}})] + public void BaselineBenchmarkMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + } + """; - public class BenchmarkClass - { - [Benchmark(Baseline = true)] - public void BaselineBenchmarkMethod() - { + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2, System.IEquatable + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; + + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + {{(useLocalConstants ? $"private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")};" : "")}} + + [Benchmark(Baseline = {{(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")}})] + public void NonBaselineBenchmarkMethod2() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; - } - - [Benchmark] - public void NonBaselineBenchmarkMethod1() - { - - } - - [Benchmark(Baseline = false)] - public void NonBaselineBenchmarkMethod2() - { - - } - } - """; + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + ReferenceConstants(("bool", "true"), ("bool", useInvalidFalseValue ? "dummy" : "false")); + + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Class_with_duplicated_benchmark_attribute_usages_per_method_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialValues(2, 3)] int baselineBenchmarkAttributeUsageCount) + { + var baselineBenchmarkAttributeUsages = string.Join("\n", Enumerable.Repeat($"[Benchmark(Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")})]", baselineBenchmarkAttributeUsageCount)); + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")}; + """ : "")}} + + {{baselineBenchmarkAttributeUsages}} + public void BaselineBenchmarkMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2, System.IEquatable + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; + + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + {{(useLocalConstants ? $"private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")};" : "")}} + + {{baselineBenchmarkAttributeUsages}} + public void NonBaselineBenchmarkMethod2() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + ReferenceConstants(("bool", "true"), ("bool", "false")); + + DisableCompilerDiagnostics(); await RunAsync(); } - [Fact] - public async Task Class_with_no_benchmark_methods_marked_as_baseline_should_not_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Class_with_no_benchmark_methods_marked_as_baseline_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantFromOtherClass, + bool useLocalConstant, + bool useInvalidFalseValue) { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - public class BenchmarkClass - { - [Benchmark] - public void NonBaselineBenchmarkMethod1() - { + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstant ? $"private const bool _xFalse = {(useConstantFromOtherClass ? "Constants.Value" : useInvalidFalseValue ? "dummy" : "false")};" : "")}} + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { - } - - [Benchmark] - public void NonBaselineBenchmarkMethod2() - { - - } - - [Benchmark(Baseline = false)] - public void NonBaselineBenchmarkMethod3() - { - - } - } - """; + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + [Benchmark(Baseline = {{(useLocalConstant ? "_xFalse" : useConstantFromOtherClass ? "Constants.Value" : useInvalidFalseValue ? "dummy" : "false")}})] + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; TestCode = testCode; + ReferenceConstants("bool", useInvalidFalseValue ? "dummy" : "false"); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + DisableCompilerDiagnostics(); await RunAsync(); } - [Fact] - public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_should_trigger_diagnostic() + [Theory, CombinatorialData] + public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_per_unique_category_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialMemberData(nameof(BenchmarkCategoryAttributeValuesContainerEnumerableLocal), true)] string valuesContainer) { - const string testCode = /* lang=c#-test */ """ - using BenchmarkDotNet.Attributes; + var baselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")})]"; + var nonBaselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : "false")})]"; - public class BenchmarkClass - { - [Benchmark({|#0:Baseline = true|})] - [Benchmark] - public void BaselineBenchmarkMethod1() - { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")}; + """ : "")}} + + [BenchmarkCategory({{string.Format(valuesContainer, """ + null, "test", null, "TEST", "test2" + """)}})] + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod1() + { + + } + + + [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test", null + """)}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test2" + """)}})] + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod2() + { + + } + + [BenchmarkCategory("Category1")] + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod1() + { + + } + + [BenchmarkCategory("Category1")] + public void DummyMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; - } - - [Benchmark] - public void NonBaselineBenchmarkMethod1() - { - - } - - [Benchmark(Baseline = false)] - public void NonBaselineBenchmarkMethod2() - { - - } - - [Benchmark({|#1:Baseline = true|})] - public void BaselineBenchmarkMethod2() - { - - } - - [Benchmark({|#2:Baseline = true|})] - [Benchmark({|#3:Baseline = true|})] - public void BaselineBenchmarkMethod3() - { - - } - } - """; + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + {{(useLocalConstants ? $"private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")};" : "")}} + + [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test", null + """)}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test2" + """)}})] + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod3() + { + + } + } + """; TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_should_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + bool useDuplicateInSameClass) + { + var baselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")})]"; + var baselineBenchmarkAttributeUsageWithLocationMarker = $"[Benchmark({{{{|#{{0}}:Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}|}}}})]"; + var nonBaselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : "false")})]"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")}; + """ : "")}} + + {{string.Format(baselineBenchmarkAttributeUsageWithLocationMarker, 0)}} + public void BaselineBenchmarkMethod1() + { + + } + + {{(useDuplicateInSameClass ? string.Format(baselineBenchmarkAttributeUsageWithLocationMarker, 1) : "")}} + public void BaselineBenchmarkMethod2() + { + + } + + [BenchmarkCategory("Category1")] + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod1() + { + + } + + [BenchmarkCategory("Category1")] + public void DummyMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + {{(useLocalConstants ? $"private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")};" : "")}} + + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + AddExpectedDiagnostic(0); + + if (useDuplicateInSameClass) + { + AddExpectedDiagnostic(1); + } + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_with_empty_category_should_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialMemberData(nameof(EmptyBenchmarkCategoryAttributeEnumerableLocal))] string emptyBenchmarkCategoryAttribute, + bool useDuplicateInSameClass) + { + var emptyBenchmarkCategoryAttributeUsages = string.Join("\n", Enumerable.Repeat(emptyBenchmarkCategoryAttribute, 3)); + var baselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")})]"; + var baselineBenchmarkAttributeUsageWithLocationMarker = $"[Benchmark({{{{|#{{0}}:Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}|}}}})]"; + var nonBaselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : "false")})]"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")}; + """ : "")}} + + {{emptyBenchmarkCategoryAttributeUsages}} + {{string.Format(baselineBenchmarkAttributeUsageWithLocationMarker, 0)}} + public void BaselineBenchmarkMethod1() + { + + } + + {{emptyBenchmarkCategoryAttributeUsages}} + {{(useDuplicateInSameClass ? string.Format(baselineBenchmarkAttributeUsageWithLocationMarker, 1) : "")}} + public void BaselineBenchmarkMethod2() + { + + } + + [BenchmarkCategory("Category1")] + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod1() + { + + } + + [BenchmarkCategory("Category1")] + public void DummyMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + {{(useLocalConstants ? $"private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")};" : "")}} + + {{emptyBenchmarkCategoryAttributeUsages}} + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + AddExpectedDiagnostic(0); + + if (useDuplicateInSameClass) + { + AddExpectedDiagnostic(1); + } + + await RunAsync(); + } + + public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + + public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerableLocal(bool useParamsValues) => BenchmarkCategoryAttributeValuesContainerEnumerable(useParamsValues); + + public static IEnumerable EmptyBenchmarkCategoryAttributeEnumerableLocal => EmptyBenchmarkCategoryAttributeEnumerable(); + } + + public class OnlyOneMethodCanBeBaselinePerCategory : AnalyzerTestFixture + { + public OnlyOneMethodCanBeBaselinePerCategory() : base(BenchmarkClassAnalyzer.OnlyOneMethodCanBeBaselinePerCategoryRule) { } + + [Theory, CombinatorialData] + public async Task Class_with_only_one_benchmark_method_marked_as_baseline_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + bool useInvalidFalseValue) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")}; + """ : "")}} + + [Benchmark(Baseline = {{(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}})] + public void BaselineBenchmarkMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2, System.IEquatable + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; + + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + {{(useLocalConstants ? $"private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")};" : "")}} + + [Benchmark(Baseline = {{(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")}})] + public void NonBaselineBenchmarkMethod2() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + ReferenceConstants(("bool", "true"), ("bool", useInvalidFalseValue ? "dummy" : "false")); + + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Class_with_only_one_benchmark_method_marked_as_baseline_per_unique_category_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + bool useInvalidFalseValue) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")}; + """ : "")}} + + [Benchmark(Baseline = {{(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}})] + public void BaselineBenchmarkMethod() + { + + } + + [BenchmarkCategory("Category1")] + [Benchmark(Baseline = {{(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}})] + public void BaselineBenchmarkMethod() + { + + } + + [BenchmarkCategory("Category2")] + [Benchmark(Baseline = {{(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}})] + public void BaselineBenchmarkMethod() + { + + } + + [BenchmarkCategory("Category1")] + [Benchmark(Baseline = {{(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")}})] + public void BaselineBenchmarkMethod() + { + + } + + [BenchmarkCategory("Category2")] + [Benchmark] + public void BaselineBenchmarkMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark(Baseline = {{(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : useInvalidFalseValue ? "dummy" : "false")}})] + public void NonBaselineBenchmarkMethod2() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + [Benchmark(Baseline = {{(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}})] + public void NonBaselineBenchmarkMethod2() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", useInvalidFalseValue ? "dummy" : "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Class_with_no_benchmark_methods_marked_as_baseline_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantFromOtherClass, + bool useLocalConstant, + bool useInvalidFalseValue) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstant ? $"private const bool _xFalse = {(useConstantFromOtherClass ? "Constants.Value" : useInvalidFalseValue ? "dummy" : "false")};" : "")}} + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + [Benchmark(Baseline = {{(useLocalConstant ? "_xFalse" : useConstantFromOtherClass ? "Constants.Value" : useInvalidFalseValue ? "dummy" : "false")}})] + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants("bool", useInvalidFalseValue ? "dummy" : "false"); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + DisableCompilerDiagnostics(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_should_trigger_not_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + bool useDuplicateInSameClass) + { + var baselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")})]"; + var nonBaselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : "false")})]"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")}; + """ : "")}} + + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod1() + { + + } + + {{(useDuplicateInSameClass ? baselineBenchmarkAttributeUsage : "")}} + public void BaselineBenchmarkMethod2() + { + + } + + [BenchmarkCategory("Category1")] + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod1() + { + + } + + [BenchmarkCategory("Category1")] + public void DummyMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + {{(useLocalConstants ? $"private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")};" : "")}} + + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_with_empty_category_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialMemberData(nameof(EmptyBenchmarkCategoryAttributeEnumerableLocal))] string emptyBenchmarkCategoryAttribute, + bool useDuplicateInSameClass) + { + var emptyBenchmarkCategoryAttributeUsages = string.Join("\n", Enumerable.Repeat(emptyBenchmarkCategoryAttribute, 3)); + var baselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")})]"; + var nonBaselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : "false")})]"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")}; + """ : "")}} + + {{emptyBenchmarkCategoryAttributeUsages}} + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod1() + { + + } + + {{emptyBenchmarkCategoryAttributeUsages}} + {{(useDuplicateInSameClass ? baselineBenchmarkAttributeUsage : "")}} + public void BaselineBenchmarkMethod2() + { + + } + + [BenchmarkCategory("Category1")] + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod1() + { + + } + + [BenchmarkCategory("Category1")] + public void DummyMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + {{(useLocalConstants ? $"private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")};" : "")}} + + {{emptyBenchmarkCategoryAttributeUsages}} + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_per_unique_category_should_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialMemberData(nameof(BenchmarkCategoryAttributeValuesContainerEnumerableLocal), true)] string valuesContainer, + bool useDuplicateInSameClass) + { + var baselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")})]"; + var baselineBenchmarkAttributeUsageWithLocationMarker = $"[Benchmark({{{{|#{{0}}:Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}|}}}})]"; + var nonBaselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : "false")})]"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")}; + """ : "")}} + + [BenchmarkCategory({{string.Format(valuesContainer, """ + null, "test", null, "TEST", "test2" + """)}})] + {{string.Format(baselineBenchmarkAttributeUsageWithLocationMarker, 0)}} + public void BaselineBenchmarkMethod1() + { + + } + + + [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test", null + """)}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test2" + """)}})] + {{(useDuplicateInSameClass ? string.Format(baselineBenchmarkAttributeUsageWithLocationMarker, 1) : "")}} + public void BaselineBenchmarkMethod2() + { + + } + + [BenchmarkCategory("Category1")] + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod1() + { + + } + + [BenchmarkCategory("Category1")] + public void DummyMethod() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } + + {{nonBaselineBenchmarkAttributeUsage}} + public void NonBaselineBenchmarkMethod3() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + {{(useLocalConstants ? $"private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")};" : "")}} + + [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test", null + """)}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test2" + """)}})] + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddExpectedDiagnostic(0); - AddExpectedDiagnostic(1); - AddExpectedDiagnostic(2); - AddExpectedDiagnostic(3); + + if (useDuplicateInSameClass) + { + AddExpectedDiagnostic(1); + } await RunAsync(); } + + public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + + public static IEnumerable EmptyBenchmarkCategoryAttributeEnumerableLocal => EmptyBenchmarkCategoryAttributeEnumerable(); + + public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerableLocal(bool useParamsValues) => BenchmarkCategoryAttributeValuesContainerEnumerable(useParamsValues); } public static TheoryData TypeParametersListLengthTheoryData => new(TypeParametersListLengthEnumerable); @@ -563,5 +1691,93 @@ public void BaselineBenchmarkMethod3() .ToList() .AsReadOnly(); private static ReadOnlyCollection GenericTypeArgumentsTheoryData => new List { "int", "string", "bool" }.AsReadOnly(); + + public static IEnumerable ClassAbstractModifiersEnumerable => [ "", "abstract " ]; + + public static IEnumerable BenchmarkAttributeUsagesEnumerable => [ "", "[Benchmark] " ]; + + //TODO: Move to a common helper class + public static IEnumerable EmptyBenchmarkCategoryAttributeArgumentEnumerable() + { + yield return ""; + yield return "()"; + + var nameColonUsages = new List + { + "", + "categories: " + }; + + var attributeUsagesBase = new List + { + "({0}new string[] {{ }})", + "({0}new string[0])", + "({0}[ ])" + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage); + } + } + } + + public static IEnumerable EmptyBenchmarkCategoryAttributeEnumerable() + { + yield return "[BenchmarkCategory]"; + yield return "[BenchmarkCategory()]"; + + var nameColonUsages = new List + { + "", + "categories: " + }; + + var attributeUsagesBase = new List + { + "({0}new string[] {{ }})", + "({0}new string[0])", + "({0}[ ])" + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + yield return $"[BenchmarkCategory{string.Format(attributeUsageBase, nameColonUsage)}]"; + } + } + } + + public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerable(bool useParamsValues) + { + return GenerateData(useParamsValues).Distinct(); + + static IEnumerable GenerateData(bool useParamsValues) + { + var nameColonUsages = new List + { + "", + "categories: " + }; + + List attributeUsagesBase = useParamsValues ? [ "{{0}}" ] : [ ]; + + attributeUsagesBase.AddRange([ + "{0}new string[] {{{{ {{0}} }}}}", + "{0}[ {{0}} ]" + ]); + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage); + } + } + } + } } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs index e92db1a01f..76c05fa9ed 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs @@ -133,17 +133,37 @@ protected void AddDefaultExpectedDiagnostic(params object[] arguments) AddExpectedDiagnostic(arguments); } + protected void AddDefaultExpectedDiagnostic(DiagnosticSeverity effectiveDiagnosticSeverity) + { + AddExpectedDiagnostic(effectiveDiagnosticSeverity: effectiveDiagnosticSeverity); + } + + protected void AddDefaultExpectedDiagnostic(DiagnosticSeverity effectiveDiagnosticSeverity, params object[] arguments) + { + AddExpectedDiagnostic(arguments, effectiveDiagnosticSeverity: effectiveDiagnosticSeverity); + } + protected void AddExpectedDiagnostic(int markupKey) { AddExpectedDiagnostic(null, markupKey); } + protected void AddExpectedDiagnostic(int markupKey, DiagnosticSeverity effectiveDiagnosticSeverity) + { + AddExpectedDiagnostic(null, markupKey, effectiveDiagnosticSeverity); + } + protected void AddExpectedDiagnostic(int markupKey, params object[] arguments) { AddExpectedDiagnostic(arguments, markupKey); } - private void AddExpectedDiagnostic(object[]? arguments = null, int markupKey = 0) + protected void AddExpectedDiagnostic(int markupKey, DiagnosticSeverity effectiveDiagnosticSeverity, params object[] arguments) + { + AddExpectedDiagnostic(arguments, markupKey, effectiveDiagnosticSeverity); + } + + private void AddExpectedDiagnostic(object[]? arguments = null, int markupKey = 0, DiagnosticSeverity? effectiveDiagnosticSeverity = null) { if (_ruleUnderTest == null) { @@ -158,6 +178,11 @@ private void AddExpectedDiagnostic(object[]? arguments = null, int markupKey = 0 diagnosticResult = diagnosticResult.WithArguments(arguments); } + if (effectiveDiagnosticSeverity.HasValue) + { + diagnosticResult = diagnosticResult.WithSeverity(effectiveDiagnosticSeverity.Value); + } + _analyzerTest.ExpectedDiagnostics.Add(diagnosticResult); } @@ -222,6 +247,18 @@ public static class Constants """); } + protected void ReferenceConstants(params (string Type, string Value)[] constants) + { + _analyzerTest.TestState.Sources.Add($$""" + using System; + + public static class Constants + { + {{string.Join("\n ", constants.Select((c, i) => $"public const {c.Type} Value{i + 1} = {c.Value};"))}} + } + """); + } + private sealed class InternalAnalyzerTest : CSharpAnalyzerTest { protected override string DefaultTestProjectName => "BenchmarksAssemblyUnderAnalysis"; From 18e22dfe3a3dd3f3f3ea259dcb502c44075e7c75 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:17:47 +0100 Subject: [PATCH 21/24] Add test to OnlyOneMethodCanBeBaselinePerCategory rule verifying that it works correctly with invalid string values --- .../General/BenchmarkClassAnalyzer.cs | 3 +- .../General/BenchmarkClassAnalyzerTests.cs | 113 ++++++++++++++++-- 2 files changed, 106 insertions(+), 10 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 8088957e2a..0f2140912f 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -458,8 +458,7 @@ private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) { foreach (var benchmarkCategory in benchmarkCategoriesArray.Values) { - // TODO: Check if this is necessary - if (benchmarkCategory.Kind != TypedConstantKind.Error) + if (benchmarkCategory.Kind == TypedConstantKind.Primitive) { if (benchmarkCategory.Value == null) { diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index f1a593ca18..99d50f4cab 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -11,8 +11,6 @@ using System.Linq; using System.Threading.Tasks; - // TODO: Verify which diagnostics rely on presence of [Benchmark] attribute on methods and test with 0, 2, or 3 attribute usages - public class BenchmarkClassAnalyzerTests { public class General : AnalyzerTestFixture @@ -626,10 +624,6 @@ public class OnlyOneMethodCanBeBaseline : AnalyzerTestFixture BenchmarkAttributeUsagesEnumerable => [ "", "[Benchmark] " ]; - //TODO: Move to a common helper class public static IEnumerable EmptyBenchmarkCategoryAttributeArgumentEnumerable() { yield return ""; From 7109692784b8b4f07d9ced17ab4322313e615e60 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:18:43 +0100 Subject: [PATCH 22/24] Disable warnings for "Missing XML comment for publicly visible type or member" (CS1591) --- src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj index c1751c49c0..26bbc1d500 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj @@ -7,6 +7,7 @@ *$(MSBuildProjectFile)* true + CS1591 From f48473c294846c878d1752ccf54980d3b1619c86 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Mon, 27 Oct 2025 17:23:08 +0100 Subject: [PATCH 23/24] Correct resource strings for OnlyOneMethodCanBeBaselinePerCategory rule --- .../General/BenchmarkClassAnalyzer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 0f2140912f..5b8ab7212d 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -76,8 +76,8 @@ public class BenchmarkClassAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true); internal static readonly DiagnosticDescriptor OnlyOneMethodCanBeBaselinePerCategoryRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_MessageFormat)), "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true); From 78a8cc1d58477f870abd5e00c3410565ff53c5a7 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Tue, 28 Oct 2025 10:55:02 +0100 Subject: [PATCH 24/24] Build error fixes --- .../BenchmarkDotNet.Analyzers.csproj | 4 ++-- .../BenchmarkDotNet.Annotations.csproj | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj index 26bbc1d500..19e6e9628f 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj @@ -5,9 +5,9 @@ false - *$(MSBuildProjectFile)* + BenchmarkDotNet.Analyzers true - CS1591 + $(NoWarn);CS1591 diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index c1d3c66209..bae452fa0b 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -14,7 +14,8 @@ - - + + + \ No newline at end of file