From a8c9a7216cac96c3e4c2ad35096f6910436bb4c3 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/38] 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 c7b5c8313895e13de21f1e728ce62e79d3b8d429 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/38] 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 cb94c78aaabc8ee1156262476a3f10f3e9d6ac3d 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/38] 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 a6bda7c6ee8a244c9d3b5808b59afdafa625cb20 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/38] 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 ac406d9dcd3aab8c006883fb4e1d841f2cf01892 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/38] 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 6711944a00b92ee723dec73febef9f9c6c122863 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/38] 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 d1c2e33a12d06d2129a5816f92195767b6238d0b 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/38] 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 f011b69099493b49762c8ff495dbf5ebe4370dbb 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/38] * 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 40606f169e0115f9e24f0c30c5ba9b73eb1648e3 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/38] * 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 6999429c89ef7b1c7a6a3eb31f421e205bdcb3ab 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/38] 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 ce304482c80a0b0f22b5491bc2cf4077667f3762 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/38] 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 4639d812810166728ba845e88fe0be38fb2dde7f 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/38] 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 e2d1694d1dd589e535320a46d99bda079fad57e2 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/38] 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 b5c0b81959..589de5c90e 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 c4440cf0a90ea4c8ef240e86a7ce32d792409b12 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/38] 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 40cff140a089dbf9c5541fc6c8eccf050c232b8f 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/38] * 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 fd382b6b52b557377bdabfceb8653acc526af7a3 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/38] 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/38] 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 64d349551bf3d5df203960fe20bae6410f05419d 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/38] 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 293da44614effb9b223aacb8787ea77095cccc26 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/38] 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 829344f5f914cf855414abf5cfc341b836d5aa67 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/38] * 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 6805c0f2b438f860f0842f5921a65a23fdb660a9 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/38] 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 07f1f076952de7bf137225d8f73110e62746a66c 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/38] 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 51cd5f92e08b57a8597865fd8d234fd786bd67db 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/38] 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 99536a3bf53fb054847ee2c71c3ee5d288a5fb1d 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/38] 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 From 9cd604341e6e08907c3d88aadacffe9cbc8ce659 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sat, 1 Nov 2025 12:52:37 -0400 Subject: [PATCH 25/38] Add analyzer projects to main sln. Run analyzer tests in CI. Fix compile errors. Ensure analyzers run against test projects. --- .github/workflows/run-tests.yaml | 10 +++++ BenchmarkDotNet.Analyzers.sln | 43 ------------------- BenchmarkDotNet.sln | 14 ++++++ build/BenchmarkDotNet.Build/Program.cs | 25 ++++++++++- .../Runners/UnitTestRunner.cs | 12 ++++++ build/sdk/global.json | 2 +- .../BenchmarkDotNet.Analyzers.csproj | 9 +--- .../BenchmarkDotNet.Annotations.csproj | 2 +- .../ParamsAttributeAnalyzerTests.cs | 4 +- .../BenchmarkDotNet.Analyzers.Tests.csproj | 30 +++---------- tests/Directory.build.props | 6 +++ 11 files changed, 77 insertions(+), 80 deletions(-) delete mode 100644 BenchmarkDotNet.Analyzers.sln create mode 100644 tests/Directory.build.props diff --git a/.github/workflows/run-tests.yaml b/.github/workflows/run-tests.yaml index f893d46147..a0b3ee4d30 100644 --- a/.github/workflows/run-tests.yaml +++ b/.github/workflows/run-tests.yaml @@ -45,6 +45,12 @@ jobs: - name: Run task 'build' shell: cmd run: ./build.cmd build + - name: Run task 'unit-tests' + shell: cmd + run: ./build.cmd unit-tests -e + - name: Run task 'analyzer-tests' + shell: cmd + run: ./build.cmd analyzer-tests -e - name: Run task 'in-tests-full' shell: cmd run: ./build.cmd in-tests-full -e @@ -79,6 +85,8 @@ jobs: # Build and Test - name: Run task 'build' run: ./build.cmd build + - name: Run task 'analyzer-tests' + run: ./build.cmd analyzer-tests -e - name: Run task 'unit-tests' run: ./build.cmd unit-tests -e - name: Run task 'in-tests-core' @@ -116,6 +124,8 @@ jobs: # Build and Test - name: Run task 'build' run: ./build.cmd build + - name: Run task 'analyzer-tests' + run: ./build.cmd analyzer-tests -e - name: Run task 'unit-tests' run: ./build.cmd unit-tests -e - name: Run task 'in-tests-core' diff --git a/BenchmarkDotNet.Analyzers.sln b/BenchmarkDotNet.Analyzers.sln deleted file mode 100644 index 2398ad7d66..0000000000 --- a/BenchmarkDotNet.Analyzers.sln +++ /dev/null @@ -1,43 +0,0 @@ - -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", "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 - {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 - {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 - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {27411BE6-6445-400B-AB04-29B993B39CFF} - EndGlobalSection -EndGlobal diff --git a/BenchmarkDotNet.sln b/BenchmarkDotNet.sln index 1df6c0aabd..2ca0af1119 100644 --- a/BenchmarkDotNet.sln +++ b/BenchmarkDotNet.sln @@ -59,6 +59,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Exporters.P EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchmarkDotNet.Exporters.Plotting.Tests", "tests\BenchmarkDotNet.Exporters.Plotting.Tests\BenchmarkDotNet.Exporters.Plotting.Tests.csproj", "{199AC83E-30BD-40CD-87CE-0C838AC0320D}" 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 @@ -161,6 +165,14 @@ Global {199AC83E-30BD-40CD-87CE-0C838AC0320D}.Debug|Any CPU.Build.0 = Debug|Any CPU {199AC83E-30BD-40CD-87CE-0C838AC0320D}.Release|Any CPU.ActiveCfg = Release|Any CPU {199AC83E-30BD-40CD-87CE-0C838AC0320D}.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 @@ -190,6 +202,8 @@ Global {2E2283A3-6DA6-4482-8518-99D6D9F689AB} = {D6597E3A-6892-4A68-8E14-042FC941FDA2} {B92ECCEF-7C27-4012-9E19-679F3C40A6A6} = {D6597E3A-6892-4A68-8E14-042FC941FDA2} {199AC83E-30BD-40CD-87CE-0C838AC0320D} = {14195214-591A-45B7-851A-19D3BA2413F9} + {AA4DDCA0-C1D8-ADA8-69FE-2F67C4CA96B1} = {D6597E3A-6892-4A68-8E14-042FC941FDA2} + {7DE89F16-2160-42E3-004E-1F5064732121} = {14195214-591A-45B7-851A-19D3BA2413F9} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4D9AF12B-1F7F-45A7-9E8C-E4E46ADCBD1F} diff --git a/build/BenchmarkDotNet.Build/Program.cs b/build/BenchmarkDotNet.Build/Program.cs index 031e74266f..825d1e1554 100644 --- a/build/BenchmarkDotNet.Build/Program.cs +++ b/build/BenchmarkDotNet.Build/Program.cs @@ -98,6 +98,28 @@ public HelpInfo GetHelp() } } +[TaskName(Name)] +[TaskDescription("Run analyzer tests")] +[IsDependentOn(typeof(BuildTask))] +public class AnalyzerTestsTask : FrostingTask, IHelpProvider +{ + private const string Name = "analyzer-tests"; + public override void Run(BuildContext context) => context.UnitTestRunner.RunAnalyzerTests(); + + public HelpInfo GetHelp() + { + return new HelpInfo + { + Examples = + [ + new Example(Name) + .WithArgument(KnownOptions.Exclusive) + .WithArgument(KnownOptions.Verbosity, "Diagnostic") + ] + }; + } +} + [TaskName(Name)] [TaskDescription("Run integration tests using .NET Framework 4.6.2+ (slow)")] [IsDependentOn(typeof(BuildTask))] @@ -123,8 +145,9 @@ public class InTestsCoreTask : FrostingTask, IHelpProvider } [TaskName(Name)] -[TaskDescription("Run all unit and integration tests (slow)")] +[TaskDescription("Run all unit, analyzer, and integration tests (slow)")] [IsDependentOn(typeof(UnitTestsTask))] +[IsDependentOn(typeof(AnalyzerTestsTask))] [IsDependentOn(typeof(InTestsFullTask))] [IsDependentOn(typeof(InTestsCoreTask))] public class AllTestsTask : FrostingTask, IHelpProvider diff --git a/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs b/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs index 89b777d09f..a62960d3ca 100644 --- a/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs +++ b/build/BenchmarkDotNet.Build/Runners/UnitTestRunner.cs @@ -20,6 +20,11 @@ public class UnitTestRunner(BuildContext context) .Combine("BenchmarkDotNet.Exporters.Plotting.Tests") .CombineWithFilePath("BenchmarkDotNet.Exporters.Plotting.Tests.csproj"); + private FilePath AnalyzerTestsProjectFile { get; } = context.RootDirectory + .Combine("tests") + .Combine("BenchmarkDotNet.Analyzers.Tests") + .CombineWithFilePath("BenchmarkDotNet.Analyzers.Tests.csproj"); + private FilePath IntegrationTestsProjectFile { get; } = context.RootDirectory .Combine("tests") .Combine("BenchmarkDotNet.IntegrationTests") @@ -70,5 +75,12 @@ public void RunUnitTests() RunUnitTests(targetFramework); } + public void RunAnalyzerTests() + { + string[] targetFrameworks = context.IsRunningOnWindows() ? ["net462", "net8.0"] : ["net8.0"]; + foreach (var targetFramework in targetFrameworks) + RunTests(AnalyzerTestsProjectFile, "analyzer", targetFramework); + } + public void RunInTests(string tfm) => RunTests(IntegrationTestsProjectFile, "integration", tfm); } \ No newline at end of file diff --git a/build/sdk/global.json b/build/sdk/global.json index be5c357f30..5c03259d72 100644 --- a/build/sdk/global.json +++ b/build/sdk/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.410", + "version": "9.0.306", "rollForward": "disable" } } diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj index 19e6e9628f..6767a9ee16 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNet.Analyzers.csproj @@ -1,24 +1,20 @@  - + netstandard2.0 false - BenchmarkDotNet.Analyzers true $(NoWarn);CS1591 - - - True @@ -30,12 +26,9 @@ BenchmarkDotNetAnalyzerResources.Designer.cs - <_Parameter1>BenchmarkDotNet.Analyzers.Tests,PublicKey=00240000048000009400000006020000002400005253413100040000010001002970bbdfca4d129fc74b4845b239973f1b183684f0d7db5e1de7e085917e3656cf94884803cb800d85d5aae5838fb3f8fd1f2829e8208c4f087afcfe970bce44037ba30a66749cd5514b410ca8a35e9c7d6eb86975853c834c9ad25051537f9a05a0c540c5d84f2c7b32ab01619d84367fd424797ba3242f08b0e6ae75f66dad - - diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index bae452fa0b..140ef4b897 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -16,6 +16,6 @@ - + \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs index 149ecfa04d..f47ab8e5f2 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -504,7 +504,7 @@ public async Task Providing_an_unexpected_array_value_type_to_params_attribute_s public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(arrayValuesContainerAttributeArgument.Value1, valueAndType.Value1, valueAndType.Value2)}})] + [{{dummyAttributeUsage}}Params({{string.Format(arrayValuesContainerAttributeArgument.Value1!, valueAndType.Value1, valueAndType.Value2)}})] public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} } """; @@ -512,7 +512,7 @@ public class BenchmarkClass ReferenceDummyAttribute(); ReferenceDummyEnum(); - AddDefaultExpectedDiagnostic(string.Format(arrayValuesContainerAttributeArgument.Value2, + AddDefaultExpectedDiagnostic(string.Format(arrayValuesContainerAttributeArgument.Value2!, valueAndType.Value1, valueAndType.Value2), expectedFieldOrPropertyType, diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj index ee1e876743..9ddac20c27 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj +++ b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj @@ -1,25 +1,23 @@  - + - net462;net6.0;net8.0 + BenchmarkDotNet.Analyzers.Tests + net462;net8.0 enable - true true true true + $(NoWarn);CS1591 - - - @@ -27,25 +25,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/tests/Directory.build.props b/tests/Directory.build.props new file mode 100644 index 0000000000..7142daab6e --- /dev/null +++ b/tests/Directory.build.props @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 6f73b5af7d712bd6720cb5a811858dc72075cc9e Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Mon, 3 Nov 2025 10:33:59 -0500 Subject: [PATCH 26/38] Revert annotations csproj. Add analyzers to samples. --- .../BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj | 4 ++++ .../BenchmarkDotNet.Annotations.csproj | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj b/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj index 948849aa05..f7b83d0a9d 100644 --- a/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj +++ b/samples/BenchmarkDotNet.Samples/BenchmarkDotNet.Samples.csproj @@ -40,4 +40,8 @@ + + + + diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index 140ef4b897..bae452fa0b 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -16,6 +16,6 @@ - + \ No newline at end of file From 8af808f977293ddd2a0a4947b5fc5d218154b443 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Tue, 4 Nov 2025 12:22:32 +0100 Subject: [PATCH 27/38] * Change severity level of "UnnecessarySingleValuePassedToAttribute" rule from warning to information * Move diagnostic "MethodWithoutAttributeMustHaveNoParameters" to its own analyzer and also take [ArgumentsSource] attribute into consideration * Suppress analyzer errors for incorrect BenchmarkDotNet usages in tests * Adjust wording of "ClassWithGenericTypeArgumentsAttributeMustBeGenericRule" diagnostic and move trigger location to the attribute level --- .../AnalyzerReleases.Unshipped.md | 10 +- .../Attributes/ArgumentsAttributeAnalyzer.cs | 18 +-- .../GeneralArgumentAttributesAnalyzer.cs | 72 ++++++++++ .../GeneralParameterAttributesAnalyzer.cs | 1 - .../Attributes/ParamsAttributeAnalyzer.cs | 13 +- ...nchmarkDotNetAnalyzerResources.Designer.cs | 63 ++++----- .../BenchmarkDotNetAnalyzerResources.resx | 18 +-- .../DiagnosticIds.cs | 8 +- .../General/BenchmarkClassAnalyzer.cs | 27 ++-- .../BenchmarkDotNet.Annotations.csproj | 2 +- .../ArgumentsAttributeAnalyzerTests.cs | 82 ----------- .../GeneralArgumentAttributesAnalyzerTests.cs | 128 ++++++++++++++++++ ...GeneralParameterAttributesAnalyzerTests.cs | 6 +- .../General/BenchmarkClassAnalyzerTests.cs | 25 ++-- .../BenchmarkDotNet.Analyzers.Tests.csproj | 2 +- .../ScottPlotExporterTests.cs | 6 +- .../ArgumentsTests.cs | 108 ++++++++++----- .../BaselineRatioColumnTest.cs | 2 + .../PriorityTests.cs | 2 + .../Attributes/ParamsAllValuesVerifyTests.cs | 8 +- .../Exporters/MarkdownExporterVerifyTests.cs | 2 + .../FullNameProviderTests.cs | 12 ++ .../GenericBuilderTests.cs | 2 + .../Running/RunningEmptyBenchmarkTests.cs | 9 +- .../BenchmarkDotNet.Tests/TypeFilterTests.cs | 2 + .../Validators/ExecutionValidatorTests.cs | 6 +- .../Validators/ParamsValidatorTests.cs | 128 +++++++++++++----- 27 files changed, 509 insertions(+), 253 deletions(-) create mode 100644 src/BenchmarkDotNet.Analyzers/Attributes/GeneralArgumentAttributesAnalyzer.cs create mode 100644 tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralArgumentAttributesAnalyzerTests.cs diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index 50a7a43055..27ba8e2573 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -29,10 +29,10 @@ BDN1206 | Usage | Error | BDN1206_Attributes_GeneralParameterAttributes_P BDN1207 | Usage | Error | BDN1207_Attributes_GeneralParameterAttributes_PropertyMustHavePublicSetter BDN1300 | Usage | Error | BDN1300_Attributes_ParamsAttribute_MustHaveValues BDN1301 | Usage | Error | BDN1301_Attributes_ParamsAttribute_MustHaveMatchingValueType -BDN1302 | Usage | Warning | BDN1302_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute +BDN1302 | Usage | Info | 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 +BDN1400 | Usage | Error | BDN1400_Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters +BDN1500 | Usage | Error | BDN1500_Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute +BDN1501 | Usage | Error | BDN1501_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount +BDN1502 | Usage | Error | BDN1502_Attributes_ArgumentsAttribute_MustHaveMatchingValueType diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index 9a6d9dfe05..8fe15e7591 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -19,14 +19,6 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer 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)), @@ -46,7 +38,6 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => [ RequiresBenchmarkAttributeRule, - MethodWithoutAttributeMustHaveNoParametersRule, MustHaveMatchingValueCountRule, MustHaveMatchingValueTypeRule ]; @@ -77,7 +68,9 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) } var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); - if (argumentsAttributeTypeSymbol == null) + var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute"); + + if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null) { return; } @@ -87,11 +80,6 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) 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), methodDeclarationSyntax.Identifier.ToString())); - } - return; } diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralArgumentAttributesAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralArgumentAttributesAnalyzer.cs new file mode 100644 index 0000000000..d4b884c106 --- /dev/null +++ b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralArgumentAttributesAnalyzer.cs @@ -0,0 +1,72 @@ +namespace BenchmarkDotNet.Analyzers.Attributes +{ + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + + using System.Collections.Immutable; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class GeneralArgumentAttributesAnalyzer : DiagnosticAnalyzer + { + internal static readonly DiagnosticDescriptor MethodWithoutAttributeMustHaveNoParametersRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Description); + + public override ImmutableArray SupportedDiagnostics => + [ + MethodWithoutAttributeMustHaveNoParametersRule, + ]; + + 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 not MethodDeclarationSyntax methodDeclarationSyntax) + { + return; + } + + var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); + var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute"); + + if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null) + { + return; + } + + var hasBenchmarkAttribute = AnalyzerHelper.AttributeListsContainAttribute(AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation), methodDeclarationSyntax.AttributeLists, context.SemanticModel); + var hasArgumentsSourceAttribute = AnalyzerHelper.AttributeListsContainAttribute(argumentsSourceAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); + + var argumentsAttributes = AnalyzerHelper.GetAttributes(argumentsAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); + if (argumentsAttributes.Length == 0) + { + if (hasBenchmarkAttribute && !hasArgumentsSourceAttribute && methodDeclarationSyntax.ParameterList.Parameters.Count > 0) + { + context.ReportDiagnostic(Diagnostic.Create(MethodWithoutAttributeMustHaveNoParametersRule, Location.Create(context.FilterTree, methodDeclarationSyntax.ParameterList.Parameters.Span), methodDeclarationSyntax.Identifier.ToString())); + } + } + } + } +} diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs index c174280c20..98f3f5b413 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs @@ -254,7 +254,6 @@ private static void AnalyzeFieldOrPropertySymbol(SyntaxNodeAnalysisContext conte { context.ReportDiagnostic(Diagnostic.Create(NotValidOnConstantFieldRule, fieldConstModifierLocation, - fieldOrPropertyIdentifier, attributeSyntax.Name.ToString())); return; diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs index 126d2daf1f..906115ccbd 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs @@ -30,14 +30,15 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_Title)), AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_MessageFormat)), "Usage", - DiagnosticSeverity.Warning, + DiagnosticSeverity.Info, isEnabledByDefault: true); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( - MustHaveValuesRule, - MustHaveMatchingValueTypeRule, - UnnecessarySingleValuePassedToAttributeRule - ); + public override ImmutableArray SupportedDiagnostics => + [ + MustHaveValuesRule, + MustHaveMatchingValueTypeRule, + UnnecessarySingleValuePassedToAttributeRule + ]; public override void Initialize(AnalysisContext analysisContext) { diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index fe8a5aea7e..cf04e85533 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -60,36 +60,6 @@ internal BenchmarkDotNetAnalyzerResources() { } } - /// - /// 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 '{0}' 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. /// @@ -162,6 +132,37 @@ internal static string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_ } } + /// + /// Looks up a localized string similar to This method declares one or more parameters but is not annotated with either an [ArgumentsSource] attribute or one or more [Arguments] attributes. To ensure correct argument binding, methods with parameters must explicitly be annotated with an [ArgumentsSource] attribute or one or more [Arguments] attributes. + ///Either add the [ArgumentsSource] or [Arguments] attribute(s) or remove the parameters.. + /// + internal static string Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Description { + get { + return ResourceManager.GetString("Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_D" + + "escription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark method '{0}' without an [ArgumentsSource] or [Arguments] attribute(s) cannot declare parameters. + /// + internal static string Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_MessageFormat { + get { + return ResourceManager.GetString("Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_M" + + "essageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Benchmark methods without an [ArgumentsSource] or [Arguments] attribute(s) cannot declare parameters. + /// + internal static string Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Title { + get { + return ResourceManager.GetString("Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_T" + + "itle", resourceCulture); + } + } + /// /// Looks up a localized string similar to A field annotated with a parameter attribute must be public. /// @@ -648,7 +649,7 @@ internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttri } /// - /// Looks up a localized string similar to Benchmark class '{0}' must be generic. + /// Looks up a localized string similar to Attribute [GenericTypeArguments] can only be applied to a generic class. /// internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeGeneric_MessageFormat { get { diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index 2e15ea7b88..dac4317c3a 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -193,7 +193,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 + Attribute [GenericTypeArguments] can only be applied to a generic class Only one benchmark method can be marked as baseline per class @@ -258,8 +258,8 @@ Expected {0} value{1} as declared by the benchmark method '{2}', but found {3}. Update the attribute usage or method to match. - - Benchmark method '{0}' without [Arguments] attribute(s) cannot declare parameters + + Benchmark method '{0}' without an [ArgumentsSource] or [Arguments] attribute(s) cannot declare parameters Unexpected type for argument value '{0}'. Expected '{1}' but found '{2}'. @@ -300,9 +300,6 @@ 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 (or be implicitly convertible to) and order @@ -351,9 +348,9 @@ 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. + + This method declares one or more parameters but is not annotated with either an [ArgumentsSource] attribute or one or more [Arguments] attributes. To ensure correct argument binding, methods with parameters must explicitly be annotated with an [ArgumentsSource] attribute or one or more [Arguments] attributes. +Either add the [ArgumentsSource] or [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 implicitly convertible to) and order @@ -370,4 +367,7 @@ Either add the [Arguments] attribute(s) or remove the parameters. Referenced benchmark class '{0}' must be public + + Benchmark methods without an [ArgumentsSource] or [Arguments] attribute(s) cannot declare parameters + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index bc18fc00c8..eb4dcaf1ef 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -29,9 +29,9 @@ public static class DiagnosticIds 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"; + public const string Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters = "BDN1400"; + public const string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute = "BDN1500"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1501"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1502"; } } diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 5b8ab7212d..c9650f8b1f 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -145,24 +145,21 @@ private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); } - if (classDeclarationSyntax.TypeParameterList == null || classDeclarationSyntax.TypeParameterList.Parameters.Count == 0) + foreach (var genericTypeArgumentsAttribute in genericTypeArgumentsAttributes) { - context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, classDeclarationSyntax.Identifier.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } - else - { - foreach (var genericTypeArgumentsAttribute in genericTypeArgumentsAttributes) + if (classDeclarationSyntax.TypeParameterList == null || classDeclarationSyntax.TypeParameterList.Parameters.Count == 0) + { + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, genericTypeArgumentsAttribute.GetLocation())); + } + else if (genericTypeArgumentsAttribute.ArgumentList is { Arguments.Count: > 0 }) { - if (genericTypeArgumentsAttribute.ArgumentList is { Arguments.Count: > 0 }) + if (genericTypeArgumentsAttribute.ArgumentList.Arguments.Count != classDeclarationSyntax.TypeParameterList.Parameters.Count) { - 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)); - } + 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)); } } } diff --git a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj index bae452fa0b..1048cd2813 100644 --- a/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj +++ b/src/BenchmarkDotNet.Annotations/BenchmarkDotNet.Annotations.csproj @@ -16,6 +16,6 @@ - + \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs index ab88d0ec73..920324645a 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -146,87 +146,6 @@ public void BenchmarkMethod() }.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) - { - const string benchmarkMethodName = "BenchmarkMethod"; - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - public void {{benchmarkMethodName}}({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) - { - - } - } - """; - - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkMethodName); - - await RunAsync(); - } - - public static TheoryData ParametersListLength => new(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) { } @@ -1192,7 +1111,6 @@ static IEnumerable GenerateData() } } } - } } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralArgumentAttributesAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralArgumentAttributesAnalyzerTests.cs new file mode 100644 index 0000000000..cdbfcc00d1 --- /dev/null +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralArgumentAttributesAnalyzerTests.cs @@ -0,0 +1,128 @@ +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 GeneralArgumentAttributesAnalyzerTests + { + public class MethodWithoutAttributeMustHaveNoParameters : AnalyzerTestFixture + { + public MethodWithoutAttributeMustHaveNoParameters() : base(GeneralArgumentAttributesAnalyzer.MethodWithoutAttributeMustHaveNoParametersRule) { } + + [Theory] + [InlineData("""ArgumentsSource("test")""")] + [InlineData("""Arguments(42, "test")""")] + [InlineData(""" + Arguments(42, "test"), Arguments(1, "test2") + """)] + public async Task A_method_with_parameters_annotated_with_an_argumentssource_or_arguments_attribute_and_the_benchmark_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{attributeUsage}}] + public void BenchmarkMethod(int a, string b) + { + + } + } + """; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ParametersListLength))] + public async Task A_method_with_parameters_and_no_argumentssource_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_with_parameters_annotated_with_the_benchmark_attribute_and_an_argumentssource_attribute_but_no_arguments_attribute_should_not_trigger_diagnostic(int parametersListLength) + { + const string benchmarkMethodName = "BenchmarkMethod"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [ArgumentsSource("test")] + public void {{benchmarkMethodName}}({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) + { + + } + } + """; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ParametersListLength))] + public async Task A_method_with_parameters_annotated_with_the_benchmark_attribute_but_no_argumentssource_or_arguments_attribute_should_trigger_diagnostic(int parametersListLength) + { + const string benchmarkMethodName = "BenchmarkMethod"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void {{benchmarkMethodName}}({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) + { + + } + } + """; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkMethodName); + + await RunAsync(); + } + + public static TheoryData ParametersListLength => new(Enumerable.Range(1, Parameters.Count)); + + private static ReadOnlyCollection Parameters => new List { + "int a", + "string b", + "bool c" + }.AsReadOnly(); + } + + } +} diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs index ea30028c12..67617110e4 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs @@ -906,19 +906,17 @@ public class BenchmarkClass [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; + public {|#0:const|} int Constant = 0; } """; TestCode = testCode; - AddDefaultExpectedDiagnostic(constantIdentifier, attributeName); + AddDefaultExpectedDiagnostic(attributeName); await RunAsync(); } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 99d50f4cab..b0ccba77c7 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -106,15 +106,14 @@ public void BenchmarkMethod() public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { - const string benchmarkClassName = "BenchmarkClass"; - - var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", genericTypeArgumentsAttributeUsageCount); + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[{{|#{0}:GenericTypeArguments(typeof(int))|}}]", genericTypeArgumentsAttributeUsageCount) + .Select((a, i) => string.Format(a, i)); var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} - public class {|#0:{{benchmarkClassName}}|} + public class BenchmarkClass { {{benchmarkAttributeUsage}} public void BenchmarkMethod() @@ -125,7 +124,11 @@ public void BenchmarkMethod() """; TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); + + for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) + { + AddExpectedDiagnostic(i); + } await RunAsync(); } @@ -135,16 +138,15 @@ public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribu [CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { - const string benchmarkClassName = "BenchmarkClass"; - var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); - var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", genericTypeArgumentsAttributeUsageCount); + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[{{{{|#{{0}}:GenericTypeArguments({genericTypeArguments})|}}}}]", genericTypeArgumentsAttributeUsageCount) + .Select((a, i) => string.Format(a, i)); var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} - public class {|#0:{{benchmarkClassName}}|} : BenchmarkClassBase<{{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}}> + public class BenchmarkClass : BenchmarkClassBase<{{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}}> { } """; @@ -165,7 +167,10 @@ public void BenchmarkMethod() TestCode = testCode; AddSource(benchmarkBaseClassDocument); - AddDefaultExpectedDiagnostic(benchmarkClassName); + for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) + { + AddExpectedDiagnostic(i); + } await RunAsync(); } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj index 9ddac20c27..6df74ed2f7 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj +++ b/tests/BenchmarkDotNet.Analyzers.Tests/BenchmarkDotNet.Analyzers.Tests.csproj @@ -16,6 +16,7 @@ + @@ -26,7 +27,6 @@ - diff --git a/tests/BenchmarkDotNet.Exporters.Plotting.Tests/ScottPlotExporterTests.cs b/tests/BenchmarkDotNet.Exporters.Plotting.Tests/ScottPlotExporterTests.cs index 8dd4a4443c..eae8f3c8c3 100644 --- a/tests/BenchmarkDotNet.Exporters.Plotting.Tests/ScottPlotExporterTests.cs +++ b/tests/BenchmarkDotNet.Exporters.Plotting.Tests/ScottPlotExporterTests.cs @@ -265,13 +265,15 @@ [Benchmark] public void Bar() { } /* Invalid */ +#pragma warning disable BDN1107 [RankColumn, LogicalGroupColumn, BaselineColumn] public class Invalid_TwoMethodBaselines { [Benchmark(Baseline = true)] public void Foo() { } [Benchmark(Baseline = true)] public void Bar() { } - } - + } +#pragma warning restore BDN1107 + [RankColumn, LogicalGroupColumn, BaselineColumn] [SimpleJob(id: "Job1", baseline: true), SimpleJob(id: "Job2", baseline: true)] public class Invalid_TwoJobBaselines diff --git a/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs b/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs index c168071169..487530244e 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs @@ -66,14 +66,16 @@ public ValueTask SimpleValueTaskAsync(bool boolean, int number) public class WithArgumentsSource { +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(ArgumentsProvider))] public void Simple(bool boolean, int number) { if (boolean && number != 1 || !boolean && number != 2) throw new InvalidOperationException("Incorrect values were passed"); - } - + } +#pragma warning restore BDN1401 + public IEnumerable ArgumentsProvider() { yield return new object[] { true, 1 }; @@ -86,6 +88,7 @@ public IEnumerable ArgumentsProvider() public class WithArgumentsSourceInAnotherClass { +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OnePrimitiveType))] public void OnePrimitiveType(int number) @@ -93,15 +96,19 @@ public void OnePrimitiveType(int number) if (number % 2 != 1) throw new InvalidOperationException("Incorrect values were passed"); } +#pragma warning restore BDN1401 +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.TwoPrimitiveTypes))] public void TwoPrimitiveTypes(bool boolean, int number) { if (boolean && number != 1 || !boolean && number != 2) throw new InvalidOperationException("Incorrect values were passed"); - } - + } +#pragma warning restore BDN1401 + +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OneNonPrimitiveType))] public void OneNonPrimitiveType(Version version) @@ -109,8 +116,10 @@ public void OneNonPrimitiveType(Version version) int[] versionNumbers = { version.Major, version.Minor, version.MinorRevision, version.Build }; if (versionNumbers.Distinct().Count() != 4) throw new InvalidOperationException("Incorrect values were passed"); - } - + } +#pragma warning restore BDN1401 + +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.TwoNonPrimitiveTypes))] public void TwoNonPrimitiveTypes(Version version, DateTime dateTime) @@ -121,8 +130,10 @@ public void TwoNonPrimitiveTypes(Version version, DateTime dateTime) if (dateTime.Month != dateTime.Day) throw new InvalidOperationException("Incorrect values were passed"); - } - + } +#pragma warning restore BDN1401 + +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OnePrimitiveAndOneNonPrimitive))] public void OnePrimitiveAndOneNonPrimitive(Version version, int number) @@ -133,7 +144,8 @@ public void OnePrimitiveAndOneNonPrimitive(Version version, int number) if (number != version.Major) throw new InvalidOperationException("Incorrect values were passed"); - } + } +#pragma warning restore BDN1401 } public static class ExternalClassWithArgumentsSource { @@ -209,6 +221,7 @@ public class WithComplexTypesReturnedFromSources [ParamsSource(nameof(SameButStatic))] public Dictionary DictionaryParamStatic; +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(NonPrimitive))] public void Simple(SomeClass someClass, SomeStruct someStruct) @@ -228,8 +241,9 @@ public void Simple(SomeClass someClass, SomeStruct someStruct) for (int i = 0; i < someStruct.RangeEnd; i++) if (someClass.Values[i] != i * 2) throw new InvalidOperationException("Incorrect array values were passed"); - } - + } +#pragma warning restore BDN1401 + public IEnumerable NonPrimitive() { yield return new object[] { new SomeClass(Enumerable.Range(0, 100).ToArray()), new SomeStruct(100) }; @@ -293,9 +307,11 @@ public IEnumerable Sources() yield return new object[] { "Iterator.Select.Where", Iterator().Select(i => i).Where(i => i % 2 == 0) }; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(Sources))] - public void Any(string name, IEnumerable source) => source.Any(); + public void Any(string name, IEnumerable source) => source.Any(); +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -303,6 +319,7 @@ public IEnumerable Sources() public class WithJaggedArray { +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(CreateMatrix))] public void Test(int[][] array) @@ -314,8 +331,9 @@ public void Test(int[][] array) for (int j = 0; j < i; j++) if (array[i][j] != i) throw new ArgumentException("Invalid value"); - } - + } +#pragma warning restore BDN1401 + public IEnumerable CreateMatrix() { int[][] jagged = new int[10][]; @@ -351,6 +369,7 @@ public Generic(T1 item1, T2 item2) } } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetInputData))] public bool ValueTupleCompareNoOpt(ref Generic byRef) @@ -362,8 +381,9 @@ public bool ValueTupleCompareNoOpt(ref Generic byRef) throw new ArgumentException("Wrong values"); return true; - } - + } +#pragma warning restore BDN1401 + public IEnumerable GetInputData() { yield return new Generic(3, "red"); @@ -484,13 +504,15 @@ public IEnumerable GetArrayOfString() yield return new string[123]; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetArrayOfString))] public void TypeReflectionArrayGetType(string[] array) { if (array.Length != 123) throw new ArgumentException("The array was empty"); - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] // make sure BDN mimics xunit's MemberData behaviour @@ -504,13 +526,15 @@ public IEnumerable GetArguments() yield return new object[] { true }; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetArguments))] public void SingleArgument(bool boolean) { if (boolean != true) throw new ArgumentException("The value of boolean was incorrect"); - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -527,6 +551,7 @@ public IEnumerable GetArrays() }; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetArrays))] public void AcceptsArrays(int[] even, int[] notEven) @@ -539,7 +564,8 @@ public void AcceptsArrays(int[] even, int[] notEven) if (!notEven.All(n => n % 2 != 0)) throw new ArgumentException("Even"); - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -552,6 +578,7 @@ public IEnumerable GetVeryBigInteger() yield return BigInteger.Parse(new string(Enumerable.Repeat('1', 1000).ToArray())); } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetVeryBigInteger))] public void Method(BigInteger passed) @@ -560,7 +587,8 @@ public void Method(BigInteger passed) if (expected != passed) throw new ArgumentException("The BigInteger has wrong value!"); - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -578,6 +606,7 @@ public IEnumerable GetSpecialDoubleValues() yield return new object[] { double.PositiveInfinity, nameof(double.PositiveInfinity) }; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetSpecialDoubleValues))] public void Method(double passed, string name) @@ -605,7 +634,8 @@ public void Method(double passed, string name) default: throw new InvalidOperationException($"Unknown case! {name}"); } - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -623,6 +653,7 @@ public IEnumerable GetSpecialFloatValues() yield return new object[] { float.PositiveInfinity, nameof(float.PositiveInfinity) }; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetSpecialFloatValues))] public void Method(float passed, string name) @@ -650,7 +681,8 @@ public void Method(float passed, string name) default: throw new InvalidOperationException($"Unknown case! {name}"); } - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -664,6 +696,7 @@ public IEnumerable GetSpecialDecimalValues() yield return new object[] { decimal.MinValue, nameof(decimal.MinValue) }; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetSpecialDecimalValues))] public void Method(decimal passed, string name) @@ -679,7 +712,8 @@ public void Method(decimal passed, string name) default: throw new InvalidOperationException($"Unknown case! {name}"); } - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -692,6 +726,7 @@ public IEnumerable DateTimeValues() yield return new DateTime(2018, 8, 15); } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(DateTimeValues))] public void Test(DateTime passed) @@ -700,7 +735,8 @@ public void Test(DateTime passed) if (expected != passed) throw new ArgumentException("The DateTime has wrong value!"); - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -810,6 +846,7 @@ public static IEnumerable StaticProperty [ParamsSource(nameof(StaticProperty))] public int ParamTwo { get; set; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(StaticMethod))] public void TestMethod(int argument) @@ -820,8 +857,10 @@ public void TestMethod(int argument) throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 2 && ParamTwo != 3) throw new ArgumentException("The ParamTwo value is incorrect!"); - } - + } +#pragma warning restore BDN1401 + +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(StaticProperty))] public void TestProperty(int argument) @@ -832,7 +871,8 @@ public void TestProperty(int argument) throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 2 && ParamTwo != 3) throw new ArgumentException("The ParamTwo value is incorrect!"); - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -857,6 +897,7 @@ public static IEnumerable StaticProperty [ParamsSource(nameof(StaticProperty))] public int ParamTwo { get; set; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(StaticMethod))] public void TestMethod(int argument) @@ -867,8 +908,10 @@ public void TestMethod(int argument) throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 2 && ParamTwo != 3) throw new ArgumentException("The ParamTwo value is incorrect!"); - } - + } +#pragma warning restore BDN1401 + +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(StaticProperty))] public void TestProperty(int argument) @@ -879,7 +922,8 @@ public void TestProperty(int argument) throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 2 && ParamTwo != 3) throw new ArgumentException("The ParamTwo value is incorrect!"); - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -952,6 +996,7 @@ public IEnumerable Arguments() yield return new object[] { LongString, LongString2 }; } +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(Arguments))] public void Test(string first, string second) @@ -960,7 +1005,8 @@ public void Test(string first, string second) throw new ArgumentException($"{nameof(first)} passed string has wrong value!"); if (second != LongString2) throw new ArgumentException($"{nameof(second)} passed string has wrong value!"); - } + } +#pragma warning restore BDN1401 } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] diff --git a/tests/BenchmarkDotNet.IntegrationTests/BaselineRatioColumnTest.cs b/tests/BenchmarkDotNet.IntegrationTests/BaselineRatioColumnTest.cs index 3da377ae6c..a579d0a5b7 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/BaselineRatioColumnTest.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/BaselineRatioColumnTest.cs @@ -115,6 +115,7 @@ public void OnlyOneMethodCanBeBaseline() Assert.True(summary.HasCriticalValidationErrors); } +#pragma warning disable BDN1107 public class BaselineRatioResultExtenderError { [Benchmark(Baseline = true)] @@ -123,6 +124,7 @@ public class BaselineRatioResultExtenderError [Benchmark(Baseline = true)] public void BenchmarkFast() => Thread.Sleep(5); } +#pragma warning restore BDN1107 } public class BaselineRatioColumnWithLongParamsTest : BenchmarkTestExecutor diff --git a/tests/BenchmarkDotNet.IntegrationTests/PriorityTests.cs b/tests/BenchmarkDotNet.IntegrationTests/PriorityTests.cs index f07a252336..582f02b846 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/PriorityTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/PriorityTests.cs @@ -62,9 +62,11 @@ public class PriorityBenchmark [Benchmark] public int OneArgument(int b) => E ? A + b : F; +#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(NumberArguments))] public int ManyArguments(int c, int d) => E ? A + c + d : F; +#pragma warning restore BDN1401 public IEnumerable NumberArguments() { diff --git a/tests/BenchmarkDotNet.Tests/Attributes/ParamsAllValuesVerifyTests.cs b/tests/BenchmarkDotNet.Tests/Attributes/ParamsAllValuesVerifyTests.cs index 8da86700c7..f5d4081a77 100644 --- a/tests/BenchmarkDotNet.Tests/Attributes/ParamsAllValuesVerifyTests.cs +++ b/tests/BenchmarkDotNet.Tests/Attributes/ParamsAllValuesVerifyTests.cs @@ -112,6 +112,7 @@ public class WithAllValuesOfNullableEnum public void Benchmark() { } } +#pragma warning disable BDN1304 public class WithNotAllowedTypeError { [ParamsAllValues] @@ -120,7 +121,9 @@ public class WithNotAllowedTypeError [Benchmark] public void Benchmark() { } } +#pragma warning restore BDN1304 +#pragma warning disable BDN1304 public class WithNotAllowedNullableTypeError { [ParamsAllValues] @@ -129,7 +132,9 @@ public class WithNotAllowedNullableTypeError [Benchmark] public void Benchmark() { } } +#pragma warning restore BDN1304 +#pragma warning disable BDN1303 public class WithNotAllowedFlagsEnumError { [ParamsAllValues] @@ -137,7 +142,8 @@ public class WithNotAllowedFlagsEnumError [Benchmark] public void Benchmark() { } - } + } +#pragma warning restore BDN1303 } } } diff --git a/tests/BenchmarkDotNet.Tests/Exporters/MarkdownExporterVerifyTests.cs b/tests/BenchmarkDotNet.Tests/Exporters/MarkdownExporterVerifyTests.cs index 2d57e308a0..211cbf2770 100644 --- a/tests/BenchmarkDotNet.Tests/Exporters/MarkdownExporterVerifyTests.cs +++ b/tests/BenchmarkDotNet.Tests/Exporters/MarkdownExporterVerifyTests.cs @@ -235,12 +235,14 @@ [Benchmark] public void Bar() {} /* Invalid */ +#pragma warning disable BDN1107 [RankColumn, LogicalGroupColumn, BaselineColumn] public class Invalid_TwoMethodBaselines { [Benchmark(Baseline = true)] public void Foo() {} [Benchmark(Baseline = true)] public void Bar() {} } +#pragma warning restore BDN1107 [RankColumn, LogicalGroupColumn, BaselineColumn] [SimpleJob(id: "Job1", baseline: true), SimpleJob(id: "Job2", baseline: true)] diff --git a/tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs b/tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs index ffc80a93d4..0c2362bddf 100644 --- a/tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs +++ b/tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs @@ -159,6 +159,7 @@ public class FewStringArguments public void Method(string arg1, string arg2, string arg3, string arg4) { } } +#pragma warning disable BDN1401 public class SingleDateTimeArgument { [Benchmark] @@ -170,7 +171,9 @@ public IEnumerable Date() yield return new object[] { DateTime.MaxValue }; } } +#pragma warning restore BDN1401 +#pragma warning disable BDN1401 public class SingleGuidArgument { [Benchmark] @@ -182,7 +185,9 @@ public IEnumerable Guid() yield return new object[] { System.Guid.Empty }; } } +#pragma warning restore BDN1401 +#pragma warning disable BDN1401 public class WithArray { [Benchmark] @@ -194,6 +199,7 @@ public IEnumerable Data() yield return new object[] { new int[] { 1, 2, 3 }, 4 }; } } +#pragma warning restore BDN1401 public class SimpleGeneric { @@ -201,6 +207,7 @@ public class SimpleGeneric public T Method() => default(T); } +#pragma warning disable BDN1401 public class WithCrazyUnicodeCharacters { [Benchmark] @@ -212,6 +219,7 @@ public IEnumerable Data() yield return new object[] { "FOO", "\u03C3", "x\u0305" }; // https://github.com/Microsoft/xunit-performance/blob/f1d1d62a934694d8cd19063e60e04c590711d904/tests/simpleharness/Program.cs#L29 } } +#pragma warning restore BDN1401 public class WithTabAndEnter { @@ -220,6 +228,7 @@ public class WithTabAndEnter public void Method(string tab, string enter) { } } +#pragma warning disable BDN1401 public class WithBigArray { [Benchmark] @@ -231,7 +240,9 @@ public IEnumerable Data() yield return new object[] { Enumerable.Range(0, 100).ToArray() }; } } +#pragma warning restore BDN1401 +#pragma warning disable BDN1401 public class WithArrayOfNullStrings { public IEnumerable GetArrayOfStrings() @@ -243,6 +254,7 @@ public IEnumerable GetArrayOfStrings() [ArgumentsSource(nameof(GetArrayOfStrings))] public int Method(string[] array) => array.Length; } +#pragma warning restore BDN1401 public class WithParameters { diff --git a/tests/BenchmarkDotNet.Tests/GenericBuilderTests.cs b/tests/BenchmarkDotNet.Tests/GenericBuilderTests.cs index 42b1e8025b..1aaa70dcab 100644 --- a/tests/BenchmarkDotNet.Tests/GenericBuilderTests.cs +++ b/tests/BenchmarkDotNet.Tests/GenericBuilderTests.cs @@ -76,7 +76,9 @@ public void TestBuildGenericWithWrongAttributes() [GenericTypeArguments(typeof(int), typeof(char))] [GenericTypeArguments(typeof(char), typeof(string))] +#pragma warning disable BDN1102 [GenericTypeArguments(typeof(char))] +#pragma warning restore BDN1102 public class GenericBenchmarkWithWrongAttribute { [Benchmark] public T1 CreateT1() => Activator.CreateInstance(); diff --git a/tests/BenchmarkDotNet.Tests/Running/RunningEmptyBenchmarkTests.cs b/tests/BenchmarkDotNet.Tests/Running/RunningEmptyBenchmarkTests.cs index 05ce92b2cf..3dcee47bb6 100644 --- a/tests/BenchmarkDotNet.Tests/Running/RunningEmptyBenchmarkTests.cs +++ b/tests/BenchmarkDotNet.Tests/Running/RunningEmptyBenchmarkTests.cs @@ -34,8 +34,11 @@ public class RunningEmptyBenchmarkTests */ #endregion #region Generic Type Tests + + +#pragma warning disable BDN1000 /// - /// Tests for BenchmarkRunner.Run method + /// Tests for method /// [Theory] [InlineData(null)] @@ -53,6 +56,7 @@ public void GenericTypeWithoutBenchmarkAttribute_ThrowsValidationError_WhenNoBen Assert.Contains(GetValidationErrorForType(typeof(EmptyBenchmark)), logger.GetLog()); } +#pragma warning restore BDN1000 [Theory] [InlineData(null)] @@ -68,6 +72,8 @@ public void GenericTypeWithBenchmarkAttribute_RunsSuccessfully(string[]? args) } #endregion #region Type-based Tests + +#pragma warning disable BDN1000 /// /// Tests for BenchmarkRunner.Run(Type) method /// @@ -84,6 +90,7 @@ public void TypeWithoutBenchmarkAttribute_ThrowsValidationError_WhenNoBenchmarkA Assert.Contains(summary.ValidationErrors, validationError => validationError.Message == GetValidationErrorForType(typeof(EmptyBenchmark))); Assert.Contains(GetValidationErrorForType(typeof(EmptyBenchmark)), logger.GetLog()); } +#pragma warning restore BDN1000 [Theory] [InlineData(null)] diff --git a/tests/BenchmarkDotNet.Tests/TypeFilterTests.cs b/tests/BenchmarkDotNet.Tests/TypeFilterTests.cs index b1bcdb1dcd..6b39211df8 100644 --- a/tests/BenchmarkDotNet.Tests/TypeFilterTests.cs +++ b/tests/BenchmarkDotNet.Tests/TypeFilterTests.cs @@ -278,6 +278,7 @@ public class SomeGeneric public T Create() => Activator.CreateInstance(); } +#pragma warning disable BDN1401 [Run] public class ClassE { @@ -292,6 +293,7 @@ public class ClassE [ArgumentsSource(nameof(Values))] public string Method1(uint value) => value.ToString(); } +#pragma warning restore BDN1401 } namespace BenchmarkDotNet.NOTTests diff --git a/tests/BenchmarkDotNet.Tests/Validators/ExecutionValidatorTests.cs b/tests/BenchmarkDotNet.Tests/Validators/ExecutionValidatorTests.cs index 70195fd959..9b7d8526ea 100644 --- a/tests/BenchmarkDotNet.Tests/Validators/ExecutionValidatorTests.cs +++ b/tests/BenchmarkDotNet.Tests/Validators/ExecutionValidatorTests.cs @@ -281,11 +281,11 @@ public void NonPublicFieldsWithParamsAreDiscovered() public class NonPublicFieldWithParams { -#pragma warning disable CS0649 +#pragma warning disable CS0649, BDN1202 [Params(1)] [UsedImplicitly] internal int Field; -#pragma warning restore CS0649 +#pragma warning restore CS0649, BDN1202 [Benchmark] public void NonThrowing() { } @@ -299,7 +299,9 @@ public void FieldsWithoutParamsValuesAreDiscovered() public class FieldsWithoutParamsValues { +#pragma warning disable BDN1300 [Params] +#pragma warning restore BDN1300 [UsedImplicitly] public int FieldWithoutValuesSpecified; diff --git a/tests/BenchmarkDotNet.Tests/Validators/ParamsValidatorTests.cs b/tests/BenchmarkDotNet.Tests/Validators/ParamsValidatorTests.cs index 86e76a25b0..c4b4987176 100644 --- a/tests/BenchmarkDotNet.Tests/Validators/ParamsValidatorTests.cs +++ b/tests/BenchmarkDotNet.Tests/Validators/ParamsValidatorTests.cs @@ -79,153 +79,202 @@ public void Foo() { } public static IEnumerable Source() => new[] { false, true }; } +#pragma warning disable BDN1205 public class Const1 : Base { [Params(false, true)] public const bool Input = false; - } - + } +#pragma warning restore BDN1205 + +#pragma warning disable BDN1205 public class Const2 : Base { [ParamsAllValues] public const bool Input = false; } +#pragma warning restore BDN1205 +#pragma warning disable BDN1205 public class Const3 : Base { [ParamsSource(nameof(Source))] public const bool Input = false; } +#pragma warning restore BDN1205 +#pragma warning disable BDN1204 public class StaticReadonly1 : Base { [Params(false, true)] public static readonly bool Input = false; } +#pragma warning restore BDN1204 +#pragma warning disable BDN1204 public class StaticReadonly2 : Base { [ParamsAllValues] public static readonly bool Input = false; - } - + } +#pragma warning restore BDN1204 + +#pragma warning disable BDN1204 public class StaticReadonly3 : Base { [ParamsSource(nameof(Source))] public static readonly bool Input = false; - } - + } +#pragma warning restore BDN1204 + +#pragma warning disable BDN1204 public class NonStaticReadonly1 : Base { [Params(false, true)] public readonly bool Input = false; - } - + } +#pragma warning restore BDN1204 + +#pragma warning disable BDN1204 public class NonStaticReadonly2 : Base { [ParamsAllValues] public readonly bool Input = false; - } - + } +#pragma warning restore BDN1204 + +#pragma warning disable BDN1204 public class NonStaticReadonly3 : Base { [ParamsSource(nameof(Source))] public readonly bool Input = false; - } - + } +#pragma warning restore BDN1204 + +#pragma warning disable BDN1207 public class PrivateSetter1 : Base { [Params(false, true)] public bool Input { get; private set; } - } - + } +#pragma warning restore BDN1207 + +#pragma warning disable BDN1207 public class PrivateSetter2 : Base { [ParamsAllValues] public bool Input { get; private set; } } +#pragma warning restore BDN1207 +#pragma warning disable BDN1207 public class PrivateSetter3 : Base { [ParamsSource(nameof(Source))] public bool Input { get; private set; } - } - + } +#pragma warning restore BDN1207 + +#pragma warning disable BDN1207 public class NoSetter1 : Base { [Params(false, true)] public bool Input { get; } = false; } +#pragma warning restore BDN1207 +#pragma warning disable BDN1207 public class NoSetter2 : Base { [ParamsAllValues] public bool Input { get; } = false; - } - + } +#pragma warning restore BDN1207 + +#pragma warning disable BDN1207 public class NoSetter3 : Base { [ParamsSource(nameof(Source))] public bool Input { get; } = false; - } - + } +#pragma warning restore BDN1207 + +#pragma warning disable BDN1202 public class InternalField1 : Base { [Params(false, true)] internal bool Input = false; } +#pragma warning restore BDN1202 +#pragma warning disable BDN1202 public class InternalField2 : Base { [ParamsAllValues] internal bool Input = false; } +#pragma warning restore BDN1202 +#pragma warning disable BDN1202 public class InternalField3 : Base { [ParamsSource(nameof(Source))] internal bool Input = false; - } - + } +#pragma warning restore BDN1202 + +#pragma warning disable BDN1203 public class InternalProp1 : Base { [Params(false, true)] internal bool Input { get; set; } - } - + } +#pragma warning restore BDN1203 + +#pragma warning disable BDN1203 public class InternalProp2 : Base { [ParamsAllValues] internal bool Input { get; set; } - } - + } +#pragma warning restore BDN1203 + +#pragma warning disable BDN1203 public class InternalProp3 : Base { [ParamsSource(nameof(Source))] internal bool Input { get; set; } - } - + } +#pragma warning restore BDN1203 + +#pragma warning disable BDN1200 public class FieldMultiple1 : Base { [Params(false, true)] [ParamsAllValues] public bool Input = false; } +#pragma warning restore BDN1200 +#pragma warning disable BDN1200 public class FieldMultiple2 : Base { [Params(false, true)] [ParamsSource(nameof(Source))] public bool Input = false; } +#pragma warning restore BDN1200 +#pragma warning disable BDN1200 public class FieldMultiple3 : Base { [ParamsAllValues] [ParamsSource(nameof(Source))] public bool Input = false; } +#pragma warning restore BDN1200 +#pragma warning disable BDN1200 public class FieldMultiple4 : Base { [Params(false, true)] @@ -233,28 +282,36 @@ public class FieldMultiple4 : Base [ParamsSource(nameof(Source))] public bool Input = false; } +#pragma warning restore BDN1200 +#pragma warning disable BDN1201 public class PropMultiple1 : Base { [Params(false, true)] [ParamsAllValues] public bool Input { get; set; } } +#pragma warning restore BDN1201 +#pragma warning disable BDN1201 public class PropMultiple2 : Base { [Params(false, true)] [ParamsSource(nameof(Source))] public bool Input { get; set; } } +#pragma warning restore BDN1201 +#pragma warning disable BDN1201 public class PropMultiple3 : Base { [ParamsAllValues] [ParamsSource(nameof(Source))] public bool Input { get; set; } } +#pragma warning restore BDN1201 +#pragma warning disable BDN1201 public class PropMultiple4 : Base { [Params(false, true)] @@ -262,6 +319,7 @@ public class PropMultiple4 : Base [ParamsSource(nameof(Source))] public bool Input { get; set; } } +#pragma warning restore BDN1201 #if NET5_0_OR_GREATER @@ -269,24 +327,30 @@ public class PropMultiple4 : Base [Fact] public void InitOnly2Test() => Check(nameof(InitOnly2.Input), "init-only", Pa); [Fact] public void InitOnly3Test() => Check(nameof(InitOnly3.Input), "init-only", Ps); +#pragma warning disable BDN1206 public class InitOnly1 : Base { [Params(false, true)] public bool Input { get; init; } } +#pragma warning restore BDN1206 +#pragma warning disable BDN1206 public class InitOnly2 : Base { [ParamsAllValues] public bool Input { get; init; } - } - + } +#pragma warning restore BDN1206 + +#pragma warning disable BDN1206 public class InitOnly3 : Base { [ParamsSource(nameof(Source))] public bool Input { get; init; } - } - + } +#pragma warning restore BDN1206 + #endif } } \ No newline at end of file From 572b1d413da8cfc4eec9b0061fca64ba6bc8d9f3 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:32:53 +0100 Subject: [PATCH 28/38] Revert suppressing of BDN1401 in ArgumentsTests.cs --- .../ArgumentsTests.cs | 108 +++++------------- 1 file changed, 31 insertions(+), 77 deletions(-) diff --git a/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs b/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs index 487530244e..c168071169 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/ArgumentsTests.cs @@ -66,16 +66,14 @@ public ValueTask SimpleValueTaskAsync(bool boolean, int number) public class WithArgumentsSource { -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(ArgumentsProvider))] public void Simple(bool boolean, int number) { if (boolean && number != 1 || !boolean && number != 2) throw new InvalidOperationException("Incorrect values were passed"); - } -#pragma warning restore BDN1401 - + } + public IEnumerable ArgumentsProvider() { yield return new object[] { true, 1 }; @@ -88,7 +86,6 @@ public IEnumerable ArgumentsProvider() public class WithArgumentsSourceInAnotherClass { -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OnePrimitiveType))] public void OnePrimitiveType(int number) @@ -96,19 +93,15 @@ public void OnePrimitiveType(int number) if (number % 2 != 1) throw new InvalidOperationException("Incorrect values were passed"); } -#pragma warning restore BDN1401 -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.TwoPrimitiveTypes))] public void TwoPrimitiveTypes(bool boolean, int number) { if (boolean && number != 1 || !boolean && number != 2) throw new InvalidOperationException("Incorrect values were passed"); - } -#pragma warning restore BDN1401 - -#pragma warning disable BDN1401 + } + [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OneNonPrimitiveType))] public void OneNonPrimitiveType(Version version) @@ -116,10 +109,8 @@ public void OneNonPrimitiveType(Version version) int[] versionNumbers = { version.Major, version.Minor, version.MinorRevision, version.Build }; if (versionNumbers.Distinct().Count() != 4) throw new InvalidOperationException("Incorrect values were passed"); - } -#pragma warning restore BDN1401 - -#pragma warning disable BDN1401 + } + [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.TwoNonPrimitiveTypes))] public void TwoNonPrimitiveTypes(Version version, DateTime dateTime) @@ -130,10 +121,8 @@ public void TwoNonPrimitiveTypes(Version version, DateTime dateTime) if (dateTime.Month != dateTime.Day) throw new InvalidOperationException("Incorrect values were passed"); - } -#pragma warning restore BDN1401 - -#pragma warning disable BDN1401 + } + [Benchmark] [ArgumentsSource(typeof(ExternalClassWithArgumentsSource), nameof(ExternalClassWithArgumentsSource.OnePrimitiveAndOneNonPrimitive))] public void OnePrimitiveAndOneNonPrimitive(Version version, int number) @@ -144,8 +133,7 @@ public void OnePrimitiveAndOneNonPrimitive(Version version, int number) if (number != version.Major) throw new InvalidOperationException("Incorrect values were passed"); - } -#pragma warning restore BDN1401 + } } public static class ExternalClassWithArgumentsSource { @@ -221,7 +209,6 @@ public class WithComplexTypesReturnedFromSources [ParamsSource(nameof(SameButStatic))] public Dictionary DictionaryParamStatic; -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(NonPrimitive))] public void Simple(SomeClass someClass, SomeStruct someStruct) @@ -241,9 +228,8 @@ public void Simple(SomeClass someClass, SomeStruct someStruct) for (int i = 0; i < someStruct.RangeEnd; i++) if (someClass.Values[i] != i * 2) throw new InvalidOperationException("Incorrect array values were passed"); - } -#pragma warning restore BDN1401 - + } + public IEnumerable NonPrimitive() { yield return new object[] { new SomeClass(Enumerable.Range(0, 100).ToArray()), new SomeStruct(100) }; @@ -307,11 +293,9 @@ public IEnumerable Sources() yield return new object[] { "Iterator.Select.Where", Iterator().Select(i => i).Where(i => i % 2 == 0) }; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(Sources))] - public void Any(string name, IEnumerable source) => source.Any(); -#pragma warning restore BDN1401 + public void Any(string name, IEnumerable source) => source.Any(); } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -319,7 +303,6 @@ public IEnumerable Sources() public class WithJaggedArray { -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(CreateMatrix))] public void Test(int[][] array) @@ -331,9 +314,8 @@ public void Test(int[][] array) for (int j = 0; j < i; j++) if (array[i][j] != i) throw new ArgumentException("Invalid value"); - } -#pragma warning restore BDN1401 - + } + public IEnumerable CreateMatrix() { int[][] jagged = new int[10][]; @@ -369,7 +351,6 @@ public Generic(T1 item1, T2 item2) } } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetInputData))] public bool ValueTupleCompareNoOpt(ref Generic byRef) @@ -381,9 +362,8 @@ public bool ValueTupleCompareNoOpt(ref Generic byRef) throw new ArgumentException("Wrong values"); return true; - } -#pragma warning restore BDN1401 - + } + public IEnumerable GetInputData() { yield return new Generic(3, "red"); @@ -504,15 +484,13 @@ public IEnumerable GetArrayOfString() yield return new string[123]; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetArrayOfString))] public void TypeReflectionArrayGetType(string[] array) { if (array.Length != 123) throw new ArgumentException("The array was empty"); - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] // make sure BDN mimics xunit's MemberData behaviour @@ -526,15 +504,13 @@ public IEnumerable GetArguments() yield return new object[] { true }; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetArguments))] public void SingleArgument(bool boolean) { if (boolean != true) throw new ArgumentException("The value of boolean was incorrect"); - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -551,7 +527,6 @@ public IEnumerable GetArrays() }; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetArrays))] public void AcceptsArrays(int[] even, int[] notEven) @@ -564,8 +539,7 @@ public void AcceptsArrays(int[] even, int[] notEven) if (!notEven.All(n => n % 2 != 0)) throw new ArgumentException("Even"); - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -578,7 +552,6 @@ public IEnumerable GetVeryBigInteger() yield return BigInteger.Parse(new string(Enumerable.Repeat('1', 1000).ToArray())); } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetVeryBigInteger))] public void Method(BigInteger passed) @@ -587,8 +560,7 @@ public void Method(BigInteger passed) if (expected != passed) throw new ArgumentException("The BigInteger has wrong value!"); - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -606,7 +578,6 @@ public IEnumerable GetSpecialDoubleValues() yield return new object[] { double.PositiveInfinity, nameof(double.PositiveInfinity) }; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetSpecialDoubleValues))] public void Method(double passed, string name) @@ -634,8 +605,7 @@ public void Method(double passed, string name) default: throw new InvalidOperationException($"Unknown case! {name}"); } - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -653,7 +623,6 @@ public IEnumerable GetSpecialFloatValues() yield return new object[] { float.PositiveInfinity, nameof(float.PositiveInfinity) }; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetSpecialFloatValues))] public void Method(float passed, string name) @@ -681,8 +650,7 @@ public void Method(float passed, string name) default: throw new InvalidOperationException($"Unknown case! {name}"); } - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -696,7 +664,6 @@ public IEnumerable GetSpecialDecimalValues() yield return new object[] { decimal.MinValue, nameof(decimal.MinValue) }; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(GetSpecialDecimalValues))] public void Method(decimal passed, string name) @@ -712,8 +679,7 @@ public void Method(decimal passed, string name) default: throw new InvalidOperationException($"Unknown case! {name}"); } - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -726,7 +692,6 @@ public IEnumerable DateTimeValues() yield return new DateTime(2018, 8, 15); } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(DateTimeValues))] public void Test(DateTime passed) @@ -735,8 +700,7 @@ public void Test(DateTime passed) if (expected != passed) throw new ArgumentException("The DateTime has wrong value!"); - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -846,7 +810,6 @@ public static IEnumerable StaticProperty [ParamsSource(nameof(StaticProperty))] public int ParamTwo { get; set; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(StaticMethod))] public void TestMethod(int argument) @@ -857,10 +820,8 @@ public void TestMethod(int argument) throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 2 && ParamTwo != 3) throw new ArgumentException("The ParamTwo value is incorrect!"); - } -#pragma warning restore BDN1401 - -#pragma warning disable BDN1401 + } + [Benchmark] [ArgumentsSource(nameof(StaticProperty))] public void TestProperty(int argument) @@ -871,8 +832,7 @@ public void TestProperty(int argument) throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 2 && ParamTwo != 3) throw new ArgumentException("The ParamTwo value is incorrect!"); - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -897,7 +857,6 @@ public static IEnumerable StaticProperty [ParamsSource(nameof(StaticProperty))] public int ParamTwo { get; set; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(StaticMethod))] public void TestMethod(int argument) @@ -908,10 +867,8 @@ public void TestMethod(int argument) throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 2 && ParamTwo != 3) throw new ArgumentException("The ParamTwo value is incorrect!"); - } -#pragma warning restore BDN1401 - -#pragma warning disable BDN1401 + } + [Benchmark] [ArgumentsSource(nameof(StaticProperty))] public void TestProperty(int argument) @@ -922,8 +879,7 @@ public void TestProperty(int argument) throw new ArgumentException("The ParamOne value is incorrect!"); if (ParamTwo != 2 && ParamTwo != 3) throw new ArgumentException("The ParamTwo value is incorrect!"); - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] @@ -996,7 +952,6 @@ public IEnumerable Arguments() yield return new object[] { LongString, LongString2 }; } -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(Arguments))] public void Test(string first, string second) @@ -1005,8 +960,7 @@ public void Test(string first, string second) throw new ArgumentException($"{nameof(first)} passed string has wrong value!"); if (second != LongString2) throw new ArgumentException($"{nameof(second)} passed string has wrong value!"); - } -#pragma warning restore BDN1401 + } } [Theory, MemberData(nameof(GetToolchains), DisableDiscoveryEnumeration = true)] From 95abf00b95723dead1936412d7e1e768f1cb7f49 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Tue, 4 Nov 2025 16:40:33 +0100 Subject: [PATCH 29/38] Revert suppressing of BDN1401 in FullNameProviderTests.cs, PriorityTests.cs and TypeFilterTests.cs --- .../PriorityTests.cs | 2 -- tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs | 12 ------------ tests/BenchmarkDotNet.Tests/TypeFilterTests.cs | 2 -- 3 files changed, 16 deletions(-) diff --git a/tests/BenchmarkDotNet.IntegrationTests/PriorityTests.cs b/tests/BenchmarkDotNet.IntegrationTests/PriorityTests.cs index 582f02b846..f07a252336 100644 --- a/tests/BenchmarkDotNet.IntegrationTests/PriorityTests.cs +++ b/tests/BenchmarkDotNet.IntegrationTests/PriorityTests.cs @@ -62,11 +62,9 @@ public class PriorityBenchmark [Benchmark] public int OneArgument(int b) => E ? A + b : F; -#pragma warning disable BDN1401 [Benchmark] [ArgumentsSource(nameof(NumberArguments))] public int ManyArguments(int c, int d) => E ? A + c + d : F; -#pragma warning restore BDN1401 public IEnumerable NumberArguments() { diff --git a/tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs b/tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs index 0c2362bddf..ffc80a93d4 100644 --- a/tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs +++ b/tests/BenchmarkDotNet.Tests/FullNameProviderTests.cs @@ -159,7 +159,6 @@ public class FewStringArguments public void Method(string arg1, string arg2, string arg3, string arg4) { } } -#pragma warning disable BDN1401 public class SingleDateTimeArgument { [Benchmark] @@ -171,9 +170,7 @@ public IEnumerable Date() yield return new object[] { DateTime.MaxValue }; } } -#pragma warning restore BDN1401 -#pragma warning disable BDN1401 public class SingleGuidArgument { [Benchmark] @@ -185,9 +182,7 @@ public IEnumerable Guid() yield return new object[] { System.Guid.Empty }; } } -#pragma warning restore BDN1401 -#pragma warning disable BDN1401 public class WithArray { [Benchmark] @@ -199,7 +194,6 @@ public IEnumerable Data() yield return new object[] { new int[] { 1, 2, 3 }, 4 }; } } -#pragma warning restore BDN1401 public class SimpleGeneric { @@ -207,7 +201,6 @@ public class SimpleGeneric public T Method() => default(T); } -#pragma warning disable BDN1401 public class WithCrazyUnicodeCharacters { [Benchmark] @@ -219,7 +212,6 @@ public IEnumerable Data() yield return new object[] { "FOO", "\u03C3", "x\u0305" }; // https://github.com/Microsoft/xunit-performance/blob/f1d1d62a934694d8cd19063e60e04c590711d904/tests/simpleharness/Program.cs#L29 } } -#pragma warning restore BDN1401 public class WithTabAndEnter { @@ -228,7 +220,6 @@ public class WithTabAndEnter public void Method(string tab, string enter) { } } -#pragma warning disable BDN1401 public class WithBigArray { [Benchmark] @@ -240,9 +231,7 @@ public IEnumerable Data() yield return new object[] { Enumerable.Range(0, 100).ToArray() }; } } -#pragma warning restore BDN1401 -#pragma warning disable BDN1401 public class WithArrayOfNullStrings { public IEnumerable GetArrayOfStrings() @@ -254,7 +243,6 @@ public IEnumerable GetArrayOfStrings() [ArgumentsSource(nameof(GetArrayOfStrings))] public int Method(string[] array) => array.Length; } -#pragma warning restore BDN1401 public class WithParameters { diff --git a/tests/BenchmarkDotNet.Tests/TypeFilterTests.cs b/tests/BenchmarkDotNet.Tests/TypeFilterTests.cs index 6b39211df8..b1bcdb1dcd 100644 --- a/tests/BenchmarkDotNet.Tests/TypeFilterTests.cs +++ b/tests/BenchmarkDotNet.Tests/TypeFilterTests.cs @@ -278,7 +278,6 @@ public class SomeGeneric public T Create() => Activator.CreateInstance(); } -#pragma warning disable BDN1401 [Run] public class ClassE { @@ -293,7 +292,6 @@ public class ClassE [ArgumentsSource(nameof(Values))] public string Method1(uint value) => value.ToString(); } -#pragma warning restore BDN1401 } namespace BenchmarkDotNet.NOTTests From 61eb9161f0993bf478247e77138126bd2ecadcc6 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Tue, 4 Nov 2025 11:01:21 -0500 Subject: [PATCH 30/38] Fix Directory.build.props included in autogenerated projects. --- tests/Directory.build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Directory.build.props b/tests/Directory.build.props index 7142daab6e..b99a15e978 100644 --- a/tests/Directory.build.props +++ b/tests/Directory.build.props @@ -1,6 +1,6 @@ - + \ No newline at end of file From c875c6014a8e8d3f179176b3a0a000cd247eb2c1 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Wed, 5 Nov 2025 13:15:41 -0500 Subject: [PATCH 31/38] Add message to failing CI test. --- .../RuntimeVersionDetectionTests.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/tests/BenchmarkDotNet.Tests/RuntimeVersionDetectionTests.cs b/tests/BenchmarkDotNet.Tests/RuntimeVersionDetectionTests.cs index f6286720fd..b52030b0cc 100644 --- a/tests/BenchmarkDotNet.Tests/RuntimeVersionDetectionTests.cs +++ b/tests/BenchmarkDotNet.Tests/RuntimeVersionDetectionTests.cs @@ -116,21 +116,11 @@ public void CurrentRuntimeIsProperlyRecognized() #if NETFRAMEWORK if (OsDetector.IsWindows()) - Assert.True(runtime is ClrRuntime); + Assert.True(runtime is ClrRuntime, $"Actual runtime: {runtime}, tfm: {runtime.MsBuildMoniker}, moniker: {runtime.RuntimeMoniker}"); else - Assert.True(runtime is MonoRuntime); -#elif NETCOREAPP2_1 - Assert.True(runtime is CoreRuntime coreRuntime && coreRuntime.RuntimeMoniker == RuntimeMoniker.NetCoreApp21); -#elif NETCOREAPP2_2 - Assert.True(runtime is CoreRuntime coreRuntime && coreRuntime.RuntimeMoniker == RuntimeMoniker.NetCoreApp22); -#elif NETCOREAPP3_0 - Assert.True(runtime is CoreRuntime coreRuntime && coreRuntime.RuntimeMoniker == RuntimeMoniker.NetCoreApp30); -#elif NETCOREAPP3_1 - Assert.True(runtime is CoreRuntime coreRuntime && coreRuntime.RuntimeMoniker == RuntimeMoniker.NetCoreApp31); -#elif NETCOREAPP5_0 - Assert.True(runtime is CoreRuntime coreRuntime && coreRuntime.RuntimeMoniker == RuntimeMoniker.NetCoreApp50); -#elif NET8_0 - Assert.True(runtime is CoreRuntime coreRuntime && coreRuntime.RuntimeMoniker == RuntimeMoniker.Net80); + Assert.True(runtime is MonoRuntime, $"Actual runtime: {runtime}, tfm: {runtime.MsBuildMoniker}, moniker: {runtime.RuntimeMoniker}"); +#else + Assert.True(runtime is CoreRuntime coreRuntime && coreRuntime.RuntimeMoniker == RuntimeMoniker.Net80, $"Actual runtime: {runtime}, tfm: {runtime.MsBuildMoniker}, moniker: {runtime.RuntimeMoniker}"); #endif } } From 6f2e38544457db846333be100236c903f8090cd5 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Wed, 5 Nov 2025 18:58:30 -0500 Subject: [PATCH 32/38] Revert sdk update. --- build/sdk/global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/sdk/global.json b/build/sdk/global.json index 5c03259d72..be5c357f30 100644 --- a/build/sdk/global.json +++ b/build/sdk/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.306", + "version": "8.0.410", "rollForward": "disable" } } From 55292d256dac3be1d048850faf7223835870ebd5 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Wed, 5 Nov 2025 19:08:45 -0500 Subject: [PATCH 33/38] Suppress warning. --- build/common.props | 1 + 1 file changed, 1 insertion(+) diff --git a/build/common.props b/build/common.props index 589de5c90e..9d62d00ae4 100644 --- a/build/common.props +++ b/build/common.props @@ -25,6 +25,7 @@ annotations true + CS9057 From 3566b91071fde652a3ca5d7032c7691bb88d2c45 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Wed, 5 Nov 2025 21:18:17 -0500 Subject: [PATCH 34/38] Skip failing test --- .../Exporters/CommonExporterVerifyTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/BenchmarkDotNet.Tests/Exporters/CommonExporterVerifyTests.cs b/tests/BenchmarkDotNet.Tests/Exporters/CommonExporterVerifyTests.cs index 7799fd2038..5d6e1b3ed1 100644 --- a/tests/BenchmarkDotNet.Tests/Exporters/CommonExporterVerifyTests.cs +++ b/tests/BenchmarkDotNet.Tests/Exporters/CommonExporterVerifyTests.cs @@ -15,6 +15,7 @@ using BenchmarkDotNet.Tests.Infra; using BenchmarkDotNet.Tests.Mocks; using BenchmarkDotNet.Tests.Reports; +using BenchmarkDotNet.Tests.XUnit; using JetBrains.Annotations; using VerifyXunit; using Xunit; @@ -41,7 +42,7 @@ public CommonExporterVerifyTests() [UsedImplicitly] public static TheoryData CultureInfoNames => TheoryDataHelper.Create(CultureInfos.Keys); - [Theory] + [TheoryEnvSpecific(".NET SDK is skipped in Framework, so the exported result does not match the verified file.", EnvRequirement.DotNetCoreOnly)] [MemberData(nameof(CultureInfoNames))] public Task Exporters(string cultureInfoName) { From a688920234a01724479ab3ea0d048fa5b778152b Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:58:38 +0100 Subject: [PATCH 35/38] Adjust wording of "ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract" diagnostic and move trigger location to the attribute level --- .../BenchmarkDotNetAnalyzerResources.Designer.cs | 2 +- .../BenchmarkDotNetAnalyzerResources.resx | 2 +- .../General/BenchmarkClassAnalyzer.cs | 10 +++++----- .../General/BenchmarkClassAnalyzerTests.cs | 10 +++++++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs index cf04e85533..aa49e5e367 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -678,7 +678,7 @@ internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttri } /// - /// Looks up a localized string similar to Benchmark class '{0}' cannot be abstract. + /// Looks up a localized string similar to Attribute [GenericTypeArguments] can only be applied to a non-abstract class. /// internal static string General_BenchmarkClass_ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract_MessageFormat { get { diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index dac4317c3a..a71c0dd64f 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -142,7 +142,7 @@ Benchmark class '{0}' cannot be static - Benchmark class '{0}' cannot be abstract + Attribute [GenericTypeArguments] can only be applied to a non-abstract class Referenced generic benchmark class '{0}' has no [GenericTypeArguments] attribute(s) diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index c9650f8b1f..01a4b82761 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -140,13 +140,13 @@ private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) var genericTypeArgumentsAttributes = AnalyzerHelper.GetAttributes("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, classDeclarationSyntax.AttributeLists, context.SemanticModel); if (genericTypeArgumentsAttributes.Length > 0 ) { - if (classAbstractModifier.HasValue) - { - context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, classAbstractModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } - foreach (var genericTypeArgumentsAttribute in genericTypeArgumentsAttributes) { + if (classAbstractModifier.HasValue) + { + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, genericTypeArgumentsAttribute.GetLocation())); + } + if (classDeclarationSyntax.TypeParameterList == null || classDeclarationSyntax.TypeParameterList.Parameters.Count == 0) { context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, genericTypeArgumentsAttribute.GetLocation())); diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index b0ccba77c7..1319ba7d9b 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -48,13 +48,13 @@ public async Task Abstract_class_annotated_with_at_least_one_generictypeargument { const string benchmarkClassName = "BenchmarkClass"; - var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", genericTypeArgumentsAttributeUsageCount); + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[{{|#{0}:GenericTypeArguments(typeof(int))|}}]", genericTypeArgumentsAttributeUsageCount).Select((a, i) => string.Format(a, i)); var testCode = /* lang=c#-test */ $$""" using BenchmarkDotNet.Attributes; {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} - public {|#0:abstract|} class {{benchmarkClassName}} + public abstract class {{benchmarkClassName}} { {{benchmarkAttributeUsage}} public void BenchmarkMethod() @@ -65,7 +65,11 @@ public void BenchmarkMethod() """; TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); + + for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) + { + AddExpectedDiagnostic(i); + } await RunAsync(); } From d760312dee37c59b371f2cf7dad918d5b19fbaca Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Fri, 7 Nov 2025 16:32:20 +0100 Subject: [PATCH 36/38] Add diagnostics for unintendedly passing a single null value to the [Arguments] or [Params] attribute --- .../AnalyzerReleases.Unshipped.md | 14 +- .../Attributes/ArgumentsAttributeAnalyzer.cs | 38 ++- .../Attributes/ParamsAttributeAnalyzer.cs | 60 +++- ...nchmarkDotNetAnalyzerResources.Designer.cs | 36 +++ .../BenchmarkDotNetAnalyzerResources.resx | 16 +- .../DiagnosticIds.cs | 14 +- .../General/BenchmarkClassAnalyzer.cs | 10 +- .../ArgumentsAttributeAnalyzerTests.cs | 259 ++++++++++++++---- .../ParamsAttributeAnalyzerTests.cs | 234 ++++++++++++++-- 9 files changed, 589 insertions(+), 92 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index 27ba8e2573..67ca52a104 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -28,11 +28,13 @@ 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_MustHaveMatchingValueType -BDN1302 | Usage | Info | BDN1302_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute -BDN1303 | Usage | Error | BDN1303_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType -BDN1304 | Usage | Error | BDN1304_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool +BDN1301 | Usage | Error | BDN1301_Attributes_ParamsAttribute_SingleNullArgumentNotAllowed +BDN1302 | Usage | Error | BDN1302_Attributes_ParamsAttribute_MustHaveMatchingValueType +BDN1303 | Usage | Info | BDN1303_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute +BDN1304 | Usage | Error | BDN1304_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType +BDN1305 | Usage | Error | BDN1305_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool BDN1400 | Usage | Error | BDN1400_Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters BDN1500 | Usage | Error | BDN1500_Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute -BDN1501 | Usage | Error | BDN1501_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount -BDN1502 | Usage | Error | BDN1502_Attributes_ArgumentsAttribute_MustHaveMatchingValueType +BDN1501 | Usage | Error | BDN1501_Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed +BDN1502 | Usage | Error | BDN1502_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount +BDN1503 | Usage | Error | BDN1503_Attributes_ArgumentsAttribute_MustHaveMatchingValueType diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index 8fe15e7591..20c264059c 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -19,6 +19,13 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); + internal static readonly DiagnosticDescriptor SingleNullArgumentNotAllowedRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + 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)), @@ -38,6 +45,7 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => [ RequiresBenchmarkAttributeRule, + SingleNullArgumentNotAllowedRule, MustHaveMatchingValueCountRule, MustHaveMatchingValueTypeRule ]; @@ -57,6 +65,7 @@ public override void Initialize(AnalysisContext analysisContext) } ctx.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); + ctx.RegisterSyntaxNodeAction(AnalyzeAttributeSyntax, SyntaxKind.Attribute); }); } @@ -67,7 +76,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) return; } - var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); + var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation); var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute"); if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null) @@ -313,6 +322,31 @@ void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLoc } } + private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) + { + if (context.Node is not AttributeSyntax attributeSyntax) + { + return; + } + + var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation); + + var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(argumentsAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) + { + var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; + + var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); + if (constantValue is { HasValue: true, Value: null }) + { + context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation())); + } + } + } + } + private static int? IndexOfNamedArgument(SeparatedSyntaxList attributeArguments) { var i = 0; @@ -329,5 +363,7 @@ void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLoc return null; } + + private static INamedTypeSymbol? GetArgumentsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); } } diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs index 906115ccbd..96a927828b 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs @@ -18,6 +18,13 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); + internal static readonly DiagnosticDescriptor SingleNullArgumentNotAllowedRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + 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)), @@ -36,6 +43,7 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => [ MustHaveValuesRule, + SingleNullArgumentNotAllowedRule, MustHaveMatchingValueTypeRule, UnnecessarySingleValuePassedToAttributeRule ]; @@ -97,12 +105,14 @@ private static void Analyze(SyntaxNodeAnalysisContext context) AnalyzeFieldOrPropertyTypeSyntax(context, fieldOrPropertyTypeSyntax, - attributeSyntax); + attributeSyntax, + paramsAttributeTypeSymbol); } private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, TypeSyntax fieldOrPropertyTypeSyntax, - AttributeSyntax attributeSyntax) + AttributeSyntax attributeSyntax, + INamedTypeSymbol paramsAttributeTypeSymbol) { if (attributeSyntax.ArgumentList == null) { @@ -128,6 +138,23 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c return; } + var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) + { + var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; + + var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); + if (constantValue is { HasValue: true, Value: null }) + { + context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation())); + + return; + } + } + } + var expectedValueTypeSymbol = context.SemanticModel.GetTypeInfo(fieldOrPropertyTypeSyntax).Type; if (expectedValueTypeSymbol == null || expectedValueTypeSymbol.TypeKind == TypeKind.Error) { @@ -287,6 +314,35 @@ void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLoc } } + private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) + { + if (context.Node is not AttributeSyntax attributeSyntax) + { + return; + } + + var paramsAttributeTypeSymbol = GetParamsAttributeTypeSymbol(context.Compilation); + if (paramsAttributeTypeSymbol == null) + { + return; + } + + var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) + { + var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; + + var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); + if (constantValue is { HasValue: true, Value: null }) + { + context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation())); + } + } + } + } + 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 aa49e5e367..55d318aa86 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -132,6 +132,24 @@ internal static string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_ } } + /// + /// Looks up a localized string similar to Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead.. + /// + internal static string Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Single null argument to the [Arguments] attribute results in unintended null array. + /// + internal static string Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_Title { + get { + return ResourceManager.GetString("Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to This method declares one or more parameters but is not annotated with either an [ArgumentsSource] attribute or one or more [Arguments] attributes. To ensure correct argument binding, methods with parameters must explicitly be annotated with an [ArgumentsSource] attribute or one or more [Arguments] attributes. ///Either add the [ArgumentsSource] or [Arguments] attribute(s) or remove the parameters.. @@ -464,6 +482,24 @@ internal static string Attributes_ParamsAttribute_MustHaveValues_Title { } } + /// + /// Looks up a localized string similar to Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead.. + /// + internal static string Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_MessageFormat { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Single null argument to the [Params] attribute results in unintended null array. + /// + internal static string Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_Title { + get { + return ResourceManager.GetString("Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_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.. /// diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index a71c0dd64f..c324362019 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -201,8 +201,8 @@ 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. + + Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead. Benchmark methods must be public @@ -225,6 +225,9 @@ Single null argument to the [BenchmarkCategory] attribute results in unintended null array + + Single null argument to the [Arguments] 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 @@ -370,4 +373,13 @@ Either add the [ArgumentsSource] or [Arguments] attribute(s) or remove the param Benchmark methods without an [ArgumentsSource] or [Arguments] attribute(s) cannot declare parameters + + Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "SomeCategory", ...) or a non-null value instead. + + + Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead. + + + Single null argument to the [Params] attribute results in unintended null array + \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index eb4dcaf1ef..4c46daa96b 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -25,13 +25,15 @@ 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_MustHaveMatchingValueType = "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_ParamsAttribute_SingleNullArgumentNotAllowed = "BDN1301"; + public const string Attributes_ParamsAttribute_MustHaveMatchingValueType = "BDN1302"; + public const string Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute = "BDN1303"; + public const string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType = "BDN1304"; + public const string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool = "BDN1305"; public const string Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters = "BDN1400"; public const string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute = "BDN1500"; - public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1501"; - public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1502"; + public const string Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed = "BDN1501"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1502"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1503"; } } diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 01a4b82761..1b02657456 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -171,7 +171,7 @@ private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) return; } - var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryTypeSymbol(context.Compilation); + var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryAttributeTypeSymbol(context.Compilation); if (benchmarkCategoryAttributeTypeSymbol == null) { return; @@ -537,11 +537,7 @@ private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) return; } - var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryTypeSymbol(context.Compilation); - if (benchmarkCategoryAttributeTypeSymbol == null) - { - return; - } + var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryAttributeTypeSymbol(context.Compilation); var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) @@ -559,7 +555,7 @@ private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) } } - private static INamedTypeSymbol? GetBenchmarkCategoryTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkCategoryAttribute"); + private static INamedTypeSymbol? GetBenchmarkCategoryAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkCategoryAttribute"); private static string FormatBenchmarkCategory(List benchmarkCategories) { diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs index 920324645a..678945d206 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -16,23 +16,16 @@ 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) + 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(EmptyArgumentsAttributeUsagesEnumerableLocal))] string emptyArgumentsAttributeUsage, + [CombinatorialRange(1, 2)] int attributeUsageCount) { - 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)}} + {{string.Join("\n", Enumerable.Repeat(emptyArgumentsAttributeUsage, attributeUsageCount))}} public void BenchmarkMethod() { @@ -45,42 +38,7 @@ public void BenchmarkMethod() 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})]", - "[Arguments({0}[]{1})]", - }; - - 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 EmptyArgumentsAttributeUsagesEnumerableLocal => EmptyArgumentsAttributeUsagesEnumerable(); } public class RequiresBenchmarkAttribute : AnalyzerTestFixture @@ -146,6 +104,178 @@ public void BenchmarkMethod() }.AsReadOnly(); } + public class SingleNullArgumentNotAllowed : AnalyzerTestFixture + { + public SingleNullArgumentNotAllowed() : base(ArgumentsAttributeAnalyzer.SingleNullArgumentNotAllowedRule) + { + } + + [Theory, CombinatorialData] + public async Task Providing_a_non_null_single_argument_should_not_trigger_diagnostic([CombinatorialRange(1, 2)] int attributeUsageCount, + bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerable))] string benchmarkAttributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "\"test\"")};" : "")}} + + {{string.Join("\n", Enumerable.Repeat($"[Arguments({(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "\"test\"")})]", attributeUsageCount))}} + {{benchmarkAttributeUsage}} + public void BenchmarkMethod(string a) + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants("string", "\"test\""); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_empty_array_argument_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(EmptyArgumentsAttributeUsagesEnumerableLocal))] string emptyAttributeusage, + [CombinatorialRange(1, 2)] int attributeUsageCount, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerable))] string benchmarkAttributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{string.Join("\n", Enumerable.Repeat(emptyAttributeusage, attributeUsageCount))}} + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_array_argument_containing_one_or_more_null_values_should_not_trigger_diagnostic([CombinatorialRange(1, 2)] int attributeUsageCount, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialValues("{0}", "{0}, {1}", "{1}, {0}", "{0}, {1}, {0}", "{1}, {0}, {1}")] string valuesTemplate, + [CombinatorialMemberData(nameof(AttributeValuesContainerEnumerable))] string valuesContainer, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerable))] string benchmarkAttributeUsage) + { + var attributeValues = string.Format(valuesContainer, string.Format(valuesTemplate, + useLocalConstants ? "_xNull" : useConstantsFromOtherClass ? "Constants.Value1" : "null", + useLocalConstants ? "_xValue" : useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")); + + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstants ? $""" + private const string _xNull = {(useConstantsFromOtherClass ? "Constants.Value1" : "null")}; + private const string _xValue = {(useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")}; + """ : "")}} + + {{string.Join("\n", Enumerable.Repeat($"[Arguments({attributeValues})]", attributeUsageCount))}} + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants(("string", "null"), ("string", "\"test\"")); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_a_null_single_argument_should_trigger_diagnostic([CombinatorialRange(1, 2)] int attributeUsageCount, + bool useConstantFromOtherClass, + bool useLocalConstant, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerable))] string benchmarkAttributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + + {{string.Join("\n", Enumerable.Repeat($"[Arguments({{{{|#{{0}}:{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}|}}}})]", attributeUsageCount).Select((a, i) => string.Format(a, i)))}} + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants("string", "null"); + + for (var i = 0; i < attributeUsageCount; i++) + { + AddExpectedDiagnostic(i); + } + + await RunAsync(); + } + + public static IEnumerable BenchmarkAttributeUsagesEnumerable => [ "", "[Benchmark] " ]; + + public static IEnumerable EmptyArgumentsAttributeUsagesEnumerableLocal => EmptyArgumentsAttributeUsagesEnumerable(); + + public static IEnumerable AttributeValuesContainerEnumerable() + { + return GenerateData().Distinct(); + + static IEnumerable GenerateData() + { + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + List attributeUsagesBase = [ ]; + + attributeUsagesBase.AddRange([ + "{0}new object[] {{{{ {{0}} }}}}{1}", + "{0}[ {{0}} ]{1}" + ]); + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + } + } + public class MustHaveMatchingValueCount : AnalyzerTestFixture { public MustHaveMatchingValueCount() : base(ArgumentsAttributeAnalyzer.MustHaveMatchingValueCountRule) { } @@ -1075,6 +1205,43 @@ static IEnumerable GenerateData() "Dummy, " ]; + public static IEnumerable EmptyArgumentsAttributeUsagesEnumerable() + { + 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})]", + "[Arguments({0}[]{1})]", + }; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + private static IEnumerable ScalarValuesContainerAttributeArgumentEnumerable() { return GenerateData().Distinct(); diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs index f47ab8e5f2..7f665720d2 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -2,7 +2,7 @@ { using Fixtures; - using BenchmarkDotNet.Analyzers.Attributes; + using Analyzers.Attributes; using Xunit; @@ -160,6 +160,170 @@ public static IEnumerable EmptyParamsAttributeUsagesWithLocationMarker() } } + public class SingleNullArgumentNotAllowed : AnalyzerTestFixture + { + public SingleNullArgumentNotAllowed() : base(ParamsAttributeAnalyzer.SingleNullArgumentNotAllowedRule) + { + } + + [Theory, CombinatorialData] + public async Task Providing_a_non_null_single_argument_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + bool useConstantFromOtherClass, + bool useLocalConstant) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "\"test\"")};" : "")}} + + [{{dummyAttributeUsage}}{{$"Params({(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "\"test\"")})"}}] + public string {{fieldOrPropertyDeclaration}} + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceConstants("string", "\"test\""); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_an_array_argument_containing_one_or_more_null_values_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialValues("{0}", "{0}, {1}", "{1}, {0}", "{0}, {1}, {0}", "{1}, {0}, {1}")] string valuesTemplate, + [CombinatorialMemberData(nameof(AttributeValuesContainerEnumerable))] string valuesContainer) + { + var attributeValues = string.Format(valuesContainer, string.Format(valuesTemplate, + useLocalConstants ? "_xNull" : useConstantsFromOtherClass ? "Constants.Value1" : "null", + useLocalConstants ? "_xValue" : useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")); + + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstants ? $""" + private const string _xNull = {(useConstantsFromOtherClass ? "Constants.Value1" : "null")}; + private const string _xValue = {(useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")}; + """ : "")}} + + [{{dummyAttributeUsage}}Params({{attributeValues}})] + public string {{fieldOrPropertyDeclaration}} + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceConstants(("string", "null"), ("string", "\"test\"")); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_a_null_single_argument_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + bool useConstantFromOtherClass, + bool useLocalConstant) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + + [{{dummyAttributeUsage}}Params({|#0:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] + public string {{fieldOrPropertyDeclaration}} + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceConstants("string", "null"); + + AddDefaultExpectedDiagnostic(); + + await RunAsync(); + } + + [Theory, CombinatorialData] + public async Task Providing_a_null_single_argument_to_attribute_annotating_a_field_or_property_with_an_invalid_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + bool useConstantFromOtherClass, + bool useLocalConstant) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + + [{{dummyAttributeUsage}}Params({|#0:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] + public dummy_literal {{fieldOrPropertyDeclaration}} + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceConstants("string", "null"); + DisableCompilerDiagnostics(); + + AddDefaultExpectedDiagnostic(); + + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); + + public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + + public static IEnumerable AttributeValuesContainerEnumerable() + { + return GenerateData().Distinct(); + + static IEnumerable GenerateData() + { + var nameColonUsages = new List + { + "", + "values: " + }; + + var priorityNamedParameterUsages = new List + { + "", + ", Priority = 1" + }; + + List attributeUsagesBase = [ ]; + + attributeUsagesBase.AddRange([ + "{0}new object[] {{{{ {{0}} }}}}{1}", + "{0}[ {{0}} ]{1}" + ]); + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } + } + } + public class MustHaveMatchingValueType : AnalyzerTestFixture { public MustHaveMatchingValueType() : base(ParamsAttributeAnalyzer.MustHaveMatchingValueTypeRule) { } @@ -209,7 +373,7 @@ public class BenchmarkClass [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(NullableStructValuesAndTypes))] ValueTupleDouble valueAndType, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { @@ -218,8 +382,8 @@ public async Task Providing_null_to_nullable_struct_value_type_should_not_trigge public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "null")}})] - public {{type}}? {{fieldOrPropertyDeclaration}} + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{valueAndType.Value1}, null")}})] + public {{valueAndType.Value2}}? {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; @@ -447,7 +611,7 @@ public class BenchmarkClass [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(NullableStructValuesAndTypes))] ValueTupleDouble valueAndType, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { @@ -456,15 +620,15 @@ public async Task Providing_null_to_nonnullable_struct_value_type_should_trigger public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "{|#0:null|}")}})] - public {{type}} {{fieldOrPropertyDeclaration}} + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{valueAndType.Value1}, {{|#0:null|}}")}})] + public {{valueAndType.Value2}} {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; ReferenceDummyAttribute(); ReferenceDummyEnum(); - AddDefaultExpectedDiagnostic("null", type, "null"); + AddDefaultExpectedDiagnostic("null", valueAndType.Value2!, "null"); await RunAsync(); } @@ -792,21 +956,22 @@ public static IEnumerable> ArrayValuesContainer ( "DummyEnum.Value1", "DummyEnum" ), ]; - public static IEnumerable NullableStructTypes => + public static IEnumerable> NullableStructValuesAndTypes => [ - "bool", - "byte", - "char", - "double", - "float", - "int", - "long", - "sbyte", - "short", - "uint", - "ulong", - "ushort", - "DummyEnum", + ( "true", "bool" ), + ( "(byte)123", "byte" ), + ( "'A'", "char" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( "123", "int" ), + ( "123L", "long" ), + ( "(sbyte)-100", "sbyte" ), + ( "(short)-123", "short" ), + ( "123U", "uint" ), + ( "123UL", "ulong" ), + ( "(ushort)123", "ushort" ), + + ( "DummyEnum.Value1", "DummyEnum" ), ]; public static IEnumerable NullReferenceConstantTypes => @@ -855,6 +1020,31 @@ public string {{fieldOrPropertyDeclaration}} await RunAsync(); } + [Theory, CombinatorialData] + public async Task Providing_a_null_single_argument_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + bool useConstantFromOtherClass, + bool useLocalConstant) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + + [{{dummyAttributeUsage}}Params({|#0:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] + public string {{fieldOrPropertyDeclaration}} + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceConstants("string", "null"); + + 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, From eed4b29478e6ffa990d3986fe624b8ab2c1377f0 Mon Sep 17 00:00:00 2001 From: Gabriel Bider <1554615+silkfire@users.noreply.github.com> Date: Sat, 8 Nov 2025 13:05:58 +0100 Subject: [PATCH 37/38] Revert commit d760312d --- .../AnalyzerReleases.Unshipped.md | 14 +- .../Attributes/ArgumentsAttributeAnalyzer.cs | 38 +-- .../Attributes/ParamsAttributeAnalyzer.cs | 60 +--- ...nchmarkDotNetAnalyzerResources.Designer.cs | 36 --- .../BenchmarkDotNetAnalyzerResources.resx | 16 +- .../DiagnosticIds.cs | 14 +- .../ArgumentsAttributeAnalyzerTests.cs | 259 ++++-------------- .../ParamsAttributeAnalyzerTests.cs | 234 ++-------------- 8 files changed, 85 insertions(+), 586 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md index 67ca52a104..27ba8e2573 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerReleases.Unshipped.md @@ -28,13 +28,11 @@ 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_SingleNullArgumentNotAllowed -BDN1302 | Usage | Error | BDN1302_Attributes_ParamsAttribute_MustHaveMatchingValueType -BDN1303 | Usage | Info | BDN1303_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute -BDN1304 | Usage | Error | BDN1304_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType -BDN1305 | Usage | Error | BDN1305_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool +BDN1301 | Usage | Error | BDN1301_Attributes_ParamsAttribute_MustHaveMatchingValueType +BDN1302 | Usage | Info | BDN1302_Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute +BDN1303 | Usage | Error | BDN1303_Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType +BDN1304 | Usage | Error | BDN1304_Attributes_ParamsAllValues_PropertyOrFieldTypeMustBeEnumOrBool BDN1400 | Usage | Error | BDN1400_Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters BDN1500 | Usage | Error | BDN1500_Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute -BDN1501 | Usage | Error | BDN1501_Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed -BDN1502 | Usage | Error | BDN1502_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount -BDN1503 | Usage | Error | BDN1503_Attributes_ArgumentsAttribute_MustHaveMatchingValueType +BDN1501 | Usage | Error | BDN1501_Attributes_ArgumentsAttribute_MustHaveMatchingValueCount +BDN1502 | Usage | Error | BDN1502_Attributes_ArgumentsAttribute_MustHaveMatchingValueType diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index 20c264059c..8fe15e7591 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -19,13 +19,6 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); - internal static readonly DiagnosticDescriptor SingleNullArgumentNotAllowedRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - 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)), @@ -45,7 +38,6 @@ public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => [ RequiresBenchmarkAttributeRule, - SingleNullArgumentNotAllowedRule, MustHaveMatchingValueCountRule, MustHaveMatchingValueTypeRule ]; @@ -65,7 +57,6 @@ public override void Initialize(AnalysisContext analysisContext) } ctx.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); - ctx.RegisterSyntaxNodeAction(AnalyzeAttributeSyntax, SyntaxKind.Attribute); }); } @@ -76,7 +67,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) return; } - var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation); + var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute"); if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null) @@ -322,31 +313,6 @@ void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLoc } } - private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) - { - if (context.Node is not AttributeSyntax attributeSyntax) - { - return; - } - - var argumentsAttributeTypeSymbol = GetArgumentsAttributeTypeSymbol(context.Compilation); - - var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(argumentsAttributeTypeSymbol, SymbolEqualityComparer.Default)) - { - if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) - { - var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; - - var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); - if (constantValue is { HasValue: true, Value: null }) - { - context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation())); - } - } - } - } - private static int? IndexOfNamedArgument(SeparatedSyntaxList attributeArguments) { var i = 0; @@ -363,7 +329,5 @@ private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) return null; } - - private static INamedTypeSymbol? GetArgumentsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); } } diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs index 96a927828b..906115ccbd 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs @@ -18,13 +18,6 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer DiagnosticSeverity.Error, isEnabledByDefault: true); - internal static readonly DiagnosticDescriptor SingleNullArgumentNotAllowedRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - 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)), @@ -43,7 +36,6 @@ public class ParamsAttributeAnalyzer : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => [ MustHaveValuesRule, - SingleNullArgumentNotAllowedRule, MustHaveMatchingValueTypeRule, UnnecessarySingleValuePassedToAttributeRule ]; @@ -105,14 +97,12 @@ private static void Analyze(SyntaxNodeAnalysisContext context) AnalyzeFieldOrPropertyTypeSyntax(context, fieldOrPropertyTypeSyntax, - attributeSyntax, - paramsAttributeTypeSymbol); + attributeSyntax); } private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, TypeSyntax fieldOrPropertyTypeSyntax, - AttributeSyntax attributeSyntax, - INamedTypeSymbol paramsAttributeTypeSymbol) + AttributeSyntax attributeSyntax) { if (attributeSyntax.ArgumentList == null) { @@ -138,23 +128,6 @@ private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext c return; } - var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default)) - { - if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) - { - var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; - - var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); - if (constantValue is { HasValue: true, Value: null }) - { - context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation())); - - return; - } - } - } - var expectedValueTypeSymbol = context.SemanticModel.GetTypeInfo(fieldOrPropertyTypeSyntax).Type; if (expectedValueTypeSymbol == null || expectedValueTypeSymbol.TypeKind == TypeKind.Error) { @@ -314,35 +287,6 @@ void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLoc } } - private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) - { - if (context.Node is not AttributeSyntax attributeSyntax) - { - return; - } - - var paramsAttributeTypeSymbol = GetParamsAttributeTypeSymbol(context.Compilation); - if (paramsAttributeTypeSymbol == null) - { - return; - } - - var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default)) - { - if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) - { - var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; - - var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); - if (constantValue is { HasValue: true, Value: null }) - { - context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentNotAllowedRule, argumentSyntax.GetLocation())); - } - } - } - } - 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 55d318aa86..aa49e5e367 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.Designer.cs @@ -132,24 +132,6 @@ internal static string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute_ } } - /// - /// Looks up a localized string similar to Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead.. - /// - internal static string Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_MessageFormat { - get { - return ResourceManager.GetString("Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Single null argument to the [Arguments] attribute results in unintended null array. - /// - internal static string Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_Title { - get { - return ResourceManager.GetString("Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed_Title", resourceCulture); - } - } - /// /// Looks up a localized string similar to This method declares one or more parameters but is not annotated with either an [ArgumentsSource] attribute or one or more [Arguments] attributes. To ensure correct argument binding, methods with parameters must explicitly be annotated with an [ArgumentsSource] attribute or one or more [Arguments] attributes. ///Either add the [ArgumentsSource] or [Arguments] attribute(s) or remove the parameters.. @@ -482,24 +464,6 @@ internal static string Attributes_ParamsAttribute_MustHaveValues_Title { } } - /// - /// Looks up a localized string similar to Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead.. - /// - internal static string Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_MessageFormat { - get { - return ResourceManager.GetString("Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_MessageFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Single null argument to the [Params] attribute results in unintended null array. - /// - internal static string Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_Title { - get { - return ResourceManager.GetString("Attributes_ParamsAttribute_SingleNullArgumentNotAllowed_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.. /// diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx index c324362019..a71c0dd64f 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkDotNetAnalyzerResources.resx @@ -201,8 +201,8 @@ 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, "testValue", ...) or a non-null value instead. + + 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 @@ -225,9 +225,6 @@ Single null argument to the [BenchmarkCategory] attribute results in unintended null array - - Single null argument to the [Arguments] 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 @@ -373,13 +370,4 @@ Either add the [ArgumentsSource] or [Arguments] attribute(s) or remove the param Benchmark methods without an [ArgumentsSource] or [Arguments] attribute(s) cannot declare parameters - - Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "SomeCategory", ...) or a non-null value instead. - - - Passing a single null argument creates a null params array. Use multiple arguments (e.g., null, "testValue", ...) or a non-null value instead. - - - Single null argument to the [Params] attribute results in unintended null array - \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index 4c46daa96b..eb4dcaf1ef 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -25,15 +25,13 @@ 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_SingleNullArgumentNotAllowed = "BDN1301"; - public const string Attributes_ParamsAttribute_MustHaveMatchingValueType = "BDN1302"; - public const string Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute = "BDN1303"; - public const string Attributes_ParamsAllValuesAttribute_NotAllowedOnFlagsEnumPropertyOrFieldType = "BDN1304"; - public const string Attributes_ParamsAllValuesAttribute_PropertyOrFieldTypeMustBeEnumOrBool = "BDN1305"; + 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"; public const string Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters = "BDN1400"; public const string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute = "BDN1500"; - public const string Attributes_ArgumentsAttribute_SingleNullArgumentNotAllowed = "BDN1501"; - public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1502"; - public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1503"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1501"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1502"; } } diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs index 678945d206..920324645a 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -16,16 +16,23 @@ 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(EmptyArgumentsAttributeUsagesEnumerableLocal))] string emptyArgumentsAttributeUsage, - [CombinatorialRange(1, 2)] int attributeUsageCount) + 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", Enumerable.Repeat(emptyArgumentsAttributeUsage, attributeUsageCount))}} + {{string.Join("\n", emptyArgumentsAttributeUsages)}} public void BenchmarkMethod() { @@ -38,7 +45,42 @@ public void BenchmarkMethod() await RunAsync(); } - public static IEnumerable EmptyArgumentsAttributeUsagesEnumerableLocal => EmptyArgumentsAttributeUsagesEnumerable(); + 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})]", + "[Arguments({0}[]{1})]", + }; + + 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 @@ -104,178 +146,6 @@ public void BenchmarkMethod() }.AsReadOnly(); } - public class SingleNullArgumentNotAllowed : AnalyzerTestFixture - { - public SingleNullArgumentNotAllowed() : base(ArgumentsAttributeAnalyzer.SingleNullArgumentNotAllowedRule) - { - } - - [Theory, CombinatorialData] - public async Task Providing_a_non_null_single_argument_should_not_trigger_diagnostic([CombinatorialRange(1, 2)] int attributeUsageCount, - bool useConstantFromOtherClass, - bool useLocalConstant, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerable))] string benchmarkAttributeUsage) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "\"test\"")};" : "")}} - - {{string.Join("\n", Enumerable.Repeat($"[Arguments({(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "\"test\"")})]", attributeUsageCount))}} - {{benchmarkAttributeUsage}} - public void BenchmarkMethod(string a) - { - - } - } - """; - - TestCode = testCode; - ReferenceConstants("string", "\"test\""); - - await RunAsync(); - } - - [Theory, CombinatorialData] - public async Task Providing_an_empty_array_argument_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(EmptyArgumentsAttributeUsagesEnumerableLocal))] string emptyAttributeusage, - [CombinatorialRange(1, 2)] int attributeUsageCount, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerable))] string benchmarkAttributeUsage) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{string.Join("\n", Enumerable.Repeat(emptyAttributeusage, attributeUsageCount))}} - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Theory, CombinatorialData] - public async Task Providing_an_array_argument_containing_one_or_more_null_values_should_not_trigger_diagnostic([CombinatorialRange(1, 2)] int attributeUsageCount, - bool useConstantsFromOtherClass, - bool useLocalConstants, - [CombinatorialValues("{0}", "{0}, {1}", "{1}, {0}", "{0}, {1}, {0}", "{1}, {0}, {1}")] string valuesTemplate, - [CombinatorialMemberData(nameof(AttributeValuesContainerEnumerable))] string valuesContainer, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerable))] string benchmarkAttributeUsage) - { - var attributeValues = string.Format(valuesContainer, string.Format(valuesTemplate, - useLocalConstants ? "_xNull" : useConstantsFromOtherClass ? "Constants.Value1" : "null", - useLocalConstants ? "_xValue" : useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")); - - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{(useLocalConstants ? $""" - private const string _xNull = {(useConstantsFromOtherClass ? "Constants.Value1" : "null")}; - private const string _xValue = {(useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")}; - """ : "")}} - - {{string.Join("\n", Enumerable.Repeat($"[Arguments({attributeValues})]", attributeUsageCount))}} - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - ReferenceConstants(("string", "null"), ("string", "\"test\"")); - - await RunAsync(); - } - - [Theory, CombinatorialData] - public async Task Providing_a_null_single_argument_should_trigger_diagnostic([CombinatorialRange(1, 2)] int attributeUsageCount, - bool useConstantFromOtherClass, - bool useLocalConstant, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerable))] string benchmarkAttributeUsage) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} - - {{string.Join("\n", Enumerable.Repeat($"[Arguments({{{{|#{{0}}:{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}|}}}})]", attributeUsageCount).Select((a, i) => string.Format(a, i)))}} - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - ReferenceConstants("string", "null"); - - for (var i = 0; i < attributeUsageCount; i++) - { - AddExpectedDiagnostic(i); - } - - await RunAsync(); - } - - public static IEnumerable BenchmarkAttributeUsagesEnumerable => [ "", "[Benchmark] " ]; - - public static IEnumerable EmptyArgumentsAttributeUsagesEnumerableLocal => EmptyArgumentsAttributeUsagesEnumerable(); - - public static IEnumerable AttributeValuesContainerEnumerable() - { - return GenerateData().Distinct(); - - static IEnumerable GenerateData() - { - var nameColonUsages = new List - { - "", - "values: " - }; - - var priorityNamedParameterUsages = new List - { - "", - ", Priority = 1" - }; - - List attributeUsagesBase = [ ]; - - attributeUsagesBase.AddRange([ - "{0}new object[] {{{{ {{0}} }}}}{1}", - "{0}[ {{0}} ]{1}" - ]); - - foreach (var attributeUsageBase in attributeUsagesBase) - { - foreach (var nameColonUsage in nameColonUsages) - { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } - } - } - } - } - } - public class MustHaveMatchingValueCount : AnalyzerTestFixture { public MustHaveMatchingValueCount() : base(ArgumentsAttributeAnalyzer.MustHaveMatchingValueCountRule) { } @@ -1205,43 +1075,6 @@ static IEnumerable GenerateData() "Dummy, " ]; - public static IEnumerable EmptyArgumentsAttributeUsagesEnumerable() - { - 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})]", - "[Arguments({0}[]{1})]", - }; - - foreach (var attributeUsageBase in attributeUsagesBase) - { - foreach (var nameColonUsage in nameColonUsages) - { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } - } - } - } - private static IEnumerable ScalarValuesContainerAttributeArgumentEnumerable() { return GenerateData().Distinct(); diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs index 7f665720d2..f47ab8e5f2 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -2,7 +2,7 @@ { using Fixtures; - using Analyzers.Attributes; + using BenchmarkDotNet.Analyzers.Attributes; using Xunit; @@ -160,170 +160,6 @@ public static IEnumerable EmptyParamsAttributeUsagesWithLocationMarker() } } - public class SingleNullArgumentNotAllowed : AnalyzerTestFixture - { - public SingleNullArgumentNotAllowed() : base(ParamsAttributeAnalyzer.SingleNullArgumentNotAllowedRule) - { - } - - [Theory, CombinatorialData] - public async Task Providing_a_non_null_single_argument_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - bool useConstantFromOtherClass, - bool useLocalConstant) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "\"test\"")};" : "")}} - - [{{dummyAttributeUsage}}{{$"Params({(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "\"test\"")})"}}] - public string {{fieldOrPropertyDeclaration}} - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceConstants("string", "\"test\""); - - await RunAsync(); - } - - [Theory, CombinatorialData] - public async Task Providing_an_array_argument_containing_one_or_more_null_values_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - bool useConstantsFromOtherClass, - bool useLocalConstants, - [CombinatorialValues("{0}", "{0}, {1}", "{1}, {0}", "{0}, {1}, {0}", "{1}, {0}, {1}")] string valuesTemplate, - [CombinatorialMemberData(nameof(AttributeValuesContainerEnumerable))] string valuesContainer) - { - var attributeValues = string.Format(valuesContainer, string.Format(valuesTemplate, - useLocalConstants ? "_xNull" : useConstantsFromOtherClass ? "Constants.Value1" : "null", - useLocalConstants ? "_xValue" : useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")); - - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{(useLocalConstants ? $""" - private const string _xNull = {(useConstantsFromOtherClass ? "Constants.Value1" : "null")}; - private const string _xValue = {(useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")}; - """ : "")}} - - [{{dummyAttributeUsage}}Params({{attributeValues}})] - public string {{fieldOrPropertyDeclaration}} - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceConstants(("string", "null"), ("string", "\"test\"")); - - await RunAsync(); - } - - [Theory, CombinatorialData] - public async Task Providing_a_null_single_argument_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - bool useConstantFromOtherClass, - bool useLocalConstant) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} - - [{{dummyAttributeUsage}}Params({|#0:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] - public string {{fieldOrPropertyDeclaration}} - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceConstants("string", "null"); - - AddDefaultExpectedDiagnostic(); - - await RunAsync(); - } - - [Theory, CombinatorialData] - public async Task Providing_a_null_single_argument_to_attribute_annotating_a_field_or_property_with_an_invalid_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - bool useConstantFromOtherClass, - bool useLocalConstant) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} - - [{{dummyAttributeUsage}}Params({|#0:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] - public dummy_literal {{fieldOrPropertyDeclaration}} - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceConstants("string", "null"); - DisableCompilerDiagnostics(); - - AddDefaultExpectedDiagnostic(); - - await RunAsync(); - } - - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); - - public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; - - public static IEnumerable AttributeValuesContainerEnumerable() - { - return GenerateData().Distinct(); - - static IEnumerable GenerateData() - { - var nameColonUsages = new List - { - "", - "values: " - }; - - var priorityNamedParameterUsages = new List - { - "", - ", Priority = 1" - }; - - List attributeUsagesBase = [ ]; - - attributeUsagesBase.AddRange([ - "{0}new object[] {{{{ {{0}} }}}}{1}", - "{0}[ {{0}} ]{1}" - ]); - - foreach (var attributeUsageBase in attributeUsagesBase) - { - foreach (var nameColonUsage in nameColonUsages) - { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } - } - } - } - } - } - public class MustHaveMatchingValueType : AnalyzerTestFixture { public MustHaveMatchingValueType() : base(ParamsAttributeAnalyzer.MustHaveMatchingValueTypeRule) { } @@ -373,7 +209,7 @@ public class BenchmarkClass [Theory, CombinatorialData] public async Task Providing_null_to_nullable_struct_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(NullableStructValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(NullableStructTypes))] string type, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { @@ -382,8 +218,8 @@ public async Task Providing_null_to_nullable_struct_value_type_should_not_trigge public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{valueAndType.Value1}, null")}})] - public {{valueAndType.Value2}}? {{fieldOrPropertyDeclaration}} + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "null")}})] + public {{type}}? {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; @@ -611,7 +447,7 @@ public class BenchmarkClass [Theory, CombinatorialData] public async Task Providing_null_to_nonnullable_struct_value_type_should_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(NullableStructValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(NullableStructTypes))] string type, [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) { @@ -620,15 +456,15 @@ public async Task Providing_null_to_nonnullable_struct_value_type_should_trigger public class BenchmarkClass { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{valueAndType.Value1}, {{|#0:null|}}")}})] - public {{valueAndType.Value2}} {{fieldOrPropertyDeclaration}} + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "{|#0:null|}")}})] + public {{type}} {{fieldOrPropertyDeclaration}} } """; TestCode = testCode; ReferenceDummyAttribute(); ReferenceDummyEnum(); - AddDefaultExpectedDiagnostic("null", valueAndType.Value2!, "null"); + AddDefaultExpectedDiagnostic("null", type, "null"); await RunAsync(); } @@ -956,22 +792,21 @@ public static IEnumerable> ArrayValuesContainer ( "DummyEnum.Value1", "DummyEnum" ), ]; - public static IEnumerable> NullableStructValuesAndTypes => + public static IEnumerable NullableStructTypes => [ - ( "true", "bool" ), - ( "(byte)123", "byte" ), - ( "'A'", "char" ), - ( "1.0D", "double" ), - ( "1.0F", "float" ), - ( "123", "int" ), - ( "123L", "long" ), - ( "(sbyte)-100", "sbyte" ), - ( "(short)-123", "short" ), - ( "123U", "uint" ), - ( "123UL", "ulong" ), - ( "(ushort)123", "ushort" ), - - ( "DummyEnum.Value1", "DummyEnum" ), + "bool", + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort", + "DummyEnum", ]; public static IEnumerable NullReferenceConstantTypes => @@ -1020,31 +855,6 @@ public string {{fieldOrPropertyDeclaration}} await RunAsync(); } - [Theory, CombinatorialData] - public async Task Providing_a_null_single_argument_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration, - [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - bool useConstantFromOtherClass, - bool useLocalConstant) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} - - [{{dummyAttributeUsage}}Params({|#0:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] - public string {{fieldOrPropertyDeclaration}} - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceConstants("string", "null"); - - 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, From c831ff1f95bb9e6fa0ee1e433615c3d4aa9b87a3 Mon Sep 17 00:00:00 2001 From: Tim Cassell Date: Sat, 8 Nov 2025 17:38:51 -0500 Subject: [PATCH 38/38] Formatting --- .../AnalyzerHelper.cs | 375 +- .../Attributes/ArgumentsAttributeAnalyzer.cs | 486 +-- .../GeneralArgumentAttributesAnalyzer.cs | 110 +- .../GeneralParameterAttributesAnalyzer.cs | 582 +-- .../ParamsAllValuesAttributeAnalyzer.cs | 200 +- .../Attributes/ParamsAttributeAnalyzer.cs | 425 +- .../BenchmarkRunner/RunAnalyzer.cs | 344 +- .../DiagnosticIds.cs | 69 +- .../General/BenchmarkClassAnalyzer.cs | 800 ++-- .../ArgumentsAttributeAnalyzerTests.cs | 1914 ++++----- .../GeneralArgumentAttributesAnalyzerTests.cs | 225 +- ...GeneralParameterAttributesAnalyzerTests.cs | 2024 +++++----- .../ParamsAllValuesAttributeAnalyzerTests.cs | 509 +-- .../ParamsAttributeAnalyzerTests.cs | 1679 ++++---- .../BenchmarkRunner/RunAnalyzerTests.cs | 1728 ++++----- .../General/BenchmarkClassAnalyzerTests.cs | 3408 +++++++++-------- .../Fixtures/AnalyzerTestFixture.cs | 381 +- .../Extensions/TheoryDataExtensions.cs | 20 +- .../Generators/CombinationsGenerator.cs | 98 +- .../Fixtures/Serializable/ValueTupleDouble.cs | 43 +- .../Fixtures/Serializable/ValueTupleTriple.cs | 49 +- .../FieldOrPropertyDeclarationsTheoryData.cs | 21 +- ...NonPublicClassAccessModifiersTheoryData.cs | 33 +- ...licClassMemberAccessModifiersTheoryData.cs | 31 +- ...PropertySetterAccessModifiersTheoryData.cs | 29 +- 25 files changed, 7871 insertions(+), 7712 deletions(-) diff --git a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs index 447f3809d9..d23ca9db89 100644 --- a/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs +++ b/src/BenchmarkDotNet.Analyzers/AnalyzerHelper.cs @@ -1,243 +1,248 @@ -namespace BenchmarkDotNet.Analyzers -{ - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; +using System.Linq; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Globalization; - using System.Linq; +namespace BenchmarkDotNet.Analyzers; - internal static class AnalyzerHelper - { - public static LocalizableResourceString GetResourceString(string name) => new(name, BenchmarkDotNetAnalyzerResources.ResourceManager, typeof(BenchmarkDotNetAnalyzerResources)); +internal static class AnalyzerHelper +{ + public static LocalizableResourceString GetResourceString(string name) + => new(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(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 || attributeTypeSymbol.TypeKind == TypeKind.Error) { - if (attributeTypeSymbol == null || attributeTypeSymbol.TypeKind == TypeKind.Error) - { - return false; - } + return false; + } - foreach (var attributeListSyntax in attributeLists) + foreach (var attributeListSyntax in attributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) { - foreach (var attributeSyntax in attributeListSyntax.Attributes) + var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null) { - var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeSyntaxTypeSymbol == null) - { - continue; - } - - if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) - { - return true; - } + continue; } - } - return false; + if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return true; + } + } } - public static bool AttributeListContainsAttribute(string attributeName, Compilation compilation, ImmutableArray attributeList) => AttributeListContainsAttribute(compilation.GetTypeByMetadataName(attributeName), attributeList); + return false; + } - public static bool AttributeListContainsAttribute(INamedTypeSymbol? attributeTypeSymbol, ImmutableArray attributeList) - { - if (attributeTypeSymbol == null || attributeTypeSymbol.TypeKind == TypeKind.Error) - { - return false; - } + public static bool AttributeListContainsAttribute(string attributeName, Compilation compilation, ImmutableArray attributeList) + => AttributeListContainsAttribute(compilation.GetTypeByMetadataName(attributeName), attributeList); - return attributeList.Any(ad => ad.AttributeClass != null && ad.AttributeClass.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)); + public static bool AttributeListContainsAttribute(INamedTypeSymbol? attributeTypeSymbol, ImmutableArray attributeList) + { + if (attributeTypeSymbol == null || attributeTypeSymbol.TypeKind == TypeKind.Error) + { + return false; } - public static ImmutableArray GetAttributes(string attributeName, Compilation compilation, SyntaxList attributeLists, SemanticModel semanticModel) => GetAttributes(compilation.GetTypeByMetadataName(attributeName), attributeLists, semanticModel); + 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) + { + var attributesBuilder = ImmutableArray.CreateBuilder(); - public static ImmutableArray GetAttributes(INamedTypeSymbol? attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) + if (attributeTypeSymbol == null) { - var attributesBuilder = ImmutableArray.CreateBuilder(); + return attributesBuilder.ToImmutable(); + } - if (attributeTypeSymbol == null) + foreach (var attributeListSyntax in attributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) { - return attributesBuilder.ToImmutable(); - } + var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null) + { + continue; + } - foreach (var attributeListSyntax in attributeLists) - { - foreach (var attributeSyntax in attributeListSyntax.Attributes) + if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) { - var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeSyntaxTypeSymbol == null) - { - continue; - } - - if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) - { - attributesBuilder.Add(attributeSyntax); - } + attributesBuilder.Add(attributeSyntax); } } - - return attributesBuilder.ToImmutable(); } - public static int GetAttributeUsageCount(string attributeName, Compilation compilation, SyntaxList attributeLists, SemanticModel semanticModel) => GetAttributeUsageCount(compilation.GetTypeByMetadataName(attributeName), attributeLists, semanticModel); + 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) + public static int GetAttributeUsageCount(INamedTypeSymbol? attributeTypeSymbol, SyntaxList attributeLists, SemanticModel semanticModel) + { + var attributeUsageCount = 0; + + if (attributeTypeSymbol == null) { - var attributeUsageCount = 0; + return 0; + } - if (attributeTypeSymbol == null) + foreach (var attributeListSyntax in attributeLists) + { + foreach (var attributeSyntax in attributeListSyntax.Attributes) { - return 0; - } + var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null) + { + continue; + } - foreach (var attributeListSyntax in attributeLists) - { - foreach (var attributeSyntax in attributeListSyntax.Attributes) + if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) { - var attributeSyntaxTypeSymbol = semanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeSyntaxTypeSymbol == null) - { - continue; - } - - if (attributeSyntaxTypeSymbol.Equals(attributeTypeSymbol, SymbolEqualityComparer.Default)) - { - attributeUsageCount++; - } + attributeUsageCount++; } } - - return attributeUsageCount; } - public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol) - { - string typeName; + return attributeUsageCount; + } - 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; - } + public static string NormalizeTypeName(INamedTypeSymbol namedTypeSymbol) + { + string typeName; - return typeName; + if (namedTypeSymbol.SpecialType != SpecialType.None) + { + typeName = namedTypeSymbol.ToString(); } - - public static bool IsAssignableToField(Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) + else if (namedTypeSymbol.IsUnboundGenericType) { - 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); + typeName = $"{namedTypeSymbol.Name}<{new string(',', namedTypeSymbol.TypeArguments.Length - 1)}>"; } - - public static bool IsAssignableToLocal(Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) + else { - 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); + typeName = namedTypeSymbol.Name; } - private static bool IsAssignableTo(string codeTemplate1, string codeTemplate2, Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) - { - var hasCompilerDiagnostics = HasNoCompilerDiagnostics(string.Format(codeTemplate1, targetType, valueExpression), compilation); - if (hasCompilerDiagnostics) - { - return true; - } + return typeName; + } - if (!constantValue.HasValue || valueType == null) - { - return false; - } + public static bool IsAssignableToField(Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) + { + 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 constantLiteral = FormatLiteral(constantValue.Value); - if (constantLiteral == null) - { - return false; - } + 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); + } - return HasNoCompilerDiagnostics(string.Format(codeTemplate2, targetType, valueType, constantLiteral), compilation); + private static bool IsAssignableTo(string codeTemplate1, string codeTemplate2, Compilation compilation, ITypeSymbol targetType, string valueExpression, Optional constantValue, string? valueType) + { + var hasCompilerDiagnostics = HasNoCompilerDiagnostics(string.Format(codeTemplate1, targetType, valueExpression), compilation); + if (hasCompilerDiagnostics) + { + return true; } - private static bool HasNoCompilerDiagnostics(string code, Compilation compilation) + if (!constantValue.HasValue || valueType == null) { - var syntaxTree = CSharpSyntaxTree.ParseText(code); - - var compilerDiagnostics = compilation.AddSyntaxTrees(syntaxTree) - .GetSemanticModel(syntaxTree) - .GetMethodBodyDiagnostics() - .Where(d => d.DefaultSeverity == DiagnosticSeverity.Error) - .ToList(); - - return compilerDiagnostics.Count == 0; + return false; } - private static string? FormatLiteral(object? value) + var constantLiteral = FormatLiteral(constantValue.Value); + if (constantLiteral == null) { - 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 - }; + return false; } - public static void Deconstruct(this KeyValuePair tuple, out T1 key, out T2 value) + return HasNoCompilerDiagnostics(string.Format(codeTemplate2, targetType, valueType, constantLiteral), compilation); + } + + private static bool HasNoCompilerDiagnostics(string code, Compilation compilation) + { + var syntaxTree = CSharpSyntaxTree.ParseText(code); + + var compilerDiagnostics = compilation + .AddSyntaxTrees(syntaxTree) + .GetSemanticModel(syntaxTree) + .GetMethodBodyDiagnostics() + .Where(d => d.DefaultSeverity == DiagnosticSeverity.Error) + .ToList(); + + return compilerDiagnostics.Count == 0; + } + + private static string? FormatLiteral(object? value) + { + return value switch { - key = tuple.Key; - value = tuple.Value; - } + 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 + }; + } + + public static void Deconstruct(this KeyValuePair tuple, out T1 key, out T2 value) + { + key = tuple.Key; + value = tuple.Value; } -} +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs index 8fe15e7591..6b6540aea5 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ArgumentsAttributeAnalyzer.cs @@ -1,333 +1,335 @@ -namespace BenchmarkDotNet.Analyzers.Attributes +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Immutable; + +namespace BenchmarkDotNet.Analyzers.Attributes; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer { - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.CSharp.Syntax; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.Diagnostics; - using Microsoft.CodeAnalysis.Text; + internal static readonly DiagnosticDescriptor RequiresBenchmarkAttributeRule = new( + 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 MustHaveMatchingValueCountRule = new( + 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( + 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 => + [ + RequiresBenchmarkAttributeRule, + MustHaveMatchingValueCountRule, + MustHaveMatchingValueTypeRule + ]; + + public override void Initialize(AnalysisContext analysisContext) + { + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - using System; - using System.Collections.Immutable; + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet.Annotations is referenced + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ArgumentsAttributeAnalyzer : DiagnosticAnalyzer + ctx.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); + }); + } + + private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) { - 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 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 => - [ - RequiresBenchmarkAttributeRule, - MustHaveMatchingValueCountRule, - MustHaveMatchingValueTypeRule - ]; - - public override void Initialize(AnalysisContext analysisContext) + if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax) { - analysisContext.EnableConcurrentExecution(); - analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + return; + } - analysisContext.RegisterCompilationStartAction(ctx => - { - // Only run if BenchmarkDotNet.Annotations is referenced - var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); - if (benchmarkAttributeTypeSymbol == null) - { - return; - } + var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); + var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute"); - ctx.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); - }); + if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null) + { + return; } - private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) - { - if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax) - { - return; - } + var hasBenchmarkAttribute = AnalyzerHelper.AttributeListsContainAttribute(AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation), methodDeclarationSyntax.AttributeLists, context.SemanticModel); - var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); - var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute"); + var argumentsAttributes = AnalyzerHelper.GetAttributes(argumentsAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); + if (argumentsAttributes.Length == 0) + { + return; + } - if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null) + if (!hasBenchmarkAttribute) + { + foreach (var argumentsAttributeSyntax in argumentsAttributes) { - return; + context.ReportDiagnostic(Diagnostic.Create(RequiresBenchmarkAttributeRule, argumentsAttributeSyntax.GetLocation())); } - var hasBenchmarkAttribute = AnalyzerHelper.AttributeListsContainAttribute(AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation), methodDeclarationSyntax.AttributeLists, context.SemanticModel); + return; + } - var argumentsAttributes = AnalyzerHelper.GetAttributes(argumentsAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); - if (argumentsAttributes.Length == 0) - { - return; - } + var methodParameterTypeSymbolsBuilder = ImmutableArray.CreateBuilder(methodDeclarationSyntax.ParameterList.Parameters.Count); - if (!hasBenchmarkAttribute) + foreach (var parameterSyntax in methodDeclarationSyntax.ParameterList.Parameters) + { + if (parameterSyntax.Type != null) { - foreach (var argumentsAttributeSyntax in argumentsAttributes) + var expectedParameterTypeSymbol = context.SemanticModel.GetTypeInfo(parameterSyntax.Type).Type; + if (expectedParameterTypeSymbol != null && expectedParameterTypeSymbol.TypeKind != TypeKind.Error) { - context.ReportDiagnostic(Diagnostic.Create(RequiresBenchmarkAttributeRule, argumentsAttributeSyntax.GetLocation())); + methodParameterTypeSymbolsBuilder.Add(expectedParameterTypeSymbol); + + continue; } - return; + methodParameterTypeSymbolsBuilder.Add(null); } + } - var methodParameterTypeSymbolsBuilder = ImmutableArray.CreateBuilder(methodDeclarationSyntax.ParameterList.Parameters.Count); + var methodParameterTypeSymbols = methodParameterTypeSymbolsBuilder.ToImmutable(); - foreach (var parameterSyntax in methodDeclarationSyntax.ParameterList.Parameters) + foreach (var argumentsAttributeSyntax in argumentsAttributes) + { + if (argumentsAttributeSyntax.ArgumentList == null) { - if (parameterSyntax.Type != null) + if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) { - var expectedParameterTypeSymbol = context.SemanticModel.GetTypeInfo(parameterSyntax.Type).Type; - if (expectedParameterTypeSymbol != null && expectedParameterTypeSymbol.TypeKind != TypeKind.Error) - { - methodParameterTypeSymbolsBuilder.Add(expectedParameterTypeSymbol); - - continue; - } - - methodParameterTypeSymbolsBuilder.Add(null); + ReportMustHaveMatchingValueCountDiagnostic(argumentsAttributeSyntax.GetLocation(), 0); } } - - var methodParameterTypeSymbols = methodParameterTypeSymbolsBuilder.ToImmutable(); - - foreach (var argumentsAttributeSyntax in argumentsAttributes) + else if (!argumentsAttributeSyntax.ArgumentList.Arguments.Any()) { - if (argumentsAttributeSyntax.ArgumentList == null) + if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) { - if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) - { - ReportMustHaveMatchingValueCountDiagnostic(argumentsAttributeSyntax.GetLocation(), 0); - } + ReportMustHaveMatchingValueCountDiagnostic(argumentsAttributeSyntax.ArgumentList.GetLocation(), 0); } - else if (!argumentsAttributeSyntax.ArgumentList.Arguments.Any()) + } + 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(argumentsAttributeSyntax.ArgumentList.GetLocation(), 0); + ReportMustHaveMatchingValueCountDiagnostic(attributeArgumentSyntax.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 + // Collection expression - else if (attributeArgumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax) + else if (attributeArgumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax) + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count != collectionExpressionSyntax.Elements.Count) { - 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; - } + ReportMustHaveMatchingValueCountDiagnostic( + collectionExpressionSyntax.Elements.Count == 0 + ? collectionExpressionSyntax.GetLocation() + : Location.Create(context.FilterTree, collectionExpressionSyntax.Elements.Span), + collectionExpressionSyntax.Elements.Count + ); - ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => collectionExpressionSyntax.Elements[i] is ExpressionElementSyntax expressionElementSyntax ? expressionElementSyntax.Expression : null); + continue; } - // Array creation expression - else + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(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 && arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object) { - var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeArgumentSyntax.Expression).Type; - if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol && 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 0) + if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) { - if (methodDeclarationSyntax.ParameterList.Parameters.Count > 0) - { - ReportMustHaveMatchingValueCountDiagnostic(literalExpressionSyntax.GetLocation(), 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 - ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]); - } } - } - else - { - // Params values - - var firstNamedArgumentIndex = IndexOfNamedArgument(argumentsAttributeSyntax.ArgumentList.Arguments); - if (firstNamedArgumentIndex > 0) + else { - if (methodDeclarationSyntax.ParameterList.Parameters.Count != firstNamedArgumentIndex.Value) + if (methodDeclarationSyntax.ParameterList.Parameters.Count != arrayCreationExpressionSyntax.Initializer.Expressions.Count) { - ReportMustHaveMatchingValueCountDiagnostic(Location.Create(context.FilterTree, TextSpan.FromBounds(argumentsAttributeSyntax.ArgumentList.Arguments.Span.Start, argumentsAttributeSyntax.ArgumentList.Arguments[firstNamedArgumentIndex.Value - 1].Span.End)), - firstNamedArgumentIndex.Value); + 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 => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => arrayCreationExpressionSyntax.Initializer.Expressions[i]); } - else + } + } + else + { + // Params values + + var firstNamedArgumentIndex = IndexOfNamedArgument(argumentsAttributeSyntax.ArgumentList.Arguments); + if (firstNamedArgumentIndex > 0) + { + if (methodDeclarationSyntax.ParameterList.Parameters.Count != firstNamedArgumentIndex.Value) { - if (methodDeclarationSyntax.ParameterList.Parameters.Count != argumentsAttributeSyntax.ArgumentList.Arguments.Count) - { - ReportMustHaveMatchingValueCountDiagnostic(Location.Create(context.FilterTree, argumentsAttributeSyntax.ArgumentList.Arguments.Span), - argumentsAttributeSyntax.ArgumentList.Arguments.Count); + ReportMustHaveMatchingValueCountDiagnostic( + Location.Create( + context.FilterTree, + TextSpan.FromBounds(argumentsAttributeSyntax.ArgumentList.Arguments.Span.Start, argumentsAttributeSyntax.ArgumentList.Arguments[firstNamedArgumentIndex.Value - 1].Span.End) + ), + firstNamedArgumentIndex.Value + ); + + continue; + } - continue; - } + // ReSharper disable once PossibleNullReferenceException + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(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 + ); - // ReSharper disable once PossibleNullReferenceException - ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); + continue; } + + // ReSharper disable once PossibleNullReferenceException + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(i => argumentsAttributeSyntax.ArgumentList.Arguments[i].Expression); } } } } + } - return; + 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 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 ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func valueExpressionSyntaxFunc) + void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(Func valueExpressionSyntaxFunc) + { + for (var i = 0; i < methodParameterTypeSymbols.Length; i++) { - for (var i = 0; i < methodParameterTypeSymbols.Length; i++) + var methodParameterTypeSymbol = methodParameterTypeSymbols[i]; + if (methodParameterTypeSymbol == null) { - var methodParameterTypeSymbol = methodParameterTypeSymbols[i]; - if (methodParameterTypeSymbol == null) - { - continue; - } + continue; + } - var valueExpressionSyntax = valueExpressionSyntaxFunc(i); - if (valueExpressionSyntax == null) - { - continue; - } + var valueExpressionSyntax = valueExpressionSyntaxFunc(i); + if (valueExpressionSyntax == null) + { + continue; + } - var valueExpressionString = valueExpressionSyntax.ToString(); + var valueExpressionString = valueExpressionSyntax.ToString(); - var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax); + var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax); - var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type; - if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error) + var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type; + if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error) + { + if (!AnalyzerHelper.IsAssignableToLocal(context.Compilation, methodParameterTypeSymbol, valueExpressionString, constantValue, actualValueTypeSymbol.ToString())) { - if (!AnalyzerHelper.IsAssignableToLocal(context.Compilation, - methodParameterTypeSymbol, - valueExpressionString, - constantValue, - actualValueTypeSymbol.ToString())) - { - ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionSyntax.ToString(), - methodParameterTypeSymbol.ToString(), - actualValueTypeSymbol.ToString()); - } + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic( + valueExpressionSyntax.GetLocation(), + valueExpressionSyntax.ToString(), + methodParameterTypeSymbol.ToString(), + actualValueTypeSymbol.ToString() + ); } - else + } + else + { + if (constantValue is { HasValue: true, Value: null } + && !AnalyzerHelper.IsAssignableToLocal(context.Compilation, methodParameterTypeSymbol, valueExpressionString, constantValue, null)) { - if (constantValue is { HasValue: true, Value: null }) - { - if (!AnalyzerHelper.IsAssignableToLocal(context.Compilation, - methodParameterTypeSymbol, - valueExpressionString, - constantValue, - null)) - { - ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionString, - methodParameterTypeSymbol.ToString(), - "null"); - } - } + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic( + valueExpressionSyntax.GetLocation(), + valueExpressionString, + methodParameterTypeSymbol.ToString(), + "null" + ); } } + } - return; + return; - void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType) - { - context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueTypeRule, - diagnosticLocation, - value, - expectedType, - actualType)); - } - } + void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType) + => context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueTypeRule, diagnosticLocation, value, expectedType, actualType)); } + } - private static int? IndexOfNamedArgument(SeparatedSyntaxList attributeArguments) - { - var i = 0; + private static int? IndexOfNamedArgument(SeparatedSyntaxList attributeArguments) + { + var i = 0; - foreach (var attributeArgumentSyntax in attributeArguments) + foreach (var attributeArgumentSyntax in attributeArguments) + { + if (attributeArgumentSyntax.NameEquals != null) { - if (attributeArgumentSyntax.NameEquals != null) - { - return i; - } - - i++; + return i; } - return null; + i++; } + + return null; } -} +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralArgumentAttributesAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralArgumentAttributesAnalyzer.cs index d4b884c106..7b386731ec 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralArgumentAttributesAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralArgumentAttributesAnalyzer.cs @@ -1,72 +1,74 @@ -namespace BenchmarkDotNet.Analyzers.Attributes +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; + +namespace BenchmarkDotNet.Analyzers.Attributes; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class GeneralArgumentAttributesAnalyzer : DiagnosticAnalyzer { - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.CSharp.Syntax; - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.Diagnostics; + internal static readonly DiagnosticDescriptor MethodWithoutAttributeMustHaveNoParametersRule = new( + DiagnosticIds.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_MessageFormat)), + "Usage", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Description); - using System.Collections.Immutable; + public override ImmutableArray SupportedDiagnostics => + [ + MethodWithoutAttributeMustHaveNoParametersRule, + ]; - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class GeneralArgumentAttributesAnalyzer : DiagnosticAnalyzer + public override void Initialize(AnalysisContext analysisContext) { - internal static readonly DiagnosticDescriptor MethodWithoutAttributeMustHaveNoParametersRule = new DiagnosticDescriptor(DiagnosticIds.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: BenchmarkDotNetAnalyzerResources.Attributes_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters_Description); - - public override ImmutableArray SupportedDiagnostics => - [ - MethodWithoutAttributeMustHaveNoParametersRule, - ]; + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - public override void Initialize(AnalysisContext analysisContext) + analysisContext.RegisterCompilationStartAction(ctx => { - 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) { - // Only run if BenchmarkDotNet.Annotations is referenced - var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); - if (benchmarkAttributeTypeSymbol == null) - { - return; - } + return; + } - ctx.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); - }); - } + ctx.RegisterSyntaxNodeAction(AnalyzeMethodDeclaration, SyntaxKind.MethodDeclaration); + }); + } - private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) + private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context) + { + if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax) { - if (context.Node is not MethodDeclarationSyntax methodDeclarationSyntax) - { - return; - } + return; + } - var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); - var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute"); + var argumentsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsAttribute"); + var argumentsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ArgumentsSourceAttribute"); - if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null) - { - return; - } + if (argumentsAttributeTypeSymbol == null || argumentsSourceAttributeTypeSymbol == null) + { + return; + } - var hasBenchmarkAttribute = AnalyzerHelper.AttributeListsContainAttribute(AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation), methodDeclarationSyntax.AttributeLists, context.SemanticModel); - var hasArgumentsSourceAttribute = AnalyzerHelper.AttributeListsContainAttribute(argumentsSourceAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); + var hasBenchmarkAttribute = AnalyzerHelper.AttributeListsContainAttribute(AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation), methodDeclarationSyntax.AttributeLists, context.SemanticModel); + var hasArgumentsSourceAttribute = AnalyzerHelper.AttributeListsContainAttribute(argumentsSourceAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); - var argumentsAttributes = AnalyzerHelper.GetAttributes(argumentsAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); - if (argumentsAttributes.Length == 0) + var argumentsAttributes = AnalyzerHelper.GetAttributes(argumentsAttributeTypeSymbol, methodDeclarationSyntax.AttributeLists, context.SemanticModel); + if (argumentsAttributes.Length == 0) + { + if (hasBenchmarkAttribute && !hasArgumentsSourceAttribute && methodDeclarationSyntax.ParameterList.Parameters.Count > 0) { - if (hasBenchmarkAttribute && !hasArgumentsSourceAttribute && methodDeclarationSyntax.ParameterList.Parameters.Count > 0) - { - context.ReportDiagnostic(Diagnostic.Create(MethodWithoutAttributeMustHaveNoParametersRule, Location.Create(context.FilterTree, methodDeclarationSyntax.ParameterList.Parameters.Span), methodDeclarationSyntax.Identifier.ToString())); - } + context.ReportDiagnostic(Diagnostic.Create( + MethodWithoutAttributeMustHaveNoParametersRule, + Location.Create(context.FilterTree, methodDeclarationSyntax.ParameterList.Parameters.Span), methodDeclarationSyntax.Identifier.ToString()) + ); } } } -} +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs index 98f3f5b413..afaf232613 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/GeneralParameterAttributesAnalyzer.cs @@ -1,332 +1,346 @@ -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; + +namespace BenchmarkDotNet.Analyzers.Attributes; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class GeneralParameterAttributesAnalyzer : DiagnosticAnalyzer { - 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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( + 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 => + [ + MutuallyExclusiveOnFieldRule, + MutuallyExclusiveOnPropertyRule, + FieldMustBePublic, + PropertyMustBePublic, + NotValidOnReadonlyFieldRule, + NotValidOnConstantFieldRule, + PropertyCannotBeInitOnlyRule, + PropertyMustHavePublicSetterRule + ]; + + public override void Initialize(AnalysisContext analysisContext) { - 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 => - [ - MutuallyExclusiveOnFieldRule, - MutuallyExclusiveOnPropertyRule, - FieldMustBePublic, - PropertyMustBePublic, - NotValidOnReadonlyFieldRule, - NotValidOnConstantFieldRule, - PropertyCannotBeInitOnlyRule, - PropertyMustHavePublicSetterRule - ]; + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - public override void Initialize(AnalysisContext analysisContext) + analysisContext.RegisterCompilationStartAction(ctx => { - analysisContext.EnableConcurrentExecution(); - analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + // Only run if BenchmarkDotNet.Annotations is referenced + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); + }); + } - analysisContext.RegisterCompilationStartAction(ctx => - { - // Only run if BenchmarkDotNet.Annotations is referenced - var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); - if (benchmarkAttributeTypeSymbol == null) - { - return; - } + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (context.Node is not AttributeSyntax attributeSyntax) + { + return; + } - ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); - }); + if (!AllAttributeTypeSymbolsExist(context, out var paramsAttributeTypeSymbol, out var paramsSourceAttributeTypeSymbol, out var paramsAllValuesAttributeTypeSymbol)) + { + return; } - private static void Analyze(SyntaxNodeAnalysisContext context) + var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null + || attributeSyntaxTypeSymbol.TypeKind == TypeKind.Error + || + (!attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default) + && !attributeSyntaxTypeSymbol.Equals(paramsSourceAttributeTypeSymbol, SymbolEqualityComparer.Default) + && !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default))) { - if (context.Node is not AttributeSyntax attributeSyntax) - { - return; - } + return; + } - if (!AllAttributeTypeSymbolsExist(context, out var paramsAttributeTypeSymbol, out var paramsSourceAttributeTypeSymbol, out var paramsAllValuesAttributeTypeSymbol)) - { - return; - } + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); + if (attributeTarget == null) + { + return; + } - var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; - if ( attributeSyntaxTypeSymbol == null - || attributeSyntaxTypeSymbol.TypeKind == TypeKind.Error - || - (!attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default) - && !attributeSyntaxTypeSymbol.Equals(paramsSourceAttributeTypeSymbol, SymbolEqualityComparer.Default) - && !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default))) - { - 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 attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); - if (attributeTarget == null) - { - return; - } + var fieldConstModifierIndex = fieldDeclarationSyntax.Modifiers.IndexOf(SyntaxKind.ConstKeyword); + fieldConstModifierLocation = fieldConstModifierIndex >= 0 ? fieldDeclarationSyntax.Modifiers[fieldConstModifierIndex].GetLocation() : null; - 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 fieldOrPropertyReadonlyModifierIndex = fieldDeclarationSyntax.Modifiers.IndexOf(SyntaxKind.ReadOnlyKeyword); + fieldReadonlyModifierLocation = fieldOrPropertyReadonlyModifierIndex >= 0 ? fieldDeclarationSyntax.Modifiers[fieldOrPropertyReadonlyModifierIndex].GetLocation() : null; - var fieldConstModifierIndex = fieldDeclarationSyntax.Modifiers.IndexOf(SyntaxKind.ConstKeyword); - fieldConstModifierLocation = fieldConstModifierIndex >= 0 ? fieldDeclarationSyntax.Modifiers[fieldConstModifierIndex].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 fieldOrPropertyReadonlyModifierIndex = fieldDeclarationSyntax.Modifiers.IndexOf(SyntaxKind.ReadOnlyKeyword); - fieldReadonlyModifierLocation = fieldOrPropertyReadonlyModifierIndex >= 0 ? fieldDeclarationSyntax.Modifiers[fieldOrPropertyReadonlyModifierIndex].GetLocation() : null; + var propertyInitAccessorIndex = propertyDeclarationSyntax.AccessorList?.Accessors.IndexOf(SyntaxKind.InitAccessorDeclaration); + propertyInitAccessorKeywordLocation = propertyInitAccessorIndex >= 0 ? propertyDeclarationSyntax.AccessorList.Accessors[propertyInitAccessorIndex.Value].Keyword.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 propertySetAccessorIndex = propertyDeclarationSyntax.AccessorList?.Accessors.IndexOf(SyntaxKind.SetAccessorDeclaration); + propertyIsMissingAssignableSetter = !propertySetAccessorIndex.HasValue || propertySetAccessorIndex.Value < 0 || propertyDeclarationSyntax.AccessorList.Accessors[propertySetAccessorIndex.Value].Modifiers.Any(); - var propertyInitAccessorIndex = propertyDeclarationSyntax.AccessorList?.Accessors.IndexOf(SyntaxKind.InitAccessorDeclaration); - propertyInitAccessorKeywordLocation = propertyInitAccessorIndex >= 0 ? propertyDeclarationSyntax.AccessorList.Accessors[propertyInitAccessorIndex.Value].Keyword.GetLocation() : null; + fieldOrPropertyIdentifierLocation = propertyDeclarationSyntax.Identifier.GetLocation(); + fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule = MutuallyExclusiveOnPropertyRule; + fieldOrPropertyMustBePublicDiagnosticRule = PropertyMustBePublic; + } + else + { + return; + } - var propertySetAccessorIndex = propertyDeclarationSyntax.AccessorList?.Accessors.IndexOf(SyntaxKind.SetAccessorDeclaration); - propertyIsMissingAssignableSetter = !propertySetAccessorIndex.HasValue || propertySetAccessorIndex.Value < 0 || propertyDeclarationSyntax.AccessorList.Accessors[propertySetAccessorIndex.Value].Modifiers.Any(); + AnalyzeFieldOrPropertySymbol( + context, + paramsAttributeTypeSymbol, + paramsSourceAttributeTypeSymbol, + paramsAllValuesAttributeTypeSymbol, + declaredAttributes, + fieldOrPropertyIsPublic, + fieldConstModifierLocation, + fieldReadonlyModifierLocation, + fieldOrPropertyIdentifier, + propertyInitAccessorKeywordLocation, + propertyIsMissingAssignableSetter, + fieldOrPropertyIdentifierLocation, + fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule, + fieldOrPropertyMustBePublicDiagnosticRule, + attributeSyntax); + } - fieldOrPropertyIdentifierLocation = propertyDeclarationSyntax.Identifier.GetLocation(); - fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule = MutuallyExclusiveOnPropertyRule; - fieldOrPropertyMustBePublicDiagnosticRule = PropertyMustBePublic; - } - else - { - return; - } + 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) + { + ImmutableArray applicableParameterAttributeTypeSymbols = + [ + paramsAttributeTypeSymbol, + paramsSourceAttributeTypeSymbol, + paramsAllValuesAttributeTypeSymbol + ]; - AnalyzeFieldOrPropertySymbol(context, - paramsAttributeTypeSymbol, - paramsSourceAttributeTypeSymbol, - paramsAllValuesAttributeTypeSymbol, - declaredAttributes, - fieldOrPropertyIsPublic, - fieldConstModifierLocation, - fieldReadonlyModifierLocation, - fieldOrPropertyIdentifier, - propertyInitAccessorKeywordLocation, - propertyIsMissingAssignableSetter, - fieldOrPropertyIdentifierLocation, - fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule, - fieldOrPropertyMustBePublicDiagnosticRule, - attributeSyntax); - } + var parameterAttributeTypeSymbols = new HashSet(SymbolEqualityComparer.Default); - 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) + foreach (var declaredAttributeSyntax in declaredAttributes) { - 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) { - var declaredAttributeTypeSymbol = context.SemanticModel.GetTypeInfo(declaredAttributeSyntax).Type; - if (declaredAttributeTypeSymbol != null) + foreach (var applicableParameterAttributeTypeSymbol in applicableParameterAttributeTypeSymbols) { - foreach (var applicableParameterAttributeTypeSymbol in applicableParameterAttributeTypeSymbols) + if (declaredAttributeTypeSymbol.Equals(applicableParameterAttributeTypeSymbol, SymbolEqualityComparer.Default)) { - if (declaredAttributeTypeSymbol.Equals(applicableParameterAttributeTypeSymbol, SymbolEqualityComparer.Default)) + if (!parameterAttributeTypeSymbols.Add(applicableParameterAttributeTypeSymbol)) { - if (!parameterAttributeTypeSymbols.Add(applicableParameterAttributeTypeSymbol)) - { - return; - } + return; } } } } + } - if (parameterAttributeTypeSymbols.Count == 0) - { - return; - } - - if (parameterAttributeTypeSymbols.Count == 1) - { - if (fieldConstModifierLocation != null) - { - context.ReportDiagnostic(Diagnostic.Create(NotValidOnConstantFieldRule, - fieldConstModifierLocation, - attributeSyntax.Name.ToString())); + if (parameterAttributeTypeSymbols.Count == 0) + { + return; + } - return; - } + if (parameterAttributeTypeSymbols.Count != 1) + { + context.ReportDiagnostic(Diagnostic.Create(fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule, + attributeSyntax.GetLocation(), + fieldOrPropertyIdentifier) + ); - if (!fieldOrPropertyIsPublic) - { - context.ReportDiagnostic(Diagnostic.Create(fieldOrPropertyMustBePublicDiagnosticRule, - fieldOrPropertyIdentifierLocation, - fieldOrPropertyIdentifier, - attributeSyntax.Name.ToString())); - } + return; + } - if (fieldReadonlyModifierLocation != null) - { - context.ReportDiagnostic(Diagnostic.Create(NotValidOnReadonlyFieldRule, - fieldReadonlyModifierLocation, - fieldOrPropertyIdentifier, - attributeSyntax.Name.ToString())); - } + if (fieldConstModifierLocation != null) + { + context.ReportDiagnostic(Diagnostic.Create(NotValidOnConstantFieldRule, + fieldConstModifierLocation, + 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; + } - return; - } + if (!fieldOrPropertyIsPublic) + { + context.ReportDiagnostic(Diagnostic.Create(fieldOrPropertyMustBePublicDiagnosticRule, + fieldOrPropertyIdentifierLocation, + fieldOrPropertyIdentifier, + attributeSyntax.Name.ToString()) + ); + } - context.ReportDiagnostic(Diagnostic.Create(fieldOrPropertyCannotHaveMoreThanOneParameterAttributeAppliedDiagnosticRule, - attributeSyntax.GetLocation(), - fieldOrPropertyIdentifier)); + if (fieldReadonlyModifierLocation != null) + { + context.ReportDiagnostic(Diagnostic.Create(NotValidOnReadonlyFieldRule, + fieldReadonlyModifierLocation, + fieldOrPropertyIdentifier, + attributeSyntax.Name.ToString()) + ); } - private static bool AllAttributeTypeSymbolsExist(in SyntaxNodeAnalysisContext context, - out INamedTypeSymbol? paramsAttributeTypeSymbol, - out INamedTypeSymbol? paramsSourceAttributeTypeSymbol, - out INamedTypeSymbol? paramsAllValuesAttributeTypeSymbol) + if (propertyInitAccessorKeywordLocation != null) { - paramsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute"); - if (paramsAttributeTypeSymbol == null) - { - paramsSourceAttributeTypeSymbol = null; - paramsAllValuesAttributeTypeSymbol = 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 false; - } + 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; - paramsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsSourceAttribute"); - if (paramsSourceAttributeTypeSymbol == null) - { - paramsAllValuesAttributeTypeSymbol = null; + return false; + } - return false; - } + paramsSourceAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsSourceAttribute"); + if (paramsSourceAttributeTypeSymbol == null) + { + paramsAllValuesAttributeTypeSymbol = null; - paramsAllValuesAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAllValuesAttribute"); - if (paramsAllValuesAttributeTypeSymbol == null) - { - return false; - } + return false; + } - return true; + paramsAllValuesAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAllValuesAttribute"); + if (paramsAllValuesAttributeTypeSymbol == null) + { + return false; } + + return true; } -} +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs index 30a8a7b1f3..4abf9fffe0 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAllValuesAttributeAnalyzer.cs @@ -1,129 +1,129 @@ -namespace BenchmarkDotNet.Analyzers.Attributes -{ - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.CSharp.Syntax; - using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using System.Linq; - using System.Collections.Immutable; - using System.Linq; +namespace BenchmarkDotNet.Analyzers.Attributes; - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ParamsAllValuesAttributeAnalyzer : DiagnosticAnalyzer +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class ParamsAllValuesAttributeAnalyzer : DiagnosticAnalyzer +{ + internal static readonly DiagnosticDescriptor NotAllowedOnFlagsEnumPropertyOrFieldTypeRule = new( + 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( + 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 => + [ + NotAllowedOnFlagsEnumPropertyOrFieldTypeRule, + PropertyOrFieldTypeMustBeEnumOrBoolRule + ]; + + public override void Initialize(AnalysisContext analysisContext) { - 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 => - [ - NotAllowedOnFlagsEnumPropertyOrFieldTypeRule, - PropertyOrFieldTypeMustBeEnumOrBoolRule - ]; - - public override void Initialize(AnalysisContext analysisContext) - { - analysisContext.EnableConcurrentExecution(); - analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - analysisContext.RegisterCompilationStartAction(ctx => - { - // Only run if BenchmarkDotNet.Annotations is referenced - if (GetParamsAllValuesAttributeTypeSymbol(ctx.Compilation) == null) - { - return; - } - - ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); - }); - } - - private static void Analyze(SyntaxNodeAnalysisContext context) + analysisContext.RegisterCompilationStartAction(ctx => { - if (context.Node is not AttributeSyntax attributeSyntax) + // Only run if BenchmarkDotNet.Annotations is referenced + if (GetParamsAllValuesAttributeTypeSymbol(ctx.Compilation) == null) { return; } - var paramsAllValuesAttributeTypeSymbol = GetParamsAllValuesAttributeTypeSymbol(context.Compilation); + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); + }); + } - var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeSyntaxTypeSymbol == null || !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default)) - { - return; - } + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (context.Node is not AttributeSyntax attributeSyntax) + { + return; + } - var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); - if (attributeTarget == null) - { - return; - } + var paramsAllValuesAttributeTypeSymbol = GetParamsAllValuesAttributeTypeSymbol(context.Compilation); - TypeSyntax fieldOrPropertyTypeSyntax; + var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null || !attributeSyntaxTypeSymbol.Equals(paramsAllValuesAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + return; + } - if (attributeTarget is FieldDeclarationSyntax fieldDeclarationSyntax) - { - fieldOrPropertyTypeSyntax = fieldDeclarationSyntax.Declaration.Type; + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); + if (attributeTarget == null) + { + return; + } - } - else if (attributeTarget is PropertyDeclarationSyntax propertyDeclarationSyntax) - { - fieldOrPropertyTypeSyntax = propertyDeclarationSyntax.Type; - } - else - { - return; - } + TypeSyntax fieldOrPropertyTypeSyntax; - AnalyzeFieldOrPropertyTypeSyntax(context, fieldOrPropertyTypeSyntax); - } + if (attributeTarget is FieldDeclarationSyntax fieldDeclarationSyntax) + { + fieldOrPropertyTypeSyntax = fieldDeclarationSyntax.Declaration.Type; - private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, TypeSyntax fieldOrPropertyTypeSyntax) + } + else if (attributeTarget is PropertyDeclarationSyntax propertyDeclarationSyntax) { - if (fieldOrPropertyTypeSyntax is NullableTypeSyntax fieldOrPropertyNullableTypeSyntax) - { - fieldOrPropertyTypeSyntax = fieldOrPropertyNullableTypeSyntax.ElementType; - } + fieldOrPropertyTypeSyntax = propertyDeclarationSyntax.Type; + } + else + { + return; + } - var fieldOrPropertyTypeSymbol = context.SemanticModel.GetTypeInfo(fieldOrPropertyTypeSyntax).Type; - if (fieldOrPropertyTypeSymbol == null || fieldOrPropertyTypeSymbol.TypeKind == TypeKind.Error) - { - return; - } + AnalyzeFieldOrPropertyTypeSyntax(context, fieldOrPropertyTypeSyntax); + } - if (fieldOrPropertyTypeSymbol.TypeKind == TypeKind.Enum) - { - var flagsAttributeTypeSymbol = context.Compilation.GetTypeByMetadataName("System.FlagsAttribute"); - if (flagsAttributeTypeSymbol == null) - { - return; - } + private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, TypeSyntax fieldOrPropertyTypeSyntax) + { + if (fieldOrPropertyTypeSyntax is NullableTypeSyntax fieldOrPropertyNullableTypeSyntax) + { + fieldOrPropertyTypeSyntax = fieldOrPropertyNullableTypeSyntax.ElementType; + } - if (fieldOrPropertyTypeSymbol.GetAttributes().Any(ad => ad.AttributeClass != null && ad.AttributeClass.Equals(flagsAttributeTypeSymbol, SymbolEqualityComparer.Default))) - { - context.ReportDiagnostic(Diagnostic.Create(NotAllowedOnFlagsEnumPropertyOrFieldTypeRule, fieldOrPropertyTypeSyntax.GetLocation(), fieldOrPropertyTypeSymbol.ToString())); - } + 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.SpecialType != SpecialType.System_Boolean) + if (fieldOrPropertyTypeSymbol.GetAttributes().Any(ad => ad.AttributeClass != null && ad.AttributeClass.Equals(flagsAttributeTypeSymbol, SymbolEqualityComparer.Default))) { - context.ReportDiagnostic(Diagnostic.Create(PropertyOrFieldTypeMustBeEnumOrBoolRule, fieldOrPropertyTypeSyntax.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(NotAllowedOnFlagsEnumPropertyOrFieldTypeRule, fieldOrPropertyTypeSyntax.GetLocation(), fieldOrPropertyTypeSymbol.ToString())); } + + return; } - private static INamedTypeSymbol? GetParamsAllValuesAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAllValuesAttribute"); + if (fieldOrPropertyTypeSymbol.SpecialType != SpecialType.System_Boolean) + { + context.ReportDiagnostic(Diagnostic.Create(PropertyOrFieldTypeMustBeEnumOrBoolRule, fieldOrPropertyTypeSyntax.GetLocation())); + } } -} + + private static INamedTypeSymbol? GetParamsAllValuesAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAllValuesAttribute"); +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs index 906115ccbd..a3b7ab6940 100644 --- a/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/Attributes/ParamsAttributeAnalyzer.cs @@ -1,292 +1,273 @@ -namespace BenchmarkDotNet.Analyzers.Attributes -{ - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.CSharp.Syntax; - using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; +using System.Linq; - using System.Collections.Immutable; - using System.Linq; +namespace BenchmarkDotNet.Analyzers.Attributes; - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ParamsAttributeAnalyzer : DiagnosticAnalyzer +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class ParamsAttributeAnalyzer : DiagnosticAnalyzer +{ + internal static readonly DiagnosticDescriptor MustHaveValuesRule = new( + 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 MustHaveMatchingValueTypeRule = new( + 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( + DiagnosticIds.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_MessageFormat)), + "Usage", + DiagnosticSeverity.Info, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => + [ + MustHaveValuesRule, + MustHaveMatchingValueTypeRule, + UnnecessarySingleValuePassedToAttributeRule + ]; + + public override void Initialize(AnalysisContext analysisContext) { - 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 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)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.Attributes_ParamsAttribute_UnnecessarySingleValuePassedToAttribute_MessageFormat)), - "Usage", - DiagnosticSeverity.Info, - isEnabledByDefault: true); - - public override ImmutableArray SupportedDiagnostics => - [ - MustHaveValuesRule, - MustHaveMatchingValueTypeRule, - UnnecessarySingleValuePassedToAttributeRule - ]; - - public override void Initialize(AnalysisContext analysisContext) - { - analysisContext.EnableConcurrentExecution(); - analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - - analysisContext.RegisterCompilationStartAction(ctx => + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet.Annotations is referenced + if (GetParamsAttributeTypeSymbol(ctx.Compilation) == null) { - // Only run if BenchmarkDotNet.Annotations is referenced - if (GetParamsAttributeTypeSymbol(ctx.Compilation) == null) - { - return; - } + return; + } + + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); + }); + } - ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.Attribute); - }); + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (context.Node is not AttributeSyntax attributeSyntax) + { + return; } - private static void Analyze(SyntaxNodeAnalysisContext context) + var paramsAttributeTypeSymbol = GetParamsAttributeTypeSymbol(context.Compilation); + + var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null || !attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default)) { - if (context.Node is not AttributeSyntax attributeSyntax) - { - return; - } + return; + } - var paramsAttributeTypeSymbol = GetParamsAttributeTypeSymbol(context.Compilation); + var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); + if (attributeTarget == null) + { + return; + } - var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeSyntaxTypeSymbol == null || !attributeSyntaxTypeSymbol.Equals(paramsAttributeTypeSymbol, SymbolEqualityComparer.Default)) - { - return; - } + TypeSyntax fieldOrPropertyTypeSyntax; - var attributeTarget = attributeSyntax.FirstAncestorOrSelf(n => n is FieldDeclarationSyntax or PropertyDeclarationSyntax); - if (attributeTarget == null) - { - return; - } + if (attributeTarget is FieldDeclarationSyntax fieldDeclarationSyntax) + { + fieldOrPropertyTypeSyntax = fieldDeclarationSyntax.Declaration.Type; - TypeSyntax fieldOrPropertyTypeSyntax; + } + else if (attributeTarget is PropertyDeclarationSyntax propertyDeclarationSyntax) + { + fieldOrPropertyTypeSyntax = propertyDeclarationSyntax.Type; + } + else + { + return; + } - if (attributeTarget is FieldDeclarationSyntax fieldDeclarationSyntax) - { - fieldOrPropertyTypeSyntax = fieldDeclarationSyntax.Declaration.Type; + AnalyzeFieldOrPropertyTypeSyntax(context, fieldOrPropertyTypeSyntax, attributeSyntax); + } - } - else if (attributeTarget is PropertyDeclarationSyntax propertyDeclarationSyntax) - { - fieldOrPropertyTypeSyntax = propertyDeclarationSyntax.Type; - } - else - { - return; - } + private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, TypeSyntax fieldOrPropertyTypeSyntax, AttributeSyntax attributeSyntax) + { + if (attributeSyntax.ArgumentList == null) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, attributeSyntax.GetLocation())); - AnalyzeFieldOrPropertyTypeSyntax(context, - fieldOrPropertyTypeSyntax, - attributeSyntax); + return; } - private static void AnalyzeFieldOrPropertyTypeSyntax(SyntaxNodeAnalysisContext context, - TypeSyntax fieldOrPropertyTypeSyntax, - AttributeSyntax attributeSyntax) + if (!attributeSyntax.ArgumentList.Arguments.Any()) { - if (attributeSyntax.ArgumentList == null) - { - context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, - attributeSyntax.GetLocation())); + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, attributeSyntax.ArgumentList.GetLocation())); - return; - } + return; + } - if (!attributeSyntax.ArgumentList.Arguments.Any()) - { - context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, - attributeSyntax.ArgumentList.GetLocation())); + if (attributeSyntax.ArgumentList.Arguments.All(aas => aas.NameEquals != null)) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, Location.Create(context.FilterTree, attributeSyntax.ArgumentList.Arguments.Span))); - return; - } + return; + } - if (attributeSyntax.ArgumentList.Arguments.All(aas => aas.NameEquals != null)) - { - context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, - Location.Create(context.FilterTree, attributeSyntax.ArgumentList.Arguments.Span))); + var expectedValueTypeSymbol = context.SemanticModel.GetTypeInfo(fieldOrPropertyTypeSyntax).Type; + if (expectedValueTypeSymbol == null || expectedValueTypeSymbol.TypeKind == TypeKind.Error) + { + return; + } - 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; + } - // Check if this is an explicit params array creation + // Collection expression - var attributeArgumentSyntax = attributeSyntax.ArgumentList.Arguments.First(); - if (attributeArgumentSyntax.NameEquals != null) + if (attributeArgumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax) + { + if (!collectionExpressionSyntax.Elements.Any()) { - // Ignore named arguments, e.g. Priority + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, collectionExpressionSyntax.GetLocation())); return; } - // Collection expression - - if (attributeArgumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax) + if (collectionExpressionSyntax.Elements.Count == 1) { - 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())); - } + context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule, collectionExpressionSyntax.Elements[0].GetLocation())); + } - foreach (var collectionElementSyntax in collectionExpressionSyntax.Elements) + foreach (var collectionElementSyntax in collectionExpressionSyntax.Elements) + { + if (collectionElementSyntax is ExpressionElementSyntax expressionElementSyntax) { - if (collectionElementSyntax is ExpressionElementSyntax expressionElementSyntax) - { - ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(expressionElementSyntax.Expression); - } + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(expressionElementSyntax.Expression); } - - return; } - // Array creation expression + return; + } - var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeArgumentSyntax.Expression).Type; - if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol && arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_Object) + // Array creation expression + + var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeArgumentSyntax.Expression).Type; + if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol && 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 0) - { - context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, - arrayCreationExpressionSyntax.GetLocation())); - } + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, arrayCreationExpressionSyntax.GetLocation())); } - - return; } - if (!arrayCreationExpressionSyntax.Initializer.Expressions.Any()) - { - context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, - arrayCreationExpressionSyntax.Initializer.GetLocation())); + return; + } - return; - } + if (!arrayCreationExpressionSyntax.Initializer.Expressions.Any()) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveValuesRule, arrayCreationExpressionSyntax.Initializer.GetLocation())); - if (arrayCreationExpressionSyntax.Initializer.Expressions.Count == 1) - { - context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule, - arrayCreationExpressionSyntax.Initializer.Expressions[0].GetLocation())); - } + return; + } - foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions) - { - ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(expressionSyntax); - } + if (arrayCreationExpressionSyntax.Initializer.Expressions.Count == 1) + { + context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule, arrayCreationExpressionSyntax.Initializer.Expressions[0].GetLocation())); } - return; + foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions) + { + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(expressionSyntax); + } } + return; + } - // Params values - if (attributeSyntax.ArgumentList.Arguments.Count(aas => aas.NameEquals == null) == 1) - { - context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule, - attributeArgumentSyntax.Expression.GetLocation())); - } + // Params values - foreach (var parameterValueAttributeArgumentSyntax in attributeSyntax.ArgumentList.Arguments) - { - if (parameterValueAttributeArgumentSyntax.NameEquals != null) - { - // Ignore named arguments, e.g. Priority - continue; - } + if (attributeSyntax.ArgumentList.Arguments.Count(aas => aas.NameEquals == null) == 1) + { + context.ReportDiagnostic(Diagnostic.Create(UnnecessarySingleValuePassedToAttributeRule, attributeArgumentSyntax.Expression.GetLocation())); + } - ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(parameterValueAttributeArgumentSyntax.Expression); + foreach (var parameterValueAttributeArgumentSyntax in attributeSyntax.ArgumentList.Arguments) + { + if (parameterValueAttributeArgumentSyntax.NameEquals != null) + { + // Ignore named arguments, e.g. Priority + continue; } - return; + ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(parameterValueAttributeArgumentSyntax.Expression); + } - void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(ExpressionSyntax valueExpressionSyntax) - { - var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax); + void ReportIfNotImplicitlyConvertibleValueTypeDiagnostic(ExpressionSyntax valueExpressionSyntax) + { + var constantValue = context.SemanticModel.GetConstantValue(valueExpressionSyntax); - var valueExpressionString = valueExpressionSyntax.ToString(); + var valueExpressionString = valueExpressionSyntax.ToString(); - var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type; - if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error) + var actualValueTypeSymbol = context.SemanticModel.GetTypeInfo(valueExpressionSyntax).Type; + if (actualValueTypeSymbol != null && actualValueTypeSymbol.TypeKind != TypeKind.Error) + { + if (!AnalyzerHelper.IsAssignableToField(context.Compilation, expectedValueTypeSymbol, valueExpressionString, constantValue, actualValueTypeSymbol.ToString())) { - if (!AnalyzerHelper.IsAssignableToField(context.Compilation, - expectedValueTypeSymbol, - valueExpressionString, - constantValue, - actualValueTypeSymbol.ToString())) - { - ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionString, - fieldOrPropertyTypeSyntax.ToString(), - actualValueTypeSymbol.ToString()); - } + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic( + valueExpressionSyntax.GetLocation(), + valueExpressionString, + fieldOrPropertyTypeSyntax.ToString(), + actualValueTypeSymbol.ToString() + ); } - else + } + else if (constantValue is { HasValue: true, Value: null }) + { + if (!AnalyzerHelper.IsAssignableToField(context.Compilation, expectedValueTypeSymbol, valueExpressionString, constantValue, null)) { - if (constantValue is { HasValue: true, Value: null }) - { - if (!AnalyzerHelper.IsAssignableToField(context.Compilation, expectedValueTypeSymbol, valueExpressionString, constantValue, null)) - { - ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(valueExpressionSyntax.GetLocation(), - valueExpressionString, - fieldOrPropertyTypeSyntax.ToString(), - "null"); - } - } + ReportValueTypeMustBeImplicitlyConvertibleDiagnostic( + valueExpressionSyntax.GetLocation(), + valueExpressionString, + fieldOrPropertyTypeSyntax.ToString(), + "null" + ); } + } - return; - - void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType) - { - context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueTypeRule, - diagnosticLocation, - value, - expectedType, - actualType)); - } + void ReportValueTypeMustBeImplicitlyConvertibleDiagnostic(Location diagnosticLocation, string value, string expectedType, string actualType) + { + context.ReportDiagnostic(Diagnostic.Create(MustHaveMatchingValueTypeRule, + diagnosticLocation, + value, + expectedType, + actualType) + ); } } - - private static INamedTypeSymbol? GetParamsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute"); } -} + + private static INamedTypeSymbol? GetParamsAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.ParamsAttribute"); +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs index 590693ed53..104ff758af 100644 --- a/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/BenchmarkRunner/RunAnalyzer.cs @@ -1,221 +1,223 @@ -namespace BenchmarkDotNet.Analyzers.BenchmarkRunner -{ - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.CSharp.Syntax; - using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System.Collections.Immutable; - using System.Collections.Immutable; +namespace BenchmarkDotNet.Analyzers.BenchmarkRunner; - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class RunAnalyzer : DiagnosticAnalyzer +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class RunAnalyzer : DiagnosticAnalyzer +{ + internal static readonly DiagnosticDescriptor TypeArgumentClassMissingBenchmarkMethodsRule = new( + 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))); + + internal static readonly DiagnosticDescriptor TypeArgumentClassMustBePublicRule = new( + 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 TypeArgumentClassMustBeUnsealedRule = new( + 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( + 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))); + + internal static readonly DiagnosticDescriptor GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule = new( + 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, + GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule, + ]; + + public override void Initialize(AnalysisContext analysisContext) { - 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))); - - 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 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)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true, - description: AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.BenchmarkRunner_Run_TypeArgumentClassMustBeNonAbstract_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, - GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule, - ]; - - 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); - }); - } + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - private static void Analyze(SyntaxNodeAnalysisContext context) + analysisContext.RegisterCompilationStartAction(ctx => { - if (context.Node is not InvocationExpressionSyntax invocationExpression) + // Only run if BenchmarkDotNet is referenced + var benchmarkRunnerTypeSymbol = ctx.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"); + if (benchmarkRunnerTypeSymbol == null) { return; } - if (invocationExpression.Expression is not MemberAccessExpressionSyntax memberAccessExpression) - { - return; - } + ctx.RegisterSyntaxNodeAction(Analyze, SyntaxKind.InvocationExpression); + }); + } - if (memberAccessExpression.Expression is not IdentifierNameSyntax identifierNameSyntax) - { - return; - } + private static void Analyze(SyntaxNodeAnalysisContext context) + { + if (context.Node is not InvocationExpressionSyntax invocationExpression) + { + return; + } - var benchmarkRunnerTypeSymbol = context.Compilation.GetTypeByMetadataName("BenchmarkDotNet.Running.BenchmarkRunner"); - if ( benchmarkRunnerTypeSymbol == null - || benchmarkRunnerTypeSymbol.TypeKind == TypeKind.Error) - { - return; - } + if (invocationExpression.Expression is not MemberAccessExpressionSyntax memberAccessExpression) + { + return; + } - var classMemberAccessTypeSymbol = context.SemanticModel.GetTypeInfo(identifierNameSyntax).Type; - if ( classMemberAccessTypeSymbol is null - || classMemberAccessTypeSymbol.TypeKind == TypeKind.Error - || !classMemberAccessTypeSymbol.Equals(benchmarkRunnerTypeSymbol, SymbolEqualityComparer.Default)) + if (memberAccessExpression.Expression is not IdentifierNameSyntax identifierNameSyntax) + { + 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(benchmarkRunnerTypeSymbol, SymbolEqualityComparer.Default)) + { + return; + } + + if (memberAccessExpression.Name.Identifier.ValueText != "Run") + { + return; + } + + INamedTypeSymbol? benchmarkClassTypeSymbol; + Location? diagnosticLocation; + + if (memberAccessExpression.Name is GenericNameSyntax genericMethod) + { + if (genericMethod.TypeArgumentList.Arguments.Count != 1) { return; } - if (memberAccessExpression.Name.Identifier.ValueText != "Run") + diagnosticLocation = Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span); + benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(genericMethod.TypeArgumentList.Arguments[0]).Type as INamedTypeSymbol; + } + else + { + if (invocationExpression.ArgumentList.Arguments.Count == 0) { return; } - INamedTypeSymbol? benchmarkClassTypeSymbol; - Location? diagnosticLocation; - - if (memberAccessExpression.Name is GenericNameSyntax genericMethod) + // TODO: Support analyzing a collection of typeof() expressions + if (invocationExpression.ArgumentList.Arguments[0].Expression is not TypeOfExpressionSyntax typeOfExpression) { - if (genericMethod.TypeArgumentList.Arguments.Count != 1) - { - return; - } - - diagnosticLocation = Location.Create(context.FilterTree, genericMethod.TypeArgumentList.Arguments.Span); - benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(genericMethod.TypeArgumentList.Arguments[0]).Type as INamedTypeSymbol; + return; } - else - { - if (invocationExpression.ArgumentList.Arguments.Count == 0) - { - return; - } - // TODO: Support analyzing a collection 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; - diagnosticLocation = typeOfExpression.Type.GetLocation(); - benchmarkClassTypeSymbol = context.SemanticModel.GetTypeInfo(typeOfExpression.Type).Type as INamedTypeSymbol; + } - } + if (benchmarkClassTypeSymbol == null || benchmarkClassTypeSymbol.TypeKind == TypeKind.Error || (benchmarkClassTypeSymbol.IsGenericType && !benchmarkClassTypeSymbol.IsUnboundGenericType)) + { + return; + } - if (benchmarkClassTypeSymbol == null || benchmarkClassTypeSymbol.TypeKind == TypeKind.Error || (benchmarkClassTypeSymbol.IsGenericType && !benchmarkClassTypeSymbol.IsUnboundGenericType)) - { - return; - } + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } - var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); - if (benchmarkAttributeTypeSymbol == null) - { - return; - } + if (!HasBenchmarkAttribute()) + { + ReportDiagnostic(TypeArgumentClassMissingBenchmarkMethodsRule); + } - if (!HasBenchmarkAttribute()) - { - ReportDiagnostic(TypeArgumentClassMissingBenchmarkMethodsRule); - } + if (benchmarkClassTypeSymbol.DeclaredAccessibility != Accessibility.Public) + { + ReportDiagnostic(TypeArgumentClassMustBePublicRule); + } - if (benchmarkClassTypeSymbol.DeclaredAccessibility != Accessibility.Public) - { - ReportDiagnostic(TypeArgumentClassMustBePublicRule); - } + if (benchmarkClassTypeSymbol.IsAbstract) + { + ReportDiagnostic(TypeArgumentClassMustBeNonAbstractRule); + } - if (benchmarkClassTypeSymbol.IsAbstract) - { - ReportDiagnostic(TypeArgumentClassMustBeNonAbstractRule); - } + if (benchmarkClassTypeSymbol.IsSealed) + { + ReportDiagnostic(TypeArgumentClassMustBeUnsealedRule); + } - if (benchmarkClassTypeSymbol.IsSealed) - { - ReportDiagnostic(TypeArgumentClassMustBeUnsealedRule); - } + if (benchmarkClassTypeSymbol.IsUnboundGenericType + && !AnalyzerHelper.AttributeListContainsAttribute("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, benchmarkClassTypeSymbol.GetAttributes())) + { + ReportDiagnostic(GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule); + } - if (benchmarkClassTypeSymbol.IsUnboundGenericType && !AnalyzerHelper.AttributeListContainsAttribute("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, benchmarkClassTypeSymbol.GetAttributes())) - { - ReportDiagnostic(GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttributeRule); - } + return; - return; + bool HasBenchmarkAttribute() + { + var baseType = benchmarkClassTypeSymbol; - bool HasBenchmarkAttribute() + while (baseType != null && baseType.SpecialType != SpecialType.System_Object) { - var baseType = benchmarkClassTypeSymbol; - - while (baseType != null && baseType.SpecialType != SpecialType.System_Object) + foreach (var member in baseType.GetMembers()) { - foreach (var member in baseType.GetMembers()) + if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary }) { - if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary }) + foreach (var attributeData in member.GetAttributes()) { - foreach (var attributeData in member.GetAttributes()) + if (attributeData.AttributeClass != null) { - if (attributeData.AttributeClass != null) + if (attributeData.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) { - if (attributeData.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) - { - return true; - } + return true; } } } } - - baseType = baseType.OriginalDefinition.BaseType; } - return false; + baseType = baseType.OriginalDefinition.BaseType; } - void ReportDiagnostic(DiagnosticDescriptor diagnosticDescriptor) - { - context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, diagnosticLocation, AnalyzerHelper.NormalizeTypeName(benchmarkClassTypeSymbol))); - } + return false; } + + void ReportDiagnostic(DiagnosticDescriptor diagnosticDescriptor) + => context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, diagnosticLocation, AnalyzerHelper.NormalizeTypeName(benchmarkClassTypeSymbol))); } -} +} \ No newline at end of file diff --git a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs index eb4dcaf1ef..a512d637c2 100644 --- a/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs +++ b/src/BenchmarkDotNet.Analyzers/DiagnosticIds.cs @@ -1,37 +1,36 @@ -namespace BenchmarkDotNet.Analyzers +namespace BenchmarkDotNet.Analyzers; + +public static class DiagnosticIds { - public static class DiagnosticIds - { - public const string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods = "BDN1000"; - public const string BenchmarkRunner_Run_TypeArgumentClassMustBePublic = "BDN1001"; - 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_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_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"; - 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_MustHaveMatchingValueType = "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_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters = "BDN1400"; - public const string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute = "BDN1500"; - public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1501"; - public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1502"; - } + public const string BenchmarkRunner_Run_TypeArgumentClassMissingBenchmarkMethods = "BDN1000"; + public const string BenchmarkRunner_Run_TypeArgumentClassMustBePublic = "BDN1001"; + 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_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_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"; + 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_MustHaveMatchingValueType = "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_GeneralArgumentAttributes_MethodWithoutAttributeMustHaveNoParameters = "BDN1400"; + public const string Attributes_ArgumentsAttribute_RequiresBenchmarkAttribute = "BDN1500"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueCount = "BDN1501"; + public const string Attributes_ArgumentsAttribute_MustHaveMatchingValueType = "BDN1502"; } diff --git a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs index 1b02657456..576664fc1d 100644 --- a/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs +++ b/src/BenchmarkDotNet.Analyzers/General/BenchmarkClassAnalyzer.cs @@ -1,385 +1,393 @@ -namespace BenchmarkDotNet.Analyzers.General +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; + +namespace BenchmarkDotNet.Analyzers.General; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public class BenchmarkClassAnalyzer : DiagnosticAnalyzer { - using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp; - using Microsoft.CodeAnalysis.CSharp.Syntax; - using Microsoft.CodeAnalysis.Diagnostics; - - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class BenchmarkClassAnalyzer : DiagnosticAnalyzer + internal static readonly DiagnosticDescriptor ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule = new( + 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 ClassWithGenericTypeArgumentsAttributeMustBeGenericRule = new( + 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( + 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 MethodMustBePublicRule = new( + 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( + 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( + 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 SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule = new( + 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( + 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); + + internal static readonly DiagnosticDescriptor OnlyOneMethodCanBeBaselinePerCategoryRule = new( + DiagnosticIds.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory, + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_Title)), + AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_MessageFormat)), + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public override ImmutableArray SupportedDiagnostics => + [ + ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, + ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, + GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, + MethodMustBePublicRule, + MethodMustBeNonGenericRule, + ClassMustBeNonStaticRule, + SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule, + OnlyOneMethodCanBeBaselineRule, + OnlyOneMethodCanBeBaselinePerCategoryRule + ]; + + public override void Initialize(AnalysisContext analysisContext) { - 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 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)), - 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 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 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)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaseline_MessageFormat)), - "Usage", - DiagnosticSeverity.Error, - isEnabledByDefault: true); - - internal static readonly DiagnosticDescriptor OnlyOneMethodCanBeBaselinePerCategoryRule = new DiagnosticDescriptor(DiagnosticIds.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory, - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_Title)), - AnalyzerHelper.GetResourceString(nameof(BenchmarkDotNetAnalyzerResources.General_BenchmarkClass_OnlyOneMethodCanBeBaselinePerCategory_MessageFormat)), - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public override ImmutableArray SupportedDiagnostics => - [ - ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, - ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, - GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule, - MethodMustBePublicRule, - MethodMustBeNonGenericRule, - ClassMustBeNonStaticRule, - SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule, - OnlyOneMethodCanBeBaselineRule, - OnlyOneMethodCanBeBaselinePerCategoryRule - ]; - - public override void Initialize(AnalysisContext analysisContext) - { - analysisContext.EnableConcurrentExecution(); - analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - + analysisContext.EnableConcurrentExecution(); + analysisContext.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - analysisContext.RegisterCompilationStartAction(ctx => + analysisContext.RegisterCompilationStartAction(ctx => + { + // Only run if BenchmarkDotNet.Annotations is referenced + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); + if (benchmarkAttributeTypeSymbol == null) { - // Only run if BenchmarkDotNet.Annotations is referenced - var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(ctx.Compilation); - if (benchmarkAttributeTypeSymbol == null) - { - return; - } + return; + } + + ctx.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration); + ctx.RegisterSyntaxNodeAction(AnalyzeAttributeSyntax, SyntaxKind.Attribute); + }); + } - ctx.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration); - ctx.RegisterSyntaxNodeAction(AnalyzeAttributeSyntax, SyntaxKind.Attribute); - }); + private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) + { + if (context.Node is not ClassDeclarationSyntax classDeclarationSyntax) + { + return; } - private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) + var classStaticModifier = null as SyntaxToken?; + var classAbstractModifier = null as SyntaxToken?; + + foreach (var modifier in classDeclarationSyntax.Modifiers) { - if (context.Node is not ClassDeclarationSyntax classDeclarationSyntax) + if (modifier.IsKind(SyntaxKind.StaticKeyword)) { - return; + classStaticModifier = modifier; } + else if (modifier.IsKind(SyntaxKind.AbstractKeyword)) + { + classAbstractModifier = modifier; + } + } - var classStaticModifier = null as SyntaxToken?; - var classAbstractModifier = null as SyntaxToken?; - - foreach (var modifier in classDeclarationSyntax.Modifiers) + var genericTypeArgumentsAttributes = AnalyzerHelper.GetAttributes("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, classDeclarationSyntax.AttributeLists, context.SemanticModel); + if (genericTypeArgumentsAttributes.Length > 0) + { + foreach (var genericTypeArgumentsAttribute in genericTypeArgumentsAttributes) { - if (modifier.IsKind(SyntaxKind.StaticKeyword)) + if (classAbstractModifier.HasValue) { - classStaticModifier = modifier; + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, genericTypeArgumentsAttribute.GetLocation())); } - else if (modifier.IsKind(SyntaxKind.AbstractKeyword)) + + if (classDeclarationSyntax.TypeParameterList == null || classDeclarationSyntax.TypeParameterList.Parameters.Count == 0) { - classAbstractModifier = modifier; + context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, genericTypeArgumentsAttribute.GetLocation())); } - } - - var genericTypeArgumentsAttributes = AnalyzerHelper.GetAttributes("BenchmarkDotNet.Attributes.GenericTypeArgumentsAttribute", context.Compilation, classDeclarationSyntax.AttributeLists, context.SemanticModel); - if (genericTypeArgumentsAttributes.Length > 0 ) - { - foreach (var genericTypeArgumentsAttribute in genericTypeArgumentsAttributes) + else if (genericTypeArgumentsAttribute.ArgumentList is { Arguments.Count: > 0 }) { - if (classAbstractModifier.HasValue) + if (genericTypeArgumentsAttribute.ArgumentList.Arguments.Count != classDeclarationSyntax.TypeParameterList.Parameters.Count) { - context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule, genericTypeArgumentsAttribute.GetLocation())); - } - - if (classDeclarationSyntax.TypeParameterList == null || classDeclarationSyntax.TypeParameterList.Parameters.Count == 0) - { - context.ReportDiagnostic(Diagnostic.Create(ClassWithGenericTypeArgumentsAttributeMustBeGenericRule, genericTypeArgumentsAttribute.GetLocation())); - } - else 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)); - } + 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 benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); - if (benchmarkAttributeTypeSymbol == null) - { - return; - } + var benchmarkAttributeTypeSymbol = AnalyzerHelper.GetBenchmarkAttributeTypeSymbol(context.Compilation); + if (benchmarkAttributeTypeSymbol == null) + { + return; + } - var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryAttributeTypeSymbol(context.Compilation); - if (benchmarkCategoryAttributeTypeSymbol == null) - { - return; - } + var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryAttributeTypeSymbol(context.Compilation); + if (benchmarkCategoryAttributeTypeSymbol == null) + { + return; + } - var hasBenchmarkMethods = false; - var nullBenchmarkCategoryBenchmarkAttributeBaselineLocations = new List(); - var benchmarkCategoryBenchmarkAttributeBaselineLocations = new Dictionary>(); + var hasBenchmarkMethods = false; + var nullBenchmarkCategoryBenchmarkAttributeBaselineLocations = new List(); + var benchmarkCategoryBenchmarkAttributeBaselineLocations = new Dictionary>(); - foreach (var memberDeclarationSyntax in classDeclarationSyntax.Members) - { - var hasBenchmarkCategoryCompilerDiagnostics = false; - var benchmarkCategories = new List(); - var benchmarkAttributeUsages = new List(); + foreach (var memberDeclarationSyntax in classDeclarationSyntax.Members) + { + var hasBenchmarkCategoryCompilerDiagnostics = false; + var benchmarkCategories = new List(); + var benchmarkAttributeUsages = new List(); - if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax) + if (memberDeclarationSyntax is MethodDeclarationSyntax methodDeclarationSyntax) + { + foreach (var attributeListSyntax in methodDeclarationSyntax.AttributeLists) { - foreach (var attributeListSyntax in methodDeclarationSyntax.AttributeLists) + foreach (var attributeSyntax in attributeListSyntax.Attributes) { - foreach (var attributeSyntax in attributeListSyntax.Attributes) + var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeSyntaxTypeSymbol == null) { - var attributeSyntaxTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeSyntaxTypeSymbol == null) - { - continue; - } + continue; + } - if (attributeSyntaxTypeSymbol.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) - { - benchmarkAttributeUsages.Add(attributeSyntax); - } - else if (attributeSyntaxTypeSymbol.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + if (attributeSyntaxTypeSymbol.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + benchmarkAttributeUsages.Add(attributeSyntax); + } + else if (attributeSyntaxTypeSymbol.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) { - if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) - { - // Check if this is an explicit params array creation + // Check if this is an explicit params array creation - Optional constantValue; + Optional constantValue; - // Collection expression + // Collection expression - if (attributeSyntax.ArgumentList.Arguments[0].Expression is CollectionExpressionSyntax collectionExpressionSyntax) + if (attributeSyntax.ArgumentList.Arguments[0].Expression is CollectionExpressionSyntax collectionExpressionSyntax) + { + foreach (var collectionElementSyntax in collectionExpressionSyntax.Elements) { - foreach (var collectionElementSyntax in collectionExpressionSyntax.Elements) + if (collectionElementSyntax is ExpressionElementSyntax expressionElementSyntax) { - if (collectionElementSyntax is ExpressionElementSyntax expressionElementSyntax) + constantValue = context.SemanticModel.GetConstantValue(expressionElementSyntax.Expression); + if (constantValue.HasValue) { - constantValue = context.SemanticModel.GetConstantValue(expressionElementSyntax.Expression); - if (constantValue.HasValue) + if (constantValue.Value is string benchmarkCategoryValue) { - if (constantValue.Value is string benchmarkCategoryValue) - { - benchmarkCategories.Add(benchmarkCategoryValue); - } - else if (constantValue.Value is null) - { - benchmarkCategories.Add(null); - } + benchmarkCategories.Add(benchmarkCategoryValue); } - else + else if (constantValue.Value is null) { - hasBenchmarkCategoryCompilerDiagnostics = true; - - break; + benchmarkCategories.Add(null); } } - } + else + { + hasBenchmarkCategoryCompilerDiagnostics = true; - continue; + break; + } + } } - // Array creation expression + continue; + } + + // Array creation expression - var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeSyntax.ArgumentList.Arguments[0].Expression).Type; - if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol) + var attributeArgumentSyntaxValueType = context.SemanticModel.GetTypeInfo(attributeSyntax.ArgumentList.Arguments[0].Expression).Type; + if (attributeArgumentSyntaxValueType is IArrayTypeSymbol arrayTypeSymbol) + { + if (arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_String) { - if (arrayTypeSymbol.ElementType.SpecialType == SpecialType.System_String) + if (attributeSyntax.ArgumentList.Arguments[0].Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax) { - if (attributeSyntax.ArgumentList.Arguments[0].Expression is ArrayCreationExpressionSyntax arrayCreationExpressionSyntax) + if (arrayCreationExpressionSyntax.Initializer != null) { - if (arrayCreationExpressionSyntax.Initializer != null) + foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions) { - foreach (var expressionSyntax in arrayCreationExpressionSyntax.Initializer.Expressions) + constantValue = context.SemanticModel.GetConstantValue(expressionSyntax); + if (constantValue.HasValue) { - constantValue = context.SemanticModel.GetConstantValue(expressionSyntax); - if (constantValue.HasValue) + if (constantValue.Value is string benchmarkCategoryValue) { - if (constantValue.Value is string benchmarkCategoryValue) - { - benchmarkCategories.Add(benchmarkCategoryValue); - } - else if (constantValue.Value is null) - { - benchmarkCategories.Add(null); - } + benchmarkCategories.Add(benchmarkCategoryValue); } - else + else if (constantValue.Value is null) { - hasBenchmarkCategoryCompilerDiagnostics = true; - - break; + benchmarkCategories.Add(null); } } + else + { + hasBenchmarkCategoryCompilerDiagnostics = true; + + break; + } } } } - - continue; } - // Params value + continue; + } - constantValue = context.SemanticModel.GetConstantValue(attributeSyntax.ArgumentList.Arguments[0].Expression); - if (constantValue.HasValue) + // Params value + + constantValue = context.SemanticModel.GetConstantValue(attributeSyntax.ArgumentList.Arguments[0].Expression); + if (constantValue.HasValue) + { + if (constantValue.Value is null) { - if (constantValue.Value is null) - { - hasBenchmarkCategoryCompilerDiagnostics = true; - } - else if (constantValue.Value is string benchmarkCategoryValue) - { - benchmarkCategories.Add(benchmarkCategoryValue); - } + hasBenchmarkCategoryCompilerDiagnostics = true; + } + else if (constantValue.Value is string benchmarkCategoryValue) + { + benchmarkCategories.Add(benchmarkCategoryValue); } } - else if (attributeSyntax.ArgumentList is { Arguments.Count: > 1 }) - { - // Params values + } + else if (attributeSyntax.ArgumentList is { Arguments.Count: > 1 }) + { + // Params values - foreach (var parameterValueAttributeArgumentSyntax in attributeSyntax.ArgumentList.Arguments) + foreach (var parameterValueAttributeArgumentSyntax in attributeSyntax.ArgumentList.Arguments) + { + var constantValue = context.SemanticModel.GetConstantValue(parameterValueAttributeArgumentSyntax.Expression); + if (constantValue.HasValue) { - var constantValue = context.SemanticModel.GetConstantValue(parameterValueAttributeArgumentSyntax.Expression); - if (constantValue.HasValue) + if (constantValue.Value is string benchmarkCategoryValue) { - if (constantValue.Value is string benchmarkCategoryValue) - { - benchmarkCategories.Add(benchmarkCategoryValue); - } - else if (constantValue.Value is null) - { - benchmarkCategories.Add(null); - } + benchmarkCategories.Add(benchmarkCategoryValue); } - else + else if (constantValue.Value is null) { - hasBenchmarkCategoryCompilerDiagnostics = true; - - break; + benchmarkCategories.Add(null); } } + else + { + hasBenchmarkCategoryCompilerDiagnostics = true; + + break; + } } } } } + } - if (benchmarkAttributeUsages.Count == 1) - { - hasBenchmarkMethods = true; + 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.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 (methodDeclarationSyntax.TypeParameterList != null) + { + context.ReportDiagnostic(Diagnostic.Create(MethodMustBeNonGenericRule, methodDeclarationSyntax.TypeParameterList.GetLocation(), methodDeclarationSyntax.Identifier.ToString())); + } - if (!hasBenchmarkCategoryCompilerDiagnostics) + if (!hasBenchmarkCategoryCompilerDiagnostics) + { + if (benchmarkCategories.Count > 0 && benchmarkAttributeUsages[0].ArgumentList != null) { - if (benchmarkCategories.Count > 0 && benchmarkAttributeUsages[0].ArgumentList != null) + foreach (var attributeArgumentSyntax in benchmarkAttributeUsages[0].ArgumentList.Arguments) { - foreach (var attributeArgumentSyntax in benchmarkAttributeUsages[0].ArgumentList.Arguments) + if (attributeArgumentSyntax.NameEquals != null && attributeArgumentSyntax.NameEquals.Name.Identifier.ValueText == "Baseline") { - 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 constantValue = context.SemanticModel.GetConstantValue(attributeArgumentSyntax.Expression); - if (constantValue is { HasValue: true, Value: true }) - { - var benchmarkCategoryFormatted = FormatBenchmarkCategory(benchmarkCategories); - var baselineLocation = attributeArgumentSyntax.GetLocation(); + var benchmarkCategoryFormatted = FormatBenchmarkCategory(benchmarkCategories); + var baselineLocation = attributeArgumentSyntax.GetLocation(); - if (benchmarkCategoryBenchmarkAttributeBaselineLocations.TryGetValue(benchmarkCategoryFormatted, out var baselineLocationsPerUniqueBenchmarkCategory)) - { - baselineLocationsPerUniqueBenchmarkCategory.Add(baselineLocation); - } - else - { - benchmarkCategoryBenchmarkAttributeBaselineLocations[benchmarkCategoryFormatted] = [ baselineLocation ]; - } + if (benchmarkCategoryBenchmarkAttributeBaselineLocations.TryGetValue(benchmarkCategoryFormatted, out var baselineLocationsPerUniqueBenchmarkCategory)) + { + baselineLocationsPerUniqueBenchmarkCategory.Add(baselineLocation); + } + else + { + benchmarkCategoryBenchmarkAttributeBaselineLocations[benchmarkCategoryFormatted] = [baselineLocation]; } } } } - else + } + else + { + if (benchmarkAttributeUsages[0].ArgumentList != null) { - if (benchmarkAttributeUsages[0].ArgumentList != null) + foreach (var attributeArgumentSyntax in benchmarkAttributeUsages[0].ArgumentList.Arguments) { - foreach (var attributeArgumentSyntax in benchmarkAttributeUsages[0].ArgumentList.Arguments) + if (attributeArgumentSyntax.NameEquals != null && attributeArgumentSyntax.NameEquals.Name.Identifier.ValueText == "Baseline") { - 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 constantValue = context.SemanticModel.GetConstantValue(attributeArgumentSyntax.Expression); - if (constantValue is { HasValue: true, Value: true }) - { - nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Add(attributeArgumentSyntax.GetLocation()); - } + nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Add(attributeArgumentSyntax.GetLocation()); } } } @@ -388,180 +396,180 @@ private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) } } } + } - if (hasBenchmarkMethods) + if (hasBenchmarkMethods) + { + if (classStaticModifier.HasValue) { - if (classStaticModifier.HasValue) - { - context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonStaticRule, classStaticModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); - } + context.ReportDiagnostic(Diagnostic.Create(ClassMustBeNonStaticRule, classStaticModifier.Value.GetLocation(), classDeclarationSyntax.Identifier.ToString())); + } - if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count >= 2) + if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count >= 2) + { + foreach (var baselineLocation in nullBenchmarkCategoryBenchmarkAttributeBaselineLocations) { - foreach (var baselineLocation in nullBenchmarkCategoryBenchmarkAttributeBaselineLocations) - { - context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselineRule, baselineLocation)); - } + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselineRule, baselineLocation)); } + } - var singularBenchmarkCategoryBenchmarkAttributeBaselineLocations = new Dictionary>(benchmarkCategoryBenchmarkAttributeBaselineLocations); + var singularBenchmarkCategoryBenchmarkAttributeBaselineLocations = new Dictionary>(benchmarkCategoryBenchmarkAttributeBaselineLocations); - foreach (var (benchmarkCategory, baselineLocations) in benchmarkCategoryBenchmarkAttributeBaselineLocations) + foreach (var (benchmarkCategory, baselineLocations) in benchmarkCategoryBenchmarkAttributeBaselineLocations) + { + if (baselineLocations.Count > 1) { - if (baselineLocations.Count > 1) + foreach (var baselineLocation in baselineLocations) { - foreach (var baselineLocation in baselineLocations) - { - context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselinePerCategoryRule, baselineLocation)); - } - - singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.Remove(benchmarkCategory); + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselinePerCategoryRule, baselineLocation)); } + + singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.Remove(benchmarkCategory); } + } + + if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 || singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count > 0) + { + var hasDuplicateBaselineBenchmarkMethodNullCategories = false; + var duplicateBaselineBenchmarkMethodCategories = new HashSet(); - if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 || singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count > 0) + var benchmarkClassTypeSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax); + if (benchmarkClassTypeSymbol is { TypeKind: TypeKind.Class }) { - var hasDuplicateBaselineBenchmarkMethodNullCategories = false; - var duplicateBaselineBenchmarkMethodCategories = new HashSet(); + var baseType = benchmarkClassTypeSymbol.OriginalDefinition.BaseType; - var benchmarkClassTypeSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclarationSyntax); - if (benchmarkClassTypeSymbol is { TypeKind: TypeKind.Class }) + while (baseType != null && baseType.SpecialType != SpecialType.System_Object) { - var baseType = benchmarkClassTypeSymbol.OriginalDefinition.BaseType; - - while (baseType != null && baseType.SpecialType != SpecialType.System_Object) + foreach (var member in baseType.GetMembers()) { - 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 hasBenchmarkCategoryCompilerDiagnostics = false; - var benchmarkCategories = new List(); - var benchmarkAttributeUsages = new List(); + var methodAttributes = methodSymbol.GetAttributes(); - if (member is IMethodSymbol { MethodKind: MethodKind.Ordinary } methodSymbol) + foreach (var attribute in methodAttributes) { - var methodAttributes = methodSymbol.GetAttributes(); - - foreach (var attribute in methodAttributes) + if (attribute.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) { - if (attribute.AttributeClass.Equals(benchmarkAttributeTypeSymbol, SymbolEqualityComparer.Default)) - { - benchmarkAttributeUsages.Add(attribute); - } - else if (attribute.AttributeClass.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + benchmarkAttributeUsages.Add(attribute); + } + else if (attribute.AttributeClass.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + foreach (var benchmarkCategoriesArray in attribute.ConstructorArguments) { - foreach (var benchmarkCategoriesArray in attribute.ConstructorArguments) + if (!benchmarkCategoriesArray.IsNull) { - if (!benchmarkCategoriesArray.IsNull) + foreach (var benchmarkCategory in benchmarkCategoriesArray.Values) { - foreach (var benchmarkCategory in benchmarkCategoriesArray.Values) + if (benchmarkCategory.Kind == TypedConstantKind.Primitive) { - if (benchmarkCategory.Kind == TypedConstantKind.Primitive) + if (benchmarkCategory.Value == null) { - if (benchmarkCategory.Value == null) - { - benchmarkCategories.Add(null); - } - else if (benchmarkCategory.Value is string benchmarkCategoryValue) - { - benchmarkCategories.Add(benchmarkCategoryValue); - } + benchmarkCategories.Add(null); } - else + else if (benchmarkCategory.Value is string benchmarkCategoryValue) { - hasBenchmarkCategoryCompilerDiagnostics = true; - - break; + benchmarkCategories.Add(benchmarkCategoryValue); } } + else + { + hasBenchmarkCategoryCompilerDiagnostics = true; + + break; + } } } } } } + } - if (benchmarkAttributeUsages.Count == 1) + if (benchmarkAttributeUsages.Count == 1) + { + if (!hasBenchmarkCategoryCompilerDiagnostics) { - if (!hasBenchmarkCategoryCompilerDiagnostics) + if (benchmarkCategories.Count > 0) { - if (benchmarkCategories.Count > 0) + var benchmarkCategoryFormatted = FormatBenchmarkCategory(benchmarkCategories); + if (singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.ContainsKey(benchmarkCategoryFormatted)) { - var benchmarkCategoryFormatted = FormatBenchmarkCategory(benchmarkCategories); - if (singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.ContainsKey(benchmarkCategoryFormatted)) + if (!duplicateBaselineBenchmarkMethodCategories.Contains(benchmarkCategoryFormatted)) { - if (!duplicateBaselineBenchmarkMethodCategories.Contains(benchmarkCategoryFormatted)) + if (benchmarkAttributeUsages[0].NamedArguments.Any(na => na is { Key: "Baseline", Value.Value: true })) { - if (benchmarkAttributeUsages[0].NamedArguments.Any(na => na is { Key: "Baseline", Value.Value: true })) - { - duplicateBaselineBenchmarkMethodCategories.Add(benchmarkCategoryFormatted); - } + duplicateBaselineBenchmarkMethodCategories.Add(benchmarkCategoryFormatted); } } } - else + } + else + { + if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 + && !hasDuplicateBaselineBenchmarkMethodNullCategories + && benchmarkAttributeUsages[0].NamedArguments.Any(na => na is { Key: "Baseline", Value.Value: true })) { - if ( nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 - && !hasDuplicateBaselineBenchmarkMethodNullCategories - && benchmarkAttributeUsages[0].NamedArguments.Any(na => na is { Key: "Baseline", Value.Value: true })) - { - hasDuplicateBaselineBenchmarkMethodNullCategories = true; - } + hasDuplicateBaselineBenchmarkMethodNullCategories = true; } } } } - - baseType = baseType.OriginalDefinition.BaseType; } - } - if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 && hasDuplicateBaselineBenchmarkMethodNullCategories) - { - context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselineRule, nullBenchmarkCategoryBenchmarkAttributeBaselineLocations[0])); + baseType = baseType.OriginalDefinition.BaseType; } + } + + if (nullBenchmarkCategoryBenchmarkAttributeBaselineLocations.Count == 1 && hasDuplicateBaselineBenchmarkMethodNullCategories) + { + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselineRule, nullBenchmarkCategoryBenchmarkAttributeBaselineLocations[0])); + } - foreach (var duplicateBaselineBenchmarkMethodCategory in duplicateBaselineBenchmarkMethodCategories) + foreach (var duplicateBaselineBenchmarkMethodCategory in duplicateBaselineBenchmarkMethodCategories) + { + if (singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.TryGetValue(duplicateBaselineBenchmarkMethodCategory, out var baselineLocations)) { - if (singularBenchmarkCategoryBenchmarkAttributeBaselineLocations.TryGetValue(duplicateBaselineBenchmarkMethodCategory, out var baselineLocations)) - { - context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselinePerCategoryRule, baselineLocations[0])); - } + context.ReportDiagnostic(Diagnostic.Create(OnlyOneMethodCanBeBaselinePerCategoryRule, baselineLocations[0])); } } } } + } - private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) + private static void AnalyzeAttributeSyntax(SyntaxNodeAnalysisContext context) + { + if (context.Node is not AttributeSyntax attributeSyntax) { - if (context.Node is not AttributeSyntax attributeSyntax) - { - return; - } + return; + } - var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryAttributeTypeSymbol(context.Compilation); + var benchmarkCategoryAttributeTypeSymbol = GetBenchmarkCategoryAttributeTypeSymbol(context.Compilation); - var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; - if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + var attributeTypeSymbol = context.SemanticModel.GetTypeInfo(attributeSyntax).Type; + if (attributeTypeSymbol != null && attributeTypeSymbol.Equals(benchmarkCategoryAttributeTypeSymbol, SymbolEqualityComparer.Default)) + { + if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) { - if (attributeSyntax.ArgumentList is { Arguments.Count: 1 }) - { - var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; + var argumentSyntax = attributeSyntax.ArgumentList.Arguments[0]; - var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); - if (constantValue is { HasValue: true, Value: null }) - { - context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule, argumentSyntax.GetLocation())); - } + var constantValue = context.SemanticModel.GetConstantValue(argumentSyntax.Expression); + if (constantValue is { HasValue: true, Value: null }) + { + context.ReportDiagnostic(Diagnostic.Create(SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule, argumentSyntax.GetLocation())); } } } + } - private static INamedTypeSymbol? GetBenchmarkCategoryAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkCategoryAttribute"); + private static INamedTypeSymbol? GetBenchmarkCategoryAttributeTypeSymbol(Compilation compilation) => compilation.GetTypeByMetadataName("BenchmarkDotNet.Attributes.BenchmarkCategoryAttribute"); - private static string FormatBenchmarkCategory(List benchmarkCategories) - { - // Default ICategoryDiscoverer implementation: DefaultCategoryDiscoverer + private static string FormatBenchmarkCategory(List benchmarkCategories) + { + // Default ICategoryDiscoverer implementation: DefaultCategoryDiscoverer - return string.Join(",", benchmarkCategories.Distinct(StringComparer.OrdinalIgnoreCase)); - } + return string.Join(",", benchmarkCategories.Distinct(StringComparer.OrdinalIgnoreCase)); } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs index 920324645a..c9a554a326 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ArgumentsAttributeAnalyzerTests.cs @@ -1,364 +1,327 @@ -namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes -{ - using Fixtures; - - using Analyzers.Attributes; - - using Xunit; +using BenchmarkDotNet.Analyzers.Attributes; +using BenchmarkDotNet.Analyzers.Tests.Fixtures; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Threading.Tasks; +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes; - public class ArgumentsAttributeAnalyzerTests +public class ArgumentsAttributeAnalyzerTests +{ + public class General : AnalyzerTestFixture { - 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) { - [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) + List emptyArgumentsAttributeUsages = []; + + for (var i = 0; i < attributeUsageMultiplier; i++) { - var emptyArgumentsAttributeUsages = new List(); + emptyArgumentsAttributeUsages.Add(emptyArgumentsAttributeUsage); + } - for (var i = 0; i < attributeUsageMultiplier; i++) + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass { - emptyArgumentsAttributeUsages.Add(emptyArgumentsAttributeUsage); + [Benchmark] + {{string.Join("\n", emptyArgumentsAttributeUsages)}} + public void BenchmarkMethod() + { + + } } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; - public class BenchmarkClass - { - [Benchmark] - {{string.Join("\n", emptyArgumentsAttributeUsages)}} - public void BenchmarkMethod() - { - - } - } - """; + await RunAsync(); + } - TestCode = testCode; + public static IEnumerable EmptyArgumentsAttributeUsages() + { + yield return "[Arguments]"; + yield return "[Arguments()]"; + yield return "[Arguments(Priority = 1)]"; - await RunAsync(); - } + string[] nameColonUsages = + [ + "", + "values: " + ]; - public static IEnumerable EmptyArgumentsAttributeUsages() - { - yield return "[Arguments]"; - yield return "[Arguments()]"; - yield return "[Arguments(Priority = 1)]"; + string[] priorityNamedParameterUsages = + [ + "", + ", 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})]", - "[Arguments({0}[]{1})]", - }; + string[] attributeUsagesBase = + [ + "[Arguments({0}new object[] {{ }}{1})]", + "[Arguments({0}new object[0]{1})]", + "[Arguments({0}[]{1})]", + ]; - foreach (var attributeUsageBase in attributeUsagesBase) + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) { - foreach (var nameColonUsage in nameColonUsages) + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); } } } } + } - public class RequiresBenchmarkAttribute : AnalyzerTestFixture - { - public RequiresBenchmarkAttribute() : base(ArgumentsAttributeAnalyzer.RequiresBenchmarkAttributeRule) { } + 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_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; - [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 + { + [Benchmark] + [{{string.Join("]\n[", ArgumentAttributeUsages.Take(argumentAttributeUsagesListLength))}}] + public void BenchmarkMethod() + { - 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++) + } + } + """; + + 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 { - AddExpectedDiagnostic(i); + {{string.Join("\n", ArgumentAttributeUsages.Take(argumentAttributeUsagesListLength).Select((a, i) => $"[{{|#{i}:{a}|}}]"))}} + public void BenchmarkMethod() + { + + } } + """; + + TestCode = testCode; - await RunAsync(); + for (var i = 0; i < argumentAttributeUsagesListLength; i++) + { + AddExpectedDiagnostic(i); } - public static TheoryData ArgumentAttributeUsagesListLength => new(Enumerable.Range(1, ArgumentAttributeUsages.Count)); + await RunAsync(); + } - private static ReadOnlyCollection ArgumentAttributeUsages => new List { - "Arguments", - "Arguments()", - "Arguments(42, \"test\")" - }.AsReadOnly(); + public static TheoryData ArgumentAttributeUsagesListLength => [.. Enumerable.Range(1, ArgumentAttributeUsages.Count)]; + + private static IReadOnlyCollection ArgumentAttributeUsages => + [ + "Arguments", + "Arguments()", + "Arguments(42, \"test\")" + ]; + } + + 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(); } - public class MustHaveMatchingValueCount : AnalyzerTestFixture + [Theory] + [MemberData(nameof(ArgumentsAttributeUsages))] + public async Task Having_a_matching_value_count_should_not_trigger_diagnostic(string argumentsAttributeUsage) { - public MustHaveMatchingValueCount() : base(ArgumentsAttributeAnalyzer.MustHaveMatchingValueCountRule) { } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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(); - } + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void BenchmarkMethod(string a, bool b) + { - [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(); + } - TestCode = testCode; + [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"; - await RunAsync(); - } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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(); - } + public class BenchmarkClass + { + [Benchmark] + {{argumentsAttributeUsage}} + public void {{benchmarkMethodName}}(string a) + { + + } + } + """; + TestCode = testCode; + AddDefaultExpectedDiagnostic(1, "", benchmarkMethodName, 0); - [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(); - } + await RunAsync(); + } - public static IEnumerable<(string, int, string)> ParameterLists => [ ("string a", 1, ""), ("", 0, "s") ]; + [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"; - public static TheoryData ArgumentsAttributeUsages() - { - return new TheoryData(GenerateData()); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - static IEnumerable GenerateData() + public class BenchmarkClass { - var nameColonUsages = new List - { - "", - "values: " - }; - - var priorityNamedParameterUsages = new List - { - "", - ", Priority = 1" - }; - - var attributeUsagesBase = new List - { - "[Arguments({1}{2})]", - "[Arguments({0}new object[] {{ {1} }}{2})]", - "[Arguments({0}[ {1} ]{2})]" - }; - - var valueLists = new List - { - "42, \"test\"", - "\"value\", 100" - }; - - foreach (var attributeUsageBase in attributeUsagesBase) + [Benchmark] + {{argumentsAttributeUsage}} + public void {{benchmarkMethodName}}({{parameterData.Parameters}}) { - foreach (var nameColonUsage in nameColonUsages) - { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Join("\n ", valueLists.Select(vv => string.Format(attributeUsageBase, nameColonUsage, vv, priorityNamedParameterUsage))); - } - } + } } - } + """; + TestCode = testCode; + AddExpectedDiagnostic(0, parameterData.ParameterCount, parameterData.PluralSuffix, benchmarkMethodName, 2); + AddExpectedDiagnostic(1, parameterData.ParameterCount, parameterData.PluralSuffix, benchmarkMethodName, 3); + + await RunAsync(); + } - public static TheoryData EmptyArgumentsAttributeUsagesWithLocationMarker() + public static IEnumerable<(string, int, string)> ParameterLists => + [ + ("string a", 1, ""), + ("", 0, "s") + ]; + + public static TheoryData ArgumentsAttributeUsages() + { + return [.. GenerateData()]; + + static IEnumerable GenerateData() { - return new TheoryData(GenerateData()); + string[] nameColonUsages = + [ + "", + "values: " + ]; + + string[] priorityNamedParameterUsages = + [ + "", + ", Priority = 1" + ]; + + string[] attributeUsagesBase = + [ + "[Arguments({1}{2})]", + "[Arguments({0}new object[] {{ {1} }}{2})]", + "[Arguments({0}[ {1} ]{2})]" + ]; + + string[] valueLists = + [ + "42, \"test\"", + "\"value\", 100" + ]; - static IEnumerable GenerateData() + foreach (var attributeUsageBase in attributeUsagesBase) { - 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})]", - "[Arguments({0}{{|#0:[]|}}{1})]", - }; - - foreach (var attributeUsageBase in attributeUsagesBase) + foreach (var nameColonUsage in nameColonUsages) { - foreach (var nameColonUsage in nameColonUsages) + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } + yield return string.Join("\n ", valueLists.Select(vv => string.Format(attributeUsageBase, nameColonUsage, vv, priorityNamedParameterUsage))); } } } } + } + + public static TheoryData EmptyArgumentsAttributeUsagesWithLocationMarker() + { + return [.. GenerateData()]; - public static IEnumerable ArgumentsAttributeUsagesWithLocationMarker() + static IEnumerable GenerateData() { - 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})]", - "[Arguments({0}[ {{|#{1}:{2}|}} ]{3})]" - }; - - var valueLists = new List - { - "42, \"test\"", - "\"value\", 100, false" - }; + yield return "[{|#0:Arguments|}]"; + yield return "[Arguments{|#0:()|}]"; + yield return "[Arguments({|#0:Priority = 1|})]"; + + string[] nameColonUsages = + [ + "", + "values: " + ]; + + string[] priorityNamedParameterUsages = + [ + "", + ", Priority = 1" + ]; + + string[] attributeUsagesBase = + [ + "[Arguments({0}new object[] {{|#0:{{ }}|}}{1})]", + "[Arguments({0}new object[{{|#0:0|}}]{1})]", + "[Arguments({0}{{|#0:[]|}}{1})]", + ]; foreach (var attributeUsageBase in attributeUsagesBase) { @@ -366,739 +329,557 @@ public static IEnumerable ArgumentsAttributeUsagesWithLocationMarker() { foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { - yield return string.Join("\n ", valueLists.Select((vv, i) => string.Format(attributeUsageBase, nameColonUsage, i, vv, priorityNamedParameterUsage))); + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); } } } } } - public class MustHaveMatchingValueType : AnalyzerTestFixture + public static IEnumerable ArgumentsAttributeUsagesWithLocationMarker() { - public MustHaveMatchingValueType() : base(ArgumentsAttributeAnalyzer.MustHaveMatchingValueTypeRule) { } + string[] nameColonUsages = + [ + "", + "values: " + ]; - [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(); - } + string[] priorityNamedParameterUsages = + [ + "", + ", Priority = 1" + ]; - [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(); - } + string[] attributeUsagesBase = + [ + "[Arguments({{|#{1}:{2}|}}{3})]", + "[Arguments({0}new object[] {{ {{|#{1}:{2}|}} }}{3})]", + "[Arguments({0}[ {{|#{1}:{2}|}} ]{3})]" + ]; - [Theory, CombinatorialData] - 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 */ $$""" - using BenchmarkDotNet.Attributes; - public class BenchmarkClass - { - [Benchmark] - [{{string.Format(scalarValuesContainerAttributeArgument, """ - 42, "test" - """)}}] - [{{string.Format(scalarValuesContainerAttributeArgument, """ - "value", 100, true - """)}}] - public void BenchmarkMethod({{parameters}}) - { - - } - } - """; - TestCode = testCode; - - await RunAsync(); - } + string[] valueLists = + [ + "42, \"test\"", + "\"value\", 100, false" + ]; - [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) + foreach (var attributeUsageBase in attributeUsagesBase) { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, valueAndType.Value1)}}] - public void BenchmarkMethod({{valueAndType.Value2}} a) - { - - } - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceDummyEnum(); - - await RunAsync(); + 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))); + } + } } + } + } - [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; - - public class BenchmarkClass - { - [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{(explicitCast ? $"({integerValueAndType.Value2})" : "")}{integerValueAndType.Value1}")}}] - public void BenchmarkMethod({{integerValueAndType.Value2}} a) - { - - } - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - - await RunAsync(); - } + public class MustHaveMatchingValueType : AnalyzerTestFixture + { + public MustHaveMatchingValueType() : base(ArgumentsAttributeAnalyzer.MustHaveMatchingValueTypeRule) { } - [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(); - } + [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; - [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 + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + + await RunAsync(); + } - public class BenchmarkClass - { - {{(useLocalConstant ? $"private const {valueAndType.Value2} _x = {(useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!)};" : "")}} + [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) + { - [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!)}}] - public void BenchmarkMethod({{valueAndType.Value2}} a) - { - - } - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceDummyEnum(); - - if (useConstantFromOtherClass) + } + } + """; + 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(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument, + [CombinatorialValues("string a", "")] string parameters) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + public class BenchmarkClass { - ReferenceConstants($"{valueAndType.Value2!}", valueAndType.Value1!); + [Benchmark] + [{{string.Format(scalarValuesContainerAttributeArgument, """ + 42, "test" + """)}}] + [{{string.Format(scalarValuesContainerAttributeArgument, """ + "value", 100, true + """)}}] + public void BenchmarkMethod({{parameters}}) + { + + } } + """; + TestCode = testCode; - await RunAsync(); - } + 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; + [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; - public class BenchmarkClass - { - {{(useLocalConstant ? $"private const {type}? _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, valueAndType.Value1)}}] + public void BenchmarkMethod({{valueAndType.Value2}} a) + { - [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}] - public void BenchmarkMethod({{type}} a) - { - - } - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceDummyEnum(); - - if (useConstantFromOtherClass) + } + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + await RunAsync(); + } + + [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; + + public class BenchmarkClass { - ReferenceConstants($"{type}?", "null"); + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{(explicitCast ? $"({integerValueAndType.Value2})" : "")}{integerValueAndType.Value1}")}}] + public void BenchmarkMethod({{integerValueAndType.Value2}} a) + { + + } } + """; - await RunAsync(); - } + TestCode = testCode; + ReferenceDummyAttribute(); - [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; - - public class BenchmarkClass - { - [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, value)}}] - public void BenchmarkMethod(int a) - { - - } - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - - await RunAsync(); - } + await RunAsync(); + } - [Theory, CombinatorialData] - 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; - - 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 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; - [Theory, CombinatorialData] - public async Task Having_unknown_parameter_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + 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) { - 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) - { - - } - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - - DisableCompilerDiagnostics(); - - await RunAsync(); + ReferenceConstants($"{valueAndType.Value2!}", valueAndType.Value1!); } - [Theory, CombinatorialData] - public async Task Providing_an_unkown_value_type_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + 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) { - 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(); + ReferenceConstants($"{type}?", "null"); } - [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"; + 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, + [CombinatorialValues("(byte)42", "'c'")] string value) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, value)}}] + public void BenchmarkMethod(int a) + { + + } + } + """; - public class BenchmarkClass - { - [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType.Value1}|}}")}}] - public void BenchmarkMethod({{expectedArgumentType}} a) - { - - } - } - """; + TestCode = testCode; + ReferenceDummyAttribute(); - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceDummyEnum(); + await RunAsync(); + } - AddDefaultExpectedDiagnostic(valueAndType.Value1!, expectedArgumentType, valueAndType.Value2!); + [Theory, CombinatorialData] + 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; - await RunAsync(); - } + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "new[] { 0, 1, 2 }")}}] + public void BenchmarkMethod(System.Span a) + { + + } + } + """; - [Theory, CombinatorialData] - 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"; + TestCode = testCode; + ReferenceDummyAttribute(); + + await RunAsync(); + } - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + [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 - { - {{(useLocalConstant ? $"private const {valueAndType.Value2} _x = {(useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1)};" : "")}} + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "42, \"test\"")}}] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "43, \"test2\"")}}] + public void BenchmarkMethod(unkown a, string b) + { - [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1)}|}}")}}] - public void BenchmarkMethod({{expectedArgumentType}} a) - { - - } - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceDummyEnum(); - - if (useConstantFromOtherClass) + } + } + """; + + TestCode = testCode; + ReferenceDummyAttribute(); + + DisableCompilerDiagnostics(); + + 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 { - ReferenceConstants(valueAndType.Value2!, valueAndType.Value1!); + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:dummy_literal|}, true")}}] + public void BenchmarkMethod(byte a, bool b) + { + + } } + """; - AddDefaultExpectedDiagnostic(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!, expectedArgumentType, valueAndType.Value2!); + TestCode = testCode; + ReferenceDummyAttribute(); - await RunAsync(); - } + DisableCompilerDiagnostics(); - [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(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:null|}")}}] - public void BenchmarkMethod({{type}} a) - { - - } - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceDummyEnum(); - - AddDefaultExpectedDiagnostic("null", type, "null"); - - await RunAsync(); - } + 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_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; - [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) + 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_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:{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1)}|}}")}}] + public void BenchmarkMethod({{expectedArgumentType}} a) + { + + } + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + if (useConstantFromOtherClass) { - 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(); + ReferenceConstants(valueAndType.Value2!, valueAndType.Value1!); } - public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + AddDefaultExpectedDiagnostic(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!, expectedArgumentType, valueAndType.Value2!); - public static TheoryData EmptyArgumentsAttributeUsagesWithMismatchingValueCount() - { - return new TheoryData(GenerateData()); + 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(ScalarValuesContainerAttributeArgumentEnumerableLocal))] string scalarValuesContainerAttributeArgument) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - static IEnumerable GenerateData() + public class BenchmarkClass { - 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})]", - "[Arguments({0}[]{1})]", - }; - - foreach (var attributeUsageBase in attributeUsagesBase) + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:null|}")}}] + public void BenchmarkMethod({{type}} a) { - foreach (var nameColonUsage in nameColonUsages) - { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } - } + } } - } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); - public static IEnumerable ScalarValuesContainerAttributeArgumentEnumerableLocal => ScalarValuesContainerAttributeArgumentEnumerable(); + AddDefaultExpectedDiagnostic("null", type, "null"); - 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"), - ]; + await RunAsync(); + } - 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"), - ]; + [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 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" ), - ( """ - "test" - """, "string" ), - ( "123U", "uint" ), - ( "123UL", "ulong" ), - ( "(ushort)123", "ushort" ), - - ( """ - (object)"test_object" - """, "object" ), - ( "typeof(string)", "System.Type" ), - ( "DummyEnum.Value1", "DummyEnum" ), - - ( "new[] { 0, 1, 2 }", "int[]" ), - ]; + public class BenchmarkClass + { + [Benchmark] + [{{dummyAttributeUsage}}{{string.Format(scalarValuesContainerAttributeArgument, "{|#0:new[] { 0, 1, 2 }|}")}}] + public void BenchmarkMethod({{expectedArgumentType}} a) + { + + } + } + """; - public static IEnumerable> NotConvertibleValuesAndTypes => - [ - ( "true", "bool" ), - ( "1.0D", "double" ), - ( "1.0F", "float" ), - ( """ - "test" - """, "string" ), - - ( """ - (object)"test_object" - """, "object" ), - ( "typeof(string)", "System.Type" ), - ( "DummyEnum.Value1", "DummyEnum" ) - ]; + TestCode = testCode; + ReferenceDummyAttribute(); - 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" ), - ]; + AddDefaultExpectedDiagnostic("new[] { 0, 1, 2 }", expectedArgumentType, "int[]"); - public static IEnumerable NullableStructTypes => - [ - "bool", - "byte", - "char", - "double", - "float", - "int", - "long", - "sbyte", - "short", - "uint", - "ulong", - "ushort", - "DummyEnum", - ]; + await RunAsync(); + } - public static IEnumerable NullReferenceConstantTypes => - [ - "object", - "string", - "System.Type", - ]; + [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 static IEnumerable> NotConvertibleConstantValuesAndTypes => - [ - ( "true", "bool" ), - ( "1.0D", "double" ), - ( "1.0F", "float" ), - ( """ - "test" - """, "string" ), - - ( "DummyEnum.Value1", "DummyEnum" ) - ]; + 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(); } - public static TheoryData DummyAttributeUsageTheoryData => [ - "", - "Dummy, " - ]; + public static IEnumerable DummyAttributeUsage + => DummyAttributeUsageTheoryData; - private static IEnumerable ScalarValuesContainerAttributeArgumentEnumerable() + public static TheoryData EmptyArgumentsAttributeUsagesWithMismatchingValueCount() { - return GenerateData().Distinct(); + return [.. GenerateData()]; static IEnumerable GenerateData() { - var nameColonUsages = new List - { - "", - "values: " - }; - - var priorityNamedParameterUsages = new List - { - "", - ", Priority = 1" - }; - - var attributeUsagesBase = new List - { - "Arguments({{0}}{1})", - "Arguments({0}new object[] {{{{ {{0}} }}}}{1})", - "Arguments({0}[ {{0}} ]{1})" - }; + yield return "[Arguments]"; + yield return "[Arguments()]"; + yield return "[Arguments(Priority = 1)]"; + + string[] nameColonUsages = ["", "values: "]; + + string[] priorityNamedParameterUsages = ["", ", Priority = 1"]; + + string[] attributeUsagesBase = + [ + "[Arguments({0}new object[] {{ }}{1})]", + "[Arguments({0}new object[0]{1})]", + "[Arguments({0}[]{1})]", + ]; foreach (var attributeUsageBase in attributeUsagesBase) { @@ -1112,5 +893,224 @@ static IEnumerable GenerateData() } } } + + public static IEnumerable ScalarValuesContainerAttributeArgumentEnumerableLocal + => ScalarValuesContainerAttributeArgumentEnumerable(); + + 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> ValuesAndTypes => + [ + ( "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" ), + + ( "new[] { 0, 1, 2 }", "int[]" ), + ]; + + public static IEnumerable> NotConvertibleValuesAndTypes => + [ + ( "true", "bool" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( """ + "test" + """, "string" ), + + ( """ + (object)"test_object" + """, "object" ), + ( "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 => ["", "Dummy, "]; + + private static IEnumerable ScalarValuesContainerAttributeArgumentEnumerable() + { + return GenerateData().Distinct(); + + static IEnumerable GenerateData() + { + string[] nameColonUsages = ["", "values: "]; + + string[] priorityNamedParameterUsages = ["", ", Priority = 1"]; + + string[] attributeUsagesBase = + [ + "Arguments({{0}}{1})", + "Arguments({0}new object[] {{{{ {{0}} }}}}{1})", + "Arguments({0}[ {{0}} ]{1})" + ]; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); + } + } + } + } } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralArgumentAttributesAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralArgumentAttributesAnalyzerTests.cs index cdbfcc00d1..70028faa80 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralArgumentAttributesAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralArgumentAttributesAnalyzerTests.cs @@ -1,128 +1,117 @@ -namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes +using BenchmarkDotNet.Analyzers.Attributes; +using BenchmarkDotNet.Analyzers.Tests.Fixtures; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes; +public class GeneralArgumentAttributesAnalyzerTests { - using Fixtures; + public class MethodWithoutAttributeMustHaveNoParameters : AnalyzerTestFixture + { + public MethodWithoutAttributeMustHaveNoParameters() : base(GeneralArgumentAttributesAnalyzer.MethodWithoutAttributeMustHaveNoParametersRule) { } + + [Theory] + [InlineData("""ArgumentsSource("test")""")] + [InlineData("""Arguments(42, "test")""")] + [InlineData(""" + Arguments(42, "test"), Arguments(1, "test2") + """)] + public async Task A_method_with_parameters_annotated_with_an_argumentssource_or_arguments_attribute_and_the_benchmark_attribute_should_not_trigger_diagnostic(string attributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [{{attributeUsage}}] + public void BenchmarkMethod(int a, string b) + { + + } + } + """; - using Analyzers.Attributes; + TestCode = testCode; - using Xunit; + await RunAsync(); + } - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Threading.Tasks; + [Theory] + [MemberData(nameof(ParametersListLength))] + public async Task A_method_with_parameters_and_no_argumentssource_arguments_or_benchmark_attributes_should_not_trigger_diagnostic(int parametersListLength) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - public class GeneralArgumentAttributesAnalyzerTests - { - public class MethodWithoutAttributeMustHaveNoParameters : AnalyzerTestFixture + public class BenchmarkClass + { + public void BenchmarkMethod({{string.Join(", ", Parameters.Take(parametersListLength))}}) + { + + } + } + """; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ParametersListLength))] + public async Task A_method_with_parameters_annotated_with_the_benchmark_attribute_and_an_argumentssource_attribute_but_no_arguments_attribute_should_not_trigger_diagnostic(int parametersListLength) + { + const string benchmarkMethodName = "BenchmarkMethod"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + [ArgumentsSource("test")] + public void {{benchmarkMethodName}}({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) + { + + } + } + """; + + TestCode = testCode; + + await RunAsync(); + } + + [Theory] + [MemberData(nameof(ParametersListLength))] + public async Task A_method_with_parameters_annotated_with_the_benchmark_attribute_but_no_argumentssource_or_arguments_attribute_should_trigger_diagnostic(int parametersListLength) { - public MethodWithoutAttributeMustHaveNoParameters() : base(GeneralArgumentAttributesAnalyzer.MethodWithoutAttributeMustHaveNoParametersRule) { } - - [Theory] - [InlineData("""ArgumentsSource("test")""")] - [InlineData("""Arguments(42, "test")""")] - [InlineData(""" - Arguments(42, "test"), Arguments(1, "test2") - """)] - public async Task A_method_with_parameters_annotated_with_an_argumentssource_or_arguments_attribute_and_the_benchmark_attribute_should_not_trigger_diagnostic(string attributeUsage) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + const string benchmarkMethodName = "BenchmarkMethod"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void {{benchmarkMethodName}}({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) + { - public class BenchmarkClass - { - [Benchmark] - [{{attributeUsage}}] - public void BenchmarkMethod(int a, string b) - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Theory] - [MemberData(nameof(ParametersListLength))] - public async Task A_method_with_parameters_and_no_argumentssource_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_with_parameters_annotated_with_the_benchmark_attribute_and_an_argumentssource_attribute_but_no_arguments_attribute_should_not_trigger_diagnostic(int parametersListLength) - { - const string benchmarkMethodName = "BenchmarkMethod"; - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - [ArgumentsSource("test")] - public void {{benchmarkMethodName}}({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) - { - - } - } - """; - - TestCode = testCode; - - await RunAsync(); - } - - [Theory] - [MemberData(nameof(ParametersListLength))] - public async Task A_method_with_parameters_annotated_with_the_benchmark_attribute_but_no_argumentssource_or_arguments_attribute_should_trigger_diagnostic(int parametersListLength) - { - const string benchmarkMethodName = "BenchmarkMethod"; - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [Benchmark] - public void {{benchmarkMethodName}}({|#0:{{string.Join(", ", Parameters.Take(parametersListLength))}}|}) - { - - } - } - """; - - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkMethodName); - - await RunAsync(); - } - - public static TheoryData ParametersListLength => new(Enumerable.Range(1, Parameters.Count)); - - private static ReadOnlyCollection Parameters => new List { - "int a", - "string b", - "bool c" - }.AsReadOnly(); + } + } + """; + + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkMethodName); + + await RunAsync(); } + public static TheoryData ParametersListLength => [.. Enumerable.Range(1, Parameters.Count)]; + + private static IReadOnlyCollection Parameters => ["int a", "string b", "bool c"]; } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs index 67617110e4..67dc10d725 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/GeneralParameterAttributesAnalyzerTests.cs @@ -1,1317 +1,1371 @@ -namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes +using BenchmarkDotNet.Analyzers.Attributes; +using BenchmarkDotNet.Analyzers.Tests.Fixtures; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes; +public class GeneralParameterAttributesAnalyzerTests { - using Fixtures; + public class MutuallyExclusiveOnField : AnalyzerTestFixture + { + public MutuallyExclusiveOnField() : base(GeneralParameterAttributesAnalyzer.MutuallyExclusiveOnFieldRule) { } - using Analyzers.Attributes; + [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; + } + """; - using Xunit; + TestCode = testCode; - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Threading.Tasks; + await RunAsync(); + } - public class GeneralParameterAttributesAnalyzerTests - { - public class MutuallyExclusiveOnField : AnalyzerTestFixture + [Fact] + public async Task A_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() { - public MutuallyExclusiveOnField() : base(GeneralParameterAttributesAnalyzer.MutuallyExclusiveOnFieldRule) { } + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + public int _field = 0, _field2 = 1; + } + """; - [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; + ReferenceDummyAttribute(); - TestCode = testCode; + await RunAsync(); + } - 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; + } + """; - [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(); - } + TestCode = testCode; + ReferenceDummyAttribute(); + DisableCompilerDiagnostics(); - [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(); - } + 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; + [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; - } - """; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int _field = 0, _field2 = 1; + } + """; - TestCode = testCode; + TestCode = testCode; - await RunAsync(); - } + 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()); + [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(); + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - for (var i = 0; i < counts.Length; i++) + for (var i = 0; i < counts.Length; i++) + { + if (i == currentUniqueAttributeUsagePosition) { - if (i == currentUniqueAttributeUsagePosition) - { - duplicateAttributeUsages.Add($"[{currentUniqueAttributeUsage}]"); - } + duplicateAttributeUsages.Add($"[{currentUniqueAttributeUsage}]"); + } - for (var j = 0; j < counts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + for (var j = 0; j < counts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); } + } - 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(); + TestCode = testCode; + DisableCompilerDiagnostics(); - await RunAsync(); - } + 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"; + [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 duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - var diagnosticCounter = 0; - for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + var diagnosticCounter = 0; + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) { - for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{{|#{diagnosticCounter++}:{uniqueParameterAttributeUsages[i]}|}}]"); - } + 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; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var i = 0; i < diagnosticCounter; i++) + public class BenchmarkClass { - AddExpectedDiagnostic(i, fieldIdentifier); + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int {{fieldIdentifier}} = 0, field2 = 1; } + """; + + TestCode = testCode; - await RunAsync(); + for (var i = 0; i < diagnosticCounter; i++) + { + AddExpectedDiagnostic(i, fieldIdentifier); } - public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); + await RunAsync(); + } - public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + public static TheoryData UniqueParameterAttributeUsages + => [.. UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)]; - public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; - } + public static TheoryData DuplicateSameParameterAttributeUsages + => DuplicateSameAttributeUsagesTheoryData; - public class MutuallyExclusiveOnProperty : AnalyzerTestFixture - { - public MutuallyExclusiveOnProperty() : base(GeneralParameterAttributesAnalyzer.MutuallyExclusiveOnPropertyRule) { } + public static TheoryData DuplicateParameterAttributeUsageCounts + => DuplicateAttributeUsageCountsTheoryData; + } - [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; } - } - """; + public class MutuallyExclusiveOnProperty : AnalyzerTestFixture + { + public MutuallyExclusiveOnProperty() : base(GeneralParameterAttributesAnalyzer.MutuallyExclusiveOnPropertyRule) { } - TestCode = testCode; + [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; } + } + """; - await RunAsync(); - } + TestCode = testCode; - [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(); - } + 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(); - } + [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; } + } + """; - [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; + TestCode = testCode; + ReferenceDummyAttribute(); - public class BenchmarkClass - { - [{{attributeUsage}}] - public int Property { get; set; } - } - """; + await RunAsync(); + } - TestCode = testCode; + [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; } + } + """; - await RunAsync(); - } + TestCode = testCode; + ReferenceDummyAttribute(); + DisableCompilerDiagnostics(); - [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()); + await RunAsync(); + } - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + [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; - for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + public class BenchmarkClass { - if (i == currentUniqueAttributeUsagePosition) - { - duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); - } - - for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + [{{attributeUsage}}] + public int Property { get; set; } } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - public int Property { get; set; } - } - """; + await RunAsync(); + } - TestCode = testCode; - DisableCompilerDiagnostics(); + [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()); - await RunAsync(); - } + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - [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) + for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) { - const string propertyIdentifier = "Property"; + if (i == currentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); + } - var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - var diagnosticCounter = 0; - for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + public class BenchmarkClass { - for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{{|#{diagnosticCounter++}:{uniqueParameterAttributeUsages[i]}|}}]"); - } + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public int Property { get; set; } } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; + DisableCompilerDiagnostics(); - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - public int {{propertyIdentifier}} { get; set; } - } - """; + 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"; - TestCode = testCode; + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); - for (var i = 0; i < diagnosticCounter; i++) + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + var diagnosticCounter = 0; + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) { - AddExpectedDiagnostic(i, propertyIdentifier); + duplicateAttributeUsages.Add($"[{{|#{diagnosticCounter++}:{uniqueParameterAttributeUsages[i]}|}}]"); } + } - await RunAsync(); + 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); } - public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); + await RunAsync(); + } + + public static TheoryData UniqueParameterAttributeUsages => [.. UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)]; - public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; - public static TheoryData DuplicateParameterAttributeUsages => DuplicateAttributeUsageCountsTheoryData; + 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(); } - public class FieldMustBePublic : AnalyzerTestFixture + [Theory] + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task A_nonpublic_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) { - public FieldMustBePublic() : base(GeneralParameterAttributesAnalyzer.FieldMustBePublic) { } + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + [Dummy] + {{classMemberAccessModifier}}int _field = 0, _field2 = 1; + } + """; - [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; + ReferenceDummyAttribute(); - TestCode = testCode; + await RunAsync(); + } - 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; - [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(); - } + public class BenchmarkClass + { + [{{attributeUsage}}] + public int _field = 0, _field2 = 2; + } + """; - [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; + TestCode = testCode; - public class BenchmarkClass - { - [{{attributeUsage}}] - public int _field = 0, _field2 = 2; - } - """; + await RunAsync(); + } - TestCode = testCode; + [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()); - await RunAsync(); - } + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - [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) + for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) { - var duplicateAttributeUsages = new List(1 + duplicateSameParameterAttributeUsages.Counts.Sum()); - - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); + } - for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) + for (var j = 0; j < duplicateSameParameterAttributeUsages.Counts[i]; j++) { - if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) - { - duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); - } + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var j = 0; j < duplicateSameParameterAttributeUsages.Counts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + {{classMemberAccessModifier}}int _field = 0, _field2 = 1; } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; + DisableCompilerDiagnostics(); - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - {{classMemberAccessModifier}}int _field = 0, _field2 = 1; - } - """; + await RunAsync(); + } - TestCode = testCode; - DisableCompilerDiagnostics(); + [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()); - await RunAsync(); - } + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - [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) + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) { - var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + public class BenchmarkClass { - for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + {{classMemberAccessModifier}}int _field = 0, _field2 = 1; } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - {{classMemberAccessModifier}}int _field = 0, _field2 = 1; - } - """; + await RunAsync(); + } - TestCode = testCode; + [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"; - await RunAsync(); - } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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"; + public class BenchmarkClass + { + [{{attribute.AttributeUsage}}] + {{classMemberAccessModifier}}int {|#0:{{fieldIdentifier}}|} = 0, field2 = 0; + } + """; + TestCode = testCode; + AddDefaultExpectedDiagnostic(fieldIdentifier, attribute.AttributeName); - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + await RunAsync(); + } - public class BenchmarkClass - { - [{{attribute.AttributeUsage}}] - {{classMemberAccessModifier}}int {|#0:{{fieldIdentifier}}|} = 0, field2 = 0; - } - """; - TestCode = testCode; - AddDefaultExpectedDiagnostic(fieldIdentifier, attribute.AttributeName); + public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations + => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicClassMemberAccessModifiers); - await RunAsync(); - } + public static TheoryData UniqueParameterAttributeUsages + => [.. UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)]; - public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicClassMemberAccessModifiers); + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes + => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); - public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); + public static IEnumerable NonPublicClassMemberAccessModifiers +#pragma warning disable IDE0028 // Simplify collection initialization + => new NonPublicClassMemberAccessModifiersTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization - public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages + => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int) tdr[1], (tdr[2] as int[])!)); - public static IEnumerable NonPublicClassMemberAccessModifiers => new NonPublicClassMemberAccessModifiersTheoryData(); + public static IEnumerable DuplicateParameterAttributeUsageCounts + => DuplicateAttributeUsageCountsTheoryData; + } - public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); + public class PropertyMustBePublic : AnalyzerTestFixture + { + public PropertyMustBePublic() : base(GeneralParameterAttributesAnalyzer.PropertyMustBePublic) { } - public static IEnumerable DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + [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(); } - public class PropertyMustBePublic : AnalyzerTestFixture + [Theory] + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task A_nonpublic_property_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic(string classMemberAccessModifier) { - public PropertyMustBePublic() : base(GeneralParameterAttributesAnalyzer.PropertyMustBePublic) { } + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + [Dummy] + {{classMemberAccessModifier}}int Property { get; set; } + } + """; - [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; + ReferenceDummyAttribute(); - TestCode = testCode; + await RunAsync(); + } - 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; - [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(); - } + public class BenchmarkClass + { + [{{attributeUsage}}] + public int Property { get; set; } + } + """; - [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; + TestCode = testCode; - public class BenchmarkClass - { - [{{attributeUsage}}] - public int Property { get; set; } - } - """; + await RunAsync(); + } - TestCode = testCode; + [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()); - await RunAsync(); - } + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - [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) + for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) { - var duplicateAttributeUsages = new List(1 + duplicateSameParameterAttributeUsages.Counts.Sum()); - - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); + } - for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) + for (var j = 0; j < duplicateSameParameterAttributeUsages.Counts[i]; j++) { - if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) - { - duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); - } + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } - 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; } } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; + DisableCompilerDiagnostics(); - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - {{classMemberAccessModifier}}int Property { get; set; } - } - """; + await RunAsync(); + } - TestCode = testCode; - DisableCompilerDiagnostics(); + [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()); - await RunAsync(); - } + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - [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) + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) { - var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + public class BenchmarkClass { - for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + {{classMemberAccessModifier}}int Property { get; set; } } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - {{classMemberAccessModifier}}int Property { get; set; } - } - """; + await RunAsync(); + } - TestCode = testCode; + [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"; - await RunAsync(); - } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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"; + public class BenchmarkClass + { + [{{attribute.AttributeUsage}}] + {{classMemberAccessModifier}}int {|#0:{{propertyIdentifier}}|} { get; set; } + } + """; + TestCode = testCode; + AddDefaultExpectedDiagnostic(propertyIdentifier, attribute.AttributeName); + + await RunAsync(); + } - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations + => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicClassMemberAccessModifiers); - public class BenchmarkClass - { - [{{attribute.AttributeUsage}}] - {{classMemberAccessModifier}}int {|#0:{{propertyIdentifier}}|} { get; set; } - } - """; - TestCode = testCode; - AddDefaultExpectedDiagnostic(propertyIdentifier, attribute.AttributeName); + public static TheoryData UniqueParameterAttributeUsages + => [.. UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)]; - await RunAsync(); - } + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes + => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); - public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicClassMemberAccessModifiersCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicClassMemberAccessModifiers); + public static IEnumerable NonPublicClassMemberAccessModifiers +#pragma warning disable IDE0028 // Simplify collection initialization + => new NonPublicClassMemberAccessModifiersTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization - public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages + => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int) tdr[1], (tdr[2] as int[])!)); - public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); + public static TheoryData DuplicateParameterAttributeUsageCounts + => DuplicateAttributeUsageCountsTheoryData; + } - public static IEnumerable NonPublicClassMemberAccessModifiers => new NonPublicClassMemberAccessModifiersTheoryData(); + 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; + } + """; - public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); + TestCode = testCode; - public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + await RunAsync(); } - public class NotValidOnReadonlyField : AnalyzerTestFixture + [Fact] + public async Task A_readonly_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() { - public NotValidOnReadonlyField() : base(GeneralParameterAttributesAnalyzer.NotValidOnReadonlyFieldRule) { } + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + public readonly int _field = 0, _field2 = 1; + } + """; - [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; + ReferenceDummyAttribute(); - TestCode = testCode; + await RunAsync(); + } - 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; - [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(); - } + public class BenchmarkClass + { + [{{attributeUsage}}] + public int _field = 0, _field2 = 1; + } + """; - [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; + TestCode = testCode; - public class BenchmarkClass - { - [{{attributeUsage}}] - public int _field = 0, _field2 = 1; - } - """; + await RunAsync(); + } - TestCode = testCode; + [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()); - await RunAsync(); - } + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - [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) + for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) { - var duplicateAttributeUsages = new List(1 + duplicateSameAttributeUsageCounts.Sum()); - - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + if (i == currentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); + } - for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) { - if (i == currentUniqueAttributeUsagePosition) - { - duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); - } + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + public class BenchmarkClass + { + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public readonly int _field = 0, _field2 = 1; } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; + DisableCompilerDiagnostics(); - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - public readonly int _field = 0, _field2 = 1; - } - """; + await RunAsync(); + } - TestCode = testCode; - DisableCompilerDiagnostics(); + [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()); - await RunAsync(); - } + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - [Theory] - [MemberData(nameof(DuplicateParameterAttributeUsageCounts))] - public async Task A_readonly_field_annotated_with_more_than_one_parameter_attribute_should_not_trigger_diagnostic(int[] duplicateAttributeUsageCounts) + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) { - var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + public class BenchmarkClass { - for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public readonly int _field = 0, _field2 = 1; } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - public readonly int _field = 0, _field2 = 1; - } - """; + await RunAsync(); + } - TestCode = testCode; + [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"; - await RunAsync(); - } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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"; + public class BenchmarkClass + { + [{{attributeUsage}}] + public {|#0:readonly|} int {{fieldIdentifier}} = 0, field2 = 1; + } + """; + TestCode = testCode; + AddDefaultExpectedDiagnostic(fieldIdentifier, attributeName); - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + await RunAsync(); + } - public class BenchmarkClass - { - [{{attributeUsage}}] - public {|#0:readonly|} int {{fieldIdentifier}} = 0, field2 = 1; - } - """; - TestCode = testCode; - AddDefaultExpectedDiagnostic(fieldIdentifier, attributeName); + public static TheoryData UniqueParameterAttributeUsages + => [.. UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)]; - await RunAsync(); - } + public static TheoryData UniqueParameterAttributes + => UniqueParameterAttributesTheoryData; - public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); + public static TheoryData DuplicateSameParameterAttributeUsages + => DuplicateSameAttributeUsagesTheoryData; - public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; + public static TheoryData DuplicateParameterAttributeUsageCounts + => DuplicateAttributeUsageCountsTheoryData; + } - public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + 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; - public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + await RunAsync(); } - public class NotValidOnConstantField : AnalyzerTestFixture + [Fact] + public async Task A_constant_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() { - public NotValidOnConstantField() : base(GeneralParameterAttributesAnalyzer.NotValidOnConstantFieldRule) { } + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + [Dummy] + public const int Constant = 0; + } + """; - [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; + ReferenceDummyAttribute(); - TestCode = testCode; + await RunAsync(); + } - 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(); - [Fact] - public async Task A_constant_field_annotated_with_a_nonparameter_attribute_should_not_trigger_diagnostic() + for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) { - const string testCode = /* lang=c#-test */ """ - public class BenchmarkClass - { - [Dummy] - public const int Constant = 0; - } - """; - - TestCode = testCode; - ReferenceDummyAttribute(); - - await RunAsync(); + if (i == currentUniqueAttributeUsagePosition) + { + duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); + } + + for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } } - [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(); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + public class BenchmarkClass { - if (i == currentUniqueAttributeUsagePosition) - { - duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); - } - - for (var j = 0; j < duplicateSameAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public const int Constant = 0; } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; + DisableCompilerDiagnostics(); - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - public const int Constant = 0; - } - """; + await RunAsync(); + } - TestCode = testCode; - DisableCompilerDiagnostics(); + [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()); - await RunAsync(); - } + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - [Theory] - [MemberData(nameof(DuplicateParameterAttributeUsageCounts))] - public async Task A_constant_field_annotated_with_more_than_one_parameter_attribute_should_not_trigger_diagnostic(int[] duplicateAttributeUsageCounts) + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) { - var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); + } + } - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + public class BenchmarkClass { - for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} + public const int Constant = 0; } + """; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + TestCode = testCode; - public class BenchmarkClass - { - {{string.Join($"{Environment.NewLine} ", duplicateAttributeUsages)}} - public const int Constant = 0; - } - """; + await RunAsync(); + } - TestCode = testCode; + [Theory] + [MemberData(nameof(UniqueParameterAttributes))] + public async Task A_constant_field_annotated_with_a_unique_parameter_attribute_should_trigger_diagnostic(string attributeName, string attributeUsage) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - await RunAsync(); - } + public class BenchmarkClass + { + [{{attributeUsage}}] + public {|#0:const|} int Constant = 0; + } + """; + TestCode = testCode; + AddDefaultExpectedDiagnostic(attributeName); - [Theory] - [MemberData(nameof(UniqueParameterAttributes))] - public async Task A_constant_field_annotated_with_a_unique_parameter_attribute_should_trigger_diagnostic(string attributeName, string attributeUsage) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [{{attributeUsage}}] - public {|#0:const|} int Constant = 0; - } - """; - TestCode = testCode; - AddDefaultExpectedDiagnostic(attributeName); - - await RunAsync(); - } + await RunAsync(); + } - public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); + public static TheoryData UniqueParameterAttributeUsages + => [.. UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)]; - public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; + public static TheoryData UniqueParameterAttributes + => UniqueParameterAttributesTheoryData; - public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + public static TheoryData DuplicateSameParameterAttributeUsages + => DuplicateSameAttributeUsagesTheoryData; + + public static TheoryData DuplicateParameterAttributeUsageCounts + => DuplicateAttributeUsageCountsTheoryData; + } - public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; - } #if NET5_0_OR_GREATER - public class PropertyCannotBeInitOnly : AnalyzerTestFixture + 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() { - public PropertyCannotBeInitOnly() : base(GeneralParameterAttributesAnalyzer.PropertyCannotBeInitOnlyRule) { } + const string testCode = /* lang=c#-test */ """ + public class BenchmarkClass + { + public int Property { get; init; } + } + """; - [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; - TestCode = testCode; + await RunAsync(); + } - 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; } + } + """; - [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(); - } + TestCode = testCode; + ReferenceDummyAttribute(); - [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; + await RunAsync(); + } - public class BenchmarkClass - { - [{{attributeUsage}}] - public int Property { get; set; } - } - """; + [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; - TestCode = testCode; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int Property { get; set; } + } + """; - await RunAsync(); - } + TestCode = testCode; - [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()); + await RunAsync(); + } - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + [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()); - for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + + for (var i = 0; i < duplicateSameAttributeUsageCounts.Length; i++) + { + if (i == currentUniqueAttributeUsagePosition) { - if (i == currentUniqueAttributeUsagePosition) - { - duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); - } + duplicateAttributeUsages.Add($"[{currentAttributeUsage}]"); + } - for (var j = 0; j (duplicateAttributeUsageCounts.Sum()); + [Theory] + [MemberData(nameof(DuplicateParameterAttributeUsageCounts))] + public async Task An_initonly_property_annotated_with_more_than_one_parameter_attribute_should_not_trigger_diagnostic(int[] duplicateAttributeUsageCounts) + { + var duplicateAttributeUsages = new List(duplicateAttributeUsageCounts.Sum()); - var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) { - for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); } + } - 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; + TestCode = testCode; - await RunAsync(); - } + 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"; + [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; + 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); + TestCode = testCode; + AddDefaultExpectedDiagnostic(propertyIdentifier, attributeName); - await RunAsync(); - } + await RunAsync(); + } - public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); + public static TheoryData UniqueParameterAttributeUsages + => [.. UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)]; - public static TheoryData UniqueParameterAttributes => UniqueParameterAttributesTheoryData; + public static TheoryData UniqueParameterAttributes + => UniqueParameterAttributesTheoryData; - public static TheoryData DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData; + public static TheoryData DuplicateSameParameterAttributeUsages + => DuplicateSameAttributeUsagesTheoryData; - public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; - } + public static TheoryData DuplicateParameterAttributeUsageCounts + => DuplicateAttributeUsageCountsTheoryData; + } #endif - public class PropertyMustHavePublicSetter : AnalyzerTestFixture + 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) { - public PropertyMustHavePublicSetter() : base(GeneralParameterAttributesAnalyzer.PropertyMustHavePublicSetterRule) { } + var testCode = /* lang=c#-test */ $$""" + public class BenchmarkClass + { + public int Property {{nonPublicPropertySetter}} + } + """; - [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; - TestCode = testCode; + await RunAsync(); + } - 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}} + } + """; - [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(); - } + TestCode = testCode; + ReferenceDummyAttribute(); - [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; + await RunAsync(); + } - public class BenchmarkClass - { - [{{attributeUsage}}] - public int Property { get; set; } - } - """; + [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; - TestCode = testCode; + public class BenchmarkClass + { + [{{attributeUsage}}] + public int Property { get; set; } + } + """; - await RunAsync(); - } + TestCode = testCode; - [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()); + 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(); + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) + for (var i = 0; i < duplicateSameParameterAttributeUsages.Counts.Length; i++) + { + if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) { - if (i == duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsagePosition) - { - duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); - } + duplicateAttributeUsages.Add($"[{duplicateSameParameterAttributeUsages.CurrentUniqueAttributeUsage}]"); + } - for (var j = 0; j < duplicateSameParameterAttributeUsages.Counts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + for (var j = 0; j < duplicateSameParameterAttributeUsages.Counts[i]; j++) + { + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); } + } - 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(); + TestCode = testCode; + DisableCompilerDiagnostics(); - await RunAsync(); - } + 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()); + [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(); + var uniqueParameterAttributeUsages = UniqueParameterAttributeUsages.AsReadOnly(); - for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + for (var i = 0; i < duplicateAttributeUsageCounts.Length; i++) + { + for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) { - for (var j = 0; j < duplicateAttributeUsageCounts[i]; j++) - { - duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); - } + duplicateAttributeUsages.Add($"[{uniqueParameterAttributeUsages[i]}]"); } + } - 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; + TestCode = testCode; - await RunAsync(); - } + 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"; + [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; + 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); + TestCode = testCode; + AddDefaultExpectedDiagnostic(propertyIdentifier, attribute.AttributeName); - await RunAsync(); - } + await RunAsync(); + } - public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicPropertySetterCombinations => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicPropertySetters()); + public static IEnumerable DuplicateAttributeUsageCountsAndNonPublicPropertySetterCombinations + => CombinationsGenerator.CombineArguments(DuplicateParameterAttributeUsageCounts, NonPublicPropertySetters()); - public static TheoryData UniqueParameterAttributeUsages => new(UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)); + public static TheoryData UniqueParameterAttributeUsages + => [.. UniqueParameterAttributesTheoryData.Select(tdr => (tdr[1] as string)!)]; - public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); + public static IEnumerable<(string AttributeName, string AttributeUsage)> UniqueParameterAttributes + => UniqueParameterAttributesTheoryData.Select(tdr => ((tdr[0] as string)!, (tdr[1] as string)!)); - public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int)tdr[1], (tdr[2] as int[])!)); + public static IEnumerable<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> DuplicateSameParameterAttributeUsages + => DuplicateSameAttributeUsagesTheoryData.Select(tdr => ((tdr[0] as string)!, (int) tdr[1], (tdr[2] as int[])!)); - public static TheoryData DuplicateParameterAttributeUsageCounts => DuplicateAttributeUsageCountsTheoryData; + public static TheoryData DuplicateParameterAttributeUsageCounts + => DuplicateAttributeUsageCountsTheoryData; - public static IEnumerable NonPublicPropertySetters() => new NonPublicPropertySetterAccessModifiersTheoryData().Select(m => $"{{ get; {m} set; }}") - .Concat([ - "{ get; }", - "=> 0;" - ]); - public static TheoryData NonPublicPropertySettersTheoryData() => new(NonPublicPropertySetters()); - } + public static IEnumerable NonPublicPropertySetters() + => new NonPublicPropertySetterAccessModifiersTheoryData() + .Select(m => $"{{ get; {m} set; }}") + .Concat(["{ get; }", "=> 0;"]); - public static TheoryData UniqueParameterAttributesTheoryData => new() - { - { "Params", "Params(3)" }, - { "ParamsSource", "ParamsSource(\"test\")" }, - { "ParamsAllValues", "ParamsAllValues" } - }; + public static TheoryData NonPublicPropertySettersTheoryData() + => [.. NonPublicPropertySetters()]; + } - public static TheoryData DuplicateSameAttributeUsagesTheoryData + public static TheoryData UniqueParameterAttributesTheoryData + => new() { - get - { - var theoryData = new TheoryData(); + { "Params", "Params(3)" }, + { "ParamsSource", "ParamsSource(\"test\")" }, + { "ParamsAllValues", "ParamsAllValues" } + }; - foreach (var duplicateSameAttributeUsageCombination in GenerateDuplicateSameAttributeUsageCombinations(UniqueParameterAttributesTheoryData)) - { - theoryData.Add(duplicateSameAttributeUsageCombination.CurrentUniqueAttributeUsage, duplicateSameAttributeUsageCombination.CurrentUniqueAttributeUsagePosition, duplicateSameAttributeUsageCombination.Counts); - } + public static TheoryData DuplicateSameAttributeUsagesTheoryData + { + get + { + var theoryData = new TheoryData(); - return theoryData; + foreach (var (CurrentUniqueAttributeUsage, CurrentUniqueAttributeUsagePosition, Counts) in GenerateDuplicateSameAttributeUsageCombinations(UniqueParameterAttributesTheoryData)) + { + theoryData.Add(CurrentUniqueAttributeUsage, CurrentUniqueAttributeUsagePosition, Counts); } + + return theoryData; } + } - public static TheoryData DuplicateAttributeUsageCountsTheoryData => new(GenerateDuplicateAttributeUsageCombinations(UniqueParameterAttributesTheoryData)); + public static TheoryData DuplicateAttributeUsageCountsTheoryData + => [.. GenerateDuplicateAttributeUsageCombinations(UniqueParameterAttributesTheoryData)]; - private static IEnumerable GenerateDuplicateAttributeUsageCombinations(TheoryData uniqueAttributeUsages) - { - var uniqueAttributeUsagesList = uniqueAttributeUsages.ToList() - .AsReadOnly(); + private static IEnumerable GenerateDuplicateAttributeUsageCombinations(TheoryData uniqueAttributeUsages) + { + var uniqueAttributeUsagesList = uniqueAttributeUsages.ToList().AsReadOnly(); - var allCombinations = CombinationsGenerator.GenerateCombinationsCounts(uniqueAttributeUsagesList.Count, 1); + var allCombinations = CombinationsGenerator.GenerateCombinationsCounts(uniqueAttributeUsagesList.Count, 1); - foreach (var currentCombination in allCombinations) + foreach (var currentCombination in allCombinations) + { + if (currentCombination.Sum() >= 2) { - if (currentCombination.Sum() >= 2) - { - yield return currentCombination; - } + yield return currentCombination; } } + } - private static ReadOnlyCollection<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> GenerateDuplicateSameAttributeUsageCombinations(TheoryData uniqueAttributeUsages) - { - var uniqueAttributeUsagesList = uniqueAttributeUsages.Select(tdr => (tdr[1] as string)!) - .ToList() - .AsReadOnly(); + private static ReadOnlyCollection<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)> GenerateDuplicateSameAttributeUsageCombinations(TheoryData uniqueAttributeUsages) + { + var uniqueAttributeUsagesList = uniqueAttributeUsages + .Select(tdr => (tdr[1] as string)!) + .ToList() + .AsReadOnly(); - var finalCombinationsList = new List<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)>(); + var finalCombinationsList = new List<(string CurrentUniqueAttributeUsage, int CurrentUniqueAttributeUsagePosition, int[] Counts)>(); - var allCombinations = CombinationsGenerator.GenerateCombinationsCounts(uniqueAttributeUsagesList.Count, 2).ToList() - .AsReadOnly(); + var allCombinations = CombinationsGenerator.GenerateCombinationsCounts(uniqueAttributeUsagesList.Count, 2) + .ToList() + .AsReadOnly(); - for (var i = 0; i < uniqueAttributeUsagesList.Count; i++) + for (var i = 0; i < uniqueAttributeUsagesList.Count; i++) + { + foreach (var currentCombination in allCombinations) { - foreach (var currentCombination in allCombinations) + if (currentCombination[i] > 0) { - if (currentCombination[i] > 0) - { - finalCombinationsList.Add((uniqueAttributeUsagesList[i], i, currentCombination)); - } + finalCombinationsList.Add((uniqueAttributeUsagesList[i], i, currentCombination)); } } - - return finalCombinationsList.AsReadOnly(); } + + return finalCombinationsList.AsReadOnly(); } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs index 7c97129107..1cf576799b 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAllValuesAttributeAnalyzerTests.cs @@ -1,258 +1,281 @@ -namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes +using BenchmarkDotNet.Analyzers.Attributes; +using BenchmarkDotNet.Analyzers.Tests.Fixtures; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes; + +public class ParamsAllValuesAttributeAnalyzerTests { - using Fixtures; + 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}} + } + """; - using BenchmarkDotNet.Analyzers.Attributes; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnumWithFlagsAttribute(); - using Xunit; + await RunAsync(); + } - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; + public static IEnumerable FieldOrPropertyDeclarations +#pragma warning disable IDE0028 // Simplify collection initialization + => new FieldOrPropertyDeclarationsTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization + + public static IEnumerable InvalidTypes => + [ + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "string", + "uint", + "ulong", + "ushort", + + "DummyEnumWithFlagsAttribute", + + "object", + "System.Type" + ]; + } - public class ParamsAllValuesAttributeAnalyzerTests + public class NotAllowedOnFlagsEnumPropertyOrFieldType : AnalyzerTestFixture { - public class General : 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) { - [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 FieldOrPropertyDeclarationsTheoryData(); - - public static IEnumerable InvalidTypes => new TheoryData - { - "byte", - "char", - "double", - "float", - "int", - "long", - "sbyte", - "short", - "string", - "uint", - "ulong", - "ushort", - - "DummyEnumWithFlagsAttribute", - - "object", - "System.Type" - }; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [ParamsAllValues] + public {{nonEnumType}}{{(isNullable ? "?" : "")}} {{fieldOrPropertyDeclaration}} + } + """; + + TestCode = testCode; + + await RunAsync(); } - public class NotAllowedOnFlagsEnumPropertyOrFieldType : AnalyzerTestFixture + [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) { - 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 FieldOrPropertyDeclarationsTheoryData(); - - 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" - ]); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [ParamsAllValues] + public DummyEnum{{(isNullable ? "?" : "")}} {{fieldOrPropertyDeclaration}} + } + """; + + TestCode = testCode; + ReferenceDummyEnum(); + + await RunAsync(); } - public class PropertyOrFieldTypeMustBeEnumOrBool : AnalyzerTestFixture + [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) { - 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 FieldOrPropertyDeclarationsTheoryData(); - - 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" - ]); + 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 +#pragma warning disable IDE0028 // Simplify collection initialization + => new FieldOrPropertyDeclarationsTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization + + public static IEnumerable NonEnumStructs => + [ + "bool", + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort", + ]; + + public static IEnumerable NonEnumTypes => NonEnumStructs.Concat( + [ + "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 +#pragma warning disable IDE0028 // Simplify collection initialization + => new FieldOrPropertyDeclarationsTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization + + public static IEnumerable NonEnumOrBoolStructs => + [ + "byte", + "char", + "double", + "float", + "int", + "long", + "sbyte", + "short", + "uint", + "ulong", + "ushort" + ]; + + public static IEnumerable NonEnumOrBoolTypes => NonEnumOrBoolStructs.Concat( + [ + "string", + "object", + "System.Type" + ]); } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs index f47ab8e5f2..d4f2eeabb1 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/Attributes/ParamsAttributeAnalyzerTests.cs @@ -1,975 +1,1012 @@ -namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes +using BenchmarkDotNet.Analyzers.Attributes; +using BenchmarkDotNet.Analyzers.Tests.Fixtures; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.Attributes; +public class ParamsAttributeAnalyzerTests { - using Fixtures; + 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; - using BenchmarkDotNet.Analyzers.Attributes; + public class BenchmarkClass + { + {{missingParamsAttributeUsage}} + public string {{fieldOrPropertyDeclaration}} + } + """; - using Xunit; + TestCode = testCode; + ReferenceDummyAttribute(); - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Threading.Tasks; + await RunAsync(); + } + + public static IEnumerable FieldOrPropertyDeclarations +#pragma warning disable IDE0028 // Simplify collection initialization + => new FieldOrPropertyDeclarationsTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization + } - public class ParamsAttributeAnalyzerTests + public class MustHaveValues : AnalyzerTestFixture { - public class General : 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) { - [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; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - public class BenchmarkClass - { - {{missingParamsAttributeUsage}} - public string {{fieldOrPropertyDeclaration}} - } - """; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, string.Join(", ", ScalarValues.Take(scalarValuesListLength)))}})] + public string {{fieldOrPropertyDeclaration}} + } + """; - TestCode = testCode; - ReferenceDummyAttribute(); + TestCode = testCode; + ReferenceDummyAttribute(); - await RunAsync(); - } - - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); + await RunAsync(); } - public class MustHaveValues : AnalyzerTestFixture + [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) { - public MustHaveValues() : base(ParamsAttributeAnalyzer.MustHaveValuesRule) { } + Assert.True(rankSpecifierSize > 0); - [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; + 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(new object[{{rankSpecifierSize}}])] + public string {{fieldOrPropertyDeclaration}} + } + """; - TestCode = testCode; - ReferenceDummyAttribute(); + TestCode = testCode; + DisableCompilerDiagnostics(); + ReferenceDummyAttribute(); - await RunAsync(); - } + 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); + [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; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}{{emptyParamsAttributeUsage}}] + public string {{fieldOrPropertyDeclaration}} + } + """; - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params(new object[{{rankSpecifierSize}}])] - public string {{fieldOrPropertyDeclaration}} - } - """; + TestCode = testCode; + ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(); - TestCode = testCode; - DisableCompilerDiagnostics(); - ReferenceDummyAttribute(); + await RunAsync(); + } - await RunAsync(); - } + public static IEnumerable FieldOrPropertyDeclarations +#pragma warning disable IDE0028 // Simplify collection initialization + => new FieldOrPropertyDeclarationsTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization - [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 static IEnumerable DummyAttributeUsage + => DummyAttributeUsageTheoryData; - public class BenchmarkClass - { - [{{dummyAttributeUsage}}{{emptyParamsAttributeUsage}}] - public string {{fieldOrPropertyDeclaration}} - } - """; + public static IEnumerable ScalarValuesListLength + => Enumerable.Range(1, ScalarValues.Count); - TestCode = testCode; - ReferenceDummyAttribute(); - AddDefaultExpectedDiagnostic(); + private static ReadOnlyCollection ScalarValues + => Enumerable.Range(1, 3) + .Select(i => $"\"test{i}\"") + .ToList() + .AsReadOnly(); - await RunAsync(); - } + public static IEnumerable ScalarValuesContainerAttributeArgument + => ScalarValuesContainerAttributeArgumentTheoryData(); - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); + public static IEnumerable EmptyParamsAttributeUsagesWithLocationMarker() + { + yield return "{|#0:Params|}"; + yield return "Params{|#0:()|}"; + yield return "Params({|#0:Priority = 1|})"; - public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + string[] nameColonUsages = + [ + "", + "values: " + ]; - public static IEnumerable ScalarValuesListLength => Enumerable.Range(1, ScalarValues.Count); + string[] priorityNamedParameterUsages = + [ + "", + ", Priority = 1" + ]; - private static ReadOnlyCollection ScalarValues => Enumerable.Range(1, 3) - .Select(i => $"\"test{i}\"") - .ToList() - .AsReadOnly(); - public static IEnumerable ScalarValuesContainerAttributeArgument => ScalarValuesContainerAttributeArgumentTheoryData(); + string[] attributeUsagesBase = + [ + "Params({0}new object[] {{|#0:{{ }}|}}{1})", + "Params({0}{{|#0:new object[0]|}}{1})", + "Params({0}{{|#0:[ ]|}}{1})", + ]; - public static IEnumerable EmptyParamsAttributeUsagesWithLocationMarker() + foreach (var attributeUsageBase in attributeUsagesBase) { - 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})", - "Params({0}{{|#0:[ ]|}}{1})", - }; - - foreach (var attributeUsageBase in attributeUsagesBase) + foreach (var nameColonUsage in nameColonUsages) { - foreach (var nameColonUsage in nameColonUsages) + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); } } } } + } + + public class MustHaveMatchingValueType : AnalyzerTestFixture + { + public MustHaveMatchingValueType() : base(ParamsAttributeAnalyzer.MustHaveMatchingValueTypeRule) { } - public class MustHaveMatchingValueType : AnalyzerTestFixture + [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(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument) { - public MustHaveMatchingValueType() : base(ParamsAttributeAnalyzer.MustHaveMatchingValueTypeRule) { } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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(ScalarValuesContainerAttributeArgumentEnumerable))] 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(); - } + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "42, 51, 33")}})] + public unknown {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + DisableCompilerDiagnostics(); - [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(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, - [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) - { - 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(); - } + 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(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_value_type_should_not_trigger_diagnostic( + [CombinatorialMemberData(nameof(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(ValuesAndTypes))] ValueTupleDouble valueAndType, + [CombinatorialMemberData(nameof(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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) + public class BenchmarkClass { - ReferenceConstants($"{valueAndType.Value2!}", valueAndType.Value1!); + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, valueAndType.Value1)}})] + public {{valueAndType.Value2}} {{fieldOrPropertyDeclaration}} } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); - await RunAsync(); - } + 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) + [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(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass { - ReferenceConstants($"{type}?", "null"); + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "null")}})] + public {{type}}? {{fieldOrPropertyDeclaration}} } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); - await RunAsync(); - } + 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(); - [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) + if (useConstantFromOtherClass) { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, value)}})] - public int {{fieldOrPropertyDeclaration}} - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - - await RunAsync(); + ReferenceConstants($"{valueAndType.Value2!}", valueAndType.Value1!); } - [Theory, CombinatorialData] - 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, - [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + 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) { - 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(); + ReferenceConstants($"{type}?", "null"); } - [Theory, CombinatorialData] - 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 valueWithUnknownType = "dummy_literal"; + await RunAsync(); + } - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + [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; - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnknownType}|}}, 33")}})] - public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, value)}})] + public int {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); - DisableCompilerDiagnostics(); + await RunAsync(); + } - await RunAsync(); - } + [Theory, CombinatorialData] + 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, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [Theory, CombinatorialData] - 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 valueWithUnexpectedType = "\"test1\""; + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{(explicitCast ? $"({integerValueAndType.Value2})" : "")}{integerValueAndType.Value1}")}})] + public {{integerValueAndType.Value2}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + await RunAsync(); + } - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnexpectedType}|}}, 33")}})] - public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); + [Theory, CombinatorialData] + 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 valueWithUnknownType = "dummy_literal"; - AddDefaultExpectedDiagnostic(valueWithUnexpectedType, expectedFieldOrPropertyType, "string"); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - await RunAsync(); - } + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnknownType}|}}, 33")}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); - [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(ScalarValuesContainerAttributeArgumentEnumerable))] string scalarValuesContainerAttributeArgument, - [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) - { - const string expectedFieldOrPropertyType = "decimal"; + DisableCompilerDiagnostics(); + + await RunAsync(); + } - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + [Theory, CombinatorialData] + 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 valueWithUnexpectedType = "\"test1\""; - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType.Value1}|}}")}})] - public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceDummyEnum(); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - AddDefaultExpectedDiagnostic(valueAndType.Value1!, expectedFieldOrPropertyType, valueAndType.Value2!); + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"42, {{|#0:{valueWithUnexpectedType}|}}, 33")}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); - await RunAsync(); - } + AddDefaultExpectedDiagnostic(valueWithUnexpectedType, expectedFieldOrPropertyType, "string"); - [Theory, CombinatorialData] - 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) + 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, + [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 { - ReferenceConstants(valueAndType.Value2!, valueAndType.Value1!); + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{valueAndType.Value1}|}}")}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); - AddDefaultExpectedDiagnostic(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!, expectedFieldOrPropertyType, valueAndType.Value2!); + AddDefaultExpectedDiagnostic(valueAndType.Value1!, expectedFieldOrPropertyType, valueAndType.Value2!); - await RunAsync(); - } + 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_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"; - [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; - - 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(); - } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [Theory, CombinatorialData] - 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(ArrayValuesContainerAttributeArgumentWithLocationMarkerEnumerable))] ValueTupleDouble arrayValuesContainerAttributeArgument, - [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) - { - const string expectedFieldOrPropertyType = "decimal"; - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params({{string.Format(arrayValuesContainerAttributeArgument.Value1!, valueAndType.Value1, valueAndType.Value2)}})] - public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - ReferenceDummyEnum(); - - AddDefaultExpectedDiagnostic(string.Format(arrayValuesContainerAttributeArgument.Value2!, - valueAndType.Value1, - valueAndType.Value2), - expectedFieldOrPropertyType, - $"{valueAndType.Value2}[]"); - - await RunAsync(); - } + 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(); - [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(DummyAttributeUsage))] string dummyAttributeUsage, - [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument, - [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + if (useConstantFromOtherClass) { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params{{emptyValuesAttributeArgument}}] - public decimal {{fieldOrPropertyDeclaration}} - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - - await RunAsync(); + ReferenceConstants(valueAndType.Value2!, valueAndType.Value1!); } - [Theory, CombinatorialData] - 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; - - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params{{emptyValuesAttributeArgument}}] - public object[] {{fieldOrPropertyDeclaration}} - } - """; - TestCode = testCode; - ReferenceDummyAttribute(); - - await RunAsync(); - } + AddDefaultExpectedDiagnostic(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : valueAndType.Value1!, expectedFieldOrPropertyType, valueAndType.Value2!); - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); + await RunAsync(); + } - public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + [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 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 class BenchmarkClass + { + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, "{|#0:null|}")}})] + public {{type}} {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); - 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"), - ]; + AddDefaultExpectedDiagnostic("null", type, "null"); - 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})", - "({0}[ ]{1})" - }; + await RunAsync(); + } - foreach (var attributeUsageBase in attributeUsagesBase) + [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; + + public class BenchmarkClass { - foreach (var nameColonUsage in nameColonUsages) - { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } - } + [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, $"{{|#0:{integerValueAndType.Value1}|}}")}})] + public {{integerValueAndType.Value2}} {{fieldOrPropertyDeclaration}} } - } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(integerValueAndType.Value1!, integerValueAndType.Value2!, integerValueAndType.Value3!); - public static IEnumerable ScalarValuesContainerAttributeArgumentEnumerable => ScalarValuesContainerAttributeArgumentTheoryData(); + await RunAsync(); + } - public static IEnumerable> ArrayValuesContainerAttributeArgumentWithLocationMarkerEnumerable() - { - 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 } } - ("{0}[ {{{{|#0:new[] {{{{ {{0}} }}}}|}}}} ]{1}", "new[] {{ {0} }}"), // [ new[] { 42 } ] - ("{0}[ {{{{|#0:new {{1}}[] {{{{ {{0}} }}}}|}}}} ]{1}", "new {1}[] {{ {0} }}"), // [ new int[] { 42 } ] - ("{0}new object[] {{{{ {{{{|#0:new {{1}}[] {{{{ }}}}|}}}} }}}}{1}", "new {1}[] {{ }}"), // new object[] { new int[] { } } - ("{0}[ {{{{|#0:new {{1}}[] {{{{ }}}}|}}}} ]{1}", "new {1}[] {{ }}"), // [ new int[] { } ] - ("{0}new object[] {{{{ {{{{|#0:new {{1}}[0]|}}}} }}}}{1}", "new {1}[0]"), // new object[] { new int[0] } - ("{0}[ {{{{|#0:new {{1}}[0]|}}}} ]{1}", "new {1}[0]"), // [ new int[0] ] - }; + [Theory, CombinatorialData] + 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(ArrayValuesContainerAttributeArgumentWithLocationMarkerEnumerable))] ValueTupleDouble arrayValuesContainerAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + const string expectedFieldOrPropertyType = "decimal"; - foreach (var attributeUsageBase in attributeUsagesBase) + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass { - foreach (var nameColonUsage in nameColonUsages) - { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return new ValueTupleDouble - { - Value1 = string.Format(attributeUsageBase.Item1, nameColonUsage, priorityNamedParameterUsage), - Value2 = attributeUsageBase.Item2 - }; - } - } + [{{dummyAttributeUsage}}Params({{string.Format(arrayValuesContainerAttributeArgument.Value1!, valueAndType.Value1, valueAndType.Value2)}})] + public {{expectedFieldOrPropertyType}} {{fieldOrPropertyDeclaration}} } - } + """; + TestCode = testCode; + ReferenceDummyAttribute(); + ReferenceDummyEnum(); + + AddDefaultExpectedDiagnostic( + string.Format(arrayValuesContainerAttributeArgument.Value2!, valueAndType.Value1, valueAndType.Value2), + expectedFieldOrPropertyType, + $"{valueAndType.Value2}[]" + ); + + 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(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [{{dummyAttributeUsage}}Params{{emptyValuesAttributeArgument}}] + public decimal {{fieldOrPropertyDeclaration}} + } + """; + TestCode = testCode; + ReferenceDummyAttribute(); - public static IEnumerable> ValuesAndTypes => + 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(DummyAttributeUsage))] string dummyAttributeUsage, + [CombinatorialMemberData(nameof(EmptyValuesAttributeArgument))] string emptyValuesAttributeArgument, + [CombinatorialMemberData(nameof(FieldOrPropertyDeclarations))] string fieldOrPropertyDeclaration) + { + 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 +#pragma warning disable IDE0028 // Simplify collection initialization + => new FieldOrPropertyDeclarationsTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization + + 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 ""; + yield return "()"; + yield return "(Priority = 1)"; + + string[] nameColonUsages = [ - ( "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" ), + "", + "values: " ]; - public static IEnumerable> NotConvertibleValuesAndTypes => + string[] priorityNamedParameterUsages = [ - ( "true", "bool" ), - ( "1.0D", "double" ), - ( "1.0F", "float" ), - ( """ - "test" - """, "string" ), - - ( """ - (object)"test_object" - """, "object" ), - ( "typeof(string)", "System.Type" ), - ( "DummyEnum.Value1", "DummyEnum" ) + "", + ", Priority = 1" ]; - public static IEnumerable> ConstantValuesAndTypes => + string[] attributeUsagesBase = [ - ( "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" ), + "({0}new object[] {{ }}{1})", + "({0}new object[0]{1})", + "({0}[ ]{1})" ]; - public static IEnumerable NullableStructTypes => + 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 ScalarValuesContainerAttributeArgumentEnumerable + => ScalarValuesContainerAttributeArgumentTheoryData(); + + public static IEnumerable> ArrayValuesContainerAttributeArgumentWithLocationMarkerEnumerable() + { + string[] nameColonUsages = [ - "bool", - "byte", - "char", - "double", - "float", - "int", - "long", - "sbyte", - "short", - "uint", - "ulong", - "ushort", - "DummyEnum", + "", + "values: " ]; - public static IEnumerable NullReferenceConstantTypes => + string[] priorityNamedParameterUsages = [ - "object", - "string", - "System.Type", + "", + ", Priority = 1" ]; - public static IEnumerable> NotConvertibleConstantValuesAndTypes => + (string, string)[] attributeUsagesBase = [ - ( "true", "bool" ), - ( "1.0D", "double" ), - ( "1.0F", "float" ), - ( """ - "test" - """, "string" ), - - ( "DummyEnum.Value1", "DummyEnum" ) + ("{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 } } + ("{0}[ {{{{|#0:new[] {{{{ {{0}} }}}}|}}}} ]{1}", "new[] {{ {0} }}"), // [ new[] { 42 } ] + ("{0}[ {{{{|#0:new {{1}}[] {{{{ {{0}} }}}}|}}}} ]{1}", "new {1}[] {{ {0} }}"), // [ new int[] { 42 } ] + ("{0}new object[] {{{{ {{{{|#0:new {{1}}[] {{{{ }}}}|}}}} }}}}{1}", "new {1}[] {{ }}"), // new object[] { new int[] { } } + ("{0}[ {{{{|#0:new {{1}}[] {{{{ }}}}|}}}} ]{1}", "new {1}[] {{ }}"), // [ new int[] { } ] + ("{0}new object[] {{{{ {{{{|#0:new {{1}}[0]|}}}} }}}}{1}", "new {1}[0]"), // new object[] { new int[0] } + ("{0}[ {{{{|#0:new {{1}}[0]|}}}} ]{1}", "new {1}[0]"), // [ new int[0] ] ]; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) + { + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) + { + yield return new ValueTupleDouble + { + Value1 = string.Format(attributeUsageBase.Item1, nameColonUsage, priorityNamedParameterUsage), + Value2 = attributeUsageBase.Item2 + }; + } + } + } } - public class UnnecessarySingleValuePassedToAttribute : AnalyzerTestFixture + 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" ), + ( """ + "test" + """, "string" ), + ( "123U", "uint" ), + ( "123UL", "ulong" ), + ( "(ushort)123", "ushort" ), + + ( """ + (object)"test_object" + """, "object" ), + ( "typeof(string)", "System.Type" ), + ( "DummyEnum.Value1", "DummyEnum" ), + ]; + + public static IEnumerable> NotConvertibleValuesAndTypes => + [ + ( "true", "bool" ), + ( "1.0D", "double" ), + ( "1.0F", "float" ), + ( """ + "test" + """, "string" ), + + ( """ + (object)"test_object" + """, "object" ), + ( "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 + { + 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) { - public UnnecessarySingleValuePassedToAttribute() : base(ParamsAttributeAnalyzer.UnnecessarySingleValuePassedToAttributeRule) { } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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}} + } + """; - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, string.Join(", ", ScalarValues.Take(scalarValuesListLength)))}})] - public string {{fieldOrPropertyDeclaration}} - } - """; + TestCode = testCode; + ReferenceDummyAttribute(); - TestCode = testCode; - ReferenceDummyAttribute(); + await RunAsync(); + } - 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; - [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}} + } + """; - public class BenchmarkClass - { - [{{dummyAttributeUsage}}Params({{string.Format(scalarValuesContainerAttributeArgument, 42)}})] - public string {{fieldOrPropertyDeclaration}} - } - """; + TestCode = testCode; + ReferenceDummyAttribute(); - TestCode = testCode; - ReferenceDummyAttribute(); + AddDefaultExpectedDiagnostic(); - AddDefaultExpectedDiagnostic(); + await RunAsync(); + } - await RunAsync(); - } + public static IEnumerable FieldOrPropertyDeclarations +#pragma warning disable IDE0028 // Simplify collection initialization + => new FieldOrPropertyDeclarationsTheoryData(); +#pragma warning restore IDE0028 // Simplify collection initialization - public static IEnumerable FieldOrPropertyDeclarations => new FieldOrPropertyDeclarationsTheoryData(); + public static IEnumerable DummyAttributeUsage + => DummyAttributeUsageTheoryData; - public static IEnumerable DummyAttributeUsage => DummyAttributeUsageTheoryData; + public static IEnumerable ScalarValuesListLength + => Enumerable.Range(2, ScalarValues.Count); - 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(); - private static ReadOnlyCollection ScalarValues => Enumerable.Range(1, 2) - .Select(i => $"\"test{i}\"") - .ToList() - .AsReadOnly(); - public static IEnumerable ScalarValuesContainerAttributeArgument => ScalarValuesContainerAttributeArgumentTheoryData(); + public static IEnumerable ScalarValuesContainerAttributeArgumentWithLocationMarker() + { + return GenerateData().Distinct(); - public static IEnumerable ScalarValuesContainerAttributeArgumentWithLocationMarker() + static IEnumerable GenerateData() { - return GenerateData().Distinct(); + string[] nameColonUsages = + [ + "", + "values: " + ]; + + string[] priorityNamedParameterUsages = + [ + "", + ", Priority = 1" + ]; + + string[] attributeUsagesBase = + [ + "{{{{|#0:{{0}}|}}}}{1}", + "{0}new object[] {{{{ {{{{|#0:{{0}}|}}}} }}}}{1}", + "{0}[ {{{{|#0:{{0}}|}}}} ]{1}", + ]; - static IEnumerable GenerateData() + foreach (var attributeUsageBase in attributeUsagesBase) { - var nameColonUsages = new List - { - "", - "values: " - }; - - 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 nameColonUsage in nameColonUsages) { - foreach (var nameColonUsage in nameColonUsages) + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); } } } } } + } - public static TheoryData DummyAttributeUsageTheoryData => [ - "", - "Dummy, " - ]; + public static TheoryData DummyAttributeUsageTheoryData => ["", "Dummy, "]; + + public static TheoryData ScalarValuesContainerAttributeArgumentTheoryData() + { + return [.. GenerateData().Distinct()]; - public static TheoryData ScalarValuesContainerAttributeArgumentTheoryData() + static IEnumerable GenerateData() { - return new TheoryData(GenerateData().Distinct()); + string[] nameColonUsages = + [ + "", + "values: " + ]; - static IEnumerable GenerateData() - { - var nameColonUsages = new List - { - "", - "values: " - }; - - var priorityNamedParameterUsages = new List - { - "", - ", Priority = 1" - }; - - var attributeUsagesBase = new List - { - "{{0}}{1}", - "{0}new object[] {{{{ {{0}} }}}}{1}", - "{0}[ {{0}} ]{1}" - }; + string[] priorityNamedParameterUsages = + [ + "", + ", Priority = 1" + ]; - foreach (var attributeUsageBase in attributeUsagesBase) + string[] attributeUsagesBase = + [ + "{{0}}{1}", + "{0}new object[] {{{{ {{0}} }}}}{1}", + "{0}[ {{0}} ]{1}" + ]; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) { - foreach (var nameColonUsage in nameColonUsages) + foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) { - foreach (var priorityNamedParameterUsage in priorityNamedParameterUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); - } + yield return string.Format(attributeUsageBase, nameColonUsage, priorityNamedParameterUsage); } } } } } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs index 34f49bfbeb..01f35bff30 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/BenchmarkRunner/RunAnalyzerTests.cs @@ -1,903 +1,919 @@ -namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.BenchmarkRunner +using BenchmarkDotNet.Analyzers.BenchmarkRunner; +using BenchmarkDotNet.Analyzers.Tests.Fixtures; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.BenchmarkRunner; + +public class RunAnalyzerTests { - using Fixtures; + public class General : AnalyzerTestFixture + { + [Theory, CombinatorialData] + 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 = isGenericInvocation ? $"<{classWithOneBenchmarkMethodName}>()" : $"(typeof({classWithOneBenchmarkMethodName}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Running; - using Analyzers.BenchmarkRunner; + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + """; - using Xunit; + const string benchmarkClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Threading.Tasks; + public class {{classWithOneBenchmarkMethodName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + + await RunAsync(); + } + } - public class RunAnalyzerTests + public class TypeArgumentClassMissingBenchmarkMethods : AnalyzerTestFixture { - public class General : AnalyzerTestFixture + public TypeArgumentClassMissingBenchmarkMethods() : base(RunAnalyzer.TypeArgumentClassMissingBenchmarkMethodsRule) { } + + [Theory, CombinatorialData] + public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic(bool isGenericInvocation) { - [Theory, CombinatorialData] - 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 = isGenericInvocation ? $"<{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}} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; - - TestCode = testCode; - AddSource(benchmarkClassDocument); - - await RunAsync(); - } + const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + + var invocationExpression = isGenericInvocation ? $"<{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}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + + await RunAsync(); } - public class TypeArgumentClassMissingBenchmarkMethods : AnalyzerTestFixture + [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 isGenericInvocation, + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier) { - public TypeArgumentClassMissingBenchmarkMethods() : base(RunAnalyzer.TypeArgumentClassMissingBenchmarkMethodsRule) { } - - [Theory, CombinatorialData] - public async Task Invoking_with_type_argument_class_having_at_least_one_public_method_annotated_with_the_benchmark_attribute_should_not_trigger_diagnostic(bool isGenericInvocation) - { - const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - - var invocationExpression = isGenericInvocation ? $"<{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}} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - - public void BenchmarkMethod2() - { - - } - - private void BenchmarkMethod3() - { - - } - } - """; - - TestCode = testCode; - AddSource(benchmarkClassDocument); - - 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(bool isGenericInvocation, [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier) - { - const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; - - var invocationExpression = isGenericInvocation ? $"<{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 */ $$""" - public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 - { - } - """; - - var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" - 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_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) - { - 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)}}>)); - } - } - """; - - var benchmarkClassDocument = /* lang=c#-test */ $$""" - public class BenchmarkClass<{{typeParameters}}> : 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 - { - [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 isGenericInvocation, [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier) - { - const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; - - var invocationExpression = isGenericInvocation ? $"<{{|#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; - - 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() - { - - } - - public void BenchmarkMethod2() - { - - } - - private void BenchmarkMethod3() - { - - } - } - """; - - TestCode = testCode; - AddSource(benchmarkClassDocument); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); - AddSource(benchmarkClassAncestor3Document); - - AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); - - await RunAsync(); - } - - [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 = "BenchmarkClass"; - - var unboundGenericTypeParameterList = new string(',', typeParametersListLength - 1); - 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({|#0:{{classWithOneBenchmarkMethodName}}<{{unboundGenericTypeParameterList}}>|})); - } - } - """; - - var benchmarkClassDocument = /* lang=c#-test */ $$""" - public class {{classWithOneBenchmarkMethodName}}<{{typeParameters}}> : 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 - { - public void BenchmarkMethod() - { - - } - - private void BenchmarkMethod2() - { - - } - } - """; - - TestCode = testCode; - AddSource(benchmarkClassDocument); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); - AddSource(benchmarkClassAncestor3Document); - - 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 isGenericInvocation) - { - const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; - - var invocationExpression = isGenericInvocation ? $"<{{|#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}} - { - public void BenchmarkMethod() - { - - } - } - """; - TestCode = testCode; - AddSource(benchmarkClassDocument); - - AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); - - await RunAsync(); - } - - public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; - - public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; + + var invocationExpression = isGenericInvocation ? $"<{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 */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; + + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + 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_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) + { + 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)}}>)); + } + } + """; + + var benchmarkClassDocument = /* lang=c#-test */ $$""" + public class BenchmarkClass<{{typeParameters}}> : 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 + { + [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 isGenericInvocation, + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier) + { + const string classWithOneBenchmarkMethodName = "TopLevelBenchmarkClass"; + + var invocationExpression = isGenericInvocation ? $"<{{|#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; + + 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() + { + + } + + public void BenchmarkMethod2() + { + + } + + private void BenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + + AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); + + await RunAsync(); } - public class TypeArgumentClassMustBePublic : AnalyzerTestFixture + [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) { - 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 isGenericInvocation) - { - const string benchmarkClassName = "BenchmarkClass"; - - var invocationExpression = isGenericInvocation ? $"<{{|#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{{invocationExpression}}; - } - - 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, 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 isGenericInvocation) - { - const string benchmarkClassName = "BenchmarkClass"; - - var invocationExpression = isGenericInvocation ? $"<{{|#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{{invocationExpression}}; - } - - {{nonPublicClassAccessModifier}}class {{benchmarkClassName}} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - } - """; - - TestCode = testCode; - - AddDefaultExpectedDiagnostic(benchmarkClassName); - - await RunAsync(); - } - - [Theory, CombinatorialData] - 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 = isGenericInvocation ? $"<{{|#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{{invocationExpression}}; - } - } + const string classWithOneBenchmarkMethodName = "BenchmarkClass"; + + var unboundGenericTypeParameterList = new string(',', typeParametersListLength - 1); + 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({|#0:{{classWithOneBenchmarkMethodName}}<{{unboundGenericTypeParameterList}}>|})); + } + } + """; + + var benchmarkClassDocument = /* lang=c#-test */ $$""" + public class {{classWithOneBenchmarkMethodName}}<{{typeParameters}}> : 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 + { + public void BenchmarkMethod() + { + + } + + private void BenchmarkMethod2() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + + 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 isGenericInvocation) + { + const string classWithOneBenchmarkMethodName = "ClassWithOneBenchmarkMethod"; + + var invocationExpression = isGenericInvocation ? $"<{{|#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}} + { + public void BenchmarkMethod() + { + + } + } + """; + TestCode = testCode; + AddSource(benchmarkClassDocument); + + AddDefaultExpectedDiagnostic(classWithOneBenchmarkMethodName); + + await RunAsync(); + } + + public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + + public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + } + + 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 isGenericInvocation) + { + const string benchmarkClassName = "BenchmarkClass"; + + var invocationExpression = isGenericInvocation ? $"<{{|#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{{invocationExpression}}; + } + + 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, 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 isGenericInvocation) + { + const string benchmarkClassName = "BenchmarkClass"; + + var invocationExpression = isGenericInvocation ? $"<{{|#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{{invocationExpression}}; + } - file class {{benchmarkClassName}} - { - [Benchmark] - public void BenchmarkMethod() - { + {{nonPublicClassAccessModifier}}class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + } + """; + + TestCode = testCode; + + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); + } - } - } - """; + [Theory, CombinatorialData] + 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 = isGenericInvocation ? $"<{{|#0:{benchmarkClassName}|}}>()" : $"(typeof({{|#0:{benchmarkClassName}|}}))"; + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Running; - TestCode = testCode; + public class Program + { + public static void Main(string[] args) { + BenchmarkRunner.Run{{invocationExpression}}; + } + } + + file class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { - AddDefaultExpectedDiagnostic(benchmarkClassName); + } + } + """; - await RunAsync(); - } + TestCode = testCode; - public static IEnumerable NonPublicClassAccessModifiersExceptFile => new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file "); + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); } - public class TypeArgumentClassMustBeUnsealed : AnalyzerTestFixture + public static IEnumerable NonPublicClassAccessModifiersExceptFile + => new NonPublicClassAccessModifiersTheoryData().Where(m => m != "file "); + } + + public class TypeArgumentClassMustBeUnsealed : AnalyzerTestFixture + { + public TypeArgumentClassMustBeUnsealed() : base(RunAnalyzer.TypeArgumentClassMustBeUnsealedRule) { } + + [Theory, CombinatorialData] + public async Task Invoking_with_a_sealed_benchmark_class_should_trigger_diagnostic(bool isGenericInvocation) { - public TypeArgumentClassMustBeUnsealed() : base(RunAnalyzer.TypeArgumentClassMustBeUnsealedRule) { } - - [Theory, CombinatorialData] - public async Task Invoking_with_a_sealed_benchmark_class_should_trigger_diagnostic(bool isGenericInvocation) - { - const string benchmarkClassName = "BenchmarkClass"; - - var invocationExpression = isGenericInvocation ? $"<{{|#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; - - public sealed class {{benchmarkClassName}} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; - TestCode = testCode; - AddSource(benchmarkClassDocument); - AddDefaultExpectedDiagnostic(benchmarkClassName); - - await RunAsync(); - } + const string benchmarkClassName = "BenchmarkClass"; + + var invocationExpression = isGenericInvocation ? $"<{{|#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; + + public sealed class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddDefaultExpectedDiagnostic(benchmarkClassName); + + await RunAsync(); } + } - public class TypeArgumentClassMustBeNonAbstract : AnalyzerTestFixture + public class TypeArgumentClassMustBeNonAbstract : AnalyzerTestFixture + { + public TypeArgumentClassMustBeNonAbstract() : base(RunAnalyzer.TypeArgumentClassMustBeNonAbstractRule) { } + + [Theory, CombinatorialData] + public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagnostic(bool isGenericInvocation) { - public TypeArgumentClassMustBeNonAbstract() : base(RunAnalyzer.TypeArgumentClassMustBeNonAbstractRule) { } - - [Theory, CombinatorialData] - public async Task Invoking_with_an_abstract_benchmark_class_should_trigger_diagnostic(bool isGenericInvocation) - { - const string benchmarkClassName = "BenchmarkClass"; - - var invocationExpression = isGenericInvocation ? $"<{{|#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; + const string benchmarkClassName = "BenchmarkClass"; + + var invocationExpression = isGenericInvocation ? $"<{{|#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; + + public abstract class {{benchmarkClassName}} + { + [Benchmark] + public void BenchmarkMethod() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddDefaultExpectedDiagnostic(benchmarkClassName); + + 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}}> + { - public abstract class {{benchmarkClassName}} - { - [Benchmark] - public void BenchmarkMethod() - { - - } - } - """; - TestCode = testCode; - AddSource(benchmarkClassDocument); - AddDefaultExpectedDiagnostic(benchmarkClassName); - - await RunAsync(); - } + } + """; + 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(); } - public class GenericTypeArgumentClassMustBeAnnotatedWithAGenericTypeArgumentsAttribute : AnalyzerTestFixture + [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) { - 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() - { + 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 benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" - public {{abstractModifier}}class BenchmarkClassAncestor1<{{typeParameters}}> : BenchmarkClassAncestor2<{{typeParameters}}> - { - - } - """; - var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + } + """; + 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_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)); + + 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 {{abstractModifier}}class BenchmarkClassAncestor2<{{typeParameters}}> - { + public void BenchmarkMethod2() + { + + } - {{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_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)); - - 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() - { - - } - } - """; + private void BenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + + await RunAsync(); + } - var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" - public {{abstractModifier}}class BenchmarkClassAncestor1<{{typeParameters}}> : BenchmarkClassAncestor2<{{typeParameters}}> - { - - } - """; + [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; + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - {{genericTypeArgumentsAttributeUsages}} - public {{abstractModifier}}class BenchmarkClassAncestor2<{{typeParameters}}> - { + {{genericTypeArgumentsAttributeUsages}} + public {{abstractModifier}}class BenchmarkClassAncestor2<{{typeParameters}}> + { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { - - } - } - """; + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; - TestCode = testCode; - AddSource(benchmarkClassDocument); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); + TestCode = testCode; + AddSource(benchmarkClassDocument); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); - AddDefaultExpectedDiagnostic($"{benchmarkClassName}<{unboundGenericTypeParameterList}>"); + AddDefaultExpectedDiagnostic($"{benchmarkClassName}<{unboundGenericTypeParameterList}>"); - await RunAsync(); - } + await RunAsync(); + } - public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + public static IEnumerable ClassAbstractModifiersEnumerableLocal + => ClassAbstractModifiersEnumerable; - public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal + => BenchmarkAttributeUsagesEnumerable; - public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; - } + public static IEnumerable TypeParametersListLengthEnumerableLocal + => TypeParametersListLengthEnumerable; + } - public static IEnumerable ClassAbstractModifiersEnumerable => [ "", "abstract " ]; + public static IEnumerable ClassAbstractModifiersEnumerable + => ["", "abstract "]; - public static IEnumerable BenchmarkAttributeUsagesEnumerable => [ "", "[Benchmark]" ]; + public static IEnumerable BenchmarkAttributeUsagesEnumerable + => ["", "[Benchmark]"]; - public static IEnumerable TypeParametersListLengthEnumerable => Enumerable.Range(1, TypeParameters.Count); + 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 TypeParameters + => Enumerable.Range(1, 3) + .Select(i => $"TParameter{i}") + .ToList() + .AsReadOnly(); - private static ReadOnlyCollection GenericTypeArguments => new List { "int", "string", "bool" }.AsReadOnly(); - } -} + private static IReadOnlyCollection GenericTypeArguments + => ["int", "string", "bool"]; +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs index 1319ba7d9b..9f341b1160 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/AnalyzerTests/General/BenchmarkClassAnalyzerTests.cs @@ -1,1889 +1,1943 @@ -namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.General +using BenchmarkDotNet.Analyzers.General; +using BenchmarkDotNet.Analyzers.Tests.Fixtures; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.AnalyzerTests.General; +public class BenchmarkClassAnalyzerTests { - using Fixtures; + public class General : AnalyzerTestFixture + { + [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) + { + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public{{abstractModifier}} class BenchmarkClass + { + public void BenchmarkMethod() + { - using Analyzers.General; + } + } + """; - using Xunit; + TestCode = testCode; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Threading.Tasks; + await RunAsync(); + } + } - public class BenchmarkClassAnalyzerTests + public class ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract : AnalyzerTestFixture { - public class General : AnalyzerTestFixture + 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, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { - [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) - { - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + const string benchmarkClassName = "BenchmarkClass"; - public{{abstractModifier}} class BenchmarkClass - { - public void BenchmarkMethod() - { + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[{{|#{0}:GenericTypeArguments(typeof(int))|}}]", genericTypeArgumentsAttributeUsageCount).Select((a, i) => string.Format(a, i)); - } - } - """; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - TestCode = testCode; + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public abstract class {{benchmarkClassName}} + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { - await RunAsync(); - } - } + } + } + """; - public class ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract : AnalyzerTestFixture - { - public ClassWithGenericTypeArgumentsAttributeMustBeNonAbstract() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeNonAbstractRule) { } + TestCode = testCode; - [Theory, CombinatorialData] - 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) + for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) { - const string benchmarkClassName = "BenchmarkClass"; + AddExpectedDiagnostic(i); + } - var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[{{|#{0}:GenericTypeArguments(typeof(int))|}}]", genericTypeArgumentsAttributeUsageCount).Select((a, i) => string.Format(a, i)); + await RunAsync(); + } - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; + } - {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} - public abstract class {{benchmarkClassName}} - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { + public class ClassWithGenericTypeArgumentsAttributeMustBeGeneric : AnalyzerTestFixture + { + public ClassWithGenericTypeArgumentsAttributeMustBeGeneric() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeGenericRule) { } - } - } - """; + [Theory, CombinatorialData] + public async Task Generic_class_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic( + [CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", genericTypeArgumentsAttributeUsageCount); - TestCode = testCode; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public class BenchmarkClass { - AddExpectedDiagnostic(i); + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } } + """; - await RunAsync(); - } + TestCode = testCode; - public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; + await RunAsync(); } - public class ClassWithGenericTypeArgumentsAttributeMustBeGeneric : AnalyzerTestFixture + [Theory, CombinatorialData] + public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_should_trigger_diagnostic( + [CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { - public ClassWithGenericTypeArgumentsAttributeMustBeGeneric() : base(BenchmarkClassAnalyzer.ClassWithGenericTypeArgumentsAttributeMustBeGenericRule) { } - - [Theory, CombinatorialData] - public async Task Generic_class_annotated_with_a_generictypearguments_attribute_should_not_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) - { - var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[GenericTypeArguments(typeof(int))]", genericTypeArgumentsAttributeUsageCount); - - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[{{|#{0}:GenericTypeArguments(typeof(int))|}}]", genericTypeArgumentsAttributeUsageCount) + .Select((a, i) => string.Format(a, i)); + + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public class BenchmarkClass + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } + } + """; - {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} - public class BenchmarkClass - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { + TestCode = testCode; - } - } - """; + for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) + { + AddExpectedDiagnostic(i); + } - TestCode = testCode; + await RunAsync(); + } - await RunAsync(); - } + [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, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[{{{{|#{{0}}:GenericTypeArguments({genericTypeArguments})|}}}}]", genericTypeArgumentsAttributeUsageCount) + .Select((a, i) => string.Format(a, i)); - [Theory, CombinatorialData] - public async Task Nongeneric_class_annotated_with_a_generictypearguments_attribute_should_trigger_diagnostic([CombinatorialRange(1, 2)] int genericTypeArgumentsAttributeUsageCount, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) - { - var genericTypeArgumentsAttributeUsages = Enumerable.Repeat("[{{|#{0}:GenericTypeArguments(typeof(int))|}}]", genericTypeArgumentsAttributeUsageCount) - .Select((a, i) => string.Format(a, i)); + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; - - {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} - public class BenchmarkClass - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { - - } - } - """; + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public class BenchmarkClass : BenchmarkClassBase<{{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}}> + { + } + """; - TestCode = testCode; + var benchmarkBaseClassDocument = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) + public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> { - AddExpectedDiagnostic(i); + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { + + } } + """; - await RunAsync(); - } + TestCode = testCode; + AddSource(benchmarkBaseClassDocument); - [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, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) { - var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); - var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[{{{{|#{{0}}:GenericTypeArguments({genericTypeArguments})|}}}}]", genericTypeArgumentsAttributeUsageCount) - .Select((a, i) => string.Format(a, i)); + AddExpectedDiagnostic(i); + } - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + await RunAsync(); + } - {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} - public class BenchmarkClass : BenchmarkClassBase<{{string.Join(", ", GenericTypeArguments.Take(typeParametersListLength))}}> - { - } - """; + public static IEnumerable TypeParametersListLengthEnumerableLocal + => TypeParametersListLengthEnumerable; - var benchmarkBaseClassDocument = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + private static ReadOnlyCollection TypeParameters + => TypeParametersTheoryData; - public abstract class BenchmarkClassBase<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { + private static IReadOnlyCollection GenericTypeArguments + => GenericTypeArgumentsTheoryData; - } - } - """; + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal + => BenchmarkAttributeUsagesEnumerable; + } - TestCode = testCode; - AddSource(benchmarkBaseClassDocument); + public class GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount : AnalyzerTestFixture + { + public GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount() : base(BenchmarkClassAnalyzer.GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCountRule) { } - for (var i = 0; i < genericTypeArgumentsAttributeUsageCount; i++) - { - AddExpectedDiagnostic(i); - } + [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, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) + { + var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); + var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", genericTypeArgumentsAttributeUsageCount); - await RunAsync(); - } + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} + public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { - private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; + } + } + """; - private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; + TestCode = testCode; - public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; + await RunAsync(); } - public class GenericTypeArgumentsAttributeMustHaveMatchingTypeParameterCount : AnalyzerTestFixture + [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, + [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) { - 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, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) - { - var genericTypeArguments = string.Join(", ", GenericTypeArguments.Select(ta => $"typeof({ta})").Take(typeParametersListLength)); - var genericTypeArgumentsAttributeUsages = Enumerable.Repeat($"[GenericTypeArguments({genericTypeArguments})]", genericTypeArgumentsAttributeUsageCount); + const string benchmarkClassName = "BenchmarkClass"; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - {{string.Join("\n", genericTypeArgumentsAttributeUsages)}} - public class BenchmarkClass<{{string.Join(", ", TypeParameters.Take(typeParametersListLength))}}> - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { + [GenericTypeArguments({|#0:{{typeArgumentsData.Value1}}|})] + [GenericTypeArguments(typeof(int))] + public class {{benchmarkClassName}} + { + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { - } - } - """; + } + } + """; - TestCode = testCode; + TestCode = testCode; + AddDefaultExpectedDiagnostic(1, "", benchmarkClassName, typeArgumentsData.Value2); - await RunAsync(); - } + await RunAsync(); + } - [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, - [CombinatorialMemberData(nameof(BenchmarkAttributeUsagesEnumerableLocal))] string benchmarkAttributeUsage) - { - const string benchmarkClassName = "BenchmarkClass"; + public static IEnumerable> TypeArgumentsData => + [ + ("typeof(int), typeof(string)", 2), + ("typeof(int), typeof(string), typeof(bool)", 3) + ]; - var testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + public static IEnumerable TypeParametersListLengthEnumerableLocal + => TypeParametersListLengthEnumerable; - [GenericTypeArguments({|#0:{{typeArgumentsData.Value1}}|})] - [GenericTypeArguments(typeof(int))] - public class {{benchmarkClassName}} - { - {{benchmarkAttributeUsage}} - public void BenchmarkMethod() - { + private static ReadOnlyCollection TypeParameters + => TypeParametersTheoryData; - } - } - """; + private static IReadOnlyCollection GenericTypeArguments + => GenericTypeArgumentsTheoryData; - TestCode = testCode; - AddDefaultExpectedDiagnostic(1, "", benchmarkClassName, typeArgumentsData.Value2); + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal + => BenchmarkAttributeUsagesEnumerable; + } - await RunAsync(); - } + public class MethodMustBePublic : AnalyzerTestFixture + { + public MethodMustBePublic() : base(BenchmarkClassAnalyzer.MethodMustBePublicRule) { } - public static IEnumerable> TypeArgumentsData => - [ - ("typeof(int), typeof(string)", 2), - ("typeof(int), typeof(string), typeof(bool)", 3) - ]; + [Fact] + public async Task Public_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() + { + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; - public static IEnumerable TypeParametersListLengthEnumerableLocal => TypeParametersListLengthEnumerable; + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { - private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; + } + + public void NonBenchmarkMethod() + { + + } + } + """; - private static ReadOnlyCollection GenericTypeArguments => GenericTypeArgumentsTheoryData; + TestCode = testCode; - public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; + await RunAsync(); } - public class MethodMustBePublic : AnalyzerTestFixture + [Theory] + [ClassData(typeof(NonPublicClassMemberAccessModifiersTheoryData))] + public async Task Nonpublic_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(string nonPublicClassAccessModifier) { - 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(); - } + const string benchmarkMethodName = "BenchmarkMethod"; - [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; + 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); + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkMethodName); - await RunAsync(); - } + await RunAsync(); } + } - public class MethodMustBeNonGeneric : AnalyzerTestFixture + public class MethodMustBeNonGeneric : AnalyzerTestFixture + { + public MethodMustBeNonGeneric() : base(BenchmarkClassAnalyzer.MethodMustBeNonGenericRule) { } + + [Fact] + public async Task Nongeneric_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - public MethodMustBeNonGeneric() : base(BenchmarkClassAnalyzer.MethodMustBeNonGenericRule) { } + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass + { + [Benchmark] + public void NonGenericBenchmarkMethod() + { + + } + } + """; - [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; - TestCode = testCode; + await RunAsync(); + } - 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; - [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() + { - public class BenchmarkClass - { - public void GenericMethod() - { + } + } + """; - } - } - """; + TestCode = testCode; - TestCode = testCode; + await RunAsync(); + } - await RunAsync(); - } + [Theory] + [MemberData(nameof(TypeParametersListLength))] + public async Task Nongeneric_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) + { + const string benchmarkMethodName = "GenericBenchmarkMethod"; - [Theory] - [MemberData(nameof(TypeParametersListLength))] - public async Task Nongeneric_method_annotated_with_benchmark_attribute_should_trigger_diagnostic(int typeParametersListLength) - { - 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); - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkMethodName); + await RunAsync(); + } - await RunAsync(); - } + public static TheoryData TypeParametersListLength + => TypeParametersListLengthTheoryData; - public static TheoryData TypeParametersListLength => TypeParametersListLengthTheoryData; + private static ReadOnlyCollection TypeParameters + => TypeParametersTheoryData; + } - private static ReadOnlyCollection TypeParameters => TypeParametersTheoryData; - } + public class ClassMustBeNonStatic : AnalyzerTestFixture + { + public ClassMustBeNonStatic() : base(BenchmarkClassAnalyzer.ClassMustBeNonStaticRule) { } - public class ClassMustBeNonStatic : AnalyzerTestFixture + [Fact] + public async Task Instance_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_not_trigger_diagnostic() { - 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(); - } + const string testCode = /* lang=c#-test */ """ + using BenchmarkDotNet.Attributes; - [Fact] - public async Task Static_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() - { - const string benchmarkClassName = "BenchmarkClass"; + public class BenchmarkClass + { + [Benchmark] + public void BenchmarkMethod() + { - const string testCode = /* lang=c#-test */ $$""" - using BenchmarkDotNet.Attributes; + } - public {|#0:static|} class {{benchmarkClassName}} - { - [Benchmark] - public static void BenchmarkMethod() - { + public void NonBenchmarkMethod() + { - } - } - """; + } + } + """; - TestCode = testCode; - AddDefaultExpectedDiagnostic(benchmarkClassName); + TestCode = testCode; - await RunAsync(); - } + await RunAsync(); } - public class SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed : AnalyzerTestFixture + [Fact] + public async Task Static_class_containing_at_least_one_method_annotated_with_benchmark_attribute_should_trigger_diagnostic() { - public SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed() : base(BenchmarkClassAnalyzer.SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule) - { - } + const string benchmarkClassName = "BenchmarkClass"; - [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 - { - } - """; + const string testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public {|#0:static|} class {{benchmarkClassName}} + { + [Benchmark] + public static void BenchmarkMethod() + { + + } + } + """; - var benchmarkCategoryAttributeUsage = $"[BenchmarkCategory({(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "\"test\"")})]"; + TestCode = testCode; + AddDefaultExpectedDiagnostic(benchmarkClassName); - 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(); - } + 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 - { - } - """; + public class SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed : AnalyzerTestFixture + { + public SingleNullArgumentToBenchmarkCategoryAttributeNotAllowed() : base(BenchmarkClassAnalyzer.SingleNullArgumentToBenchmarkCategoryAttributeNotAllowedRule) + { + } - 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_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 + { + } + """; - [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 benchmarkCategoryAttributeUsage = $"[BenchmarkCategory({(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "\"test\"")})]"; - var testCode = /* lang=c#-test */ $$""" - [assembly: BenchmarkDotNet.Attributes.BenchmarkCategory({{assemblyLevelAttributeValues}})] - - public class BenchmarkClass : BenchmarkClassAncestor1 - { - } - """; + 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() + { + + } + } + """; - var classAndMethodAttributeLevelValues = string.Format(valuesContainer, string.Format(valuesTemplate, - useLocalConstants ? "_xNull" : useConstantsFromOtherClass ? "Constants.Value1" : "null", - useLocalConstants ? "_xValue" : useConstantsFromOtherClass ? "Constants.Value2" : "\"test\"")); + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + ReferenceConstants("string", "\"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(); - } + 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 - { - } - """; + [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({|#1:{{(useLocalConstant ? "_x" : useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] - public {{abstractModifier}}class BenchmarkClassAncestor1 - { - {{(useLocalConstant ? $"private const string _x = {(useConstantFromOtherClass ? "Constants.Value" : "null")};" : "")}} + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + [BenchmarkCategory{{emptyBenchmarkCategoryAttributeArgument}}] + public {{abstractModifier}}class BenchmarkClassAncestor1 + { + [BenchmarkCategory{{emptyBenchmarkCategoryAttributeArgument}}] + {{benchmarkAttributeUsage}} + public void BenchmarkMethod() + { - [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(); - } + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); - public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + await RunAsync(); + } - public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal => BenchmarkAttributeUsagesEnumerable; + [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() + { + + } + } + """; - public static IEnumerable EmptyBenchmarkCategoryAttributeArgumentEnumerableLocal => EmptyBenchmarkCategoryAttributeArgumentEnumerable(); + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + ReferenceConstants(("string", "null"), ("string", "\"test\"")); - public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerableLocal(bool useParamsValues) => BenchmarkCategoryAttributeValuesContainerEnumerable(useParamsValues); + await RunAsync(); } - public class OnlyOneMethodCanBeBaseline : AnalyzerTestFixture + [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) { - public OnlyOneMethodCanBeBaseline() : base(BenchmarkClassAnalyzer.OnlyOneMethodCanBeBaselineRule) { } + var testCode = /* lang=c#-test */ $$""" + [assembly: BenchmarkDotNet.Attributes.BenchmarkCategory({|#0:{{(useConstantFromOtherClass ? "Constants.Value" : "null")}}|})] + + public class BenchmarkClass : BenchmarkClassAncestor1 + { + } + """; - [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")};" : "")}} + 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() + { - [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_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(); - } + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + ReferenceConstants("string", "null"); - [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(); - } + AddExpectedDiagnostic(0); + AddExpectedDiagnostic(1); + AddExpectedDiagnostic(2); - [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) - { - 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")}; - """ : "")}} - - [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() - { - - } - } - """; - - 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(); - } + 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() - { + public static IEnumerable ClassAbstractModifiersEnumerableLocal + => ClassAbstractModifiersEnumerable; - } - } - """; + public static IEnumerable BenchmarkAttributeUsagesEnumerableLocal + => BenchmarkAttributeUsagesEnumerable; - TestCode = testCode; - ReferenceConstants(("bool", "true"), ("bool", "false")); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); + public static IEnumerable EmptyBenchmarkCategoryAttributeArgumentEnumerableLocal + => EmptyBenchmarkCategoryAttributeArgumentEnumerable(); - AddExpectedDiagnostic(0); + public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerableLocal(bool useParamsValues) + => BenchmarkCategoryAttributeValuesContainerEnumerable(useParamsValues); + } - if (useDuplicateInSameClass) + public class OnlyOneMethodCanBeBaseline : AnalyzerTestFixture + { + public OnlyOneMethodCanBeBaseline() : base(BenchmarkClassAnalyzer.OnlyOneMethodCanBeBaselineRule) { } + + [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 { - AddExpectedDiagnostic(1); + {{(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() + { + + } } + """; - await RunAsync(); - } + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2, System.IEquatable + { + } + """; - [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() - { + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; - } - } - """; + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - TestCode = testCode; - ReferenceConstants(("bool", "true"), ("bool", "false")); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); + 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() + { - AddExpectedDiagnostic(0); + } + + public void BenchmarkMethod2() + { - if (useDuplicateInSameClass) - { - AddExpectedDiagnostic(1); + } + + private void BenchmarkMethod3() + { + + } } + """; - await RunAsync(); - } - - public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + ReferenceConstants(("bool", "true"), ("bool", useInvalidFalseValue ? "dummy" : "false")); - public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerableLocal(bool useParamsValues) => BenchmarkCategoryAttributeValuesContainerEnumerable(useParamsValues); + DisableCompilerDiagnostics(); - public static IEnumerable EmptyBenchmarkCategoryAttributeEnumerableLocal => EmptyBenchmarkCategoryAttributeEnumerable(); + await RunAsync(); } - public class OnlyOneMethodCanBeBaselinePerCategory : AnalyzerTestFixture + [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) { - public OnlyOneMethodCanBeBaselinePerCategory() : base(BenchmarkClassAnalyzer.OnlyOneMethodCanBeBaselinePerCategoryRule) { } + 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() + { + + } + } + """; - [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(); - } + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2, System.IEquatable + { + } + """; - [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(); - } + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor2 : BenchmarkClassAncestor3 + { + } + """; - [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(); - } + var benchmarkClassAncestor3Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - [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() - { + public {{abstractModifier}}class BenchmarkClassAncestor3 + { + {{(useLocalConstants ? $"private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")};" : "")}} + + {{baselineBenchmarkAttributeUsages}} + public void NonBaselineBenchmarkMethod2() + { - } - } - """; + } + + public void BenchmarkMethod2() + { - TestCode = testCode; - ReferenceConstants(("bool", "true"), ("bool", "false")); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); + } + + private void BenchmarkMethod3() + { + + } + } + """; - await RunAsync(); - } + TestCode = testCode; + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + AddSource(benchmarkClassAncestor3Document); + ReferenceConstants(("bool", "true"), ("bool", "false")); - [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() - { + DisableCompilerDiagnostics(); - } - } - """; + await RunAsync(); + } - TestCode = testCode; - ReferenceConstants(("bool", "true"), ("bool", "false")); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); + [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; - await RunAsync(); - } + 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_per_unique_category_should_not_trigger_diagnostic( + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialMemberData(nameof(BenchmarkCategoryAttributeValuesContainerEnumerableLocal), true)] string valuesContainer) + { + 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")}; + """ : "")}} + + [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() + { + + } + } + """; + + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; - [Theory, CombinatorialData] - public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_per_unique_category_with_unknown_values_should_not_trigger_diagnostic([CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, - bool useConstantsFromOtherClass, - bool useLocalConstants, - [CombinatorialMemberData(nameof(BenchmarkCategoryAttributeValuesContainerEnumerableLocal), true)] string valuesContainer, - [CombinatorialValues("dummy_literal", "1", "true")] string invalidCategoryStringValue, - bool useDuplicateInSameClass) + 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) { - 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")}; - """ : "")}} - - [BenchmarkCategory({{string.Format(valuesContainer, $""" - null, {invalidCategoryStringValue}, null, "TEST", "test2" - """)}})] - {{baselineBenchmarkAttributeUsage}} - public void BaselineBenchmarkMethod1() - { - - } - - [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] - [BenchmarkCategory({{string.Format(valuesContainer, """ - "test", null - """)}})] - [BenchmarkCategory({{string.Format(valuesContainer, """ - "test2" - """)}})] - {{(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")};" : "")}} - - [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] - [BenchmarkCategory({{string.Format(valuesContainer, $"{invalidCategoryStringValue}, null")}})] - [BenchmarkCategory({{string.Format(valuesContainer, """ - "test2" - """)}})] - {{baselineBenchmarkAttributeUsage}} - public void BaselineBenchmarkMethod3() - { - - } - } - """; - - TestCode = testCode; - ReferenceConstants(("bool", "true"), ("bool", "false")); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); - DisableCompilerDiagnostics(); - - await RunAsync(); + AddExpectedDiagnostic(1); } - [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) + 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) { - 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")};" : "")}} + 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() + { + + } - [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] - [BenchmarkCategory({{string.Format(valuesContainer, """ - "test", null - """)}})] - [BenchmarkCategory({{string.Format(valuesContainer, """ - "test2" - """)}})] - {{baselineBenchmarkAttributeUsage}} - public void BaselineBenchmarkMethod3() - { + 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 + { + } + """; - TestCode = testCode; - ReferenceConstants(("bool", "true"), ("bool", "false")); - AddSource(benchmarkClassAncestor1Document); - AddSource(benchmarkClassAncestor2Document); + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - AddExpectedDiagnostic(0); + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + [Benchmark(Baseline = {{(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")}})] + public void NonBaselineBenchmarkMethod2() + { - if (useDuplicateInSameClass) + } + } + """; + + 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 { - AddExpectedDiagnostic(1); + {{(useLocalConstant ? $"private const bool _xFalse = {(useConstantFromOtherClass ? "Constants.Value" : useInvalidFalseValue ? "dummy" : "false")};" : "")}} + + [Benchmark] + public void NonBaselineBenchmarkMethod1() + { + + } + + [Benchmark] + public void NonBaselineBenchmarkMethod2() + { + + } } + """; - await RunAsync(); - } + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 + { + } + """; - public static IEnumerable ClassAbstractModifiersEnumerableLocal => ClassAbstractModifiersEnumerable; + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - public static IEnumerable EmptyBenchmarkCategoryAttributeEnumerableLocal => EmptyBenchmarkCategoryAttributeEnumerable(); + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + [Benchmark(Baseline = {{(useLocalConstant ? "_xFalse" : useConstantFromOtherClass ? "Constants.Value" : useInvalidFalseValue ? "dummy" : "false")}})] + public void NonBaselineBenchmarkMethod3() + { - public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerableLocal(bool useParamsValues) => BenchmarkCategoryAttributeValuesContainerEnumerable(useParamsValues); + } + } + """; + + TestCode = testCode; + ReferenceConstants("bool", useInvalidFalseValue ? "dummy" : "false"); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + + DisableCompilerDiagnostics(); + + await RunAsync(); } - public static TheoryData TypeParametersListLengthTheoryData => new(TypeParametersListLengthEnumerable); + [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 + { + } + """; - public static IEnumerable TypeParametersListLengthEnumerable => Enumerable.Range(1, TypeParametersTheoryData.Count); + var benchmarkClassAncestor2Document = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; - private static ReadOnlyCollection TypeParametersTheoryData => Enumerable.Range(1, 3) - .Select(i => $"TParameter{i}") - .ToList() - .AsReadOnly(); - private static ReadOnlyCollection GenericTypeArgumentsTheoryData => new List { "int", "string", "bool" }.AsReadOnly(); + public {{abstractModifier}}class BenchmarkClassAncestor2 + { + {{(useLocalConstants ? $"private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")};" : "")}} + + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod3() + { - public static IEnumerable ClassAbstractModifiersEnumerable => [ "", "abstract " ]; + } + } + """; + + TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); - public static IEnumerable BenchmarkAttributeUsagesEnumerable => [ "", "[Benchmark] " ]; + await RunAsync(); + } - public static IEnumerable EmptyBenchmarkCategoryAttributeArgumentEnumerable() + [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) { - yield return ""; - yield return "()"; - - var nameColonUsages = new List - { - "", - "categories: " - }; - - var attributeUsagesBase = new List - { - "({0}new string[] {{ }})", - "({0}new string[0])", - "({0}[ ])" - }; + 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() + { + + } + } + """; - foreach (var attributeUsageBase in attributeUsagesBase) - { - foreach (var nameColonUsage in nameColonUsages) + var benchmarkClassAncestor1Document = /* lang=c#-test */ $$""" + public {{abstractModifier}}class BenchmarkClassAncestor1 : BenchmarkClassAncestor2 { - yield return string.Format(attributeUsageBase, nameColonUsage); } - } + """; + + 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(); } - public static IEnumerable EmptyBenchmarkCategoryAttributeEnumerable() + [Theory, CombinatorialData] + public async Task Class_with_more_than_one_benchmark_method_marked_as_baseline_per_unique_category_with_unknown_values_should_not_trigger_diagnostic( + [CombinatorialMemberData(nameof(ClassAbstractModifiersEnumerableLocal))] string abstractModifier, + bool useConstantsFromOtherClass, + bool useLocalConstants, + [CombinatorialMemberData(nameof(BenchmarkCategoryAttributeValuesContainerEnumerableLocal), true)] string valuesContainer, + [CombinatorialValues("dummy_literal", "1", "true")] string invalidCategoryStringValue, + bool useDuplicateInSameClass) { - yield return "[BenchmarkCategory]"; - yield return "[BenchmarkCategory()]"; - - var nameColonUsages = new List - { - "", - "categories: " - }; - - var attributeUsagesBase = new List - { - "({0}new string[] {{ }})", - "({0}new string[0])", - "({0}[ ])" - }; + var baselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xTrue" : useConstantsFromOtherClass ? "Constants.Value1" : "true")})]"; + var nonBaselineBenchmarkAttributeUsage = $"[Benchmark(Baseline = {(useLocalConstants ? "_xFalse" : useConstantsFromOtherClass ? "Constants.Value2" : "false")})]"; - foreach (var attributeUsageBase in attributeUsagesBase) - { - foreach (var nameColonUsage in nameColonUsages) + var testCode = /* lang=c#-test */ $$""" + using BenchmarkDotNet.Attributes; + + public class BenchmarkClass : BenchmarkClassAncestor1 { - yield return $"[BenchmarkCategory{string.Format(attributeUsageBase, nameColonUsage)}]"; + {{(useLocalConstants ? $""" + private const bool _xTrue = {(useConstantsFromOtherClass ? "Constants.Value1" : "true")}; + private const bool _xFalse = {(useConstantsFromOtherClass ? "Constants.Value2" : "false")}; + """ : "")}} + + [BenchmarkCategory({{string.Format(valuesContainer, $""" + null, {invalidCategoryStringValue}, null, "TEST", "test2" + """)}})] + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod1() + { + + } + + [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test", null + """)}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test2" + """)}})] + {{(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")};" : "")}} + + [BenchmarkCategory({{string.Format(valuesContainer, "null, null")}})] + [BenchmarkCategory({{string.Format(valuesContainer, $"{invalidCategoryStringValue}, null")}})] + [BenchmarkCategory({{string.Format(valuesContainer, """ + "test2" + """)}})] + {{baselineBenchmarkAttributeUsage}} + public void BaselineBenchmarkMethod3() + { + + } + } + """; + + TestCode = testCode; + ReferenceConstants(("bool", "true"), ("bool", "false")); + AddSource(benchmarkClassAncestor1Document); + AddSource(benchmarkClassAncestor2Document); + DisableCompilerDiagnostics(); + + 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); + + if (useDuplicateInSameClass) + { + AddExpectedDiagnostic(1); } + + await RunAsync(); } - public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerable(bool useParamsValues) + public static IEnumerable ClassAbstractModifiersEnumerableLocal + => ClassAbstractModifiersEnumerable; + + public static IEnumerable EmptyBenchmarkCategoryAttributeEnumerableLocal + => EmptyBenchmarkCategoryAttributeEnumerable(); + + public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerableLocal(bool useParamsValues) + => BenchmarkCategoryAttributeValuesContainerEnumerable(useParamsValues); + } + + public static TheoryData TypeParametersListLengthTheoryData + => [.. TypeParametersListLengthEnumerable]; + + public static IEnumerable TypeParametersListLengthEnumerable + => Enumerable.Range(1, TypeParametersTheoryData.Count); + + private static ReadOnlyCollection TypeParametersTheoryData + => Enumerable.Range(1, 3) + .Select(i => $"TParameter{i}") + .ToList() + .AsReadOnly(); + + private static IReadOnlyCollection GenericTypeArgumentsTheoryData + => ["int", "string", "bool"]; + + public static IEnumerable ClassAbstractModifiersEnumerable + => ["", "abstract "]; + + public static IEnumerable BenchmarkAttributeUsagesEnumerable + => ["", "[Benchmark] "]; + + public static IEnumerable EmptyBenchmarkCategoryAttributeArgumentEnumerable() + { + yield return ""; + yield return "()"; + + string[] nameColonUsages = + [ + "", + "categories: " + ]; + + string[] attributeUsagesBase = + [ + "({0}new string[] {{ }})", + "({0}new string[0])", + "({0}[ ])" + ]; + + foreach (var attributeUsageBase in attributeUsagesBase) { - return GenerateData(useParamsValues).Distinct(); + foreach (var nameColonUsage in nameColonUsages) + { + yield return string.Format(attributeUsageBase, nameColonUsage); + } + } + } - static IEnumerable GenerateData(bool useParamsValues) + public static IEnumerable EmptyBenchmarkCategoryAttributeEnumerable() + { + yield return "[BenchmarkCategory]"; + yield return "[BenchmarkCategory()]"; + + string[] nameColonUsages = + [ + "", + "categories: " + ]; + + string[] attributeUsagesBase = + [ + "({0}new string[] {{ }})", + "({0}new string[0])", + "({0}[ ])" + ]; + + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) { - var nameColonUsages = new List - { - "", - "categories: " - }; + yield return $"[BenchmarkCategory{string.Format(attributeUsageBase, nameColonUsage)}]"; + } + } + } + + public static IEnumerable BenchmarkCategoryAttributeValuesContainerEnumerable(bool useParamsValues) + { + return GenerateData(useParamsValues).Distinct(); + + static IEnumerable GenerateData(bool useParamsValues) + { + string[] nameColonUsages = + [ + "", + "categories: " + ]; - List attributeUsagesBase = useParamsValues ? [ "{{0}}" ] : [ ]; + List attributeUsagesBase = useParamsValues ? ["{{0}}"] : []; - attributeUsagesBase.AddRange([ - "{0}new string[] {{{{ {{0}} }}}}", - "{0}[ {{0}} ]" - ]); + attributeUsagesBase.AddRange([ + "{0}new string[] {{{{ {{0}} }}}}", + "{0}[ {{0}} ]" + ]); - foreach (var attributeUsageBase in attributeUsagesBase) + foreach (var attributeUsageBase in attributeUsagesBase) + { + foreach (var nameColonUsage in nameColonUsages) { - foreach (var nameColonUsage in nameColonUsages) - { - yield return string.Format(attributeUsageBase, nameColonUsage); - } + yield return string.Format(attributeUsageBase, nameColonUsage); } } } } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs index 76c05fa9ed..cb5db4fd5c 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/AnalyzerTestFixture.cs @@ -1,267 +1,244 @@ -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; + +public abstract class AnalyzerTestFixture + where TAnalyzer : DiagnosticAnalyzer, new() { - using Microsoft.CodeAnalysis; - 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; + private readonly CSharpAnalyzerTest _analyzerTest; - private readonly DiagnosticDescriptor? _ruleUnderTest; + private readonly DiagnosticDescriptor? _ruleUnderTest; - private AnalyzerTestFixture(bool assertUniqueSupportedDiagnostics) + private AnalyzerTestFixture(bool assertUniqueSupportedDiagnostics) + { + _analyzerTest = new InternalAnalyzerTest { - _analyzerTest = new InternalAnalyzerTest - { #if NET8_0_OR_GREATER - ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, #elif NET6_0_OR_GREATER - ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, #else - ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard20, + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard20, #endif - TestState = - { - AdditionalReferences = - { - "BenchmarkDotNet.dll", - "BenchmarkDotNet.Annotations.dll", + TestState = + { + AdditionalReferences = + { + "BenchmarkDotNet.dll", + "BenchmarkDotNet.Annotations.dll", #if !NET6_0_OR_GREATER - "System.Memory.dll" + "System.Memory.dll" #endif - } - } - }; - - if (assertUniqueSupportedDiagnostics) - { - AssertUniqueSupportedDiagnostics(); + } } - } - - protected AnalyzerTestFixture() : this(true) { } + }; - protected AnalyzerTestFixture(DiagnosticDescriptor diagnosticDescriptor) : this(false) + if (assertUniqueSupportedDiagnostics) { - var analyzer = AssertUniqueSupportedDiagnostics(); + AssertUniqueSupportedDiagnostics(); + } + } - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (diagnosticDescriptor == null) - { - Assert.Fail("Diagnostic under test cannot be null when using this constructor"); - } + protected AnalyzerTestFixture() : this(true) { } - AssertDiagnosticUnderTestIsSupportedByAnalyzer(); - DisableAllSupportedDiagnosticsExceptDiagnosticUnderTest(); + protected AnalyzerTestFixture(DiagnosticDescriptor diagnosticDescriptor) : this(false) + { + var analyzer = AssertUniqueSupportedDiagnostics(); - _ruleUnderTest = diagnosticDescriptor; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (diagnosticDescriptor == null) + { + Assert.Fail("Diagnostic under test cannot be null when using this constructor"); + } - return; + AssertDiagnosticUnderTestIsSupportedByAnalyzer(); + DisableAllSupportedDiagnosticsExceptDiagnosticUnderTest(); - 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}"); - } - } + _ruleUnderTest = diagnosticDescriptor; - void DisableAllSupportedDiagnosticsExceptDiagnosticUnderTest() + void AssertDiagnosticUnderTestIsSupportedByAnalyzer() + { + if (!analyzer.SupportedDiagnostics.Any(dd => dd.Id == diagnosticDescriptor.Id)) { - _analyzerTest.DisabledDiagnostics.Clear(); - _analyzerTest.DisabledDiagnostics.AddRange(analyzer.SupportedDiagnostics.Select(dd => dd.Id).Except([ - diagnosticDescriptor.Id - ])); + Assert.Fail($"Diagnostic descriptor with ID {diagnosticDescriptor.Id} is not supported by the analyzer {typeof(TAnalyzer).Name}"); } } - private static TAnalyzer AssertUniqueSupportedDiagnostics() + void DisableAllSupportedDiagnosticsExceptDiagnosticUnderTest() { - var allSupportedDiagnostics = new Dictionary(); + _analyzerTest.DisabledDiagnostics.Clear(); + _analyzerTest.DisabledDiagnostics.AddRange( + analyzer.SupportedDiagnostics + .Select(dd => dd.Id) + .Except([diagnosticDescriptor.Id]) + ); + } + } + + private static TAnalyzer AssertUniqueSupportedDiagnostics() + { + var allSupportedDiagnostics = new Dictionary(); - var analyzer = new TAnalyzer(); - foreach (var supportedDiagnostic in analyzer.SupportedDiagnostics) + var analyzer = new TAnalyzer(); + foreach (var supportedDiagnostic in analyzer.SupportedDiagnostics) + { + if (allSupportedDiagnostics.TryGetValue(supportedDiagnostic.Id, out int value)) { - if (allSupportedDiagnostics.ContainsKey(supportedDiagnostic.Id)) - { - allSupportedDiagnostics[supportedDiagnostic.Id]++; - } - else - { - allSupportedDiagnostics[supportedDiagnostic.Id] = 1; - } + allSupportedDiagnostics[supportedDiagnostic.Id] = ++value; } - - var duplicateSupportedDiagnostics = allSupportedDiagnostics.Where(kvp => kvp.Value > 1) - .OrderBy(kvp => kvp.Key) - .ToList(); - - if (duplicateSupportedDiagnostics.Count > 0) + else { - 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}"); + allSupportedDiagnostics[supportedDiagnostic.Id] = 1; } - - return analyzer; } - protected string TestCode + var duplicateSupportedDiagnostics = allSupportedDiagnostics + .Where(kvp => kvp.Value > 1) + .OrderBy(kvp => kvp.Key) + .ToArray(); + + if (duplicateSupportedDiagnostics.Length > 0) { - set => _analyzerTest.TestCode = value; + 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}"); } - protected void AddSource(string filename, string content) => _analyzerTest.TestState.Sources.Add((filename, content)); + return analyzer; + } - protected void AddSource(string content) => _analyzerTest.TestState.Sources.Add(content); + protected string TestCode + { + set => _analyzerTest.TestCode = value; + } - protected void AddDefaultExpectedDiagnostic() - { - AddExpectedDiagnostic(); - } + protected void AddSource(string filename, string content) + => _analyzerTest.TestState.Sources.Add((filename, content)); - protected void AddDefaultExpectedDiagnostic(params object[] arguments) - { - AddExpectedDiagnostic(arguments); - } + protected void AddSource(string content) + => _analyzerTest.TestState.Sources.Add(content); - protected void AddDefaultExpectedDiagnostic(DiagnosticSeverity effectiveDiagnosticSeverity) - { - AddExpectedDiagnostic(effectiveDiagnosticSeverity: effectiveDiagnosticSeverity); - } + protected void AddDefaultExpectedDiagnostic() + => AddExpectedDiagnostic(); - protected void AddDefaultExpectedDiagnostic(DiagnosticSeverity effectiveDiagnosticSeverity, params object[] arguments) - { - AddExpectedDiagnostic(arguments, effectiveDiagnosticSeverity: effectiveDiagnosticSeverity); - } + protected void AddDefaultExpectedDiagnostic(params object[] arguments) + => AddExpectedDiagnostic(arguments); - protected void AddExpectedDiagnostic(int markupKey) - { - AddExpectedDiagnostic(null, markupKey); - } + 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) + protected void AddExpectedDiagnostic(int markupKey, DiagnosticSeverity effectiveDiagnosticSeverity) + => AddExpectedDiagnostic(null, markupKey, effectiveDiagnosticSeverity); + + protected void AddExpectedDiagnostic(int markupKey, params object[] arguments) + => AddExpectedDiagnostic(arguments, markupKey); + + 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) { - AddExpectedDiagnostic(null, markupKey, effectiveDiagnosticSeverity); + throw new InvalidOperationException("Failed to add expected diagnostic: no diagnostic rule specified for this fixture"); } - protected void AddExpectedDiagnostic(int markupKey, params object[] arguments) + var diagnosticResult = new DiagnosticResult(_ruleUnderTest) + .WithLocation(markupKey) + .WithMessageFormat(_ruleUnderTest.MessageFormat); + + if (arguments != null) { - AddExpectedDiagnostic(arguments, markupKey); + diagnosticResult = diagnosticResult.WithArguments(arguments); } - protected void AddExpectedDiagnostic(int markupKey, DiagnosticSeverity effectiveDiagnosticSeverity, params object[] arguments) + if (effectiveDiagnosticSeverity.HasValue) { - AddExpectedDiagnostic(arguments, markupKey, effectiveDiagnosticSeverity); + diagnosticResult = diagnosticResult.WithSeverity(effectiveDiagnosticSeverity.Value); } - private void AddExpectedDiagnostic(object[]? arguments = null, int markupKey = 0, DiagnosticSeverity? effectiveDiagnosticSeverity = null) - { - if (_ruleUnderTest == null) - { - throw new InvalidOperationException("Failed to add expected diagnostic: no diagnostic rule specified for this fixture"); - } + _analyzerTest.ExpectedDiagnostics.Add(diagnosticResult); + } + + protected void DisableCompilerDiagnostics() + => _analyzerTest.CompilerDiagnostics = CompilerDiagnostics.None; + + protected Task RunAsync() + => _analyzerTest.RunAsync(CancellationToken.None); - var diagnosticResult = new DiagnosticResult(_ruleUnderTest).WithLocation(markupKey) - .WithMessageFormat(_ruleUnderTest.MessageFormat); + protected void ReferenceDummyAttribute() + => _analyzerTest.TestState.Sources.Add(""" + using System; - if (arguments != null) + public class DummyAttribute : Attribute { - diagnosticResult = diagnosticResult.WithArguments(arguments); + } + """ + ); - if (effectiveDiagnosticSeverity.HasValue) + protected void ReferenceDummyEnum() + => _analyzerTest.TestState.Sources.Add(""" + public enum DummyEnum { - diagnosticResult = diagnosticResult.WithSeverity(effectiveDiagnosticSeverity.Value); + Value1, + Value2, + Value3 } + """ + ); - _analyzerTest.ExpectedDiagnostics.Add(diagnosticResult); - } + protected void ReferenceDummyEnumWithFlagsAttribute() + => _analyzerTest.TestState.Sources.Add(""" + using System; - protected void DisableCompilerDiagnostics() - { - _analyzerTest.CompilerDiagnostics = CompilerDiagnostics.None; - } - - protected Task RunAsync() - { - return _analyzerTest.RunAsync(CancellationToken.None); - } + [Flags] + public enum DummyEnumWithFlagsAttribute + { + Value1, + Value2, + Value3 + } + """ + ); - protected void ReferenceDummyAttribute() - { - _analyzerTest.TestState.Sources.Add(""" - using System; - - public class DummyAttribute : Attribute - { - - } - """); - } + protected void ReferenceConstants(string type, string value) + => _analyzerTest.TestState.Sources.Add($$""" + using System; - 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 - } - """); - } + public static class Constants + { + public const {{type}} Value = {{value}}; + } + """ + ); - protected void ReferenceConstants(string type, string value) - { - _analyzerTest.TestState.Sources.Add($$""" - using System; - - public static class Constants - { - public const {{type}} Value = {{value}}; - } - """); - } + protected void ReferenceConstants(params (string Type, string Value)[] constants) + => _analyzerTest.TestState.Sources.Add($$""" + using System; - 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};"))}} - } - """); - } + 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"; - } + private sealed class InternalAnalyzerTest : CSharpAnalyzerTest + { + protected override string DefaultTestProjectName => "BenchmarksAssemblyUnderAnalysis"; } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs index e51a6fbc00..4e61d8bb87 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Extensions/TheoryDataExtensions.cs @@ -1,14 +1,12 @@ -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures -{ - using Xunit; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Xunit; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; - public static class TheoryDataExtensions - { - public static ReadOnlyCollection AsReadOnly(this TheoryData theoryData) => (theoryData as IEnumerable).ToList() - .AsReadOnly(); - } +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/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs index 55532f3ef5..c1eaf547a3 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Generators/CombinationsGenerator.cs @@ -1,66 +1,66 @@ -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; - public static class CombinationsGenerator +public static class CombinationsGenerator +{ + public static IEnumerable GenerateCombinationsCounts(int length, int maxValue) { - public static IEnumerable GenerateCombinationsCounts(int length, int maxValue) + if (length <= 0) { - if (length <= 0) - { - yield break; - } + yield break; + } - var baseN = maxValue + 1; - var total = 1; + var baseN = maxValue + 1; + var total = 1; - for (var i = 0; i < length; i++) - { - total *= baseN; - } + 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]; + 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; - } + 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); + // Copy from Span (stack) to heap-allocated array + var result = new int[length]; + currentCombination.CopyTo(result); - yield return result; - } + yield return result; } + } - public static IEnumerable CombineArguments(params IEnumerable[] argumentSets) + public static IEnumerable CombineArguments(params IEnumerable[] argumentSets) + { + if (argumentSets.Length == 0) { - if (argumentSets.Length == 0) - { - yield break; - } + yield break; + } - IEnumerable combinations = [[]]; + IEnumerable combinations = [[]]; - foreach (var argumentValues in argumentSets) - { - combinations = combinations.SelectMany(_ => argumentValues.Cast(), (c, v) => c.Concat([v]) - .ToArray()); - } + foreach (var argumentValues in argumentSets) + { + combinations = combinations + .SelectMany(_ => argumentValues.Cast(), (c, v) => c.Concat([v]) + .ToArray()); + } - foreach (var combination in combinations) - { - yield return combination; - } + foreach (var combination in combinations) + { + yield return combination; } } -} +} \ 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 index c7b6309cea..9a2a69bab4 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleDouble.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleDouble.cs @@ -1,27 +1,28 @@ -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures -{ - using Xunit.Abstractions; - - public class ValueTupleDouble : IXunitSerializable - { - public T1? Value1 { get; set; } +using Xunit.Abstractions; - public T2? Value2 { get; set; } +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; - public void Deserialize(IXunitSerializationInfo info) - { - Value1 = info.GetValue(nameof(Value1)); - Value2 = info.GetValue(nameof(Value2)); - } +public class ValueTupleDouble : IXunitSerializable +{ + public T1? Value1 { get; set; } - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue(nameof(Value1), Value1); - info.AddValue(nameof(Value2), Value2); - } + public T2? Value2 { get; set; } - public static implicit operator ValueTupleDouble((T1, T2) valueTupleDouble) => new() { Value1 = valueTupleDouble.Item1, Value2 = valueTupleDouble.Item2 }; + public void Deserialize(IXunitSerializationInfo info) + { + Value1 = info.GetValue(nameof(Value1)); + Value2 = info.GetValue(nameof(Value2)); + } - public override string ToString() => Value1 == null || Value2 == null ? "" : $"{Value1} · {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}"; +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleTriple.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleTriple.cs index ae3cd9614b..8fd256378b 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleTriple.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/Serializable/ValueTupleTriple.cs @@ -1,31 +1,32 @@ -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures -{ - using Xunit.Abstractions; - - public class ValueTupleTriple : IXunitSerializable - { - public T1? Value1 { get; set; } +using Xunit.Abstractions; - public T2? Value2 { get; set; } +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; - public T3? Value3 { get; set; } +public class ValueTupleTriple : IXunitSerializable +{ + public T1? Value1 { get; set; } - public void Deserialize(IXunitSerializationInfo info) - { - Value1 = info.GetValue(nameof(Value1)); - Value2 = info.GetValue(nameof(Value2)); - Value3 = info.GetValue(nameof(Value3)); - } + public T2? Value2 { get; set; } - public void Serialize(IXunitSerializationInfo info) - { - info.AddValue(nameof(Value1), Value1); - info.AddValue(nameof(Value2), Value2); - info.AddValue(nameof(Value3), Value3); - } + public T3? Value3 { get; set; } - public static implicit operator ValueTupleTriple((T1, T2, T3) valueTupleTriple) => new() { Value1 = valueTupleTriple.Item1, Value2 = valueTupleTriple.Item2, Value3 = valueTupleTriple.Item3 }; + public void Deserialize(IXunitSerializationInfo info) + { + Value1 = info.GetValue(nameof(Value1)); + Value2 = info.GetValue(nameof(Value2)); + Value3 = info.GetValue(nameof(Value3)); + } - public override string ToString() => Value1 == null || Value2 == null || Value3 == null ? "" : $"{Value1} · {Value2} · {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}"; +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationsTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationsTheoryData.cs index 9ddaacf203..a56d3bbad2 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationsTheoryData.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/FieldOrPropertyDeclarationsTheoryData.cs @@ -1,19 +1,18 @@ using Xunit; -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; + +internal sealed class FieldOrPropertyDeclarationsTheoryData : TheoryData { - internal sealed class FieldOrPropertyDeclarationsTheoryData : TheoryData + public FieldOrPropertyDeclarationsTheoryData() { - public FieldOrPropertyDeclarationsTheoryData() - { - AddRange( + AddRange( #if NET5_0_OR_GREATER - "Property { get; init; }", + "Property { get; init; }", #else - "Property { get; set; }", + "Property { get; set; }", #endif - "_field;" - ); - } + "_field;" + ); } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs index 76f270f8af..6d6a669c90 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassAccessModifiersTheoryData.cs @@ -1,20 +1,19 @@ -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures -{ - using Xunit; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; - internal sealed class NonPublicClassAccessModifiersTheoryData : TheoryData +internal sealed class NonPublicClassAccessModifiersTheoryData : TheoryData +{ + public NonPublicClassAccessModifiersTheoryData() { - public NonPublicClassAccessModifiersTheoryData() - { - AddRange( - "protected internal ", - "protected ", - "internal ", - "private protected ", - "private ", - "file ", - "" - ); - } + AddRange( + "protected internal ", + "protected ", + "internal ", + "private protected ", + "private ", + "file ", + "" + ); } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs index 6b3599dc4a..1e88c2ea75 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicClassMemberAccessModifiersTheoryData.cs @@ -1,19 +1,18 @@ -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures -{ - using Xunit; +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; + +using Xunit; - internal sealed class NonPublicClassMemberAccessModifiersTheoryData : TheoryData +internal sealed class NonPublicClassMemberAccessModifiersTheoryData : TheoryData +{ + public NonPublicClassMemberAccessModifiersTheoryData() { - public NonPublicClassMemberAccessModifiersTheoryData() - { - AddRange( - "protected internal ", - "protected ", - "internal ", - "private protected ", - "private ", - "" - ); - } + AddRange( + "protected internal ", + "protected ", + "internal ", + "private protected ", + "private ", + "" + ); } -} +} \ No newline at end of file diff --git a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs index 7138a02507..7c0d754e6d 100644 --- a/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs +++ b/tests/BenchmarkDotNet.Analyzers.Tests/Fixtures/TheoryData/NonPublicPropertySetterAccessModifiersTheoryData.cs @@ -1,18 +1,17 @@ -namespace BenchmarkDotNet.Analyzers.Tests.Fixtures -{ - using Xunit; +using Xunit; + +namespace BenchmarkDotNet.Analyzers.Tests.Fixtures; - internal sealed class NonPublicPropertySetterAccessModifiersTheoryData : TheoryData +internal sealed class NonPublicPropertySetterAccessModifiersTheoryData : TheoryData +{ + public NonPublicPropertySetterAccessModifiersTheoryData() { - public NonPublicPropertySetterAccessModifiersTheoryData() - { - AddRange( - "protected internal", - "protected", - "internal", - "private protected", - "private" - ); - } + AddRange( + "protected internal", + "protected", + "internal", + "private protected", + "private" + ); } -} +} \ No newline at end of file