From 1168a47d4a7bfded24c3666f0c3bdd07197bf88b Mon Sep 17 00:00:00 2001 From: Gregory Paidis <115458417+gregory-paidis-sonarsource@users.noreply.github.com> Date: Tue, 24 Jan 2023 14:48:19 +0100 Subject: [PATCH 01/14] Update rspec scaffolding script after the SonarAnalysisContext refactoring and implicit namespaces (#6668) --- scripts/rspec/rspec-templates/Rule.Base.cs | 5 ----- scripts/rspec/rspec-templates/Rule.CS.cs | 11 +---------- scripts/rspec/rspec-templates/Rule.VB.cs | 11 +---------- 3 files changed, 2 insertions(+), 25 deletions(-) diff --git a/scripts/rspec/rspec-templates/Rule.Base.cs b/scripts/rspec/rspec-templates/Rule.Base.cs index 6cbcb64a80f..31080ba1f50 100644 --- a/scripts/rspec/rspec-templates/Rule.Base.cs +++ b/scripts/rspec/rspec-templates/Rule.Base.cs @@ -18,11 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using SonarAnalyzer.Common; -using SonarAnalyzer.Helpers; - namespace SonarAnalyzer.Rules; public abstract class $DiagnosticClassName$Base : SonarDiagnosticAnalyzer diff --git a/scripts/rspec/rspec-templates/Rule.CS.cs b/scripts/rspec/rspec-templates/Rule.CS.cs index b2ab0f3c0ec..f3f35120fea 100644 --- a/scripts/rspec/rspec-templates/Rule.CS.cs +++ b/scripts/rspec/rspec-templates/Rule.CS.cs @@ -18,15 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; -using SonarAnalyzer.Common; -using SonarAnalyzer.Helpers; - namespace SonarAnalyzer.Rules.CSharp; [DiagnosticAnalyzer(LanguageNames.CSharp)] @@ -40,7 +31,7 @@ public sealed class $DiagnosticClassName$ : SonarDiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => - context.RegisterSyntaxNodeActionInNonGenerated(c => + context.RegisterNodeAction(c => { var node = c.Node; if (true) diff --git a/scripts/rspec/rspec-templates/Rule.VB.cs b/scripts/rspec/rspec-templates/Rule.VB.cs index ea26f71b0fd..7a2eb22d765 100644 --- a/scripts/rspec/rspec-templates/Rule.VB.cs +++ b/scripts/rspec/rspec-templates/Rule.VB.cs @@ -18,15 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.VisualBasic; -using Microsoft.CodeAnalysis.VisualBasic.Syntax; -using SonarAnalyzer.Common; -using SonarAnalyzer.Helpers; - namespace SonarAnalyzer.Rules.VisualBasic; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] @@ -40,7 +31,7 @@ public sealed class $DiagnosticClassName$ : SonarDiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); protected override void Initialize(SonarAnalysisContext context) => - context.RegisterSyntaxNodeActionInNonGenerated(c => + context.RegisterNodeAction(c => { var node = c.Node; if (true) From c62cc0c4f4178cb946af51b4b2ea3d1226b2141c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Caba=20=C5=A0agi?= <75226367+csaba-sagi-sonarsource@users.noreply.github.com> Date: Wed, 25 Jan 2023 10:50:35 +0100 Subject: [PATCH 02/14] Fix FN S2190: No issues raised if recursion is inside an init accessor of a property (#6647) --- .../ShimLayer/SyntaxKindEx.cs | 1 + .../Rules/InfiniteRecursion.RoslynCfg.cs | 2 +- .../Rules/InfiniteRecursionTest.cs | 6 ++++++ .../InfiniteRecursion.RoslynCfg.CSharp9.cs | 19 +++++++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/InfiniteRecursion.RoslynCfg.CSharp9.cs diff --git a/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs b/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs index f8428351a7c..fec7e0ca6e9 100644 --- a/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs +++ b/analyzers/src/SonarAnalyzer.CFG/ShimLayer/SyntaxKindEx.cs @@ -11,6 +11,7 @@ public static class SyntaxKindEx public const SyntaxKind QuestionQuestionEqualsToken = (SyntaxKind)8284; public const SyntaxKind GreaterThanGreaterThanGreaterThanToken = (SyntaxKind)8286; public const SyntaxKind GreaterThanGreaterThanGreaterThanEqualsToken = (SyntaxKind)8287; + public const SyntaxKind InitKeyword = (SyntaxKind)8443; public const SyntaxKind ManagedKeyword = (SyntaxKind)8445; public const SyntaxKind UnmanagedKeyword = (SyntaxKind)8446; public const SyntaxKind NullableKeyword = (SyntaxKind)8486; diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs index 21373ee7e68..5bc6c8a46c7 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/InfiniteRecursion.RoslynCfg.cs @@ -41,7 +41,7 @@ public void CheckForNoExitProperty(SonarSyntaxNodeReportingContext c, PropertyDe { var cfg = ControlFlowGraph.Create(accessor, c.SemanticModel, c.Cancel); var context = new RecursionContext(c, cfg, propertySymbol, accessor.Keyword.GetLocation(), "property accessor's recursion"); - var walker = new RecursionSearcher(context, !accessor.Keyword.IsKind(SyntaxKind.SetKeyword)); + var walker = new RecursionSearcher(context, !accessor.Keyword.IsAnyKind(SyntaxKind.SetKeyword, SyntaxKindEx.InitKeyword)); walker.CheckPaths(); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/InfiniteRecursionTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/InfiniteRecursionTest.cs index 5cd750f63bd..bb6e0bd15f0 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/InfiniteRecursionTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/InfiniteRecursionTest.cs @@ -47,6 +47,12 @@ public class InfiniteRecursionTest #if NET + [TestMethod] + public void InfiniteRecursion_RoslynCfg_CSharp9() => + roslynCfg.AddPaths("InfiniteRecursion.RoslynCfg.CSharp9.cs") + .WithOptions(ParseOptionsHelper.FromCSharp9) + .Verify(); + [TestMethod] public void InfiniteRecursion_CSharp11() => roslynCfg.AddPaths("InfiniteRecursion.CSharp11.cs") diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/InfiniteRecursion.RoslynCfg.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/InfiniteRecursion.RoslynCfg.CSharp9.cs new file mode 100644 index 00000000000..fe0ca18fe7f --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/InfiniteRecursion.RoslynCfg.CSharp9.cs @@ -0,0 +1,19 @@ +// https://github.com/SonarSource/sonar-dotnet/issues/6646 +namespace Repro_6646 +{ + public class Repro + { + public string Name + { + init // Noncompliant + { + Name = value; + } + } + + public string Arrow + { + init => Arrow = value; // Noncompliant + } + } +} From 7cce08b3cd8399b6f5916c3386b0164d0adcc2b3 Mon Sep 17 00:00:00 2001 From: Corniel Nobel Date: Thu, 26 Jan 2023 10:22:41 +0100 Subject: [PATCH 03/14] Implement S3898 -ValueTypes should implement IEquatable - for VB.NET (#5740) --- README.md | 2 +- ...AB-AF12-4012-B945-284C2448DC81}-S3898.json | 30 ++ ...5E-C6AE-4D2D-A9DD-B6EFD19A4279}-S3898.json | 368 ++++++++++++++++ ...01-6478-44D2-A39C-89B0850D7833}-S3898.json | 17 + ...89-E8E4-45D9-B942-97FBD4ADD106}-S3898.json | 17 + ...C2-EB9C-4090-B9A9-B703DF00CCEF}-S3898.json | 17 + ...3A-D04F-4262-923D-21AEDF86E2B7}-S3898.json | 17 + ...35-F186-4193-AF35-D20126B8DA64}-S3898.json | 17 + ...61-E2C2-4982-9536-63AEF0A98AAA}-S3898.json | 17 + ...0E-DD76-4F4D-8250-8598140F828B}-S3898.json | 225 ++++++++++ ...31-1F7B-4637-9B3A-806988DE50CF}-S3898.json | 69 +++ analyzers/rspec/vbnet/S3898_vb.net.html | 29 ++ analyzers/rspec/vbnet/S3898_vb.net.json | 17 + .../Facade/CSharpSyntaxFacade.cs | 5 + .../Facade/CSharpSyntaxKindFacade.cs | 2 + .../ValueTypeShouldImplementIEquatable.cs | 29 +- .../Facade/ISyntaxKindFacade.cs | 2 + .../Facade/SyntaxFacade.cs | 1 + .../ValueTypeShouldImplementIEquatableBase.cs | 47 ++ .../Facade/VisualBasicSyntaxFacade.cs | 5 + .../Facade/VisualBasicSyntaxKindFacade.cs | 2 + .../Helpers/VisualBasicSyntaxHelper.cs | 400 +++++++++--------- .../ValueTypeShouldImplementIEquatable.cs | 32 ++ .../Common/ReadMeTest.cs | 2 +- .../Facade/SyntaxFacadeTest.cs | 193 +++++---- .../PackagingTests/RuleTypeMappingVB.cs | 2 +- .../ValueTypeShouldImplementIEquatableTest.cs | 30 +- ...eTypeShouldImplementIEquatable.CSharp10.cs | 16 +- .../ValueTypeShouldImplementIEquatable.cs | 30 +- .../ValueTypeShouldImplementIEquatable.vb | 19 + 30 files changed, 1303 insertions(+), 356 deletions(-) create mode 100644 analyzers/its/expected/Ember-MM/Ember Media Manager-{9B57D3AB-AF12-4012-B945-284C2448DC81}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/EmberAPI-{208AA35E-C6AE-4D2D-A9DD-B6EFD19A4279}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/generic.EmberCore.BulkRename-{EAAB0601-6478-44D2-A39C-89B0850D7833}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/generic.EmberCore.MediaFileManager-{F6CACA89-E8E4-45D9-B942-97FBD4ADD106}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/generic.EmberCore.MovieExporter-{B0BDF9C2-EB9C-4090-B9A9-B703DF00CCEF}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/generic.EmberCore.NMT-{84B2143A-D04F-4262-923D-21AEDF86E2B7}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/generic.EmberCore.WebServer-{64D6F035-F186-4193-AF35-D20126B8DA64}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/generic.EmberCore.XBMC-{6FE33C61-E2C2-4982-9536-63AEF0A98AAA}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/scraper.EmberCore-{EF6A550E-DD76-4F4D-8250-8598140F828B}-S3898.json create mode 100644 analyzers/its/expected/Ember-MM/scraper.EmberCore.XML-{E567C031-1F7B-4637-9B3A-806988DE50CF}-S3898.json create mode 100644 analyzers/rspec/vbnet/S3898_vb.net.html create mode 100644 analyzers/rspec/vbnet/S3898_vb.net.json create mode 100644 analyzers/src/SonarAnalyzer.Common/Rules/ValueTypeShouldImplementIEquatableBase.cs create mode 100644 analyzers/src/SonarAnalyzer.VisualBasic/Rules/ValueTypeShouldImplementIEquatable.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.vb diff --git a/README.md b/README.md index 40d269c935b..33bdb1ce666 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ languages in [SonarQube](http://www.sonarqube.org/), [SonarCloud](https://sonarc ## Features -* [370+ C# rules](https://rules.sonarsource.com/csharp) and [160+ VB.​NET rules](https://rules.sonarsource.com/vbnet) +* [370+ C# rules](https://rules.sonarsource.com/csharp) and [170+ VB.​NET rules](https://rules.sonarsource.com/vbnet) * Metrics (cognitive complexity, duplications, number of lines etc.) * Import of [test coverage reports](https://community.sonarsource.com/t/9871) from Visual Studio Code Coverage, dotCover, OpenCover, Coverlet, Altcover. * Import of third party Roslyn Analyzers results diff --git a/analyzers/its/expected/Ember-MM/Ember Media Manager-{9B57D3AB-AF12-4012-B945-284C2448DC81}-S3898.json b/analyzers/its/expected/Ember-MM/Ember Media Manager-{9B57D3AB-AF12-4012-B945-284C2448DC81}-S3898.json new file mode 100644 index 00000000000..cab156df15e --- /dev/null +++ b/analyzers/its/expected/Ember-MM/Ember Media Manager-{9B57D3AB-AF12-4012-B945-284C2448DC81}-S3898.json @@ -0,0 +1,30 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Ember%20Media%20Manager\frmMain.vb", +"region": { +"startLine": 8978, +"startColumn": 23, +"endLine": 8978, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Ember%20Media%20Manager\frmMain.vb", +"region": { +"startLine": 8997, +"startColumn": 23, +"endLine": 8997, +"endColumn": 30 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/EmberAPI-{208AA35E-C6AE-4D2D-A9DD-B6EFD19A4279}-S3898.json b/analyzers/its/expected/Ember-MM/EmberAPI-{208AA35E-C6AE-4D2D-A9DD-B6EFD19A4279}-S3898.json new file mode 100644 index 00000000000..1ca533d6273 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/EmberAPI-{208AA35E-C6AE-4D2D-A9DD-B6EFD19A4279}-S3898.json @@ -0,0 +1,368 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'CustomUpdaterStruct'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1079, +"startColumn": 22, +"endLine": 1079, +"endColumn": 41 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'MovieSource'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1091, +"startColumn": 22, +"endLine": 1091, +"endColumn": 33 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'TVSource'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1099, +"startColumn": 22, +"endLine": 1099, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'DBMovie'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1105, +"startColumn": 22, +"endLine": 1105, +"endColumn": 29 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'DBTV'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1135, +"startColumn": 22, +"endLine": 1135, +"endColumn": 26 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Scans'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1169, +"startColumn": 22, +"endLine": 1169, +"endColumn": 27 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ScrapeInfo'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1180, +"startColumn": 22, +"endLine": 1180, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ScrapeModifier'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1201, +"startColumn": 22, +"endLine": 1201, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ScrapeOptions'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1217, +"startColumn": 22, +"endLine": 1217, +"endColumn": 35 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'SettingsResult'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1252, +"startColumn": 22, +"endLine": 1252, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'TVScrapeOptions'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1264, +"startColumn": 22, +"endLine": 1264, +"endColumn": 37 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ModulesMenus'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPICommon.vb", +"region": { +"startLine": 1291, +"startColumn": 22, +"endLine": 1291, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'DVD_Time_Type'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 522, +"startColumn": 23, +"endLine": 522, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'PGC_Cell_Info_Type'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 536, +"startColumn": 23, +"endLine": 536, +"endColumn": 41 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_AudioAttributes_VTSM_VTS'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 547, +"startColumn": 23, +"endLine": 547, +"endColumn": 54 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_IFO_VST_Parse'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 560, +"startColumn": 23, +"endLine": 560, +"endColumn": 43 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_Program_Chain_Type'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 599, +"startColumn": 23, +"endLine": 599, +"endColumn": 48 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_SRPT'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 617, +"startColumn": 23, +"endLine": 617, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'struct_VideoAttributes_VTS_VOBS'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 630, +"startColumn": 23, +"endLine": 630, +"endColumn": 54 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'SubPictureAtt_VTSM_VTS_Type'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 645, +"startColumn": 23, +"endLine": 645, +"endColumn": 50 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'VTS_PTT_SRPT'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIDVD.vb", +"region": { +"startLine": 658, +"startColumn": 23, +"endLine": 658, +"endColumn": 35 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ModuleResult'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIInterfaces.vb", +"region": { +"startLine": 202, +"startColumn": 22, +"endLine": 202, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Locs'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPILocalization.vb", +"region": { +"startLine": 267, +"startColumn": 15, +"endLine": 267, +"endColumn": 19 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type '_ISOLanguage'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPILocalization.vb", +"region": { +"startLine": 279, +"startColumn": 15, +"endLine": 279, +"endColumn": 27 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'AssemblyListItem'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIModules.vb", +"region": { +"startLine": 635, +"startColumn": 15, +"endLine": 635, +"endColumn": 31 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'VersionItem'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIModules.vb", +"region": { +"startLine": 646, +"startColumn": 15, +"endLine": 646, +"endColumn": 26 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIScanner.vb", +"region": { +"startLine": 1363, +"startColumn": 23, +"endLine": 1363, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ProgressValue'.", +"location": { +"uri": "sources\Ember-MM\EmberAPI\clsAPIScanner.vb", +"region": { +"startLine": 1374, +"startColumn": 23, +"endLine": 1374, +"endColumn": 36 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.BulkRename-{EAAB0601-6478-44D2-A39C-89B0850D7833}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.BulkRename-{EAAB0601-6478-44D2-A39C-89B0850D7833}-S3898.json new file mode 100644 index 00000000000..18a303bb232 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.BulkRename-{EAAB0601-6478-44D2-A39C-89B0850D7833}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type '_MySettings'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.BulkRename\Module.BulkRenamer.vb", +"region": { +"startLine": 268, +"startColumn": 15, +"endLine": 268, +"endColumn": 26 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.MediaFileManager-{F6CACA89-E8E4-45D9-B942-97FBD4ADD106}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.MediaFileManager-{F6CACA89-E8E4-45D9-B942-97FBD4ADD106}-S3898.json new file mode 100644 index 00000000000..545b3fbb10b --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.MediaFileManager-{F6CACA89-E8E4-45D9-B942-97FBD4ADD106}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.MediaFileManager\Module.MediaFileManagerModule.vb", +"region": { +"startLine": 379, +"startColumn": 23, +"endLine": 379, +"endColumn": 32 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.MovieExporter-{B0BDF9C2-EB9C-4090-B9A9-B703DF00CCEF}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.MovieExporter-{B0BDF9C2-EB9C-4090-B9A9-B703DF00CCEF}-S3898.json new file mode 100644 index 00000000000..4aed09c2c4e --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.MovieExporter-{B0BDF9C2-EB9C-4090-B9A9-B703DF00CCEF}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.MovieExport\dlgExportMovies.vb", +"region": { +"startLine": 837, +"startColumn": 23, +"endLine": 837, +"endColumn": 32 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.NMT-{84B2143A-D04F-4262-923D-21AEDF86E2B7}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.NMT-{84B2143A-D04F-4262-923D-21AEDF86E2B7}-S3898.json new file mode 100644 index 00000000000..f2dc9acef87 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.NMT-{84B2143A-D04F-4262-923D-21AEDF86E2B7}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.NMT\dlgNMTMovies.vb", +"region": { +"startLine": 1340, +"startColumn": 23, +"endLine": 1340, +"endColumn": 32 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.WebServer-{64D6F035-F186-4193-AF35-D20126B8DA64}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.WebServer-{64D6F035-F186-4193-AF35-D20126B8DA64}-S3898.json new file mode 100644 index 00000000000..1b431f38f9a --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.WebServer-{64D6F035-F186-4193-AF35-D20126B8DA64}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'MimeType'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.WebServer\Module.WebServer.vb", +"region": { +"startLine": 33, +"startColumn": 19, +"endLine": 33, +"endColumn": 27 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/generic.EmberCore.XBMC-{6FE33C61-E2C2-4982-9536-63AEF0A98AAA}-S3898.json b/analyzers/its/expected/Ember-MM/generic.EmberCore.XBMC-{6FE33C61-E2C2-4982-9536-63AEF0A98AAA}-S3898.json new file mode 100644 index 00000000000..aeed6c2060a --- /dev/null +++ b/analyzers/its/expected/Ember-MM/generic.EmberCore.XBMC-{6FE33C61-E2C2-4982-9536-63AEF0A98AAA}-S3898.json @@ -0,0 +1,17 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'EmberSource'.", +"location": { +"uri": "sources\Ember-MM\Addons\generic.EmberCore.XBMC\dlgXBMCHost.vb", +"region": { +"startLine": 15, +"startColumn": 15, +"endLine": 15, +"endColumn": 26 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/scraper.EmberCore-{EF6A550E-DD76-4F4D-8250-8598140F828B}-S3898.json b/analyzers/its/expected/Ember-MM/scraper.EmberCore-{EF6A550E-DD76-4F4D-8250-8598140F828B}-S3898.json new file mode 100644 index 00000000000..ed2f2233e69 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/scraper.EmberCore-{EF6A550E-DD76-4F4D-8250-8598140F828B}-S3898.json @@ -0,0 +1,225 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeIMDB.vb", +"region": { +"startLine": 1001, +"startColumn": 27, +"endLine": 1001, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeIMDB.vb", +"region": { +"startLine": 1016, +"startColumn": 27, +"endLine": 1016, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeIMPA.vb", +"region": { +"startLine": 152, +"startColumn": 27, +"endLine": 152, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeIMPA.vb", +"region": { +"startLine": 163, +"startColumn": 27, +"endLine": 163, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeMPDB.vb", +"region": { +"startLine": 134, +"startColumn": 27, +"endLine": 134, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeMPDB.vb", +"region": { +"startLine": 144, +"startColumn": 27, +"endLine": 144, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\clsScrapeTMDB.vb", +"region": { +"startLine": 228, +"startColumn": 27, +"endLine": 228, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\dlgIMDBSearchResults.vb", +"region": { +"startLine": 486, +"startColumn": 23, +"endLine": 486, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\dlgIMDBSearchResults.vb", +"region": { +"startLine": 497, +"startColumn": 23, +"endLine": 497, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\Scraper\dlgTrailer.vb", +"region": { +"startLine": 418, +"startColumn": 23, +"endLine": 418, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type '_MySettings'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\scraperMovieNativeModule.vb", +"region": { +"startLine": 668, +"startColumn": 15, +"endLine": 668, +"endColumn": 26 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'TVImages'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\clsScrapeTVDB.vb", +"region": { +"startLine": 130, +"startColumn": 22, +"endLine": 130, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\clsScrapeTVDB.vb", +"region": { +"startLine": 1262, +"startColumn": 27, +"endLine": 1262, +"endColumn": 36 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\clsScrapeTVDB.vb", +"region": { +"startLine": 1269, +"startColumn": 27, +"endLine": 1269, +"endColumn": 34 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\dlgTVDBSearchResults.vb", +"region": { +"startLine": 415, +"startColumn": 23, +"endLine": 415, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\dlgTVDBSearchResults.vb", +"region": { +"startLine": 425, +"startColumn": 23, +"endLine": 425, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'ImageTag'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore\TVScraper\dlgTVImageSelect.vb", +"region": { +"startLine": 1196, +"startColumn": 23, +"endLine": 1196, +"endColumn": 31 +} +} +} +] +} diff --git a/analyzers/its/expected/Ember-MM/scraper.EmberCore.XML-{E567C031-1F7B-4637-9B3A-806988DE50CF}-S3898.json b/analyzers/its/expected/Ember-MM/scraper.EmberCore.XML-{E567C031-1F7B-4637-9B3A-806988DE50CF}-S3898.json new file mode 100644 index 00000000000..a586800aa4d --- /dev/null +++ b/analyzers/its/expected/Ember-MM/scraper.EmberCore.XML-{E567C031-1F7B-4637-9B3A-806988DE50CF}-S3898.json @@ -0,0 +1,69 @@ +{ +"issues": [ +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\dlgSearchResults.vb", +"region": { +"startLine": 302, +"startColumn": 23, +"endLine": 302, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Results'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\dlgSearchResults.vb", +"region": { +"startLine": 312, +"startColumn": 23, +"endLine": 312, +"endColumn": 30 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\dlgTrailer.vb", +"region": { +"startLine": 336, +"startColumn": 23, +"endLine": 336, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'Arguments'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\scraperMovieXMLModule.vb", +"region": { +"startLine": 49, +"startColumn": 23, +"endLine": 49, +"endColumn": 32 +} +} +}, +{ +"id": "S3898", +"message": "Implement 'IEquatable' in value type 'BufferContents'.", +"location": { +"uri": "sources\Ember-MM\Addons\scraper.EmberCore.XML\XMLScraper\ScraperLib\FunctionInformation.vb", +"region": { +"startLine": 33, +"startColumn": 26, +"endLine": 33, +"endColumn": 40 +} +} +} +] +} diff --git a/analyzers/rspec/vbnet/S3898_vb.net.html b/analyzers/rspec/vbnet/S3898_vb.net.html new file mode 100644 index 00000000000..3c5beea75f2 --- /dev/null +++ b/analyzers/rspec/vbnet/S3898_vb.net.html @@ -0,0 +1,29 @@ +

If you’re using a Structure, it is likely because you’re interested in performance. But by failing to implement +IEquatable<T> you’re loosing performance when comparisons are made because without IEquatable<T>, boxing and +reflection are used to make comparisons.

+

Noncompliant Code Example

+
+Structure MyStruct ' Noncompliant
+
+    Public Property Value As Integer
+
+End Structure
+
+

Compliant Solution

+
+Structure MyStruct
+    Implements IEquatable(Of MyStruct)
+
+    Public Property Value As Integer
+
+    Public Overloads Function Equals(other As MyStruct) As Boolean Implements IEquatable(Of MyStruct).Equals
+        ' ...
+    End Function
+
+End Structure
+
+

See

+ + diff --git a/analyzers/rspec/vbnet/S3898_vb.net.json b/analyzers/rspec/vbnet/S3898_vb.net.json new file mode 100644 index 00000000000..2d73bcab85a --- /dev/null +++ b/analyzers/rspec/vbnet/S3898_vb.net.json @@ -0,0 +1,17 @@ +{ + "title": "Value types should implement \"IEquatable\u003cT\u003e\"", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "20min" + }, + "tags": [ + "performance" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-3898", + "sqKey": "S3898", + "scope": "All", + "quickfix": "unknown" +} diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs index 3c5d390c8be..c57298bf631 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs @@ -83,6 +83,11 @@ node switch public override ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node) => Cast(node).Declaration.Variables.Select(x => x.Identifier).ToImmutableArray(); + public override SyntaxKind[] ModifierKinds(SyntaxNode node) => + node is TypeDeclarationSyntax typeDeclaration + ? typeDeclaration.Modifiers.Select(x => x.Kind()).ToArray() + : Array.Empty(); + public override SyntaxNode NodeExpression(SyntaxNode node) => node switch { diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs index eb759eaa675..0a97a8d62a8 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxKindFacade.cs @@ -51,10 +51,12 @@ internal sealed class CSharpSyntaxKindFacade : ISyntaxKindFacade public SyntaxKind[] ObjectCreationExpressions => new[] { SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression }; public SyntaxKind Parameter => SyntaxKind.Parameter; public SyntaxKind ParameterList => SyntaxKind.ParameterList; + public SyntaxKind RefKeyword => SyntaxKind.RefKeyword; public SyntaxKind ReturnStatement => SyntaxKind.ReturnStatement; public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentExpression; public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression; public SyntaxKind[] StringLiteralExpressions => new[] { SyntaxKind.StringLiteralExpression, SyntaxKindEx.Utf8StringLiteralExpression }; + public SyntaxKind StructDeclaration => SyntaxKind.StructDeclaration; public SyntaxKind[] TypeDeclaration => new[] { SyntaxKind.ClassDeclaration, diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs index 224087f118e..ca0ec44cf28 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ValueTypeShouldImplementIEquatable.cs @@ -18,29 +18,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarAnalyzer.Rules.CSharp -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public sealed class ValueTypeShouldImplementIEquatable : SonarDiagnosticAnalyzer - { - private const string DiagnosticId = "S3898"; - private const string MessageFormat = "Implement 'IEquatable' in value type '{0}'."; - - private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); +namespace SonarAnalyzer.Rules.CSharp; - public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); - - protected override void Initialize(SonarAnalysisContext context) => - context.RegisterNodeAction( - c => - { - var declaration = (StructDeclarationSyntax)c.Node; - if (!declaration.Modifiers.Any(SyntaxKind.RefKeyword) - && c.SemanticModel.GetDeclaredSymbol(declaration) is { } structSymbol - && !structSymbol.Implements(KnownType.System_IEquatable_T)) - { - c.ReportIssue(Diagnostic.Create(Rule, declaration.Identifier.GetLocation(), declaration.Identifier.ValueText)); - } - }, SyntaxKind.StructDeclaration); - } +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class ValueTypeShouldImplementIEquatable : ValueTypeShouldImplementIEquatableBase +{ + protected override ILanguageFacade Language => CSharpFacade.Instance; } diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs index b81a812e6ff..3d8a0052fc4 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/ISyntaxKindFacade.cs @@ -39,11 +39,13 @@ public interface ISyntaxKindFacade abstract TSyntaxKind[] MethodDeclarations { get; } abstract TSyntaxKind[] ObjectCreationExpressions { get; } abstract TSyntaxKind Parameter { get; } + abstract TSyntaxKind RefKeyword { get; } abstract TSyntaxKind ParameterList { get; } abstract TSyntaxKind ReturnStatement { get; } abstract TSyntaxKind SimpleAssignment { get; } abstract TSyntaxKind SimpleMemberAccessExpression { get; } abstract TSyntaxKind[] StringLiteralExpressions { get; } + abstract TSyntaxKind StructDeclaration { get; } abstract TSyntaxKind[] TypeDeclaration { get; } abstract TSyntaxKind LeftShiftExpression { get; } abstract TSyntaxKind RightShiftExpression { get; } diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs index cd6fbef5c44..d97f5cfa666 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs @@ -40,6 +40,7 @@ public abstract class SyntaxFacade public abstract SyntaxToken? InvocationIdentifier(SyntaxNode invocation); public abstract ImmutableArray LocalDeclarationIdentifiers(SyntaxNode node); public abstract ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node); + public abstract TSyntaxKind[] ModifierKinds(SyntaxNode node); public abstract SyntaxNode NodeExpression(SyntaxNode node); public abstract SyntaxToken? NodeIdentifier(SyntaxNode node); public abstract SyntaxNode RemoveConditionalAccess(SyntaxNode node); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/ValueTypeShouldImplementIEquatableBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/ValueTypeShouldImplementIEquatableBase.cs new file mode 100644 index 00000000000..75158d89b8c --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Rules/ValueTypeShouldImplementIEquatableBase.cs @@ -0,0 +1,47 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Rules; + +public abstract class ValueTypeShouldImplementIEquatableBase : SonarDiagnosticAnalyzer + where TSyntaxKind : struct +{ + private const string DiagnosticId = "S3898"; + + protected override string MessageFormat => "Implement 'IEquatable' in value type '{0}'."; + + protected ValueTypeShouldImplementIEquatableBase() : base(DiagnosticId) { } + + protected sealed override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction( + Language.GeneratedCodeRecognizer, + c => + { + var modifiers = Language.Syntax.ModifierKinds(c.Node); + if (!modifiers.Any(x => x.Equals(Language.SyntaxKind.RefKeyword)) + && c.SemanticModel.GetDeclaredSymbol(c.Node) is INamedTypeSymbol structSymbol + && !structSymbol.Implements(KnownType.System_IEquatable_T)) + { + var identifier = Language.Syntax.NodeIdentifier(c.Node).Value; + c.ReportIssue(Diagnostic.Create(Rule, identifier.GetLocation(), identifier.ValueText)); + } + }, + Language.SyntaxKind.StructDeclaration); +} diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs index b387b82a0fb..b49dea3e7b7 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs @@ -81,6 +81,11 @@ node switch public override ImmutableArray FieldDeclarationIdentifiers(SyntaxNode node) => Cast(node).Declarators.SelectMany(d => d.Names.Select(n => n.Identifier)).ToImmutableArray(); + public override SyntaxKind[] ModifierKinds(SyntaxNode node) => + node is StructureBlockSyntax structureBlock + ? structureBlock.StructureStatement.Modifiers.Select(x => x.Kind()).ToArray() + : Array.Empty(); + public override SyntaxNode NodeExpression(SyntaxNode node) => node switch { diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs index a5380fcc3c2..602fd70132b 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxKindFacade.cs @@ -47,10 +47,12 @@ internal sealed class VisualBasicSyntaxKindFacade : ISyntaxKindFacade new[] { SyntaxKind.ObjectCreationExpression }; public SyntaxKind Parameter => SyntaxKind.Parameter; public SyntaxKind ParameterList => SyntaxKind.ParameterList; + public SyntaxKind RefKeyword => SyntaxKind.ByRefKeyword; public SyntaxKind ReturnStatement => SyntaxKind.ReturnStatement; public SyntaxKind SimpleAssignment => SyntaxKind.SimpleAssignmentStatement; public SyntaxKind SimpleMemberAccessExpression => SyntaxKind.SimpleMemberAccessExpression; public SyntaxKind[] StringLiteralExpressions => new[] { SyntaxKind.StringLiteralExpression }; + public SyntaxKind StructDeclaration => SyntaxKind.StructureBlock; public SyntaxKind[] TypeDeclaration => new[] { SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.EnumBlock }; public SyntaxKind LeftShiftExpression => SyntaxKind.LeftShiftExpression; public SyntaxKind RightShiftExpression => SyntaxKind.RightShiftExpression; diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicSyntaxHelper.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicSyntaxHelper.cs index 91cde3efb69..b9ceda82773 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicSyntaxHelper.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Helpers/VisualBasicSyntaxHelper.cs @@ -18,250 +18,250 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarAnalyzer.Helpers +namespace SonarAnalyzer.Helpers; + +internal static class VisualBasicSyntaxHelper { - internal static class VisualBasicSyntaxHelper + private static readonly SyntaxKind[] LiteralSyntaxKinds = + new[] + { + SyntaxKind.CharacterLiteralExpression, + SyntaxKind.FalseLiteralExpression, + SyntaxKind.NothingLiteralExpression, + SyntaxKind.NumericLiteralExpression, + SyntaxKind.StringLiteralExpression, + SyntaxKind.TrueLiteralExpression, + }; + + public static SyntaxNode GetTopMostContainingMethod(this SyntaxNode node) => + node.AncestorsAndSelf().LastOrDefault(ancestor => ancestor is MethodBaseSyntax || ancestor is PropertyBlockSyntax); + + public static SyntaxNode RemoveParentheses(this SyntaxNode expression) { - private static readonly SyntaxKind[] LiteralSyntaxKinds = - new[] - { - SyntaxKind.CharacterLiteralExpression, - SyntaxKind.FalseLiteralExpression, - SyntaxKind.NothingLiteralExpression, - SyntaxKind.NumericLiteralExpression, - SyntaxKind.StringLiteralExpression, - SyntaxKind.TrueLiteralExpression, - }; - - public static SyntaxNode GetTopMostContainingMethod(this SyntaxNode node) => - node.AncestorsAndSelf().LastOrDefault(ancestor => ancestor is MethodBaseSyntax || ancestor is PropertyBlockSyntax); - - public static SyntaxNode RemoveParentheses(this SyntaxNode expression) + var current = expression; + while (current is ParenthesizedExpressionSyntax parenthesized) { - var current = expression; - while (current is ParenthesizedExpressionSyntax parenthesized) - { - current = parenthesized.Expression; - } - return current; + current = parenthesized.Expression; } + return current; + } - public static ExpressionSyntax RemoveParentheses(this ExpressionSyntax expression) => - (ExpressionSyntax)RemoveParentheses((SyntaxNode)expression); + public static ExpressionSyntax RemoveParentheses(this ExpressionSyntax expression) => + (ExpressionSyntax)RemoveParentheses((SyntaxNode)expression); - public static SyntaxNode GetSelfOrTopParenthesizedExpression(this SyntaxNode node) + public static SyntaxNode GetSelfOrTopParenthesizedExpression(this SyntaxNode node) + { + var current = node; + while (current?.Parent?.IsKind(SyntaxKind.ParenthesizedExpression) ?? false) { - var current = node; - while (current?.Parent?.IsKind(SyntaxKind.ParenthesizedExpression) ?? false) - { - current = current.Parent; - } - return current; + current = current.Parent; } + return current; + } - public static ExpressionSyntax GetSelfOrTopParenthesizedExpression(this ExpressionSyntax expression) => - (ExpressionSyntax)GetSelfOrTopParenthesizedExpression((SyntaxNode)expression); + public static ExpressionSyntax GetSelfOrTopParenthesizedExpression(this ExpressionSyntax expression) => + (ExpressionSyntax)GetSelfOrTopParenthesizedExpression((SyntaxNode)expression); - public static SyntaxNode GetFirstNonParenthesizedParent(this SyntaxNode node) => - node.GetSelfOrTopParenthesizedExpression().Parent; + public static SyntaxNode GetFirstNonParenthesizedParent(this SyntaxNode node) => + node.GetSelfOrTopParenthesizedExpression().Parent; - #region Statement + #region Statement - public static StatementSyntax GetPrecedingStatement(this StatementSyntax currentStatement) - { - var children = currentStatement.Parent.ChildNodes().ToList(); - var index = children.IndexOf(currentStatement); - return index == 0 ? null : children[index - 1] as StatementSyntax; - } + public static StatementSyntax GetPrecedingStatement(this StatementSyntax currentStatement) + { + var children = currentStatement.Parent.ChildNodes().ToList(); + var index = children.IndexOf(currentStatement); + return index == 0 ? null : children[index - 1] as StatementSyntax; + } - public static StatementSyntax GetSucceedingStatement(this StatementSyntax currentStatement) - { - var children = currentStatement.Parent.ChildNodes().ToList(); - var index = children.IndexOf(currentStatement); - return index == children.Count - 1 ? null : children[index + 1] as StatementSyntax; - } + public static StatementSyntax GetSucceedingStatement(this StatementSyntax currentStatement) + { + var children = currentStatement.Parent.ChildNodes().ToList(); + var index = children.IndexOf(currentStatement); + return index == children.Count - 1 ? null : children[index + 1] as StatementSyntax; + } - #endregion Statement + #endregion Statement - public static bool IsNothingLiteral(this SyntaxNode syntaxNode) => - syntaxNode != null && syntaxNode.IsKind(SyntaxKind.NothingLiteralExpression); + public static bool IsNothingLiteral(this SyntaxNode syntaxNode) => + syntaxNode != null && syntaxNode.IsKind(SyntaxKind.NothingLiteralExpression); - public static bool IsAnyKind(this SyntaxNode syntaxNode, params SyntaxKind[] syntaxKinds) => - syntaxNode != null && syntaxKinds.Contains((SyntaxKind)syntaxNode.RawKind); + public static bool IsAnyKind(this SyntaxNode syntaxNode, params SyntaxKind[] syntaxKinds) => + syntaxNode != null && syntaxKinds.Contains((SyntaxKind)syntaxNode.RawKind); - public static bool IsAnyKind(this SyntaxToken syntaxToken, ISet collection) => - collection.Contains((SyntaxKind)syntaxToken.RawKind); + public static bool IsAnyKind(this SyntaxToken syntaxToken, ISet collection) => + collection.Contains((SyntaxKind)syntaxToken.RawKind); - public static bool IsAnyKind(this SyntaxNode syntaxNode, ISet collection) => - syntaxNode != null && collection.Contains((SyntaxKind)syntaxNode.RawKind); + public static bool IsAnyKind(this SyntaxNode syntaxNode, ISet collection) => + syntaxNode != null && collection.Contains((SyntaxKind)syntaxNode.RawKind); - public static bool IsAnyKind(this SyntaxToken syntaxToken, params SyntaxKind[] syntaxKinds) => - syntaxKinds.Contains((SyntaxKind)syntaxToken.RawKind); + public static bool IsAnyKind(this SyntaxToken syntaxToken, params SyntaxKind[] syntaxKinds) => + syntaxKinds.Contains((SyntaxKind)syntaxToken.RawKind); - public static bool AnyOfKind(this IEnumerable nodes, SyntaxKind kind) => - nodes.Any(n => n.RawKind == (int)kind); + public static bool AnyOfKind(this IEnumerable nodes, SyntaxKind kind) => + nodes.Any(n => n.RawKind == (int)kind); - public static SyntaxToken? GetMethodCallIdentifier(this InvocationExpressionSyntax invocation) + public static SyntaxToken? GetMethodCallIdentifier(this InvocationExpressionSyntax invocation) + { + if (invocation == null || + invocation.Expression == null) { - if (invocation == null || - invocation.Expression == null) - { + return null; + } + + var expressionType = invocation.Expression.Kind(); + // in vb.net when using the null - conditional operator (e.g.handle?.IsClosed), the parser + // will generate a SimpleMemberAccessExpression and not a MemberBindingExpressionSyntax like for C# + switch (expressionType) + { + case SyntaxKind.IdentifierName: + return ((IdentifierNameSyntax)invocation.Expression).Identifier; + case SyntaxKind.SimpleMemberAccessExpression: + return ((MemberAccessExpressionSyntax)invocation.Expression).Name.Identifier; + default: return null; - } - - var expressionType = invocation.Expression.Kind(); - // in vb.net when using the null - conditional operator (e.g.handle?.IsClosed), the parser - // will generate a SimpleMemberAccessExpression and not a MemberBindingExpressionSyntax like for C# - switch (expressionType) - { - case SyntaxKind.IdentifierName: - return ((IdentifierNameSyntax)invocation.Expression).Identifier; - case SyntaxKind.SimpleMemberAccessExpression: - return ((MemberAccessExpressionSyntax)invocation.Expression).Name.Identifier; - default: - return null; - } } - public static bool IsMethodInvocation(this InvocationExpressionSyntax expression, KnownType type, string methodName, SemanticModel semanticModel) => - semanticModel.GetSymbolInfo(expression).Symbol is IMethodSymbol methodSymbol && - methodSymbol.IsInType(type) && - // vbnet is case insensitive - methodName.Equals(methodSymbol.Name, System.StringComparison.InvariantCultureIgnoreCase); + } + public static bool IsMethodInvocation(this InvocationExpressionSyntax expression, KnownType type, string methodName, SemanticModel semanticModel) => + semanticModel.GetSymbolInfo(expression).Symbol is IMethodSymbol methodSymbol && + methodSymbol.IsInType(type) && + // vbnet is case insensitive + methodName.Equals(methodSymbol.Name, System.StringComparison.InvariantCultureIgnoreCase); - public static bool IsOnBase(this ExpressionSyntax expression) => - IsOn(expression, SyntaxKind.MyBaseExpression); + public static bool IsOnBase(this ExpressionSyntax expression) => + IsOn(expression, SyntaxKind.MyBaseExpression); - private static bool IsOn(this ExpressionSyntax expression, SyntaxKind onKind) + private static bool IsOn(this ExpressionSyntax expression, SyntaxKind onKind) + { + switch (expression?.Kind()) { - switch (expression?.Kind()) - { - case SyntaxKind.InvocationExpression: - return IsOn(((InvocationExpressionSyntax)expression).Expression, onKind); - - case SyntaxKind.GlobalName: - case SyntaxKind.GenericName: - case SyntaxKind.IdentifierName: - case SyntaxKind.QualifiedName: - // This is a simplification as we don't check where the method is defined (so this could be this or base) - return true; - - case SyntaxKind.SimpleMemberAccessExpression: - return ((MemberAccessExpressionSyntax)expression).Expression.RemoveParentheses().IsKind(onKind); - - case SyntaxKind.ConditionalAccessExpression: - return ((ConditionalAccessExpressionSyntax)expression).Expression.RemoveParentheses().IsKind(onKind); - - default: - return false; - } + case SyntaxKind.InvocationExpression: + return IsOn(((InvocationExpressionSyntax)expression).Expression, onKind); + + case SyntaxKind.GlobalName: + case SyntaxKind.GenericName: + case SyntaxKind.IdentifierName: + case SyntaxKind.QualifiedName: + // This is a simplification as we don't check where the method is defined (so this could be this or base) + return true; + + case SyntaxKind.SimpleMemberAccessExpression: + return ((MemberAccessExpressionSyntax)expression).Expression.RemoveParentheses().IsKind(onKind); + + case SyntaxKind.ConditionalAccessExpression: + return ((ConditionalAccessExpressionSyntax)expression).Expression.RemoveParentheses().IsKind(onKind); + + default: + return false; } + } - public static SyntaxToken? GetIdentifier(this SyntaxNode node) => - node?.RemoveParentheses() switch - { - ClassBlockSyntax x => x.ClassStatement.Identifier, - ClassStatementSyntax x => x.Identifier, - IdentifierNameSyntax x => x.Identifier, - MemberAccessExpressionSyntax x => x.Name.Identifier, - MethodBlockSyntax x => x.SubOrFunctionStatement?.GetIdentifier(), - MethodStatementSyntax x => x.Identifier, - EnumStatementSyntax x => x.Identifier, - EnumMemberDeclarationSyntax x => x.Identifier, - InvocationExpressionSyntax x => x.Expression?.GetIdentifier(), - ModifiedIdentifierSyntax x => x.Identifier, - PredefinedTypeSyntax x => x.Keyword, - ParameterSyntax x => x.Identifier?.GetIdentifier(), - PropertyStatementSyntax x => x.Identifier, - SimpleArgumentSyntax x => x.NameColonEquals?.Name.Identifier, - SimpleNameSyntax x => x.Identifier, - QualifiedNameSyntax x => x.Right.Identifier, - _ => null, - }; - - public static string GetName(this SyntaxNode expression) => - expression.GetIdentifier()?.ValueText ?? string.Empty; - - public static bool NameIs(this ExpressionSyntax expression, string name) => - expression.GetName().Equals(name, StringComparison.InvariantCultureIgnoreCase); - - public static bool HasConstantValue(this ExpressionSyntax expression, SemanticModel semanticModel) => - expression.RemoveParentheses().IsAnyKind(LiteralSyntaxKinds) || expression.FindConstantValue(semanticModel) != null; - - public static string StringValue(this SyntaxNode node, SemanticModel semanticModel) => - node switch - { - LiteralExpressionSyntax literal when literal.IsKind(SyntaxKind.StringLiteralExpression) => literal.Token.ValueText, - InterpolatedStringExpressionSyntax expression => expression.TryGetInterpolatedTextValue(semanticModel, out var interpolatedValue) ? interpolatedValue : expression.GetContentsText(), - _ => null - }; - - public static bool IsLeftSideOfAssignment(this ExpressionSyntax expression) + public static SyntaxToken? GetIdentifier(this SyntaxNode node) => + node?.RemoveParentheses() switch { - var topParenthesizedExpression = expression.GetSelfOrTopParenthesizedExpression(); - return topParenthesizedExpression.Parent.IsKind(SyntaxKind.SimpleAssignmentStatement) && - topParenthesizedExpression.Parent is AssignmentStatementSyntax assignment && - assignment.Left == topParenthesizedExpression; - } + ClassBlockSyntax x => x.ClassStatement.Identifier, + ClassStatementSyntax x => x.Identifier, + IdentifierNameSyntax x => x.Identifier, + MemberAccessExpressionSyntax x => x.Name.Identifier, + MethodBlockSyntax x => x.SubOrFunctionStatement?.GetIdentifier(), + MethodStatementSyntax x => x.Identifier, + EnumStatementSyntax x => x.Identifier, + EnumMemberDeclarationSyntax x => x.Identifier, + InvocationExpressionSyntax x => x.Expression?.GetIdentifier(), + ModifiedIdentifierSyntax x => x.Identifier, + PredefinedTypeSyntax x => x.Keyword, + ParameterSyntax x => x.Identifier?.GetIdentifier(), + PropertyStatementSyntax x => x.Identifier, + SimpleArgumentSyntax x => x.NameColonEquals?.Name.Identifier, + SimpleNameSyntax x => x.Identifier, + StructureBlockSyntax x => x.StructureStatement.Identifier, + QualifiedNameSyntax x => x.Right.Identifier, + _ => null, + }; + + public static string GetName(this SyntaxNode expression) => + expression.GetIdentifier()?.ValueText ?? string.Empty; + + public static bool NameIs(this ExpressionSyntax expression, string name) => + expression.GetName().Equals(name, StringComparison.InvariantCultureIgnoreCase); + + public static bool HasConstantValue(this ExpressionSyntax expression, SemanticModel semanticModel) => + expression.RemoveParentheses().IsAnyKind(LiteralSyntaxKinds) || expression.FindConstantValue(semanticModel) != null; + + public static string StringValue(this SyntaxNode node, SemanticModel semanticModel) => + node switch + { + LiteralExpressionSyntax literal when literal.IsKind(SyntaxKind.StringLiteralExpression) => literal.Token.ValueText, + InterpolatedStringExpressionSyntax expression => expression.TryGetInterpolatedTextValue(semanticModel, out var interpolatedValue) ? interpolatedValue : expression.GetContentsText(), + _ => null + }; + + public static bool IsLeftSideOfAssignment(this ExpressionSyntax expression) + { + var topParenthesizedExpression = expression.GetSelfOrTopParenthesizedExpression(); + return topParenthesizedExpression.Parent.IsKind(SyntaxKind.SimpleAssignmentStatement) && + topParenthesizedExpression.Parent is AssignmentStatementSyntax assignment && + assignment.Left == topParenthesizedExpression; + } - public static bool IsComment(this SyntaxTrivia trivia) + public static bool IsComment(this SyntaxTrivia trivia) + { + switch (trivia.Kind()) { - switch (trivia.Kind()) - { - case SyntaxKind.CommentTrivia: - case SyntaxKind.DocumentationCommentExteriorTrivia: - case SyntaxKind.DocumentationCommentTrivia: - return true; - - default: - return false; - } + case SyntaxKind.CommentTrivia: + case SyntaxKind.DocumentationCommentExteriorTrivia: + case SyntaxKind.DocumentationCommentTrivia: + return true; + + default: + return false; } + } - public static Location FindIdentifierLocation(this MethodBlockBaseSyntax methodBlockBase) => - GetIdentifierOrDefault(methodBlockBase)?.GetLocation(); + public static Location FindIdentifierLocation(this MethodBlockBaseSyntax methodBlockBase) => + GetIdentifierOrDefault(methodBlockBase)?.GetLocation(); - public static SyntaxToken? GetIdentifierOrDefault(this MethodBlockBaseSyntax methodBlockBase) - { - var blockStatement = methodBlockBase?.BlockStatement; + public static SyntaxToken? GetIdentifierOrDefault(this MethodBlockBaseSyntax methodBlockBase) + { + var blockStatement = methodBlockBase?.BlockStatement; - switch (blockStatement?.Kind()) - { - case SyntaxKind.SubNewStatement: - return (blockStatement as SubNewStatementSyntax)?.NewKeyword; + switch (blockStatement?.Kind()) + { + case SyntaxKind.SubNewStatement: + return (blockStatement as SubNewStatementSyntax)?.NewKeyword; - case SyntaxKind.FunctionStatement: - case SyntaxKind.SubStatement: - return (blockStatement as MethodStatementSyntax)?.Identifier; + case SyntaxKind.FunctionStatement: + case SyntaxKind.SubStatement: + return (blockStatement as MethodStatementSyntax)?.Identifier; - default: - return null; - } + default: + return null; } + } - public static string GetIdentifierText(this MethodBlockSyntax method) - => method.SubOrFunctionStatement.Identifier.ValueText; + public static string GetIdentifierText(this MethodBlockSyntax method) + => method.SubOrFunctionStatement.Identifier.ValueText; - public static SeparatedSyntaxList? GetParameters(this MethodBlockSyntax method) - => method.BlockStatement?.ParameterList?.Parameters; + public static SeparatedSyntaxList? GetParameters(this MethodBlockSyntax method) + => method.BlockStatement?.ParameterList?.Parameters; - public static ExpressionSyntax Get(this ArgumentListSyntax argumentList, int index) => - argumentList != null && argumentList.Arguments.Count > index - ? argumentList.Arguments[index].GetExpression().RemoveParentheses() - : null; + public static ExpressionSyntax Get(this ArgumentListSyntax argumentList, int index) => + argumentList != null && argumentList.Arguments.Count > index + ? argumentList.Arguments[index].GetExpression().RemoveParentheses() + : null; - /// - /// Returns argument expressions for given parameter. - /// - /// There can be zero, one or more results based on parameter type (Optional or ParamArray/params). - /// - public static ImmutableArray ArgumentValuesForParameter(SemanticModel semanticModel, ArgumentListSyntax argumentList, string parameterName) + /// + /// Returns argument expressions for given parameter. + /// + /// There can be zero, one or more results based on parameter type (Optional or ParamArray/params). + /// + public static ImmutableArray ArgumentValuesForParameter(SemanticModel semanticModel, ArgumentListSyntax argumentList, string parameterName) + { + var methodParameterLookup = new VisualBasicMethodParameterLookup(argumentList, semanticModel); + if (methodParameterLookup.TryGetSyntax(parameterName, out var expressions)) { - var methodParameterLookup = new VisualBasicMethodParameterLookup(argumentList, semanticModel); - if (methodParameterLookup.TryGetSyntax(parameterName, out var expressions)) - { - return expressions; - } - return ImmutableArray.Empty; + return expressions; } + return ImmutableArray.Empty; } } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ValueTypeShouldImplementIEquatable.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ValueTypeShouldImplementIEquatable.cs new file mode 100644 index 00000000000..d74a08a373d --- /dev/null +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/ValueTypeShouldImplementIEquatable.cs @@ -0,0 +1,32 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.VisualBasic; +using SonarAnalyzer.Helpers; + +namespace SonarAnalyzer.Rules.VisualBasic; + +[DiagnosticAnalyzer(LanguageNames.VisualBasic)] +public sealed class ValueTypeShouldImplementIEquatable : ValueTypeShouldImplementIEquatableBase +{ + protected override ILanguageFacade Language => VisualBasicFacade.Instance; +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Common/ReadMeTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Common/ReadMeTest.cs index 7b35e164d61..4e31432483b 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Common/ReadMeTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Common/ReadMeTest.cs @@ -51,7 +51,7 @@ private void HasCorrectRuleCount(AnalyzerLanguage language, string name) var count = int.Parse(match.Groups["count"].Value); var min = (count / 10) * 10; - rules.Should().BeInRange(min, min + 10); + rules.Should().BeInRange(min, min + 9); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Facade/SyntaxFacadeTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Facade/SyntaxFacadeTest.cs index 621798750a1..e413aef6b56 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Facade/SyntaxFacadeTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Facade/SyntaxFacadeTest.cs @@ -22,98 +22,105 @@ using CS = Microsoft.CodeAnalysis.CSharp; using VB = Microsoft.CodeAnalysis.VisualBasic; -namespace SonarAnalyzer.UnitTest.Helpers +namespace SonarAnalyzer.UnitTest.Helpers; + +[TestClass] +public class SyntaxFacadeTest { - [TestClass] - public class SyntaxFacadeTest - { - private readonly CSharpSyntaxFacade cs = new(); - private readonly VisualBasicSyntaxFacade vb = new(); - - [TestMethod] - public void EnumMembers_Null_CS() => - cs.EnumMembers(null).Should().BeEmpty(); - - [TestMethod] - public void EnumMembers_Null_VB() => - vb.EnumMembers(null).Should().BeEmpty(); - - [TestMethod] - public void InvocationIdentifier_Null_CS() => - cs.InvocationIdentifier(null).Should().BeNull(); - - [TestMethod] - public void InvocationIdentifier_Null_VB() => - vb.InvocationIdentifier(null).Should().BeNull(); - - [TestMethod] - public void InvocationIdentifier_UnexpectedTypeThrows_CS() => - cs.Invoking(x => x.InvocationIdentifier(CS.SyntaxFactory.IdentifierName("ThisIsNotInvocation"))).Should().Throw(); - - [TestMethod] - public void InvocationIdentifier_UnexpectedTypeThrows_VB() => - vb.Invoking(x => x.InvocationIdentifier(VB.SyntaxFactory.IdentifierName("ThisIsNotInvocation"))).Should().Throw(); - - [TestMethod] - public void NodeExpression_Null_CS() => - cs.NodeExpression(null).Should().BeNull(); - - [TestMethod] - public void NodeExpression_Null_VB() => - vb.NodeExpression(null).Should().BeNull(); - - [TestMethod] - public void NodeExpression_UnexpectedTypeThrows_CS() => - cs.Invoking(x => x.NodeExpression(CS.SyntaxFactory.IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); - - [TestMethod] - public void NodeExpression_UnexpectedTypeThrows_VB() => - vb.Invoking(x => x.NodeExpression(VB.SyntaxFactory.IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); - - [TestMethod] - public void NodeIdentifier_Null_CS() => - cs.NodeIdentifier(null).Should().BeNull(); - - [TestMethod] - public void NodeIdentifier_Null_VB() => - vb.NodeIdentifier(null).Should().BeNull(); - - [TestMethod] - public void NodeIdentifier_Unexpected_Returns_Null_CS() => - cs.NodeIdentifier(CS.SyntaxFactory.AttributeList()).Should().BeNull(); - - [TestMethod] - public void NodeIdentifier_Unexpected_Returns_Null_VB() => - vb.NodeIdentifier(VB.SyntaxFactory.AttributeList()).Should().BeNull(); - - [TestMethod] - public void StringValue_UnexpectedType_CS() => - cs.StringValue(CS.SyntaxFactory.ThrowStatement(), null).Should().BeNull(); - - [TestMethod] - public void StringValue_UnexpectedType_VB() => - vb.StringValue(VB.SyntaxFactory.ThrowStatement(), null).Should().BeNull(); - - [TestMethod] - public void StringValue_NodeIsNull_CS() => - cs.StringValue(null, null).Should().BeNull(); - - [TestMethod] - public void StringValue_NodeIsNull_VB() => - vb.StringValue(null, null).Should().BeNull(); - - [TestMethod] - public void RemoveConditionalAccess_Null_CS() => - cs.RemoveConditionalAccess(null).Should().BeNull(); - - [DataTestMethod] - [DataRow("M()", "M()")] - [DataRow("this.M()", "this.M()")] - [DataRow("A.B.C.M()", "A.B.C.M()")] - [DataRow("A.B?.C.M()", ".C.M()")] - [DataRow("A.B?.C?.M()", ".M()")] - [DataRow("A.B?.C?.D", ".D")] - public void RemoveConditionalAccess_SimpleInvocation_CS(string invocation, string expected) => - cs.RemoveConditionalAccess(CS.SyntaxFactory.ParseExpression(invocation)).ToString().Should().Be(expected); - } + private readonly CSharpSyntaxFacade cs = new(); + private readonly VisualBasicSyntaxFacade vb = new(); + + [TestMethod] + public void EnumMembers_Null_CS() => + cs.EnumMembers(null).Should().BeEmpty(); + + [TestMethod] + public void EnumMembers_Null_VB() => + vb.EnumMembers(null).Should().BeEmpty(); + + [TestMethod] + public void InvocationIdentifier_Null_CS() => + cs.InvocationIdentifier(null).Should().BeNull(); + + [TestMethod] + public void InvocationIdentifier_Null_VB() => + vb.InvocationIdentifier(null).Should().BeNull(); + + [TestMethod] + public void InvocationIdentifier_UnexpectedTypeThrows_CS() => + cs.Invoking(x => x.InvocationIdentifier(CS.SyntaxFactory.IdentifierName("ThisIsNotInvocation"))).Should().Throw(); + + [TestMethod] + public void InvocationIdentifier_UnexpectedTypeThrows_VB() => + vb.Invoking(x => x.InvocationIdentifier(VB.SyntaxFactory.IdentifierName("ThisIsNotInvocation"))).Should().Throw(); + + [TestMethod] + public void ModifierKinds_Null_CS() => + cs.ModifierKinds(null).Should().BeEmpty(); + + [TestMethod] + public void ModifierKinds_Null_VB() => + vb.ModifierKinds(null).Should().BeEmpty(); + + [TestMethod] + public void NodeExpression_Null_CS() => + cs.NodeExpression(null).Should().BeNull(); + + [TestMethod] + public void NodeExpression_Null_VB() => + vb.NodeExpression(null).Should().BeNull(); + + [TestMethod] + public void NodeExpression_UnexpectedTypeThrows_CS() => + cs.Invoking(x => x.NodeExpression(CS.SyntaxFactory.IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); + + [TestMethod] + public void NodeExpression_UnexpectedTypeThrows_VB() => + vb.Invoking(x => x.NodeExpression(VB.SyntaxFactory.IdentifierName("ThisTypeDoesNotHaveExpression"))).Should().Throw(); + + [TestMethod] + public void NodeIdentifier_Null_CS() => + cs.NodeIdentifier(null).Should().BeNull(); + + [TestMethod] + public void NodeIdentifier_Null_VB() => + vb.NodeIdentifier(null).Should().BeNull(); + + [TestMethod] + public void NodeIdentifier_Unexpected_Returns_Null_CS() => + cs.NodeIdentifier(CS.SyntaxFactory.AttributeList()).Should().BeNull(); + + [TestMethod] + public void NodeIdentifier_Unexpected_Returns_Null_VB() => + vb.NodeIdentifier(VB.SyntaxFactory.AttributeList()).Should().BeNull(); + + [TestMethod] + public void StringValue_UnexpectedType_CS() => + cs.StringValue(CS.SyntaxFactory.ThrowStatement(), null).Should().BeNull(); + + [TestMethod] + public void StringValue_UnexpectedType_VB() => + vb.StringValue(VB.SyntaxFactory.ThrowStatement(), null).Should().BeNull(); + + [TestMethod] + public void StringValue_NodeIsNull_CS() => + cs.StringValue(null, null).Should().BeNull(); + + [TestMethod] + public void StringValue_NodeIsNull_VB() => + vb.StringValue(null, null).Should().BeNull(); + + [TestMethod] + public void RemoveConditionalAccess_Null_CS() => + cs.RemoveConditionalAccess(null).Should().BeNull(); + + [DataTestMethod] + [DataRow("M()", "M()")] + [DataRow("this.M()", "this.M()")] + [DataRow("A.B.C.M()", "A.B.C.M()")] + [DataRow("A.B?.C.M()", ".C.M()")] + [DataRow("A.B?.C?.M()", ".M()")] + [DataRow("A.B?.C?.D", ".D")] + public void RemoveConditionalAccess_SimpleInvocation_CS(string invocation, string expected) => + cs.RemoveConditionalAccess(CS.SyntaxFactory.ParseExpression(invocation)).ToString().Should().Be(expected); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs index 16f4ee98537..3cab127083f 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs @@ -3822,7 +3822,7 @@ internal static class RuleTypeMappingVB // ["S3895"], // ["S3896"], // ["S3897"], - // ["S3898"], + ["S3898"] = "CODE_SMELL", // ["S3899"], // ["S3900"], // ["S3901"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ValueTypeShouldImplementIEquatableTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ValueTypeShouldImplementIEquatableTest.cs index df93570e5e6..acd8c79b2c8 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ValueTypeShouldImplementIEquatableTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/ValueTypeShouldImplementIEquatableTest.cs @@ -18,26 +18,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using SonarAnalyzer.Rules.CSharp; +using CS = SonarAnalyzer.Rules.CSharp; +using VB = SonarAnalyzer.Rules.VisualBasic; -namespace SonarAnalyzer.UnitTest.Rules +namespace SonarAnalyzer.UnitTest.Rules; + +[TestClass] +public class ValueTypeShouldImplementIEquatableTest { - [TestClass] - public class ValueTypeShouldImplementIEquatableTest - { - private readonly VerifierBuilder builder = new VerifierBuilder(); + private readonly VerifierBuilder builderCS = new VerifierBuilder(); + private readonly VerifierBuilder builderVB = new VerifierBuilder(); - [TestMethod] - public void ValueTypeShouldImplementIEquatable() => - builder.AddPaths("ValueTypeShouldImplementIEquatable.cs").WithOptions(ParseOptionsHelper.FromCSharp8).Verify(); + [TestMethod] + public void ValueTypeShouldImplementIEquatable_CS() => + builderCS.AddPaths("ValueTypeShouldImplementIEquatable.cs").WithOptions(ParseOptionsHelper.FromCSharp8).Verify(); #if NET - [TestMethod] - public void ValueTypeShouldImplementIEquatable_CSharp10() => - builder.AddPaths("ValueTypeShouldImplementIEquatable.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify(); + [TestMethod] + public void ValueTypeShouldImplementIEquatable_CSharp10() => + builderCS.AddPaths("ValueTypeShouldImplementIEquatable.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify(); #endif - } + [TestMethod] + public void ValueTypeShouldImplementIEquatable_VB() => + builderVB.AddPaths("ValueTypeShouldImplementIEquatable.vb").Verify(); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.CSharp10.cs index 46b509027dd..22b99839342 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.CSharp10.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.CSharp10.cs @@ -1,17 +1,15 @@ using System; using System.Collections.Generic; -namespace Tests.Diagnostics + +record struct MyStruct // Compliant. Record struct implement IEquatable by definition { - record struct MyStruct // Compliant. Record struct implement IEquatable by definition - { - } +} - record struct MyCompliantStruct : IEquatable // Compliant +record struct MyCompliantStruct : IEquatable // Compliant +{ + public bool Equals(MyCompliantStruct other) { - public bool Equals(MyCompliantStruct other) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.cs index 4b358ac30aa..8280710378d 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.cs @@ -1,23 +1,25 @@ using System; using System.Collections.Generic; -namespace Tests.Diagnostics +struct MyStruct // Noncompliant {{Implement 'IEquatable' in value type 'MyStruct'.}} +// ^^^^^^^^ { - struct MyStruct // Noncompliant {{Implement 'IEquatable' in value type 'MyStruct'.}} -// ^^^^^^^^ - { - } +} - struct MyCompliantStruct : IEquatable // Compliant +struct MyCompliantStruct : IEquatable // Compliant +{ + public bool Equals(MyCompliantStruct other) { - public bool Equals(MyCompliantStruct other) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); } +} - // https://github.com/SonarSource/sonar-dotnet/issues/3157 - ref struct Repro_3157 // Compliant, ref structs can not implement interfaces - { - } +struct ComparableStruct : IComparable // Noncompliant +{ + public int CompareTo(ComparableStruct other) { return 0; } +} + +// https://github.com/SonarSource/sonar-dotnet/issues/3157 +ref struct Repro_3157 // Compliant, ref structs can not implement interfaces +{ } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.vb new file mode 100644 index 00000000000..fb225182123 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ValueTypeShouldImplementIEquatable.vb @@ -0,0 +1,19 @@ +Public Structure MyCompliantStruct ' Compliant + Implements IEquatable(Of MyCompliantStruct) + + Public Overloads Function Equals(other As MyCompliantStruct) As Boolean Implements IEquatable(Of MyCompliantStruct).Equals + Return True + End Function +End Structure + +Structure MyStruct ' Noncompliant {{Implement 'IEquatable' in value type 'MyStruct'.}} + ' ^^^^^^^^ +End Structure + +Structure ComparableStruct ' Noncompliant + Implements IComparable(Of ComparableStruct) + + Public Function CompareTo(other As ComparableStruct) As Integer Implements IComparable(Of ComparableStruct).CompareTo + Return 0 + End Function +End Structure From de08a9062460108da558ee56ded190c401d2fc14 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay <121798625+zsolt-kolbay-sonarsource@users.noreply.github.com> Date: Thu, 26 Jan 2023 12:00:15 +0100 Subject: [PATCH 04/14] Fix FP S2930: IAsyncDisposable objects should not be flagged if disposed properly (#6660) Also refactored the test cases to be more granular according to the C# version they use. --- analyzers/rspec/cs/S2930_c#.html | 16 +- .../Rules/DisposableNotDisposed.cs | 20 ++- .../Rules/DisposableNotDisposedTest.cs | 11 +- .../DisposableNotDisposed.CSharp8.cs | 143 ++++++++++++++++++ .../DisposableNotDisposed.CSharp9.cs | 16 -- .../TestCases/DisposableNotDisposed.cs | 48 +++--- 6 files changed, 200 insertions(+), 54 deletions(-) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs diff --git a/analyzers/rspec/cs/S2930_c#.html b/analyzers/rspec/cs/S2930_c#.html index faab67d528c..a318f8bf8a9 100644 --- a/analyzers/rspec/cs/S2930_c#.html +++ b/analyzers/rspec/cs/S2930_c#.html @@ -6,8 +6,8 @@ file descriptors (e.g. FileStream) or sockets (e.g. WebClient) open at any given time. Therefore, it is important to Dispose of them as soon as they are no longer needed, rather than relying on the garbage collector to call these objects' finalizers at some nondeterministic point in the future.

-

This rule tracks private fields and local variables of the following IDisposable types, which are never disposed, closed, -aliased, returned, or passed to other methods.

+

This rule tracks private fields and local variables of the following IDisposable / IAsyncDisposable types, +which are never disposed, closed, aliased, returned, or passed to other methods.

  • System.IO namespace
      @@ -60,7 +60,7 @@

      Noncompliant Code Example

      Compliant Solution

      -public class ResourceHolder : IDisposable
      +public class ResourceHolder : IDisposable, IAsyncDisposable
       {
         private FileStream fs;
       
      @@ -74,6 +74,11 @@ 

      Compliant Solution

      this.fs.Dispose(); } + public async ValueTask DisposeAsync() + { + await fs.DisposeAsync().ConfigureAwait(false); + } + public void WriteToFile(string path, string text) { using (var fs = new FileStream(path, FileMode.Open)) @@ -85,8 +90,9 @@

      Compliant Solution

      }

      Exceptions

      -

      IDisposable variables returned from a method or passed to other methods are ignored, as are local IDisposables that are -initialized with other IDisposables.

      +

      IDisposable / IAsyncDisposable variables returned from a method or passed to other methods are ignored, as are local +IDisposable / IAsyncDisposable objects that are initialized with other IDisposable / +IAsyncDisposable objects.

       public Stream WriteToFile(string path, string text)
       {
      diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs
      index 10554b311a4..e545f52b199 100644
      --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs
      +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs
      @@ -40,7 +40,9 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer
                       KnownType.System_Net_Sockets_TcpClient,
                       KnownType.System_Net_Sockets_UdpClient);
       
      -        private static readonly ISet DisposeMethods = new HashSet { "Dispose", "Close" };
      +        private static readonly ImmutableArray DisposableTypes = ImmutableArray.Create(KnownType.System_IDisposable, KnownType.System_IAsyncDisposable);
      +
      +        private static readonly ISet DisposeMethods = new HashSet { "Dispose", "DisposeAsync", "Close" };
       
               private static readonly ISet FactoryMethods = new HashSet
               {
      @@ -142,7 +144,7 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer
       
                   foreach (var simpleAssignment in simpleAssignments)
                   {
      -                if (!simpleAssignment.Parent.IsKind(SyntaxKind.UsingStatement)
      +                if (!IsNodeInsideUsingStatement(simpleAssignment)
                           && IsInstantiation(simpleAssignment.Right, semanticModel)
                           && semanticModel.GetSymbolInfo(simpleAssignment.Left).Symbol is { } referencedSymbol
                           && IsLocalOrPrivateField(referencedSymbol))
      @@ -152,6 +154,16 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer
                   }
               }
       
      +        private static bool IsNodeInsideUsingStatement(SyntaxNode node)
      +        {
      +            var ancestors = node.AncestorsAndSelf().ToArray();
      +            var usingStatements = ancestors.OfType();
      +            var usingDeclarations = ancestors.OfType();
      +
      +            return usingStatements.Any(x => ancestors.Contains(x.Expression) || ancestors.Contains(x.Declaration))
      +                || usingDeclarations.Any(x => ancestors.Contains(x.Declaration));
      +        }
      +
               private static IEnumerable GetDescendantNodes(INamedTypeSymbol namedType, SyntaxNode typeDeclaration) =>
                   namedType.IsTopLevelProgram()
                       ? typeDeclaration.ChildNodes().OfType().Select(x => x.ChildNodes().First())
      @@ -185,7 +197,7 @@ private static void ExcludeDisposedAndClosedLocalsAndPrivateFields(SyntaxNode ty
                       }
       
                       if (name != null
      -                    && DisposeMethods.Contains(name.Identifier.Text)
      +                    && (DisposeMethods.Contains(name.Identifier.Text) || IsNodeInsideUsingStatement(expression))
                           && semanticModel.GetSymbolInfo(expression).Symbol is { } referencedSymbol
                           && IsLocalOrPrivateField(referencedSymbol))
                       {
      @@ -247,7 +259,7 @@ private static void ExcludeReturnedPassedAndAliasedLocalsAndPrivateFields(Syntax
                   && semanticModel.GetTypeInfo(expression).Type is var type
                   && type.IsAny(TrackedTypes)
                   && semanticModel.GetSymbolInfo(expression).Symbol is IMethodSymbol constructor
      -            && !constructor.Parameters.Any(x => x.Type.Implements(KnownType.System_IDisposable));
      +            && !constructor.Parameters.Any(x => x.Type.ImplementsAny(DisposableTypes));
       
               private static bool IsDisposableRefStructCreation(ExpressionSyntax expression, SemanticModel semanticModel) =>
                   expression.IsAnyKind(SyntaxKind.ObjectCreationExpression, SyntaxKindEx.ImplicitObjectCreationExpression)
      diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DisposableNotDisposedTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DisposableNotDisposedTest.cs
      index 4b690c4faea..74a98a08d46 100644
      --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DisposableNotDisposedTest.cs
      +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DisposableNotDisposedTest.cs
      @@ -30,12 +30,19 @@ public class DisposableNotDisposedTest
               [TestMethod]
               public void DisposableNotDisposed() =>
                   builder.AddPaths("DisposableNotDisposed.cs")
      -                .WithOptions(ParseOptionsHelper.FromCSharp8)
      -                .AddReferences(MetadataReferenceFacade.SystemNetHttp.Concat(NuGetMetadataReference.FluentAssertions("5.9.0")))
      +                .WithOptions(ParseOptionsHelper.FromCSharp7)
      +                .AddReferences(MetadataReferenceFacade.SystemNetHttp)
                       .Verify();
       
       #if NET
       
      +        [TestMethod]
      +        public void DisposableNotDisposed_CSharp8() =>
      +            builder.AddPaths("DisposableNotDisposed.CSharp8.cs")
      +                .WithOptions(ParseOptionsHelper.FromCSharp8)
      +                .AddReferences(NuGetMetadataReference.FluentAssertions("5.9.0"))
      +                .Verify();
      +
               [TestMethod]
               public void DisposableNotDisposed_CSharp9() =>
                   builder.AddPaths("DisposableNotDisposed.CSharp9.cs")
      diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs
      new file mode 100644
      index 00000000000..75aaca2d819
      --- /dev/null
      +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs
      @@ -0,0 +1,143 @@
      +using System;
      +using System.IO;
      +using System.Threading.Tasks;
      +using FluentAssertions.Execution;
      +
      +namespace Tests.Diagnostics
      +{
      +    public class DisposableNotDisposedAsync
      +    {
      +        private FileStream field_fs1 = new FileStream(@"c:\foo.txt", FileMode.Open);    // Compliant - disposed in a public async method
      +        private FileStream field_fs2 = File.Open(@"c:\foo.txt", FileMode.Open);         // Compliant - disposed in a public async method
      +        private FileStream field_fs3 = new FileStream(@"c:\foo.txt", FileMode.Open);    // FN - the method which disposes it is private, and it's not referenced anywhere
      +        private FileStream field_fs4 = new FileStream(@"c:\foo.txt", FileMode.Open);    // Compliant - disposed in a public async ValueTask method
      +        private FileStream field_fs5 = new FileStream(@"c:\foo.txt", FileMode.Open);    // Compliant - disposed in a public ValueTask method (without async/await)
      +        private FileStream field_fs6 = new FileStream(@"c:\foo.txt", FileMode.Open);    // Compliant - disposed in a public ValueTask method (without async/await)
      +
      +        public async Task DisposeAsynchronously()
      +        {
      +            await using (var fs = new FileStream(@"c:\foo.txt", FileMode.Open))         // Compliant - automatically disposed with the async using block
      +            {
      +                // do nothing
      +            }
      +
      +            FileStream fs2;
      +            await using (fs2 = new FileStream(@"c:\foo.txt", FileMode.Open))
      +            {
      +                // do nothing
      +            }
      +
      +            FileStream fs3 = new FileStream(@"c:\foo.txt", FileMode.Open);
      +            await using (fs3)
      +            {
      +                // do nothing
      +            }
      +
      +            FileStream fs4 = new FileStream(@"c:\foo.txt", FileMode.Open);
      +            await using (fs4.ConfigureAwait(false))
      +            {
      +                // do nothing
      +            }
      +
      +            FileStream fs5;
      +            await using ((fs5 = new FileStream(@"c:\foo.txt", FileMode.Open)).ConfigureAwait(false))
      +            {
      +                // do nothing
      +            }
      +
      +            FileStream fs6;
      +            await using ((fs6 = File.Open(@"c:\foo.txt", FileMode.Open)).ConfigureAwait(false))
      +            {
      +                // do nothing
      +            }
      +
      +            FileStream fs7 = new FileStream(@"c:\foo.txt", FileMode.Open);
      +            await using (var ignored = fs7.ConfigureAwait(false));
      +
      +            FileStream fs8 = new FileStream(@"c:\foo.txt", FileMode.Open);
      +            await using var ignored2 = fs8.ConfigureAwait(false);
      +
      +            using var fs9 = new FileStream(@"c:\foo.txt", FileMode.Open);
      +
      +            await using var fs10 = new FileStream(@"c:\foo.txt", FileMode.Open);
      +
      +            await using var fs11 = File.Open(@"c:\foo.txt", FileMode.Open);
      +
      +            var fs12 = new FileStream(@"c:\foo.txt", FileMode.Open);                     // Compliant - asynchronously disposed manually
      +            await fs12.DisposeAsync();
      +        }
      +
      +        public async Task SomePublicAsyncMethod()
      +        {
      +            await field_fs1.DisposeAsync().ConfigureAwait(false);            
      +            await field_fs2.DisposeAsync();
      +        }
      +
      +        private async Task SomePrivateAsyncMethod()
      +        {
      +            await field_fs3.DisposeAsync();
      +        }
      +
      +        public async ValueTask SomePublicAsyncMethodWithValueTask()
      +        {
      +            await field_fs4.DisposeAsync();
      +        }
      +
      +        public ValueTask SomePublicMethodWithValueTask()
      +        {
      +            return field_fs5.DisposeAsync();
      +        }
      +
      +        public ValueTask AnotherPublicValueTaskMethod() => field_fs6.DisposeAsync();
      +    }
      +
      +    public sealed class ImplementsAsyncDisposable : IAsyncDisposable
      +    {
      +        private readonly FileStream stream;
      +
      +        public ImplementsAsyncDisposable()
      +        {
      +            stream = new FileStream(@"c:\foo.txt", FileMode.Open);                      // Compliant - see GitHub issue: https://github.com/SonarSource/sonar-dotnet/issues/5879
      +        }
      +
      +        public async ValueTask DisposeAsync()
      +        {
      +            await stream.DisposeAsync();
      +        }
      +    }
      +
      +    public class AsyncDisposableTest
      +    {
      +        private ImplementsAsyncDisposable stream = new ImplementsAsyncDisposable();     // Compliant - the rule only tracks specific IDisposable / IAsyncDisposable types
      +    }
      +
      +    public class FluentAssertionsTest
      +    {
      +        public void FluentAssertionTypes()
      +        {
      +            var scope = new AssertionScope();                                           // Noncompliant
      +            var s = new FluentAssertions.Execution.AssertionScope();                    // Noncompliant
      +
      +            using var _ = new AssertionScope();
      +            using (var disposed = new AssertionScope())
      +            {
      +            }
      +        }
      +    }
      +
      +    public ref struct Struct
      +    {
      +        public void Dispose()
      +        {
      +        }
      +    }
      +
      +    public class Consumer
      +    {
      +        public void Method()
      +        {
      +            using var x = new Struct();
      +            var y = new Struct();                                                       // Noncompliant
      +        }
      +    }
      +}
      diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp9.cs
      index 3dd2b57c804..330c8860b56 100644
      --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp9.cs
      +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp9.cs
      @@ -97,19 +97,3 @@ public void Foo()
               var s = new WebClient();                                   // Noncompliant - another tracked type
           }
       }
      -
      -// Reproducer for https://github.com/SonarSource/sonar-dotnet/issues/5879
      -public class Test : IAsyncDisposable
      -{
      -    private readonly FileStream stream;
      -
      -    public Test()
      -    {
      -        stream = new FileStream("C://some-path", FileMode.CreateNew); // Noncompliant - FP stream is disposed in DisposeAsync
      -    }
      -
      -    public async ValueTask DisposeAsync()
      -    {
      -        await stream.DisposeAsync();
      -    }
      -}
      diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs
      index 48bfd71eb55..57cfd032185 100644
      --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs
      +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.cs
      @@ -1,7 +1,7 @@
      -using System.IO;
      +using System;
      +using System.IO;
       using System.Net;
       using System.Net.Sockets;
      -using FluentAssertions.Execution;
       
       namespace Tests.Diagnostics
       {
      @@ -57,6 +57,12 @@ public DisposableNotDisposed(FileStream fs)
                       // do nothing but dispose
                   }
       
      +            FileStream fs6_1 = new FileStream(@"c:\foo.txt", FileMode.Open);
      +            using (fs6_1)
      +            {
      +                // do nothing but dispose
      +            }
      +
                   var fs7 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - Dispose()
                   fs7.Dispose();
       
      @@ -98,7 +104,7 @@ public DisposableNotDisposed(FileStream fs)
                   field_fs5 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant
       
                   NoOperation(field_fs6);
      -            field_fs6 = new FileStream(@"c:\foo.txt", FileMode.Open); // Compliant - field_fs6 gets passed to a method
      +            field_fs6 = new FileStream(@"c:\foo.txt", FileMode.Open); // FN - field_fs6 is re-assigned a new FileStream (and not disposed) after passing it to a method
       
                   field_fs7 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant - even if field_fs7's type is object
       
      @@ -118,41 +124,29 @@ private void NoOperation(FileStream fs)
               {
                   // do nothing
               }
      +    }
       
      -
      -        private void Clear()
      -        {
      -            using var inner_field_fs1 = new FileStream(@"c:\foo.txt", FileMode.Open);
      -        }
      -
      -        public void FluentAssertionTypes()
      -        {
      -            var scope = new AssertionScope();                           // Noncompliant
      -            var s = new FluentAssertions.Execution.AssertionScope();    // Noncompliant
      -
      -            using var _ = new AssertionScope();
      -            using (var disposed = new AssertionScope()) {
      -            }
      -        }
      +    public class Empty
      +    {
           }
       
      -    public ref struct Struct
      +    public sealed class ImplementsDisposable : IDisposable
           {
      -        public void Dispose()
      +        private readonly FileStream stream;
      +
      +        public ImplementsDisposable()
               {
      +            stream = new FileStream(@"c:\foo.txt", FileMode.Open);              // Compliant
               }
      -    }
       
      -    public class Consumer
      -    {
      -        public void Method()
      +        public void Dispose()
               {
      -            using var x = new Struct();
      -            var y = new Struct(); // Noncompliant
      +            stream.Dispose();
               }
           }
       
      -    public class Empty
      +    public class DisposableTest
           {
      +        private ImplementsDisposable stream = new ImplementsDisposable();     // Compliant - the rule only tracks specific IDisposable / IAsyncDisposable types
           }
       }
      
      From 986b17a3f644a64ed9afd8f848eb7a2e33a914c8 Mon Sep 17 00:00:00 2001
      From: Gregory Paidis
       <115458417+gregory-paidis-sonarsource@users.noreply.github.com>
      Date: Thu, 26 Jan 2023 12:24:47 +0100
      Subject: [PATCH 05/14] Remove S4457 from SonarWay profile (#6673)
      
      ---
       analyzers/rspec/cs/Sonar_way_profile.json | 1 -
       1 file changed, 1 deletion(-)
      
      diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json
      index 740b1c539ad..d16b8c34168 100644
      --- a/analyzers/rspec/cs/Sonar_way_profile.json
      +++ b/analyzers/rspec/cs/Sonar_way_profile.json
      @@ -231,7 +231,6 @@
           "S4428",
           "S4433",
           "S4456",
      -    "S4457",
           "S4487",
           "S4502",
           "S4507",
      
      From 1e71ac199fbea9aded8a10a60d81a799525c3e2c Mon Sep 17 00:00:00 2001
      From: Gregory Paidis
       <115458417+gregory-paidis-sonarsource@users.noreply.github.com>
      Date: Thu, 26 Jan 2023 16:22:36 +0100
      Subject: [PATCH 06/14] Fix S4457 FP: Argument check after async code (#6624)
      
      ---
       .../ParameterValidationInMethodWalker.cs      | 15 ++++++-----
       ...rameterValidationInAsyncShouldBeWrapped.cs | 13 ++++++++-
       ...rameterValidationInYieldShouldBeWrapped.cs | 16 +++++------
       ...rameterValidationInAsyncShouldBeWrapped.cs | 15 +++++++++++
       ...lidationInYieldShouldBeWrapped.CSharp10.cs | 27 +++++++++++++++++++
       ...rameterValidationInYieldShouldBeWrapped.cs | 14 ++++++++++
       6 files changed, 83 insertions(+), 17 deletions(-)
       rename analyzers/src/SonarAnalyzer.CSharp/{Rules => Helpers}/ParameterValidationInMethodWalker.cs (88%)
      
      diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInMethodWalker.cs b/analyzers/src/SonarAnalyzer.CSharp/Helpers/ParameterValidationInMethodWalker.cs
      similarity index 88%
      rename from analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInMethodWalker.cs
      rename to analyzers/src/SonarAnalyzer.CSharp/Helpers/ParameterValidationInMethodWalker.cs
      index a627677ac74..2b24e98c81b 100644
      --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInMethodWalker.cs
      +++ b/analyzers/src/SonarAnalyzer.CSharp/Helpers/ParameterValidationInMethodWalker.cs
      @@ -18,7 +18,7 @@
        * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
        */
       
      -namespace SonarAnalyzer.Rules.CSharp
      +namespace SonarAnalyzer.Helpers
       {
           internal class ParameterValidationInMethodWalker : SafeCSharpSyntaxWalker
           {
      @@ -33,7 +33,7 @@ internal class ParameterValidationInMethodWalker : SafeCSharpSyntaxWalker
               private readonly SemanticModel semanticModel;
               private readonly List argumentExceptionLocations = new();
       
      -        private bool keepWalking = true;
      +        protected bool keepWalking = true;
       
               public IEnumerable ArgumentExceptionLocations => argumentExceptionLocations;
       
      @@ -49,9 +49,6 @@ public override void Visit(SyntaxNode node)
                   }
               }
       
      -        public override void VisitAwaitExpression(AwaitExpressionSyntax node) =>
      -            keepWalking = false;
      -
               public override void VisitThrowStatement(ThrowStatementSyntax node)
               {
                   // When throw is like `throw new XXX` where XXX derives from ArgumentException, save location
      @@ -61,6 +58,7 @@ public override void VisitThrowStatement(ThrowStatementSyntax node)
                   {
                       argumentExceptionLocations.Add(node.Expression.GetLocation());
                   }
      +
                   // there is no need to visit children
               }
       
      @@ -68,9 +66,14 @@ public override void VisitInvocationExpression(InvocationExpressionSyntax node)
               {
                   if (node.IsMemberAccessOnKnownType("ThrowIfNull", KnownType.System_ArgumentNullException, semanticModel))
                   {
      +                // "ThrowIfNull" returns void so it cannot be an argument. We can stop.
                       argumentExceptionLocations.Add(node.GetLocation());
                   }
      -            // "ThrowIfNull" returns void so it cannot be an argument. We can stop.
      +            else
      +            {
      +                // Need to check the children of this node because of the pattern (await SomeTask()).Invocation()
      +                base.VisitInvocationExpression(node);
      +            }
               }
           }
       }
      diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs
      index b7e0a4e4545..2943a8c0ed6 100644
      --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs
      +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInAsyncShouldBeWrapped.cs
      @@ -41,7 +41,7 @@ public sealed class ParameterValidationInAsyncShouldBeWrapped : SonarDiagnosticA
                               return;
                           }
       
      -                    var walker = new ParameterValidationInMethodWalker(c.SemanticModel);
      +                    var walker = new ParameterValidationInAsyncWalker(c.SemanticModel);
                           walker.SafeVisit(method);
                           if (walker.ArgumentExceptionLocations.Any())
                           {
      @@ -49,5 +49,16 @@ public sealed class ParameterValidationInAsyncShouldBeWrapped : SonarDiagnosticA
                           }
                       },
                       SyntaxKind.MethodDeclaration);
      +
      +        private sealed class ParameterValidationInAsyncWalker : ParameterValidationInMethodWalker
      +        {
      +            public ParameterValidationInAsyncWalker(SemanticModel semanticModel)
      +                : base(semanticModel)
      +            {
      +            }
      +
      +            public override void VisitAwaitExpression(AwaitExpressionSyntax node) =>
      +                keepWalking = false;
      +        }
           }
       }
      diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs
      index ab85302138b..d06306b2932 100644
      --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs
      +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/ParameterValidationInYieldShouldBeWrapped.cs
      @@ -23,16 +23,14 @@ namespace SonarAnalyzer.Rules.CSharp
           [DiagnosticAnalyzer(LanguageNames.CSharp)]
           public sealed class ParameterValidationInYieldShouldBeWrapped : SonarDiagnosticAnalyzer
           {
      -        internal const string DiagnosticId = "S4456";
      +        private const string DiagnosticId = "S4456";
               private const string MessageFormat = "Split this method into two, one handling parameters check and the other " +
                  "handling the iterator.";
       
      -        private static readonly DiagnosticDescriptor rule =
      -            DescriptorFactory.Create(DiagnosticId, MessageFormat);
      -        public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(rule);
      +        private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat);
      +        public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
       
      -        protected override void Initialize(SonarAnalysisContext context)
      -        {
      +        protected override void Initialize(SonarAnalysisContext context) =>
                   context.RegisterNodeAction(
                       c =>
                       {
      @@ -44,13 +42,12 @@ protected override void Initialize(SonarAnalysisContext context)
                           if (walker.HasYieldStatement &&
                               walker.ArgumentExceptionLocations.Any())
                           {
      -                        c.ReportIssue(rule.CreateDiagnostic(c.Compilation, methodDeclaration.Identifier.GetLocation(), additionalLocations: walker.ArgumentExceptionLocations));
      +                        c.ReportIssue(Rule.CreateDiagnostic(c.Compilation, methodDeclaration.Identifier.GetLocation(), additionalLocations: walker.ArgumentExceptionLocations));
                           }
                       },
                       SyntaxKind.MethodDeclaration);
      -        }
       
      -        private class ParameterValidationInYieldWalker : ParameterValidationInMethodWalker
      +        private sealed class ParameterValidationInYieldWalker : ParameterValidationInMethodWalker
               {
                   public bool HasYieldStatement { get; private set; }
       
      @@ -62,7 +59,6 @@ public ParameterValidationInYieldWalker(SemanticModel semanticModel)
                   public override void VisitYieldStatement(YieldStatementSyntax node)
                   {
                       HasYieldStatement = true;
      -
                       base.VisitYieldStatement(node);
                   }
               }
      diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInAsyncShouldBeWrapped.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInAsyncShouldBeWrapped.cs
      index b4864aa4475..0e4c1df555c 100644
      --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInAsyncShouldBeWrapped.cs
      +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInAsyncShouldBeWrapped.cs
      @@ -1,5 +1,6 @@
       using System;
       using System.IO;
      +using System.Linq;
       using System.Threading.Tasks;
       
       namespace Tests.Diagnostics
      @@ -55,6 +56,20 @@ private static ArgumentNullException GetArgumentExpression(string name)
       
           public class ValidCases
           {
      +        // https://github.com/SonarSource/sonar-dotnet/issues/6449
      +        public class Repro_6449
      +        {
      +            public Task CheckAsync() => Task.FromResult(new int[] { 1 });
      +            public Task Check2Async() => Task.FromResult(1);
      +
      +            public async Task HasS4457Async(int request) // Compliant 
      +            {
      +                var identifierType = (await CheckAsync()).FirstOrDefault(x => x == request);
      +                if (identifierType == 0)
      +                    throw new ArgumentException("message"); 
      +            }
      +        }
      +
               public static Task FooAsync(string something)
               {
                   if (something == null) { throw new ArgumentNullException(nameof(something)); }
      diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.CSharp10.cs
      index d7905a6b8f8..cf38aad2e9b 100644
      --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.CSharp10.cs
      +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.CSharp10.cs
      @@ -1,5 +1,6 @@
       using System;
       using System.Collections.Generic;
      +using System.Threading.Tasks;
       
       namespace Tests.Diagnostics
       {
      @@ -27,6 +28,32 @@ public static class InvalidCases
       
                   yield break;
               }
      +
      +        // For details, check https://github.com/SonarSource/sonar-dotnet/pull/6624.
      +        public static async IAsyncEnumerable AsyncThenYield(object arg) // Noncompliant
      +        {
      +            if (arg is null)
      +            {
      +                throw new ArgumentException(nameof(arg));
      +//                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Secondary
      +            }
      +
      +            var res = await Task.Run(() => 42);
      +            yield return res;
      +        }
      +
      +        // For details, check https://github.com/SonarSource/sonar-dotnet/pull/6624.
      +        public static async IAsyncEnumerable NestedAsyncThenYield(object arg) // Noncompliant
      +        {
      +            if (arg is null)
      +            {
      +                throw new ArgumentException(nameof(arg));
      +//                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Secondary
      +            }
      +
      +            var res = (await Task.Run(() => 42)).GetHashCode();
      +            yield return res;
      +        }
           }
       
           public static class ValidCases
      diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.cs
      index dc8dc267400..c249a8aabd7 100644
      --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.cs
      +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/ParameterValidationInYieldShouldBeWrapped.cs
      @@ -54,6 +54,20 @@ private static ArgumentNullException GetArgumentExpression(string name)
               {
                   return new ArgumentNullException(name);
               }
      +
      +        // Documenting that this rule fires even if T:ArgumentException has no argument
      +        public static IEnumerable ThrowWithoutArgument(int a) // Noncompliant
      +        {
      +            throw new ArgumentNullException(); // Secondary
      +            yield return 42;
      +        }
      +
      +        // Documenting that this rule fires even if T:ArgumentException has an ad-hoc argument
      +        public static IEnumerable ThrowWithAdHocArgument(int a) // Noncompliant
      +        {
      +            throw new ArgumentNullException("i am not a parameter name"); // Secondary
      +            yield return 42;
      +        }
           }
       
           public static class ValidCases
      
      From 4da3c1db89834f48f6f9585ad54d0c8f3943e586 Mon Sep 17 00:00:00 2001
      From: Zsolt Kolbay
       <121798625+zsolt-kolbay-sonarsource@users.noreply.github.com>
      Date: Fri, 27 Jan 2023 10:57:38 +0100
      Subject: [PATCH 07/14] Update RSPEC for release (#6681)
      
      ---
       analyzers/rspec/cs/S1168_c#.html              | 24 ++++++++++++-------
       analyzers/rspec/cs/S2197_c#.html              |  4 ++--
       analyzers/rspec/cs/S2221_c#.json              |  2 +-
       analyzers/rspec/cs/S3898_c#.html              | 24 ++++++++-----------
       analyzers/rspec/cs/S4200_c#.html              | 12 +++++-----
       analyzers/rspec/cs/S4507_c#.html              |  4 ++--
       analyzers/rspec/vbnet/S4507_vb.net.html       |  4 ++--
       .../src/SonarAnalyzer.CSharp/sonarpedia.json  |  2 +-
       .../SonarAnalyzer.VisualBasic/sonarpedia.json |  2 +-
       9 files changed, 40 insertions(+), 38 deletions(-)
      
      diff --git a/analyzers/rspec/cs/S1168_c#.html b/analyzers/rspec/cs/S1168_c#.html
      index 57a10136058..a648af3a67c 100644
      --- a/analyzers/rspec/cs/S1168_c#.html
      +++ b/analyzers/rspec/cs/S1168_c#.html
      @@ -1,6 +1,6 @@
      -

      Returning null instead of an actual array, collection or map forces callers of the method to explicitly test for nullity, making them -more complex and less readable.

      -

      Moreover, in many cases, null is used as a synonym for empty.

      +

      Returning null or default instead of an actual collection forces the method callers to explicitly test for null, making +the code more complex and less readable.

      +

      Moreover, in many cases, null or default is used as a synonym for empty.

      Noncompliant Code Example

       public Result[] GetResults()
      @@ -8,9 +8,12 @@ 

      Noncompliant Code Example

      return null; // Noncompliant } -public IEnumerable<Result> GetResults() +public IEnumerable<Result> GetResults(bool condition) { - return null; // Noncompliant + var results = GenerateResults(); + return condition + ? results + : null; // Noncompliant } public IEnumerable<Result> GetResults() => null; // Noncompliant @@ -19,11 +22,11 @@

      Noncompliant Code Example

      { get { - return null; // Noncompliant + return default(IEnumerable<Result>); // Noncompliant } } -public IEnumerable<Result> Results => null; // Noncompliant +public IEnumerable<Result> Results => default; // Noncompliant

      Compliant Solution

      @@ -32,9 +35,12 @@ 

      Compliant Solution

      return new Result[0]; } -public IEnumerable<Result> GetResults() +public IEnumerable<Result> GetResults(bool condition) { - return Enumerable.Empty<Result>(); + var results = GenerateResults(); + return condition + ? results + : Enumerable.Empty<Result>(); } public IEnumerable<Result> GetResults() => Enumerable.Empty<Result>(); diff --git a/analyzers/rspec/cs/S2197_c#.html b/analyzers/rspec/cs/S2197_c#.html index d9d2710d162..a7294b01d05 100644 --- a/analyzers/rspec/cs/S2197_c#.html +++ b/analyzers/rspec/cs/S2197_c#.html @@ -11,14 +11,14 @@

      Compliant Solution

       public bool IsOdd(int x)
       {
      -  return x %2 != 0;
      +  return x % 2 != 0;
       }
       

      or

       public bool IsOdd(uint x)
       {
      -  return x %2 == 1;
      +  return x % 2 == 1;
       }
       
      diff --git a/analyzers/rspec/cs/S2221_c#.json b/analyzers/rspec/cs/S2221_c#.json index 44a372f9783..daf7f8cff3b 100644 --- a/analyzers/rspec/cs/S2221_c#.json +++ b/analyzers/rspec/cs/S2221_c#.json @@ -1,5 +1,5 @@ { - "title": "\"Exception\" should not be caught when not required by called methods", + "title": "\"Exception\" should not be caught", "type": "CODE_SMELL", "status": "ready", "remediation": { diff --git a/analyzers/rspec/cs/S3898_c#.html b/analyzers/rspec/cs/S3898_c#.html index 3d724d2531f..2b56d4bd69d 100644 --- a/analyzers/rspec/cs/S3898_c#.html +++ b/analyzers/rspec/cs/S3898_c#.html @@ -5,27 +5,23 @@

      Noncompliant Code Example

       struct MyStruct  // Noncompliant
       {
      -  private int i;
      -  public int I
      -  {
      -    //...
      -  }
      +    public int Value { get; set; }
       }
       

      Compliant Solution

       struct MyStruct : IEquatable<MyStruct>
       {
      -  private int i;
      -  public int I
      -  {
      -    //...
      -  }
      +    public int Value { get; set; }
       
      -  public bool Equals(MyStruct other)
      -  {
      -    throw new NotImplementedException();
      -  }
      +    public bool Equals(MyStruct other)
      +    {
      +        // ...
      +    }
       }
       
      +

      See

      + diff --git a/analyzers/rspec/cs/S4200_c#.html b/analyzers/rspec/cs/S4200_c#.html index 61f2bbdad93..74b572f7e42 100644 --- a/analyzers/rspec/cs/S4200_c#.html +++ b/analyzers/rspec/cs/S4200_c#.html @@ -1,8 +1,8 @@ -

      Native methods are functions that reside in libraries outside the virtual machine. Being able to call them is useful for interoperability with -applications and libraries written in other programming languages, in particular when performing platform-specific operations. However doing so comes -with extra risks since it means stepping out of the security model of the virtual machine. It is therefore highly recommended to take extra steps, -like input validation, when invoking native methods. This is best done by making the native method private and by providing a wrapper -that performs these extra steps and verifications.

      +

      Native methods are functions that reside in libraries outside the .NET runtime. Calling them is helpful for interoperability with applications and +libraries written in other programming languages, mainly when performing platform-specific operations. However, doing so comes with additional risks +since it means stepping out of the memory-safety model of the runtime. It is therefore highly recommended to take extra steps, like input validation, +when invoking native methods. Making the native method private and providing a wrapper that performs these additional steps is the best +way to do so.

      This rule raises an issue when a native method is declared public or its wrapper is too trivial.

      Noncompliant Code Example

      @@ -34,7 +34,7 @@ 

      Compliant Solution

      { if (s != null && x >= 0 && x < s.Length) { - bar(s, x); + Bar(s, x); } } } diff --git a/analyzers/rspec/cs/S4507_c#.html b/analyzers/rspec/cs/S4507_c#.html index d523f855276..1f990e1c455 100644 --- a/analyzers/rspec/cs/S4507_c#.html +++ b/analyzers/rspec/cs/S4507_c#.html @@ -8,8 +8,8 @@ detailed information on both the system running the application and users.

      Ask Yourself Whether

        -
      • the code or configuration enabling the application debug features is deployed on production servers or distributed to end users.
      • -
      • the application runs by default with debug features activated.
      • +
      • The code or configuration enabling the application debug features is deployed on production servers or distributed to end users.
      • +
      • The application runs by default with debug features activated.

      There is a risk if you answered yes to any of those questions.

      Recommended Secure Coding Practices

      diff --git a/analyzers/rspec/vbnet/S4507_vb.net.html b/analyzers/rspec/vbnet/S4507_vb.net.html index b9a53e47ef5..11fbb756404 100644 --- a/analyzers/rspec/vbnet/S4507_vb.net.html +++ b/analyzers/rspec/vbnet/S4507_vb.net.html @@ -8,8 +8,8 @@ detailed information on both the system running the application and users.

      Ask Yourself Whether

        -
      • the code or configuration enabling the application debug features is deployed on production servers or distributed to end users.
      • -
      • the application runs by default with debug features activated.
      • +
      • The code or configuration enabling the application debug features is deployed on production servers or distributed to end users.
      • +
      • The application runs by default with debug features activated.

      There is a risk if you answered yes to any of those questions.

      Recommended Secure Coding Practices

      diff --git a/analyzers/src/SonarAnalyzer.CSharp/sonarpedia.json b/analyzers/src/SonarAnalyzer.CSharp/sonarpedia.json index f50f2c0a936..4aa94652203 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/sonarpedia.json +++ b/analyzers/src/SonarAnalyzer.CSharp/sonarpedia.json @@ -3,5 +3,5 @@ "languages": [ "CSH" ], - "latest-update": "2022-12-16T14:29:21.863815900Z" + "latest-update": "2023-01-27T09:06:22.842339300Z" } \ No newline at end of file diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/sonarpedia.json b/analyzers/src/SonarAnalyzer.VisualBasic/sonarpedia.json index 06e3fe757d6..3ab3c9d1c94 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/sonarpedia.json +++ b/analyzers/src/SonarAnalyzer.VisualBasic/sonarpedia.json @@ -3,5 +3,5 @@ "languages": [ "VBNET" ], - "latest-update": "2022-12-16T14:30:02.140782800Z" + "latest-update": "2023-01-27T09:06:56.299025700Z" } \ No newline at end of file From f8aff1bc008ecbf1b8d8fda0d0f2ee4876556897 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay <121798625+zsolt-kolbay-sonarsource@users.noreply.github.com> Date: Mon, 30 Jan 2023 12:07:49 +0100 Subject: [PATCH 08/14] Fix S2930 FN (#6685) --- .../Rules/DisposableNotDisposed.cs | 14 ++++++++++---- .../TestCases/DisposableNotDisposed.CSharp8.cs | 8 +++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs index e545f52b199..8ddcf45946b 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposableNotDisposed.cs @@ -157,11 +157,17 @@ public sealed class DisposableNotDisposed : SonarDiagnosticAnalyzer private static bool IsNodeInsideUsingStatement(SyntaxNode node) { var ancestors = node.AncestorsAndSelf().ToArray(); - var usingStatements = ancestors.OfType(); - var usingDeclarations = ancestors.OfType(); - return usingStatements.Any(x => ancestors.Contains(x.Expression) || ancestors.Contains(x.Declaration)) - || usingDeclarations.Any(x => ancestors.Contains(x.Declaration)); + var isPartOfUsingStatement = ancestors + .OfType() + .Any(x => (x.Expression is not null && x.Expression.DescendantNodesAndSelf().Contains(node)) + || (x.Declaration is not null && x.Declaration.DescendantNodesAndSelf().Contains(node))); + + var isPartOfUsingDeclaration = ancestors + .OfType() + .Any(x => x.UsingKeyword() != default); + + return isPartOfUsingStatement || isPartOfUsingDeclaration; } private static IEnumerable GetDescendantNodes(INamedTypeSymbol namedType, SyntaxNode typeDeclaration) => diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs index 75aaca2d819..83b471e5921 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DisposableNotDisposed.CSharp8.cs @@ -42,7 +42,13 @@ await using (fs4.ConfigureAwait(false)) FileStream fs5; await using ((fs5 = new FileStream(@"c:\foo.txt", FileMode.Open)).ConfigureAwait(false)) { - // do nothing + var fs5_1 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant + fs5_1 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant + + using (var fs5_2 = new FileStream(@"c:\foo.txt", FileMode.Open)) + { + fs5_1 = new FileStream(@"c:\foo.txt", FileMode.Open); // Noncompliant + } } FileStream fs6; From 791eddba833042d16f9456ca8677f7358cff161f Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay <121798625+zsolt-kolbay-sonarsource@users.noreply.github.com> Date: Tue, 31 Jan 2023 11:55:28 +0100 Subject: [PATCH 09/14] Revert release.yml to automatically use latest minor version (#6693) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c2f78b44977..03ea1a9c6bd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: permissions: id-token: write contents: write - uses: SonarSource/gh-action_release/.github/workflows/main.yaml@v5.0.1 + uses: SonarSource/gh-action_release/.github/workflows/main.yaml@v5 #uses the latest v5.x.y version rather than a specific version with: publishToBinaries: true mavenCentralSync: true From 217a73924391ce96c7c751444e24f52c551f6e54 Mon Sep 17 00:00:00 2001 From: Zsolt Kolbay <121798625+zsolt-kolbay-sonarsource@users.noreply.github.com> Date: Tue, 31 Jan 2023 14:01:29 +0100 Subject: [PATCH 10/14] Bump version number to 8.53.0.0 (#6692) --- analyzers/packaging/SonarAnalyzer.CSharp.nuspec | 4 ++-- analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec | 4 ++-- analyzers/src/AssemblyInfo.Shared.cs | 6 +++--- analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec | 2 +- its/pom.xml | 2 +- pom.xml | 2 +- scripts/version/Version.props | 4 ++-- sonar-csharp-plugin/pom.xml | 2 +- sonar-dotnet-shared-library/pom.xml | 2 +- sonar-vbnet-plugin/pom.xml | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/analyzers/packaging/SonarAnalyzer.CSharp.nuspec b/analyzers/packaging/SonarAnalyzer.CSharp.nuspec index 1cb28f09452..d1dd23dc189 100644 --- a/analyzers/packaging/SonarAnalyzer.CSharp.nuspec +++ b/analyzers/packaging/SonarAnalyzer.CSharp.nuspec @@ -2,7 +2,7 @@ SonarAnalyzer.CSharp - 8.52.0.0 + 8.53.0.0 SonarAnalyzer for C# SonarSource SonarSource @@ -13,7 +13,7 @@ false Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarLint for Visual Studio, which is a free extension that can be used standalone or with SonarQube and/or SonarCloud. Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarLint for Visual Studio, which is a free extension (https://www.sonarlint.org/visualstudio/) that can be used standalone or with SonarQube (https://www.sonarqube.org/) and/or SonarCloud (https://sonarcloud.io/). - https://github.com/SonarSource/sonar-dotnet/releases/tag/8.52.0.0 + https://github.com/SonarSource/sonar-dotnet/releases/tag/8.53.0.0 en-US Copyright © 2015-2023 SonarSource SA Roslyn Analyzers Refactoring CodeAnalysis CleanCode Clean Code diff --git a/analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec b/analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec index 4486a317e26..fed74aa6f05 100644 --- a/analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec +++ b/analyzers/packaging/SonarAnalyzer.VisualBasic.nuspec @@ -2,7 +2,7 @@ SonarAnalyzer.VisualBasic - 8.52.0.0 + 8.53.0.0 SonarAnalyzer for Visual Basic SonarSource SonarSource @@ -13,7 +13,7 @@ false Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarLint for Visual Studio, which is a free extension that can be used standalone or with SonarQube and/or SonarCloud. Roslyn analyzers that spot Bugs, Vulnerabilities and Code Smells in your code. For an even better overall experience, you can use SonarLint for Visual Studio, which is a free extension (https://www.sonarlint.org/visualstudio/) that can be used standalone or with SonarQube (https://www.sonarqube.org/) and/or SonarCloud (https://sonarcloud.io/). - https://github.com/SonarSource/sonar-dotnet/releases/tag/8.52.0.0 + https://github.com/SonarSource/sonar-dotnet/releases/tag/8.53.0.0 en-US Copyright © 2015-2023 SonarSource SA Roslyn Analyzers Refactoring CodeAnalysis CleanCode Clean Code diff --git a/analyzers/src/AssemblyInfo.Shared.cs b/analyzers/src/AssemblyInfo.Shared.cs index ddba2672df3..e135d235835 100644 --- a/analyzers/src/AssemblyInfo.Shared.cs +++ b/analyzers/src/AssemblyInfo.Shared.cs @@ -23,10 +23,10 @@ using System.Resources; using System.Runtime.InteropServices; -[assembly: AssemblyVersion("8.52.0")] -[assembly: AssemblyFileVersion("8.52.0.0")] +[assembly: AssemblyVersion("8.53.0")] +[assembly: AssemblyFileVersion("8.53.0.0")] // The value should look like "Version:X.X.X.X Branch:not-set Sha1:not-set" -[assembly: AssemblyInformationalVersion("Version:8.52.0.0 Branch: Sha1:")] +[assembly: AssemblyInformationalVersion("Version:8.53.0.0 Branch: Sha1:")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("SonarSource")] [assembly: AssemblyCopyright("Copyright © 2015-2023 SonarSource SA")] diff --git a/analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec b/analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec index 6e102c0e388..496bd4f6645 100644 --- a/analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec +++ b/analyzers/src/SonarAnalyzer.CFG/SonarAnalyzer.CFG.cs.nuspec @@ -2,7 +2,7 @@ SonarAnalyzer.CFG.CSharp - 8.52.0.0 + 8.53.0.0 C# CFG library for SonarAnalyzer SonarSource SonarSource diff --git a/its/pom.xml b/its/pom.xml index fb81166a246..ada7ad39da7 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT com.sonarsource.it diff --git a/pom.xml b/pom.xml index b5ab3964581..17cc7e342d6 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT pom .NET Analyzers parent diff --git a/scripts/version/Version.props b/scripts/version/Version.props index 77df7836fd4..f2a2379e7b7 100644 --- a/scripts/version/Version.props +++ b/scripts/version/Version.props @@ -1,8 +1,8 @@ - 8.52.0 + 8.53.0 0 - 8.52 + 8.53 $(Sha1) $(BranchName) $(MainVersion).$(BuildNumber) diff --git a/sonar-csharp-plugin/pom.xml b/sonar-csharp-plugin/pom.xml index 5e0401b77ba..1de5ec5a07f 100644 --- a/sonar-csharp-plugin/pom.xml +++ b/sonar-csharp-plugin/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT sonar-csharp-plugin diff --git a/sonar-dotnet-shared-library/pom.xml b/sonar-dotnet-shared-library/pom.xml index 3861ae6a47a..aae7f596ab5 100644 --- a/sonar-dotnet-shared-library/pom.xml +++ b/sonar-dotnet-shared-library/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT sonar-dotnet-shared-library diff --git a/sonar-vbnet-plugin/pom.xml b/sonar-vbnet-plugin/pom.xml index 3cdd1160119..6468fb199d0 100644 --- a/sonar-vbnet-plugin/pom.xml +++ b/sonar-vbnet-plugin/pom.xml @@ -6,7 +6,7 @@ org.sonarsource.dotnet sonar-dotnet - 8.52-SNAPSHOT + 8.53-SNAPSHOT sonar-vbnet-plugin From c1175674ce58abb27dd4c07a1b334311e51540f8 Mon Sep 17 00:00:00 2001 From: Costin Zaharia <56015273+costin-zaharia-sonarsource@users.noreply.github.com> Date: Tue, 31 Jan 2023 14:07:14 +0100 Subject: [PATCH 11/14] Bump scanner version in ITs (#6680) --- its/pom.xml | 2 +- its/src/test/java/com/sonar/it/shared/TestUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/its/pom.xml b/its/pom.xml index ada7ad39da7..1bf9d63ae06 100644 --- a/its/pom.xml +++ b/its/pom.xml @@ -20,7 +20,7 @@ - 5.10.0.59947 + 5.11.0.60783 -server diff --git a/its/src/test/java/com/sonar/it/shared/TestUtils.java b/its/src/test/java/com/sonar/it/shared/TestUtils.java index d7d19d23984..fa60ead3386 100644 --- a/its/src/test/java/com/sonar/it/shared/TestUtils.java +++ b/its/src/test/java/com/sonar/it/shared/TestUtils.java @@ -141,7 +141,7 @@ private static String getProjectBaseDir(Path projectDir, String subProjectName) private static Build newScanner(Path projectDir) { // We need to set the fallback version to run from inside the IDE when the property isn't set return ScannerForMSBuild.create(projectDir.toFile()) - .setScannerVersion(System.getProperty("scannerMsbuild.version", "5.10.0.59947")) + .setScannerVersion(System.getProperty("scannerMsbuild.version", "5.11.0.60783")) // In order to be able to run tests on Azure pipelines, the AGENT_BUILDDIRECTORY environment variable // needs to be set to the analyzed project directory. From b8a73c0070a62aaf1de634ef7fa9e107eb7a162d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 16:14:23 +0100 Subject: [PATCH 12/14] Bump mockito-core from 5.0.0 to 5.1.1 (#6690) --- sonar-csharp-plugin/pom.xml | 2 +- sonar-dotnet-shared-library/pom.xml | 2 +- sonar-vbnet-plugin/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sonar-csharp-plugin/pom.xml b/sonar-csharp-plugin/pom.xml index 1de5ec5a07f..f34c2683811 100644 --- a/sonar-csharp-plugin/pom.xml +++ b/sonar-csharp-plugin/pom.xml @@ -100,7 +100,7 @@ org.mockito mockito-core - 5.0.0 + 5.1.1 test diff --git a/sonar-dotnet-shared-library/pom.xml b/sonar-dotnet-shared-library/pom.xml index aae7f596ab5..4237acb33a9 100644 --- a/sonar-dotnet-shared-library/pom.xml +++ b/sonar-dotnet-shared-library/pom.xml @@ -87,7 +87,7 @@ org.mockito mockito-core - 5.0.0 + 5.1.1 test diff --git a/sonar-vbnet-plugin/pom.xml b/sonar-vbnet-plugin/pom.xml index 6468fb199d0..eeb433d5b3d 100644 --- a/sonar-vbnet-plugin/pom.xml +++ b/sonar-vbnet-plugin/pom.xml @@ -100,7 +100,7 @@ org.mockito mockito-core - 5.0.0 + 5.1.1 test From d1e7b6df6c998b3cd61c0ba0d267781f8af31014 Mon Sep 17 00:00:00 2001 From: Pavel Mikula <57188685+pavel-mikula-sonarsource@users.noreply.github.com> Date: Tue, 31 Jan 2023 16:14:33 +0100 Subject: [PATCH 13/14] S3655: Add FP repro for #6682 (#6683) --- .../Sonar/EmptyNullableValueAccess.CSharp9.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Sonar/EmptyNullableValueAccess.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Sonar/EmptyNullableValueAccess.CSharp9.cs index d1767f71819..8edd9b0bc2a 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Sonar/EmptyNullableValueAccess.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/SymbolicExecution/Sonar/EmptyNullableValueAccess.CSharp9.cs @@ -125,3 +125,27 @@ public partial class Partial var v = nullable.Value; // Noncompliant } } + +// https://github.com/SonarSource/sonar-dotnet/issues/6682 +public struct Repro_6682 +{ + public bool SomeProperty { get; } + + public void PatternMatching(Repro_6682? arg, bool condition) + { + if (condition) + { + arg = null; + } + + if (arg is { SomeProperty: true }) // A null check + { + var value = arg.Value; // Noncompliant FP + } + + if (arg is { }) // A null check + { + var value = arg.Value; // Noncompliant FP + } + } +} From 5ac0db2f666ea1c5fd0895ea56f8370c6665c90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=8Caba=20=C5=A0agi?= <75226367+csaba-sagi-sonarsource@users.noreply.github.com> Date: Tue, 31 Jan 2023 18:34:16 +0100 Subject: [PATCH 14/14] Set issue title template (#6688) --- .github/ISSUE_TEMPLATE/false-positive---false-negative.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/false-positive---false-negative.md b/.github/ISSUE_TEMPLATE/false-positive---false-negative.md index dd32c8659bd..876ded3c037 100644 --- a/.github/ISSUE_TEMPLATE/false-positive---false-negative.md +++ b/.github/ISSUE_TEMPLATE/false-positive---false-negative.md @@ -1,7 +1,7 @@ --- name: Report a False Positive / False Negative about: Open an issue about a C#/VB.NET analysis rule. -title: '' +title: "Fix Sxxxx [FP/FN]: Issue title" labels: '' assignees: ''