From 42cca8fa3d36a59eb241f5f45eb4060339c443ac Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Mon, 6 Feb 2023 11:31:18 +0100 Subject: [PATCH 01/19] Scaffolding --- analyzers/rspec/cs/S4545_c#.html | 23 +++++++++++ analyzers/rspec/cs/S4545_c#.json | 15 +++++++ analyzers/rspec/cs/Sonar_way_profile.json | 1 + analyzers/rspec/vbnet/S4545_vb.net.html | 23 +++++++++++ analyzers/rspec/vbnet/S4545_vb.net.json | 15 +++++++ analyzers/rspec/vbnet/Sonar_way_profile.json | 1 + .../DebuggerDisplayUsesExistingMembers.cs | 39 ++++++++++++++++++ .../DebuggerDisplayUsesExistingMembersBase.cs | 31 ++++++++++++++ .../DebuggerDisplayUsesExistingMembers.cs | 39 ++++++++++++++++++ .../PackagingTests/RuleTypeMappingCS.cs | 2 +- .../PackagingTests/RuleTypeMappingVB.cs | 2 +- .../DebuggerDisplayUsesExistingMembersTest.cs | 41 +++++++++++++++++++ .../DebuggerDisplayUsesExistingMembers.cs | 5 +++ .../DebuggerDisplayUsesExistingMembers.vb | 7 ++++ 14 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 analyzers/rspec/cs/S4545_c#.html create mode 100644 analyzers/rspec/cs/S4545_c#.json create mode 100644 analyzers/rspec/vbnet/S4545_vb.net.html create mode 100644 analyzers/rspec/vbnet/S4545_vb.net.json create mode 100644 analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs create mode 100644 analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs create mode 100644 analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb diff --git a/analyzers/rspec/cs/S4545_c#.html b/analyzers/rspec/cs/S4545_c#.html new file mode 100644 index 00000000000..1ddd69d9df0 --- /dev/null +++ b/analyzers/rspec/cs/S4545_c#.html @@ -0,0 +1,23 @@ +

The DebuggerDisplayAttribute is used to determine how an object is displayed in the debugger window.

+

The DebuggerDisplayAttribute constructor takes a single argument: the string to be displayed in the value column for instances of the +type. Any text within curly braces is evaluated as the name of a field, property, or method.

+

Naming a non-existent field, property or method between curly braces will result in a CS0103 error in the debug window when debugging objects. +Although there is no impact on the production code, providing a wrong value can lead to difficulties when debugging the application.

+

This rule raises an issue when text specified between curly braces refers to members that don’t exist in the current context.

+

Noncompliant Code Example

+
+[DebuggerDisplay("Name: {Name}")] // Noncompliant - Name doesn't exist in this context
+public class Person
+{
+    public string FullName { get; private set; }
+}
+
+

Compliant Solution

+
+[DebuggerDisplay("Name: {FullName}")]
+public class Person
+{
+    public string FullName { get; private set; }
+}
+
+ diff --git a/analyzers/rspec/cs/S4545_c#.json b/analyzers/rspec/cs/S4545_c#.json new file mode 100644 index 00000000000..70b4930cf47 --- /dev/null +++ b/analyzers/rspec/cs/S4545_c#.json @@ -0,0 +1,15 @@ +{ + "title": "\"DebuggerDisplayAttribute\" strings should reference existing members", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-4545", + "sqKey": "S4545", + "scope": "All", + "quickfix": "unknown" +} diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json index 2b7c15c5eb5..48d295b68dc 100644 --- a/analyzers/rspec/cs/Sonar_way_profile.json +++ b/analyzers/rspec/cs/Sonar_way_profile.json @@ -240,6 +240,7 @@ "S4502", "S4507", "S4524", + "S4545", "S4581", "S4583", "S4586", diff --git a/analyzers/rspec/vbnet/S4545_vb.net.html b/analyzers/rspec/vbnet/S4545_vb.net.html new file mode 100644 index 00000000000..1ddd69d9df0 --- /dev/null +++ b/analyzers/rspec/vbnet/S4545_vb.net.html @@ -0,0 +1,23 @@ +

The DebuggerDisplayAttribute is used to determine how an object is displayed in the debugger window.

+

The DebuggerDisplayAttribute constructor takes a single argument: the string to be displayed in the value column for instances of the +type. Any text within curly braces is evaluated as the name of a field, property, or method.

+

Naming a non-existent field, property or method between curly braces will result in a CS0103 error in the debug window when debugging objects. +Although there is no impact on the production code, providing a wrong value can lead to difficulties when debugging the application.

+

This rule raises an issue when text specified between curly braces refers to members that don’t exist in the current context.

+

Noncompliant Code Example

+
+[DebuggerDisplay("Name: {Name}")] // Noncompliant - Name doesn't exist in this context
+public class Person
+{
+    public string FullName { get; private set; }
+}
+
+

Compliant Solution

+
+[DebuggerDisplay("Name: {FullName}")]
+public class Person
+{
+    public string FullName { get; private set; }
+}
+
+ diff --git a/analyzers/rspec/vbnet/S4545_vb.net.json b/analyzers/rspec/vbnet/S4545_vb.net.json new file mode 100644 index 00000000000..70b4930cf47 --- /dev/null +++ b/analyzers/rspec/vbnet/S4545_vb.net.json @@ -0,0 +1,15 @@ +{ + "title": "\"DebuggerDisplayAttribute\" strings should reference existing members", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-4545", + "sqKey": "S4545", + "scope": "All", + "quickfix": "unknown" +} diff --git a/analyzers/rspec/vbnet/Sonar_way_profile.json b/analyzers/rspec/vbnet/Sonar_way_profile.json index d6da56534fc..294f898deda 100644 --- a/analyzers/rspec/vbnet/Sonar_way_profile.json +++ b/analyzers/rspec/vbnet/Sonar_way_profile.json @@ -107,6 +107,7 @@ "S4423", "S4428", "S4507", + "S4545", "S4581", "S4583", "S4586", diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs new file mode 100644 index 00000000000..5c201771fd7 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs @@ -0,0 +1,39 @@ +/* + * 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.CSharp; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExistingMembersBase +{ + + protected override ILanguageFacade Language => CSharpFacade.Instance; + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction(c => + { + var node = c.Node; + if (true) + { + c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); + } + }, + SyntaxKind.InvocationExpression); +} diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs new file mode 100644 index 00000000000..c21209e630e --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs @@ -0,0 +1,31 @@ +/* + * 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 DebuggerDisplayUsesExistingMembersBase : SonarDiagnosticAnalyzer + where TSyntaxKind : struct +{ + private const string DiagnosticId = "S4545"; + + protected override string MessageFormat => "FIXME"; + + protected DebuggerDisplayUsesExistingMembersBase() : base(DiagnosticId) { } +} diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs new file mode 100644 index 00000000000..6aa5c3e3c70 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs @@ -0,0 +1,39 @@ +/* + * 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.VisualBasic; + +[DiagnosticAnalyzer(LanguageNames.VisualBasic)] +public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExistingMembersBase +{ + + protected override ILanguageFacade Language => VisualBasicFacade.Instance; + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction(c => + { + var node = c.Node; + if (true) + { + c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); + } + }, + SyntaxKind.InvocationExpression); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs index 4dc49032b0f..fd8fb281530 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingCS.cs @@ -4469,7 +4469,7 @@ internal static class RuleTypeMappingCS // ["S4542"], // ["S4543"], // ["S4544"], - // ["S4545"], + ["S4545"] = "CODE_SMELL", // ["S4546"], // ["S4547"], // ["S4548"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs index c4f42cdce08..21e1affe40c 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/PackagingTests/RuleTypeMappingVB.cs @@ -4469,7 +4469,7 @@ internal static class RuleTypeMappingVB // ["S4542"], // ["S4543"], // ["S4544"], - // ["S4545"], + ["S4545"] = "CODE_SMELL", // ["S4546"], // ["S4547"], // ["S4548"], diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs new file mode 100644 index 00000000000..6aa2d4c8dca --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs @@ -0,0 +1,41 @@ +/* + * 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 CS = SonarAnalyzer.Rules.CSharp; +using VB = SonarAnalyzer.Rules.VisualBasic; + +namespace SonarAnalyzer.UnitTest.Rules; + +[TestClass] +public class DebuggerDisplayUsesExistingMembersTest +{ + private readonly VerifierBuilder builderCS = new VerifierBuilder(); + + [TestMethod] + public void DebuggerDisplayUsesExistingMembers_CS() => + builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.cs").Verify(); + + private readonly VerifierBuilder builderVB = new VerifierBuilder(); // FIXME: Move this up + + [TestMethod] + public void DebuggerDisplayUsesExistingMembers_VB() => + builderVB.AddPaths("DebuggerDisplayUsesExistingMembers.vb").Verify(); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs new file mode 100644 index 00000000000..a790d6d1f92 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -0,0 +1,5 @@ +using System; + +public class Program +{ +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb new file mode 100644 index 00000000000..f5b9e9276c6 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -0,0 +1,7 @@ +Public Class Program + + Public Sub Test() + + End Sub + +End Class From 252d09096426c78eb852e9a6e9b5ba5e575b093b Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Tue, 7 Feb 2023 13:13:30 +0100 Subject: [PATCH 02/19] Implementation --- .../DebuggerDisplayUsesExistingMembers.cs | 21 +- .../DebuggerDisplayUsesExistingMembersBase.cs | 60 ++++- .../DebuggerDisplayUsesExistingMembers.cs | 21 +- .../DebuggerDisplayUsesExistingMembersTest.cs | 24 +- ...ggerDisplayUsesExistingMembers.CSharp10.cs | 34 +++ ...uggerDisplayUsesExistingMembers.CSharp8.cs | 19 ++ ...uggerDisplayUsesExistingMembers.CSharp9.cs | 9 + .../DebuggerDisplayUsesExistingMembers.cs | 235 +++++++++++++++++- .../DebuggerDisplayUsesExistingMembers.vb | 217 +++++++++++++++- 9 files changed, 608 insertions(+), 32 deletions(-) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs index 5c201771fd7..a79c1c3ff85 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs @@ -21,19 +21,16 @@ namespace SonarAnalyzer.Rules.CSharp; [DiagnosticAnalyzer(LanguageNames.CSharp)] -public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExistingMembersBase +public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExistingMembersBase { - protected override ILanguageFacade Language => CSharpFacade.Instance; - protected override void Initialize(SonarAnalysisContext context) => - context.RegisterNodeAction(c => - { - var node = c.Node; - if (true) - { - c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); - } - }, - SyntaxKind.InvocationExpression); + protected override SyntaxNode GetAttributeFormatString(AttributeSyntax attribute) => + attribute.ArgumentList.Arguments.FirstOrDefault() is { Expression: LiteralExpressionSyntax { RawKind: (int)SyntaxKind.StringLiteralExpression } formatString } + ? formatString + : null; + + protected override string GetAttributeName(AttributeSyntax attribute) => attribute.Name.ToString(); + + protected override bool IsValidMemberName(string memberName) => SyntaxFacts.IsValidIdentifier(memberName); } diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs index c21209e630e..1f8938ea880 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs @@ -18,14 +18,70 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +using System.Text.RegularExpressions; + namespace SonarAnalyzer.Rules; -public abstract class DebuggerDisplayUsesExistingMembersBase : SonarDiagnosticAnalyzer +public abstract class DebuggerDisplayUsesExistingMembersBase : SonarDiagnosticAnalyzer + where TAttributeSyntax : SyntaxNode where TSyntaxKind : struct { private const string DiagnosticId = "S4545"; - protected override string MessageFormat => "FIXME"; + private static readonly Regex NqModifierExpressionRegex = new(@",\s*nq\s*$", RegexOptions.Compiled); + private static readonly Regex EvaluatedExpressionRegex = new(@"\{(?[^\}]+)\}", RegexOptions.Compiled); + + protected abstract string GetAttributeName(TAttributeSyntax attribute); + protected abstract SyntaxNode GetAttributeFormatString(TAttributeSyntax attribute); + protected abstract bool IsValidMemberName(string memberName); + + protected override string MessageFormat => "'{0}' doesn't exist in this context."; protected DebuggerDisplayUsesExistingMembersBase() : base(DiagnosticId) { } + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction(Language.GeneratedCodeRecognizer, + c => + { + var attribute = (TAttributeSyntax)c.Node; + var attributeName = GetAttributeName(attribute); + if ((string.Equals(attributeName, "DebuggerDisplayAttribute", Language.NameComparison) || string.Equals(attributeName, "DebuggerDisplay", Language.NameComparison)) + && GetAttributeFormatString(attribute) is { } formatString + && FirstInvalidMemberName(c, formatString.GetFirstToken().ValueText, attribute) is { } firstInvalidMember) + { + c.ReportIssue(Diagnostic.Create(Rule, formatString.GetLocation(), firstInvalidMember)); + } + }, + Language.SyntaxKind.Attribute); + + private string FirstInvalidMemberName(SonarSyntaxNodeReportingContext context, string formatString, TAttributeSyntax attributeSyntax) + { + foreach (Match match in EvaluatedExpressionRegex.Matches(formatString)) + { + if (match.Groups["EvaluatedExpression"] is { Success: true, Value: var evaluatedExpression } + && ExtractValidMemberName(evaluatedExpression) is { } memberName + && attributeSyntax.Parent?.Parent is { } targetSyntax + && context.SemanticModel.GetDeclaredSymbol(targetSyntax) is { } targetSymbol + && RelevantType(targetSymbol) is { } typeSymbol + && typeSymbol.GetSelfAndBaseTypes().SelectMany(x => x.GetMembers()).All(x => Language.NameComparer.Compare(x.Name, memberName) != 0)) + { + return memberName; + } + } + + return null; + } + + private string ExtractValidMemberName(string evaluatedExpression) + { + var sanitizedExpression = RemoveNqModifier(evaluatedExpression).Trim(); + return IsValidMemberName(sanitizedExpression) ? sanitizedExpression : null; + } + + private static string RemoveNqModifier(string evaluatedExpression) => + NqModifierExpressionRegex.Match(evaluatedExpression) is { Success: true, Length: var matchLength } + ? evaluatedExpression.Substring(0, evaluatedExpression.Length - matchLength) + : evaluatedExpression; + + private static ITypeSymbol RelevantType(ISymbol symbol) => symbol is ITypeSymbol typeSymbol ? typeSymbol : symbol.ContainingType; } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs index 6aa5c3e3c70..f762fbca76f 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs @@ -21,19 +21,16 @@ namespace SonarAnalyzer.Rules.VisualBasic; [DiagnosticAnalyzer(LanguageNames.VisualBasic)] -public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExistingMembersBase +public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExistingMembersBase { - protected override ILanguageFacade Language => VisualBasicFacade.Instance; - protected override void Initialize(SonarAnalysisContext context) => - context.RegisterNodeAction(c => - { - var node = c.Node; - if (true) - { - c.ReportIssue(Diagnostic.Create(Rule, node.GetLocation())); - } - }, - SyntaxKind.InvocationExpression); + protected override SyntaxNode GetAttributeFormatString(AttributeSyntax attribute) => + attribute.ArgumentList.Arguments.FirstOrDefault() is SimpleArgumentSyntax { Expression: LiteralExpressionSyntax { RawKind: (int)SyntaxKind.StringLiteralExpression } formatString } + ? formatString + : null; + + protected override string GetAttributeName(AttributeSyntax attribute) => attribute.Name.ToString(); + + protected override bool IsValidMemberName(string memberName) => SyntaxFacts.IsValidIdentifier(memberName); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs index 6aa2d4c8dca..06ae6f6b6aa 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs @@ -18,7 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ - using CS = SonarAnalyzer.Rules.CSharp; using VB = SonarAnalyzer.Rules.VisualBasic; @@ -28,12 +27,33 @@ namespace SonarAnalyzer.UnitTest.Rules; public class DebuggerDisplayUsesExistingMembersTest { private readonly VerifierBuilder builderCS = new VerifierBuilder(); + private readonly VerifierBuilder builderVB = new VerifierBuilder(); [TestMethod] public void DebuggerDisplayUsesExistingMembers_CS() => builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.cs").Verify(); - private readonly VerifierBuilder builderVB = new VerifierBuilder(); // FIXME: Move this up +#if NET + + [TestMethod] + public void DebuggerDisplayUsesExistingMembers_CSharp8() => + builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp8.cs") + .WithOptions(ParseOptionsHelper.FromCSharp8) + .Verify(); + + [TestMethod] + public void DebuggerDisplayUsesExistingMembers_CSharp9() => + builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp9.cs") + .WithOptions(ParseOptionsHelper.FromCSharp9) + .Verify(); + + [TestMethod] + public void DebuggerDisplayUsesExistingMembers_CSharp10() => + builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp10.cs") + .WithOptions(ParseOptionsHelper.FromCSharp10) + .Verify(); + +#endif [TestMethod] public void DebuggerDisplayUsesExistingMembers_VB() => diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs new file mode 100644 index 00000000000..43fda196aae --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs @@ -0,0 +1,34 @@ +using System; +using System.Diagnostics; + +[DebuggerDisplay("{RecordProperty}")] +public record SomeRecord(int RecordProperty) +{ + [DebuggerDisplay("{RecordProperty}")] public record struct RecordStruct1(int RecordStructProperty); // Noncompliant + [DebuggerDisplay("{RecordStructProperty}")] public record struct RecordStruct2(int RecordStructProperty); // Compliant +} + +[DebuggerDisplay("{RecordProperty1} bla bla {RecordProperty2}")] +public record struct SomeRecordStruct(int RecordProperty1, string RecordProperty2) +{ + [DebuggerDisplay("{RecordProperty}")] // Noncompliant + public class NestedClass1 + { + [DebuggerDisplay("{NestedClassProperty}")] // Compliant + public int NestedClassProperty => 1; + } + + [DebuggerDisplay("{NestedClassProperty}")] // Compliant + public class NestedClass2 + { + [DebuggerDisplay("{NestedClassProperty}")] // Compliant + public int NestedClassProperty => 1; + } +} + +public class SupportConstantInterpolatedStrings +{ + [DebuggerDisplay($"{{{nameof(SomeProperty)}}}")] // Compliant + [DebuggerDisplay($"{{{nameof(SomeProperty)}}}")] // Compliant, FN: constant interpolated strings not supported + public int SomeProperty => 1; +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs new file mode 100644 index 00000000000..c287a9cb86e --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs @@ -0,0 +1,19 @@ +using System; +using System.Diagnostics; + +public class SupportAccessModifiers +{ + public class BaseClass + { + private protected int PrivateProtectedProperty => 1; + + [DebuggerDisplay("{PrivateProtectedProperty}")] // Compliant + public int SomeProperty => 1; + } + + public class SubClass : BaseClass + { + [DebuggerDisplay("{PrivateProtectedProperty}")] // Compliant + public int OtherProperty => 1; + } +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs new file mode 100644 index 00000000000..49b171c5fd5 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs @@ -0,0 +1,9 @@ +using System; +using System.Diagnostics; + +[DebuggerDisplay("{RecordProperty}")] +public record SomeRecord(int RecordProperty) +{ + [DebuggerDisplay("{RecordProperty}")] public record NestedRecord1(int NestedRecordProperty); // Noncompliant + [DebuggerDisplay("{NestedRecordProperty}")] public record NestedRecord2(int NestedRecordProperty); // Compliant +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs index a790d6d1f92..ffc5fe032f9 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -1,5 +1,238 @@ using System; +using System.Diagnostics; -public class Program +class TestOnPropertiesAndFields { + const string ConstantWithoutInvalidMembers = "1"; + const string ConstantWithInvalidMember = "{NonExisting}"; + const string ConstantFragment1 = "{Non"; + const string ConstantFragment2 = "Existing}"; + + int SomeProperty => 1; + int SomeField = 2; + + [DebuggerDisplayAttribute("1")] int WithSuffix => 1; + [System.Diagnostics.DebuggerDisplay("1")] int WithNamespace => 1; + [DebuggerDisplay(value: "1")] int WithExplicitParameterName => 1; + + [DebuggerDisplay(null)] int WithEmptyArgList => 1; + [DebuggerDisplay("")] int WithEmptyFormat => 1; + [DebuggerDisplay(ConstantWithoutInvalidMembers)] int WithFormatAsConstant1 => 1; + [DebuggerDisplay(nameof(ConstantWithoutInvalidMembers))] int WithFormatAsNameOf => 1; + + [DebuggerDisplay("{SomeProperty}")] int WithExistingProperty => 1; + [DebuggerDisplay("{SomeField}")] int WithExistingField => 1; + [DebuggerDisplay(@"{SomeField}")] int WithExistingFieldVerbatim => 1; + [DebuggerDisplay("{1 + 1}")] int WithNoMemberReferenced1 => 1; + [DebuggerDisplay(@"{""1"" + ""1""}")] int WithNoMemberReferenced2 => 1; + + [DebuggerDisplay("{NonExisting}")] int WithNonExistingMember1 => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + // ^^^^^^^^^^^^^^^ + [DebuggerDisplay("1 + {NonExisting}")] int WithNonExistingMember2 => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + // ^^^^^^^^^^^^^^^^^^^ + [DebuggerDisplay("{NonExisting1} bla bla {NonExisting2}")] int WithMultipleNonExisting => 1; // Noncompliant {{'NonExisting1' doesn't exist in this context.}} + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + [DebuggerDisplay(@"{NonExisting}")] int WithNonExistingMemberVerbatim => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + + [DebuggerDisplay(ConstantWithInvalidMember)] int WithFormatAsConstant2 => 1; // Compliant, FN: constants are not checked + [DebuggerDisplay("{Non" + "Existing}")] int WithFormatAsConcatenationOfLiterals => 1; // Compliant, FN: only simple literal supported + [DebuggerDisplay(ConstantFragment1 + ConstantFragment2)] int WithFormatAsConcatenationOfConstants => 1; // Compliant, FN: only simple literal supported + + [DebuggerDisplay("{this.NonExistingProperty}")] int PropertyWithExplicitThis => 1; // Compliant, FN: "this." not supported + [DebuggerDisplay("{this.NonExistingField}")] int FieldWithExplicitThis => 1; // Compliant, FN: "this." not supported + [DebuggerDisplay("{1 + NonExistingProperty}")] int ContainingInvalidMembers => 1; // Compliant, FN: expressions not supported +} + +[DebuggerDisplay("{this.ToString()}")] // Compliant +[DebuggerDisplay("{NonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} +public enum TopLevelEnum { One, Two, Three } + +[DebuggerDisplay("{SomeProperty}")] +[DebuggerDisplay("{SomeField}")] +[DebuggerDisplay("{NonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} +public class TestOnNestedTypes +{ + int SomeProperty => 1; + int SomeField = 1; + + [DebuggerDisplay("{ExistingProperty}")] + [DebuggerDisplay("{ExistingField}")] + [DebuggerDisplay("{SomeProperty}")] // Noncompliant {{'SomeProperty' doesn't exist in this context.}} + [DebuggerDisplay("{SomeField}")] // Noncompliant {{'SomeField' doesn't exist in this context.}} + public class NestedClass + { + int ExistingProperty => 1; + int ExistingField => 1; + } + + [DebuggerDisplay("{ExistingProperty}")] + [DebuggerDisplay("{ExistingField}")] + [DebuggerDisplay("{SomeProperty}")] // Noncompliant {{'SomeProperty' doesn't exist in this context.}} + [DebuggerDisplay("{SomeField}")] // Noncompliant {{'SomeField' doesn't exist in this context.}} + public struct NestedStruct + { + int ExistingProperty => 1; + int ExistingField => 1; + } + + public enum NestedEnum { One, Two, Three } +} + +public class TestOnDelegates +{ + int ExistingProperty => 1; + + [DebuggerDisplay("{ExistingProperty}")] // Noncompliant + [DebuggerDisplay("{1}")] // Compliant + public delegate void Delegate1(); +} + +public class TestOnIndexers +{ + int ExistingProperty => 1; + int ExistingField => 1; + + [DebuggerDisplay("{ExistingProperty}")] + [DebuggerDisplay("{ExistingField}")] + [DebuggerDisplay("{NonExisting}")] // Noncompliant + int this[int i] => 1; +} + +[DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{NonExisting}")] // Noncompliant +// ^^^^^^^^^^^^^^^ +public class TestMultipleAttributes +{ + int SomeProperty => 1; + int SomeField = 1; + + [DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{NonExisting}")] // Noncompliant + // ^^^^^^^^^^^^^^^ + int OtherProperty1 => 1; + + [DebuggerDisplay("{NonExisting1}"), DebuggerDisplay("{NonExisting2}")] + // ^^^^^^^^^^^^^^^^ + // ^^^^^^^^^^^^^^^^@-1 + int OtherProperty2 => 1; + + [DebuggerDisplay("{NonExisting1}")][DebuggerDisplay("{NonExisting2}")] + // ^^^^^^^^^^^^^^^^ + // ^^^^^^^^^^^^^^^^@-1 + int OtherProperty3 => 1; +} + +public class SupportCaseSensitivity +{ + int SOMEPROPERTY => 1; + int SomeProperty => 1; + + [DebuggerDisplay("{SOMEPROPERTY}")] // Compliant + [DebuggerDisplay("{SomeProperty}")] // Compliant + [DebuggerDisplay("{someProperty}")] // Noncompliant {{'someProperty' doesn't exist in this context.}} + [DebuggerDisplay("{someproperty}")] // Noncompliant {{'someproperty' doesn't exist in this context.}} + int OtherProperty => 1; +} + +public class SupportNonAlphanumericChars +{ + int Aa1_뿓 => 1; + + [DebuggerDisplay("{Aa1_뿓}")] // Compliant + [DebuggerDisplay("{Aa1_㤬}")] // Noncompliant {{'Aa1_㤬' doesn't exist in this context.}} + int SomeProperty1 => 1; +} + +public class SupportWhitespaces +{ + [DebuggerDisplay("{ SomeProperty}")] + [DebuggerDisplay("{SomeProperty }")] + [DebuggerDisplay("{\tSomeProperty}")] + [DebuggerDisplay("{\tSomeProperty\t}")] + [DebuggerDisplay("{ NonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("{NonExisting }")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("{\tNonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("{\tNonExisting\t}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + int SomeProperty => 1; +} + +public class SupportNq +{ + [DebuggerDisplay("{SomeProperty,nq}")] + [DebuggerDisplay("{SomeProperty ,nq}")] + [DebuggerDisplay("{SomeProperty, nq}")] + [DebuggerDisplay("{SomeProperty,nq }")] + [DebuggerDisplay("{NonExisting,nq}")] // Noncompliant + [DebuggerDisplay("{NonExisting ,nq}")] // Noncompliant + [DebuggerDisplay("{NonExisting, nq}")] // Noncompliant + [DebuggerDisplay("{NonExisting,nq }")] // Noncompliant + int SomeProperty => 1; +} + +public class SupportOptionalAttributeParameter +{ + [DebuggerDisplay("{SomeProperty}", Name = "Any name")] // Compliant + [DebuggerDisplay("{NonExisting}", Name = "Any name")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + // ^^^^^^^^^^^^^^^ + [DebuggerDisplay("{NonExisting}", Name = "Any name", Type = nameof(SupportOptionalAttributeParameter))] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + // ^^^^^^^^^^^^^^^ + [DebuggerDisplay("{NonExisting}", Name = "Any name", Target = typeof(SupportOptionalAttributeParameter))] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + // ^^^^^^^^^^^^^^^ + int SomeProperty => 1; +} + +public class SupportInheritance +{ + public class BaseClass + { + int SomeProperty => 1; + } + + public class SubClass : BaseClass + { + [DebuggerDisplay("{SomeProperty}")] // Compliant, defined in base class + int OtherProperty => 1; + } +} + +public class SupportAccessModifiers +{ + public class BaseClass + { + public int PublicProperty => 1; + internal int InternalProperty => 1; + protected int ProtectedProperty => 1; + private int PrivateProperty => 1; + + [DebuggerDisplay("{PublicProperty}")] // Compliant + [DebuggerDisplay("{InternalProperty}")] // Compliant + [DebuggerDisplay("{ProtectedProperty}")] // Compliant + [DebuggerDisplay("{PrivateProperty}")] // Compliant + int SomeProperty => 1; + } + + public class SubClass : BaseClass + { + [DebuggerDisplay("{PublicProperty}")] // Compliant + [DebuggerDisplay("{InternalProperty}")] // Compliant + [DebuggerDisplay("{ProtectedProperty}")] // Compliant + [DebuggerDisplay("{PrivateProperty}")] // Compliant + int OtherProperty => 1; + } +} + +public class SupportAttributeTargets +{ + [assembly: DebuggerDisplay("{NonExisting}")] // Noncompliant, attribute at assembly level, referencing a non-existing member + [field: DebuggerDisplay("{NonExisting}")] // Noncompliant, attribute ignored, still referencing a non-existing member + [property: DebuggerDisplay("{NonExisting}")] // Noncompliant, attribute taken into account + int SomeProperty => 1; +} + +namespace WithTypeAlias +{ + using DebuggerDisplayAlias = System.Diagnostics.DebuggerDisplayAttribute; + + public class Test + { + [DebuggerDisplayAlias("{NonExisting}")] int WithAlias => 1; // Compliant, FN: attribute name checked at syntax level + } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb index f5b9e9276c6..190b74e5993 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -1,7 +1,218 @@ -Public Class Program +Imports System +Imports System.Diagnostics - Public Sub Test() +Public Class TestOnPropertiesAndFields + Const ConstantWithoutInvalidMembers As String = "1" + Const ConstantWithInvalidMember As String = "{NonExisting}" + Const ConstantFragment1 As String = "{Non" + Const ConstantFragment2 As String = "Existing}" - End Sub + Property SomeProperty As Integer + Public SomeField As Integer + Property WithSuffix As Integer + Property WithNamespace As Integer + + Property WithEmptyArgList As Integer + Property WithEmptyFormat As Integer + Property WithFormatAsConstant1 As Integer + Property WithFormatAsNameOf As Integer + + Property WithExistingProperty As Integer + Property WithExistingField As Integer + Property WithExistingFieldVerbatim As Integer + Property WithNoMemberReferenced1 As Integer + Property WithNoMemberReferenced2 As Integer + + Property WithNonExistingMember1 As Integer ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' ^^^^^^^^^^^^^^^ + Property WithNonExistingMember2 As Integer ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' ^^^^^^^^^^^^^^^^^^^ + Property WithMultipleNonExisting As Integer ' Noncompliant {{'NonExisting1' doesn't exist in this context.}} + ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + Property WithNonExistingMemberVerbatim As Integer ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' ^^^^^^^^^^^^^^^ + + Property WithFormatAsConstant2 As Integer ' Compliant, FN: constants are not checked + Property WithFormatAsConcatenationOfLiterals As Integer ' Compliant, FN: only simple literal supported + Property WithFormatAsConcatenationOfConstants As Integer ' Compliant, FN: only simple literal supported + + Property PropertyWithExplicitThis As Integer ' Compliant, FN: "this." not supported + Property FieldWithExplicitThis As Integer ' Compliant, FN: "this." not supported + Property ContainingInvalidMembers As Integer ' Compliant, FN: expressions not supported +End Class + + ' Compliant + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} +Public Enum TopLevelEnum + One + Two + Three +End Enum + + + + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} +Public Class TestOnNestedTypes + Property SomeProperty As Integer + Public SomeField As Integer + + + + ' Noncompliant {{'SomeProperty' doesn't exist in this context.}} + ' Noncompliant {{'SomeField' doesn't exist in this context.}} + Public Class NestedClass + Property ExistingProperty As Integer + Property ExistingField As Integer + End Class + + + + ' Noncompliant {{'SomeProperty' doesn't exist in this context.}} + ' Noncompliant {{'SomeField' doesn't exist in this context.}} + Public Structure NestedStruct + Property ExistingProperty As Integer + Property ExistingField As Integer + End Structure + + Public Enum NestedEnum + One + Two + Three + End Enum +End Class + +Class TestOnDelegates + Property ExistingProperty As Integer + + ' Noncompliant + ' Compliant + Delegate Sub Delegate1() +End Class + +Class TestOnIndexers + Property ExistingProperty As Integer + Public ExistingField As Integer + + + + ' Noncompliant + Default Property Item(ByVal i As Integer) As Integer + Get + Return 1 + End Get + Set(value As Integer) + End Set + End Property +End Class + + ' Noncompliant +Class TestMultipleAttributes + ' ^^^^^^^^^^^^^^^@-1 + + Property SomeProperty As Integer + Public SomeField As Integer = 1 + + Property OtherProperty1 As Integer ' Noncompliant + ' ^^^^^^^^^^^^^^^ + + Property OtherProperty2 As Integer + ' ^^^^^^^^^^^^^^^^ + ' ^^^^^^^^^^^^^^^^@-1 + + Property OtherProperty3 As Integer + ' ^^^^^^^^^^^^^^^^ + ' ^^^^^^^^^^^^^^^^@-1 End Class + +Class SupportCaseInsensitivity + Property SomeProperty As Integer = 1 + + ' Compliant + ' Compliant + ' Compliant + ' Compliant + Property OtherProperty As Integer +End Class + +Class SupportNonAlphanumericChars + Property Aa1_뿓 As Integer + + ' Compliant + ' Noncompliant {{'Aa1_㤬' doesn't exist in this context.}} + Property SomeProperty1 As Integer +End Class + +Class SupportWhitespaces + + + + + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' Compliant, FN: string concatenation not supported + ' Compliant, FN: string concatenation not supported + Property SomeProperty As Integer +End Class + +Class SupportNq + + + + + ' Noncompliant + ' Noncompliant + ' Noncompliant + ' Noncompliant + Property SomeProperty As Integer +End Class + +Class SupportOptionalAttributeParameter + ' Compliant + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + Property SomeProperty As Integer + ' ^^^^^^^^^^^^^^^@-3 + ' ^^^^^^^^^^^^^^^@-3 + ' ^^^^^^^^^^^^^^^@-3 +End Class + +Class SupportInheritance + Class BaseClass + Property SomeProperty As Integer + End Class + + Class SubClass + Inherits BaseClass + + ' Compliant, defined in base class + Property OtherProperty As Integer + End Class +End Class + +Class SupportAccessModifiers + Class BaseClass + Public Property PublicProperty As Integer + Friend Property InternalProperty As Integer + Protected Property ProtectedProperty As Integer + Private Property PrivateProperty As Integer + + + + + + Property SomeProperty As Integer + End Class + + Class SubClass + Inherits BaseClass + + + + + + Property OtherProperty As Integer + End Class +End Class + From 6784befee8bdf36158c63fb0a890ea8bdd31173b Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Tue, 7 Feb 2023 18:17:06 +0100 Subject: [PATCH 03/19] Fix security hotspots --- .../DebuggerDisplayUsesExistingMembersBase.cs | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs index 1f8938ea880..6303f66ab49 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs @@ -28,8 +28,9 @@ public abstract class DebuggerDisplayUsesExistingMembersBase[^\}]+)\}", RegexOptions.Compiled); + private static readonly TimeSpan RegexTimeout = TimeSpan.FromMilliseconds(100); + private static readonly Regex NqModifierExpressionRegex = new(@",\s*nq\s*$", RegexOptions.Compiled, RegexTimeout); + private static readonly Regex EvaluatedExpressionRegex = new(@"\{(?[^\}]+)\}", RegexOptions.Compiled, RegexTimeout); protected abstract string GetAttributeName(TAttributeSyntax attribute); protected abstract SyntaxNode GetAttributeFormatString(TAttributeSyntax attribute); @@ -56,20 +57,27 @@ public abstract class DebuggerDisplayUsesExistingMembersBase x.GetMembers()).All(x => Language.NameComparer.Compare(x.Name, memberName) != 0)) + foreach (Match match in EvaluatedExpressionRegex.Matches(formatString)) { - return memberName; + if (match.Groups["EvaluatedExpression"] is { Success: true, Value: var evaluatedExpression } + && ExtractValidMemberName(evaluatedExpression) is { } memberName + && attributeSyntax.Parent?.Parent is { } targetSyntax + && context.SemanticModel.GetDeclaredSymbol(targetSyntax) is { } targetSymbol + && RelevantType(targetSymbol) is { } typeSymbol + && typeSymbol.GetSelfAndBaseTypes().SelectMany(x => x.GetMembers()).All(x => Language.NameComparer.Compare(x.Name, memberName) != 0)) + { + return memberName; + } } - } - return null; + return null; + } + catch (RegexMatchTimeoutException) + { + return null; + } } private string ExtractValidMemberName(string evaluatedExpression) From 5845ad583053e01e40e5ae96543b69038fc6fcc9 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Wed, 8 Feb 2023 11:47:45 +0100 Subject: [PATCH 04/19] Fix code smells and "compliant" comments --- .../Common/RegexConstants.cs | 27 +++++++++++ .../DebuggerDisplayUsesExistingMembersBase.cs | 11 ++--- ...ggerDisplayUsesExistingMembers.CSharp10.cs | 14 +++--- ...uggerDisplayUsesExistingMembers.CSharp8.cs | 4 +- ...uggerDisplayUsesExistingMembers.CSharp9.cs | 2 +- .../DebuggerDisplayUsesExistingMembers.cs | 48 +++++++++---------- .../DebuggerDisplayUsesExistingMembers.vb | 34 ++++++------- 7 files changed, 84 insertions(+), 56 deletions(-) create mode 100644 analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs diff --git a/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs b/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs new file mode 100644 index 00000000000..894f0cb0ed8 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs @@ -0,0 +1,27 @@ +/* + * SonarScanner for .NET + * Copyright (C) 2016-2023 SonarSource SA + * mailto: info 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.Common +{ + public static class RegexConstants + { + public static TimeSpan DefaultTimeout => TimeSpan.FromMilliseconds(100); + } +} diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs index 6303f66ab49..5f88a116b3f 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs @@ -28,9 +28,8 @@ public abstract class DebuggerDisplayUsesExistingMembersBase[^\}]+)\}", RegexOptions.Compiled, RegexTimeout); + private readonly Regex nqModifierExpressionRegex = new(@",\s*nq\s*$", RegexOptions.None, RegexConstants.DefaultTimeout); + private readonly Regex evaluatedExpressionRegex = new(@"\{(?[^\}]+)\}", RegexOptions.None, RegexConstants.DefaultTimeout); protected abstract string GetAttributeName(TAttributeSyntax attribute); protected abstract SyntaxNode GetAttributeFormatString(TAttributeSyntax attribute); @@ -59,7 +58,7 @@ private string FirstInvalidMemberName(SonarSyntaxNodeReportingContext context, s { try { - foreach (Match match in EvaluatedExpressionRegex.Matches(formatString)) + foreach (Match match in evaluatedExpressionRegex.Matches(formatString)) { if (match.Groups["EvaluatedExpression"] is { Success: true, Value: var evaluatedExpression } && ExtractValidMemberName(evaluatedExpression) is { } memberName @@ -86,8 +85,8 @@ private string ExtractValidMemberName(string evaluatedExpression) return IsValidMemberName(sanitizedExpression) ? sanitizedExpression : null; } - private static string RemoveNqModifier(string evaluatedExpression) => - NqModifierExpressionRegex.Match(evaluatedExpression) is { Success: true, Length: var matchLength } + private string RemoveNqModifier(string evaluatedExpression) => + nqModifierExpressionRegex.Match(evaluatedExpression) is { Success: true, Length: var matchLength } ? evaluatedExpression.Substring(0, evaluatedExpression.Length - matchLength) : evaluatedExpression; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs index 43fda196aae..c7f8bff3cfa 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs @@ -5,7 +5,7 @@ public record SomeRecord(int RecordProperty) { [DebuggerDisplay("{RecordProperty}")] public record struct RecordStruct1(int RecordStructProperty); // Noncompliant - [DebuggerDisplay("{RecordStructProperty}")] public record struct RecordStruct2(int RecordStructProperty); // Compliant + [DebuggerDisplay("{RecordStructProperty}")] public record struct RecordStruct2(int RecordStructProperty); // Compliant, RecordStructProperty is a property } [DebuggerDisplay("{RecordProperty1} bla bla {RecordProperty2}")] @@ -14,21 +14,23 @@ public record struct SomeRecordStruct(int RecordProperty1, string RecordProperty [DebuggerDisplay("{RecordProperty}")] // Noncompliant public class NestedClass1 { - [DebuggerDisplay("{NestedClassProperty}")] // Compliant + [DebuggerDisplay("{NestedClassProperty}")] public int NestedClassProperty => 1; } - [DebuggerDisplay("{NestedClassProperty}")] // Compliant + [DebuggerDisplay("{NestedClassProperty}")] public class NestedClass2 { - [DebuggerDisplay("{NestedClassProperty}")] // Compliant + [DebuggerDisplay("{NestedClassProperty}")] public int NestedClassProperty => 1; } } public class SupportConstantInterpolatedStrings { - [DebuggerDisplay($"{{{nameof(SomeProperty)}}}")] // Compliant - [DebuggerDisplay($"{{{nameof(SomeProperty)}}}")] // Compliant, FN: constant interpolated strings not supported + [DebuggerDisplay($"{{{nameof(SomeProperty)}}}")] + [DebuggerDisplay($"{{{nameof(NotAProperty)}}}")] // FN: constant interpolated strings not supported public int SomeProperty => 1; + + public class NotAProperty { } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs index c287a9cb86e..70e234cca02 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs @@ -7,13 +7,13 @@ public class BaseClass { private protected int PrivateProtectedProperty => 1; - [DebuggerDisplay("{PrivateProtectedProperty}")] // Compliant + [DebuggerDisplay("{PrivateProtectedProperty}")] public int SomeProperty => 1; } public class SubClass : BaseClass { - [DebuggerDisplay("{PrivateProtectedProperty}")] // Compliant + [DebuggerDisplay("{PrivateProtectedProperty}")] public int OtherProperty => 1; } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs index 49b171c5fd5..3222dfd34a0 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs @@ -5,5 +5,5 @@ public record SomeRecord(int RecordProperty) { [DebuggerDisplay("{RecordProperty}")] public record NestedRecord1(int NestedRecordProperty); // Noncompliant - [DebuggerDisplay("{NestedRecordProperty}")] public record NestedRecord2(int NestedRecordProperty); // Compliant + [DebuggerDisplay("{NestedRecordProperty}")] public record NestedRecord2(int NestedRecordProperty); // Compliant, NestedRecordProperty is a property } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs index ffc5fe032f9..9b1e720f0ad 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -34,16 +34,16 @@ class TestOnPropertiesAndFields // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [DebuggerDisplay(@"{NonExisting}")] int WithNonExistingMemberVerbatim => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} - [DebuggerDisplay(ConstantWithInvalidMember)] int WithFormatAsConstant2 => 1; // Compliant, FN: constants are not checked - [DebuggerDisplay("{Non" + "Existing}")] int WithFormatAsConcatenationOfLiterals => 1; // Compliant, FN: only simple literal supported - [DebuggerDisplay(ConstantFragment1 + ConstantFragment2)] int WithFormatAsConcatenationOfConstants => 1; // Compliant, FN: only simple literal supported + [DebuggerDisplay(ConstantWithInvalidMember)] int WithFormatAsConstant2 => 1; // FN: constants are not checked + [DebuggerDisplay("{Non" + "Existing}")] int WithFormatAsConcatenationOfLiterals => 1; // FN: only simple literal supported + [DebuggerDisplay(ConstantFragment1 + ConstantFragment2)] int WithFormatAsConcatenationOfConstants => 1; // FN: only simple literal supported - [DebuggerDisplay("{this.NonExistingProperty}")] int PropertyWithExplicitThis => 1; // Compliant, FN: "this." not supported - [DebuggerDisplay("{this.NonExistingField}")] int FieldWithExplicitThis => 1; // Compliant, FN: "this." not supported - [DebuggerDisplay("{1 + NonExistingProperty}")] int ContainingInvalidMembers => 1; // Compliant, FN: expressions not supported + [DebuggerDisplay("{this.NonExistingProperty}")] int PropertyWithExplicitThis => 1; // FN: "this." not supported + [DebuggerDisplay("{this.NonExistingField}")] int FieldWithExplicitThis => 1; // FN: "this." not supported + [DebuggerDisplay("{1 + NonExistingProperty}")] int ContainingInvalidMembers => 1; // FN: expressions not supported } -[DebuggerDisplay("{this.ToString()}")] // Compliant +[DebuggerDisplay("{this.ToString()}")] [DebuggerDisplay("{NonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} public enum TopLevelEnum { One, Two, Three } @@ -83,7 +83,7 @@ public class TestOnDelegates int ExistingProperty => 1; [DebuggerDisplay("{ExistingProperty}")] // Noncompliant - [DebuggerDisplay("{1}")] // Compliant + [DebuggerDisplay("{1}")] public delegate void Delegate1(); } @@ -125,10 +125,10 @@ public class SupportCaseSensitivity int SOMEPROPERTY => 1; int SomeProperty => 1; - [DebuggerDisplay("{SOMEPROPERTY}")] // Compliant - [DebuggerDisplay("{SomeProperty}")] // Compliant - [DebuggerDisplay("{someProperty}")] // Noncompliant {{'someProperty' doesn't exist in this context.}} - [DebuggerDisplay("{someproperty}")] // Noncompliant {{'someproperty' doesn't exist in this context.}} + [DebuggerDisplay("{SOMEPROPERTY}")] + [DebuggerDisplay("{SomeProperty}")] + [DebuggerDisplay("{someProperty}")] // Noncompliant {{'someProperty' doesn't exist in this context.}} + [DebuggerDisplay("{someproperty}")] // Noncompliant {{'someproperty' doesn't exist in this context.}} int OtherProperty => 1; } @@ -136,8 +136,8 @@ public class SupportNonAlphanumericChars { int Aa1_뿓 => 1; - [DebuggerDisplay("{Aa1_뿓}")] // Compliant - [DebuggerDisplay("{Aa1_㤬}")] // Noncompliant {{'Aa1_㤬' doesn't exist in this context.}} + [DebuggerDisplay("{Aa1_뿓}")] + [DebuggerDisplay("{Aa1_㤬}")] // Noncompliant {{'Aa1_㤬' doesn't exist in this context.}} int SomeProperty1 => 1; } @@ -169,7 +169,7 @@ public class SupportNq public class SupportOptionalAttributeParameter { - [DebuggerDisplay("{SomeProperty}", Name = "Any name")] // Compliant + [DebuggerDisplay("{SomeProperty}", Name = "Any name")] [DebuggerDisplay("{NonExisting}", Name = "Any name")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^ [DebuggerDisplay("{NonExisting}", Name = "Any name", Type = nameof(SupportOptionalAttributeParameter))] // Noncompliant {{'NonExisting' doesn't exist in this context.}} @@ -202,19 +202,19 @@ public class BaseClass protected int ProtectedProperty => 1; private int PrivateProperty => 1; - [DebuggerDisplay("{PublicProperty}")] // Compliant - [DebuggerDisplay("{InternalProperty}")] // Compliant - [DebuggerDisplay("{ProtectedProperty}")] // Compliant - [DebuggerDisplay("{PrivateProperty}")] // Compliant + [DebuggerDisplay("{PublicProperty}")] + [DebuggerDisplay("{InternalProperty}")] + [DebuggerDisplay("{ProtectedProperty}")] + [DebuggerDisplay("{PrivateProperty}")] int SomeProperty => 1; } public class SubClass : BaseClass { - [DebuggerDisplay("{PublicProperty}")] // Compliant - [DebuggerDisplay("{InternalProperty}")] // Compliant - [DebuggerDisplay("{ProtectedProperty}")] // Compliant - [DebuggerDisplay("{PrivateProperty}")] // Compliant + [DebuggerDisplay("{PublicProperty}")] + [DebuggerDisplay("{InternalProperty}")] + [DebuggerDisplay("{ProtectedProperty}")] + [DebuggerDisplay("{PrivateProperty}")] int OtherProperty => 1; } } @@ -233,6 +233,6 @@ namespace WithTypeAlias public class Test { - [DebuggerDisplayAlias("{NonExisting}")] int WithAlias => 1; // Compliant, FN: attribute name checked at syntax level + [DebuggerDisplayAlias("{NonExisting}")] int WithAlias => 1; // FN: attribute name checked at syntax level } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb index 190b74e5993..6c401193c12 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -33,17 +33,17 @@ Public Class TestOnPropertiesAndFields Property WithNonExistingMemberVerbatim As Integer ' Noncompliant {{'NonExisting' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^ - Property WithFormatAsConstant2 As Integer ' Compliant, FN: constants are not checked - Property WithFormatAsConcatenationOfLiterals As Integer ' Compliant, FN: only simple literal supported - Property WithFormatAsConcatenationOfConstants As Integer ' Compliant, FN: only simple literal supported + Property WithFormatAsConstant2 As Integer ' FN: constants are not checked + Property WithFormatAsConcatenationOfLiterals As Integer ' FN: only simple literal supported + Property WithFormatAsConcatenationOfConstants As Integer ' FN: only simple literal supported - Property PropertyWithExplicitThis As Integer ' Compliant, FN: "this." not supported - Property FieldWithExplicitThis As Integer ' Compliant, FN: "this." not supported - Property ContainingInvalidMembers As Integer ' Compliant, FN: expressions not supported + Property PropertyWithExplicitThis As Integer ' FN: "this." not supported + Property FieldWithExplicitThis As Integer ' FN: "this." not supported + Property ContainingInvalidMembers As Integer ' FN: expressions not supported End Class - ' Compliant - ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} Public Enum TopLevelEnum One Two @@ -86,7 +86,7 @@ Class TestOnDelegates Property ExistingProperty As Integer ' Noncompliant - ' Compliant + Delegate Sub Delegate1() End Class @@ -128,17 +128,17 @@ End Class Class SupportCaseInsensitivity Property SomeProperty As Integer = 1 - ' Compliant - ' Compliant - ' Compliant - ' Compliant + + + + Property OtherProperty As Integer End Class Class SupportNonAlphanumericChars Property Aa1_뿓 As Integer - ' Compliant + ' Noncompliant {{'Aa1_㤬' doesn't exist in this context.}} Property SomeProperty1 As Integer End Class @@ -150,8 +150,8 @@ Class SupportWhitespaces ' Noncompliant {{'NonExisting' doesn't exist in this context.}} ' Noncompliant {{'NonExisting' doesn't exist in this context.}} - ' Compliant, FN: string concatenation not supported - ' Compliant, FN: string concatenation not supported + ' FN: string concatenation not supported + ' FN: string concatenation not supported Property SomeProperty As Integer End Class @@ -168,7 +168,7 @@ Class SupportNq End Class Class SupportOptionalAttributeParameter - ' Compliant + ' Noncompliant {{'NonExisting' doesn't exist in this context.}} ' Noncompliant {{'NonExisting' doesn't exist in this context.}} ' Noncompliant {{'NonExisting' doesn't exist in this context.}} From f1eb9aee640373258d83a17493fb0829f497a6ee Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Wed, 8 Feb 2023 11:49:59 +0100 Subject: [PATCH 05/19] Fix license, use file-scoped namespace --- .../src/SonarAnalyzer.Common/Common/RegexConstants.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs b/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs index 894f0cb0ed8..6bb6dbd9da2 100644 --- a/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs +++ b/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs @@ -1,5 +1,5 @@ /* - * SonarScanner for .NET + * SonarAnalyzer for .NET * Copyright (C) 2016-2023 SonarSource SA * mailto: info AT sonarsource DOT com * @@ -18,10 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarAnalyzer.Common +namespace SonarAnalyzer.Common; + +public static class RegexConstants { - public static class RegexConstants - { - public static TimeSpan DefaultTimeout => TimeSpan.FromMilliseconds(100); - } + public static TimeSpan DefaultTimeout => TimeSpan.FromMilliseconds(100); } From 4d61393a8b73eeef9e65fc97183ec7429b37c956 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Wed, 8 Feb 2023 14:17:47 +0100 Subject: [PATCH 06/19] Fix license 2 --- analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs b/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs index 6bb6dbd9da2..58c78e05571 100644 --- a/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs +++ b/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs @@ -1,7 +1,7 @@ /* * SonarAnalyzer for .NET - * Copyright (C) 2016-2023 SonarSource SA - * mailto: info AT sonarsource DOT com + * 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 From 9e777efe85bb3471066c38c3788f7202a95932d1 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Thu, 9 Feb 2023 13:20:23 +0100 Subject: [PATCH 07/19] Review --- .../Facade/CSharpFacade.cs | 3 ++ .../Facade/CSharpSyntaxFacade.cs | 3 ++ .../DebuggerDisplayUsesExistingMembers.cs | 2 -- .../Facade/SyntaxFacade.cs | 1 + .../DebuggerDisplayUsesExistingMembersBase.cs | 31 ++++++++----------- .../Facade/VisualBasicFacade.cs | 3 ++ .../Facade/VisualBasicSyntaxFacade.cs | 3 ++ .../DebuggerDisplayUsesExistingMembers.cs | 2 -- .../DebuggerDisplayUsesExistingMembersTest.cs | 6 ++++ ...ggerDisplayUsesExistingMembers.CSharp11.cs | 31 +++++++++++++++++++ .../DebuggerDisplayUsesExistingMembers.cs | 15 +++++++-- .../DebuggerDisplayUsesExistingMembers.vb | 4 +-- 12 files changed, 77 insertions(+), 27 deletions(-) create mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs index c0ecf611a64..eade4fc7fbc 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs @@ -63,4 +63,7 @@ invocation switch public string GetName(SyntaxNode expression) => expression.GetName(); + + public bool IsValidIdentifier(string name) => + SyntaxFacts.IsValidIdentifier(name); } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs index c26432354f0..0659799b0ae 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs @@ -50,6 +50,9 @@ internal sealed class CSharpSyntaxFacade : SyntaxFacade public override bool IsNullLiteral(SyntaxNode node) => node.IsNullLiteral(); + public override bool IsKnownAttribute(SyntaxNode attribute, KnownType knownAttribute, SemanticModel semanticModel) => + AttributeSyntaxExtensions.IsKnownType(Cast(attribute), knownAttribute, semanticModel); + public override IEnumerable ArgumentExpressions(SyntaxNode node) => node switch { diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs index a79c1c3ff85..d77039da715 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs @@ -30,7 +30,5 @@ public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExis ? formatString : null; - protected override string GetAttributeName(AttributeSyntax attribute) => attribute.Name.ToString(); - protected override bool IsValidMemberName(string memberName) => SyntaxFacts.IsValidIdentifier(memberName); } diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs index 9144a08af14..73c2d835a28 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs @@ -32,6 +32,7 @@ public abstract class SyntaxFacade public abstract bool IsAnyKind(SyntaxNode node, ISet syntaxKinds); public abstract bool IsAnyKind(SyntaxNode node, params TSyntaxKind[] syntaxKinds); public abstract bool IsAnyKind(SyntaxTrivia trivia, params TSyntaxKind[] syntaxKinds); + public abstract bool IsKnownAttribute(SyntaxNode attribute, KnownType knownAttribute, SemanticModel semanticModel); public abstract IEnumerable ArgumentExpressions(SyntaxNode node); public abstract ImmutableArray AssignmentTargets(SyntaxNode assignment); public abstract SyntaxNode AssignmentLeft(SyntaxNode assignment); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs index 5f88a116b3f..89bca7652d0 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs @@ -28,10 +28,9 @@ public abstract class DebuggerDisplayUsesExistingMembersBase[^\}]+)\}", RegexOptions.None, RegexConstants.DefaultTimeout); + private readonly Regex noQuotesModifierExpressionRegex = new(@",\s*nq\s*$", RegexOptions.None, RegexConstants.DefaultTimeout); + private readonly Regex evaluatedExpressionRegex = new(@"\{(?[^\}]+)\}", RegexOptions.Multiline, RegexConstants.DefaultTimeout); - protected abstract string GetAttributeName(TAttributeSyntax attribute); protected abstract SyntaxNode GetAttributeFormatString(TAttributeSyntax attribute); protected abstract bool IsValidMemberName(string memberName); @@ -44,8 +43,7 @@ public abstract class DebuggerDisplayUsesExistingMembersBase { var attribute = (TAttributeSyntax)c.Node; - var attributeName = GetAttributeName(attribute); - if ((string.Equals(attributeName, "DebuggerDisplayAttribute", Language.NameComparison) || string.Equals(attributeName, "DebuggerDisplay", Language.NameComparison)) + if (Language.Syntax.IsKnownAttribute(attribute, KnownType.System_Diagnostics_DebuggerDisplayAttribute, c.SemanticModel) && GetAttributeFormatString(attribute) is { } formatString && FirstInvalidMemberName(c, formatString.GetFirstToken().ValueText, attribute) is { } firstInvalidMember) { @@ -62,9 +60,9 @@ private string FirstInvalidMemberName(SonarSyntaxNodeReportingContext context, s { if (match.Groups["EvaluatedExpression"] is { Success: true, Value: var evaluatedExpression } && ExtractValidMemberName(evaluatedExpression) is { } memberName - && attributeSyntax.Parent?.Parent is { } targetSyntax + && GetAttributeTarget(attributeSyntax) is { } targetSyntax && context.SemanticModel.GetDeclaredSymbol(targetSyntax) is { } targetSymbol - && RelevantType(targetSymbol) is { } typeSymbol + && GetTypeContainingReferencedMembers(targetSymbol) is { } typeSymbol && typeSymbol.GetSelfAndBaseTypes().SelectMany(x => x.GetMembers()).All(x => Language.NameComparer.Compare(x.Name, memberName) != 0)) { return memberName; @@ -77,18 +75,15 @@ private string FirstInvalidMemberName(SonarSyntaxNodeReportingContext context, s { return null; } - } - private string ExtractValidMemberName(string evaluatedExpression) - { - var sanitizedExpression = RemoveNqModifier(evaluatedExpression).Trim(); - return IsValidMemberName(sanitizedExpression) ? sanitizedExpression : null; - } + string ExtractValidMemberName(string evaluatedExpression) + { + var sanitizedExpression = noQuotesModifierExpressionRegex.Replace(evaluatedExpression, string.Empty).Trim(); + return IsValidMemberName(sanitizedExpression) ? sanitizedExpression : null; + } - private string RemoveNqModifier(string evaluatedExpression) => - nqModifierExpressionRegex.Match(evaluatedExpression) is { Success: true, Length: var matchLength } - ? evaluatedExpression.Substring(0, evaluatedExpression.Length - matchLength) - : evaluatedExpression; + static SyntaxNode GetAttributeTarget(TAttributeSyntax attributeSyntax) => attributeSyntax.Parent?.Parent; - private static ITypeSymbol RelevantType(ISymbol symbol) => symbol is ITypeSymbol typeSymbol ? typeSymbol : symbol.ContainingType; + static ITypeSymbol GetTypeContainingReferencedMembers(ISymbol symbol) => symbol is ITypeSymbol typeSymbol ? typeSymbol : symbol.ContainingType; + } } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs index 83d6afc1fab..71d2270b2c6 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs @@ -61,4 +61,7 @@ invocation switch public string GetName(SyntaxNode expression) => expression.GetName(); + + public bool IsValidIdentifier(string name) => + SyntaxFacts.IsValidIdentifier(name); } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs index c03a1e1f4bd..7e01f5ccb63 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs @@ -50,6 +50,9 @@ internal sealed class VisualBasicSyntaxFacade : SyntaxFacade public override bool IsAnyKind(SyntaxTrivia trivia, params SyntaxKind[] syntaxKinds) => trivia.IsAnyKind(syntaxKinds); + public override bool IsKnownAttribute(SyntaxNode attribute, KnownType knownAttribute, SemanticModel semanticModel) => + AttributeSyntaxExtensions.IsKnownType(Cast(attribute), knownAttribute, semanticModel); + public override IEnumerable ArgumentExpressions(SyntaxNode node) => node switch { diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs index f762fbca76f..e9f54a448ec 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs @@ -30,7 +30,5 @@ public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExis ? formatString : null; - protected override string GetAttributeName(AttributeSyntax attribute) => attribute.Name.ToString(); - protected override bool IsValidMemberName(string memberName) => SyntaxFacts.IsValidIdentifier(memberName); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs index 06ae6f6b6aa..d3a4893129b 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs @@ -53,6 +53,12 @@ public class DebuggerDisplayUsesExistingMembersTest .WithOptions(ParseOptionsHelper.FromCSharp10) .Verify(); + [TestMethod] + public void DebuggerDisplayUsesExistingMembers_CSharp11() => + builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp11.cs") + .WithOptions(ParseOptionsHelper.FromCSharp11) + .Verify(); + #endif [TestMethod] diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs new file mode 100644 index 00000000000..e436cdaff64 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; + +class SupportRawStringLiterals +{ + int SomeProperty => 1; + int SomeField = 2; + + [DebuggerDisplay("""{SomeProperty}""")] int ExistingMemberTripleQuotes => 1; + [DebuggerDisplay(""""{SomeField}"""")] int ExistingMemberQuadrupleQuotes => 1; + [DebuggerDisplay(""" + Some text{SomeField} + """)] int ExistingMultiLine => 1; + [DebuggerDisplay($$""""" + Some text{SomeField} + """"")] int ExistingMultiLineInterpolated => 1; + + [DebuggerDisplay("""{NonExisting}""")] int NonExistingTripleQuotes => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + // ^^^^^^^^^^^^^^^^^^^ + [DebuggerDisplay(""""{NonExisting}"""")] int NonExistingQuadrupleQuotes => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + // ^^^^^^^^^^^^^^^^^^^^^ + [DebuggerDisplay(""" + Some text{NonExisting} + """)] int NonExistingMultiLine1 => 1; // Noncompliant@-2^22#48 {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay(""" + Some text{Some + Property} + """)] int NonExistingMultiLine2 => 1; // FN: the new line char make the expression within braces not a valid identifier + [DebuggerDisplay($$""""" + Some text{NonExisting} + """"")] int NonExistingMultiLineInterpolated => 1; // FN: interpolated raw string literals strings not supported +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs index 9b1e720f0ad..c3f172420a3 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -23,6 +23,9 @@ class TestOnPropertiesAndFields [DebuggerDisplay("{SomeProperty}")] int WithExistingProperty => 1; [DebuggerDisplay("{SomeField}")] int WithExistingField => 1; [DebuggerDisplay(@"{SomeField}")] int WithExistingFieldVerbatim => 1; + [DebuggerDisplay(@"Some text + {SomeField}")] int WithExistingFieldVerbatimMultiLine => 1; + [DebuggerDisplay("{1 + 1}")] int WithNoMemberReferenced1 => 1; [DebuggerDisplay(@"{""1"" + ""1""}")] int WithNoMemberReferenced2 => 1; @@ -33,13 +36,19 @@ class TestOnPropertiesAndFields [DebuggerDisplay("{NonExisting1} bla bla {NonExisting2}")] int WithMultipleNonExisting => 1; // Noncompliant {{'NonExisting1' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [DebuggerDisplay(@"{NonExisting}")] int WithNonExistingMemberVerbatim => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay(@"Some text + {NonExisting}")] int WithNonExistingMemberVerbatimMultiLine1 => 1; // Noncompliant@-1^22#34 {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay(@"Some text {Some + Property}")] int WithNonExistingMemberVerbatimMultiLine2 => 1; // FN@-1: the new line char make the expression within braces not a valid identifier [DebuggerDisplay(ConstantWithInvalidMember)] int WithFormatAsConstant2 => 1; // FN: constants are not checked [DebuggerDisplay("{Non" + "Existing}")] int WithFormatAsConcatenationOfLiterals => 1; // FN: only simple literal supported + [DebuggerDisplay("{Non" + + "Existing}")] int WithFormatAsConcatenationOfLiteralsMultiLine => 1; // FN: only simple literal supported [DebuggerDisplay(ConstantFragment1 + ConstantFragment2)] int WithFormatAsConcatenationOfConstants => 1; // FN: only simple literal supported - [DebuggerDisplay("{this.NonExistingProperty}")] int PropertyWithExplicitThis => 1; // FN: "this." not supported - [DebuggerDisplay("{this.NonExistingField}")] int FieldWithExplicitThis => 1; // FN: "this." not supported + [DebuggerDisplay("{this.NonExistingProperty}")] int PropertyWithExplicitThis => 1; // FN: "this." not supported (valid when debugging a C# project) + [DebuggerDisplay("{Me.NonExistingField}")] int FieldWithExplicitThis => 1; // FN: "Me." not supported (valid when debugging a VB.NET project) [DebuggerDisplay("{1 + NonExistingProperty}")] int ContainingInvalidMembers => 1; // FN: expressions not supported } @@ -233,6 +242,6 @@ namespace WithTypeAlias public class Test { - [DebuggerDisplayAlias("{NonExisting}")] int WithAlias => 1; // FN: attribute name checked at syntax level + [DebuggerDisplayAlias("{NonExisting}")] int WithAlias => 1; // Noncompliant: attribute name also checked at semantic level } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb index 6c401193c12..f124a6f8bda 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -37,8 +37,8 @@ Public Class TestOnPropertiesAndFields Property WithFormatAsConcatenationOfLiterals As Integer ' FN: only simple literal supported Property WithFormatAsConcatenationOfConstants As Integer ' FN: only simple literal supported - Property PropertyWithExplicitThis As Integer ' FN: "this." not supported - Property FieldWithExplicitThis As Integer ' FN: "this." not supported + Property PropertyWithExplicitThis As Integer ' FN: "Me." not supported (valid when debugging a VB.NET project) + Property FieldWithExplicitThis As Integer ' FN: "this." not supported (valid when debugging a C# project) Property ContainingInvalidMembers As Integer ' FN: expressions not supported End Class From fc8199f0e4989e13ceaf5dfa5d7972a0f4cc86bf Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Thu, 9 Feb 2023 14:09:30 +0100 Subject: [PATCH 08/19] Fix UT --- .../TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs index e436cdaff64..b08b5444179 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs @@ -20,7 +20,7 @@ class SupportRawStringLiterals // ^^^^^^^^^^^^^^^^^^^^^ [DebuggerDisplay(""" Some text{NonExisting} - """)] int NonExistingMultiLine1 => 1; // Noncompliant@-2^22#48 {{'NonExisting' doesn't exist in this context.}} + """)] int NonExistingMultiLine1 => 1; // Noncompliant@-2^22#46 {{'NonExisting' doesn't exist in this context.}} [DebuggerDisplay(""" Some text{Some Property} From e728f0933e87d723608cef945b39868f31a48b8d Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Thu, 9 Feb 2023 14:16:18 +0100 Subject: [PATCH 09/19] Fix wording and new line issue in UT --- ...ggerDisplayUsesExistingMembers.CSharp11.cs | 14 ++-- .../DebuggerDisplayUsesExistingMembers.cs | 66 +++++++++---------- .../DebuggerDisplayUsesExistingMembers.vb | 52 +++++++-------- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs index b08b5444179..e5a5f89fa50 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs @@ -14,18 +14,18 @@ class SupportRawStringLiterals Some text{SomeField} """"")] int ExistingMultiLineInterpolated => 1; - [DebuggerDisplay("""{NonExisting}""")] int NonExistingTripleQuotes => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("""{Nonexistent}""")] int NonexistentTripleQuotes => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^^^^^ - [DebuggerDisplay(""""{NonExisting}"""")] int NonExistingQuadrupleQuotes => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay(""""{Nonexistent}"""")] int NonexistentQuadrupleQuotes => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^^^^^^^ [DebuggerDisplay(""" - Some text{NonExisting} - """)] int NonExistingMultiLine1 => 1; // Noncompliant@-2^22#46 {{'NonExisting' doesn't exist in this context.}} + Some text{Nonexistent} + """)] int NonexistentMultiLine1 => 1; // Noncompliant@-2^22#46 {{'Nonexistent' doesn't exist in this context.}} [DebuggerDisplay(""" Some text{Some Property} - """)] int NonExistingMultiLine2 => 1; // FN: the new line char make the expression within braces not a valid identifier + """)] int NonexistentMultiLine2 => 1; // FN: the new line char make the expression within braces not a valid identifier [DebuggerDisplay($$""""" - Some text{NonExisting} - """"")] int NonExistingMultiLineInterpolated => 1; // FN: interpolated raw string literals strings not supported + Some text{Nonexistent} + """"")] int NonexistentMultiLineInterpolated => 1; // FN: interpolated raw string literals strings not supported } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs index c3f172420a3..119d63c14d3 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -4,9 +4,9 @@ class TestOnPropertiesAndFields { const string ConstantWithoutInvalidMembers = "1"; - const string ConstantWithInvalidMember = "{NonExisting}"; + const string ConstantWithInvalidMember = "{Nonexistent}"; const string ConstantFragment1 = "{Non"; - const string ConstantFragment2 = "Existing}"; + const string ConstantFragment2 = "existent}"; int SomeProperty => 1; int SomeField = 2; @@ -29,17 +29,17 @@ class TestOnPropertiesAndFields [DebuggerDisplay("{1 + 1}")] int WithNoMemberReferenced1 => 1; [DebuggerDisplay(@"{""1"" + ""1""}")] int WithNoMemberReferenced2 => 1; - [DebuggerDisplay("{NonExisting}")] int WithNonExistingMember1 => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent}")] int WithNonexistentMember1 => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^ - [DebuggerDisplay("1 + {NonExisting}")] int WithNonExistingMember2 => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("1 + {Nonexistent}")] int WithNonexistentMember2 => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^^^^^ - [DebuggerDisplay("{NonExisting1} bla bla {NonExisting2}")] int WithMultipleNonExisting => 1; // Noncompliant {{'NonExisting1' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent1} bla bla {Nonexistent2}")] int WithMultipleNonexistent => 1; // Noncompliant {{'Nonexistent1' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - [DebuggerDisplay(@"{NonExisting}")] int WithNonExistingMemberVerbatim => 1; // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay(@"{Nonexistent}")] int WithNonexistentMemberVerbatim => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}} [DebuggerDisplay(@"Some text - {NonExisting}")] int WithNonExistingMemberVerbatimMultiLine1 => 1; // Noncompliant@-1^22#34 {{'NonExisting' doesn't exist in this context.}} + {Nonexistent}")] int WithNonexistentMemberVerbatimMultiLine1 => 1; // Noncompliant@-1^22#34 {{'Nonexistent' doesn't exist in this context.}} [DebuggerDisplay(@"Some text {Some - Property}")] int WithNonExistingMemberVerbatimMultiLine2 => 1; // FN@-1: the new line char make the expression within braces not a valid identifier + Property}")] int WithNonexistentMemberVerbatimMultiLine2 => 1; // FN@-1: the new line char make the expression within braces not a valid identifier [DebuggerDisplay(ConstantWithInvalidMember)] int WithFormatAsConstant2 => 1; // FN: constants are not checked [DebuggerDisplay("{Non" + "Existing}")] int WithFormatAsConcatenationOfLiterals => 1; // FN: only simple literal supported @@ -47,18 +47,18 @@ class TestOnPropertiesAndFields + "Existing}")] int WithFormatAsConcatenationOfLiteralsMultiLine => 1; // FN: only simple literal supported [DebuggerDisplay(ConstantFragment1 + ConstantFragment2)] int WithFormatAsConcatenationOfConstants => 1; // FN: only simple literal supported - [DebuggerDisplay("{this.NonExistingProperty}")] int PropertyWithExplicitThis => 1; // FN: "this." not supported (valid when debugging a C# project) - [DebuggerDisplay("{Me.NonExistingField}")] int FieldWithExplicitThis => 1; // FN: "Me." not supported (valid when debugging a VB.NET project) - [DebuggerDisplay("{1 + NonExistingProperty}")] int ContainingInvalidMembers => 1; // FN: expressions not supported + [DebuggerDisplay("{this.NonexistentProperty}")] int PropertyWithExplicitThis => 1; // FN: "this." not supported (valid when debugging a C# project) + [DebuggerDisplay("{Me.NonexistentField}")] int FieldWithExplicitThis => 1; // FN: "Me." not supported (valid when debugging a VB.NET project) + [DebuggerDisplay("{1 + NonexistentProperty}")] int ContainingInvalidMembers => 1; // FN: expressions not supported } [DebuggerDisplay("{this.ToString()}")] -[DebuggerDisplay("{NonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} +[DebuggerDisplay("{Nonexistent}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} public enum TopLevelEnum { One, Two, Three } [DebuggerDisplay("{SomeProperty}")] [DebuggerDisplay("{SomeField}")] -[DebuggerDisplay("{NonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} +[DebuggerDisplay("{Nonexistent}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} public class TestOnNestedTypes { int SomeProperty => 1; @@ -103,27 +103,27 @@ public class TestOnIndexers [DebuggerDisplay("{ExistingProperty}")] [DebuggerDisplay("{ExistingField}")] - [DebuggerDisplay("{NonExisting}")] // Noncompliant + [DebuggerDisplay("{Nonexistent}")] // Noncompliant int this[int i] => 1; } -[DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{NonExisting}")] // Noncompliant +[DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{Nonexistent}")] // Noncompliant // ^^^^^^^^^^^^^^^ public class TestMultipleAttributes { int SomeProperty => 1; int SomeField = 1; - [DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{NonExisting}")] // Noncompliant + [DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{Nonexistent}")] // Noncompliant // ^^^^^^^^^^^^^^^ int OtherProperty1 => 1; - [DebuggerDisplay("{NonExisting1}"), DebuggerDisplay("{NonExisting2}")] + [DebuggerDisplay("{Nonexistent1}"), DebuggerDisplay("{Nonexistent2}")] // ^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^@-1 int OtherProperty2 => 1; - [DebuggerDisplay("{NonExisting1}")][DebuggerDisplay("{NonExisting2}")] + [DebuggerDisplay("{Nonexistent1}")][DebuggerDisplay("{Nonexistent2}")] // ^^^^^^^^^^^^^^^^ // ^^^^^^^^^^^^^^^^@-1 int OtherProperty3 => 1; @@ -156,10 +156,10 @@ public class SupportWhitespaces [DebuggerDisplay("{SomeProperty }")] [DebuggerDisplay("{\tSomeProperty}")] [DebuggerDisplay("{\tSomeProperty\t}")] - [DebuggerDisplay("{ NonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} - [DebuggerDisplay("{NonExisting }")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} - [DebuggerDisplay("{\tNonExisting}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} - [DebuggerDisplay("{\tNonExisting\t}")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("{ Nonexistent}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent }")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{\tNonexistent}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{\tNonexistent\t}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} int SomeProperty => 1; } @@ -169,21 +169,21 @@ public class SupportNq [DebuggerDisplay("{SomeProperty ,nq}")] [DebuggerDisplay("{SomeProperty, nq}")] [DebuggerDisplay("{SomeProperty,nq }")] - [DebuggerDisplay("{NonExisting,nq}")] // Noncompliant - [DebuggerDisplay("{NonExisting ,nq}")] // Noncompliant - [DebuggerDisplay("{NonExisting, nq}")] // Noncompliant - [DebuggerDisplay("{NonExisting,nq }")] // Noncompliant + [DebuggerDisplay("{Nonexistent,nq}")] // Noncompliant + [DebuggerDisplay("{Nonexistent ,nq}")] // Noncompliant + [DebuggerDisplay("{Nonexistent, nq}")] // Noncompliant + [DebuggerDisplay("{Nonexistent,nq }")] // Noncompliant int SomeProperty => 1; } public class SupportOptionalAttributeParameter { [DebuggerDisplay("{SomeProperty}", Name = "Any name")] - [DebuggerDisplay("{NonExisting}", Name = "Any name")] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent}", Name = "Any name")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^ - [DebuggerDisplay("{NonExisting}", Name = "Any name", Type = nameof(SupportOptionalAttributeParameter))] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent}", Name = "Any name", Type = nameof(SupportOptionalAttributeParameter))] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^ - [DebuggerDisplay("{NonExisting}", Name = "Any name", Target = typeof(SupportOptionalAttributeParameter))] // Noncompliant {{'NonExisting' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent}", Name = "Any name", Target = typeof(SupportOptionalAttributeParameter))] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^ int SomeProperty => 1; } @@ -230,9 +230,9 @@ public class SubClass : BaseClass public class SupportAttributeTargets { - [assembly: DebuggerDisplay("{NonExisting}")] // Noncompliant, attribute at assembly level, referencing a non-existing member - [field: DebuggerDisplay("{NonExisting}")] // Noncompliant, attribute ignored, still referencing a non-existing member - [property: DebuggerDisplay("{NonExisting}")] // Noncompliant, attribute taken into account + [assembly: DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute at assembly level, referencing a non-existing member + [field: DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute ignored, still referencing a non-existing member + [property: DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute taken into account int SomeProperty => 1; } @@ -242,6 +242,6 @@ namespace WithTypeAlias public class Test { - [DebuggerDisplayAlias("{NonExisting}")] int WithAlias => 1; // Noncompliant: attribute name also checked at semantic level + [DebuggerDisplayAlias("{Nonexistent}")] int WithAlias => 1; // Noncompliant: attribute name also checked at semantic level } } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb index f124a6f8bda..3aa47302806 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -3,7 +3,7 @@ Imports System.Diagnostics Public Class TestOnPropertiesAndFields Const ConstantWithoutInvalidMembers As String = "1" - Const ConstantWithInvalidMember As String = "{NonExisting}" + Const ConstantWithInvalidMember As String = "{Nonexistent}" Const ConstantFragment1 As String = "{Non" Const ConstantFragment2 As String = "Existing}" @@ -24,26 +24,26 @@ Public Class TestOnPropertiesAndFields Property WithNoMemberReferenced1 As Integer Property WithNoMemberReferenced2 As Integer - Property WithNonExistingMember1 As Integer ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + Property WithNonexistentMember1 As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^ - Property WithNonExistingMember2 As Integer ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + Property WithNonexistentMember2 As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^^^^^ - Property WithMultipleNonExisting As Integer ' Noncompliant {{'NonExisting1' doesn't exist in this context.}} + Property WithMultipleNonexistent As Integer ' Noncompliant {{'Nonexistent1' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Property WithNonExistingMemberVerbatim As Integer ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + Property WithNonexistentMemberVerbatim As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^ Property WithFormatAsConstant2 As Integer ' FN: constants are not checked Property WithFormatAsConcatenationOfLiterals As Integer ' FN: only simple literal supported Property WithFormatAsConcatenationOfConstants As Integer ' FN: only simple literal supported - Property PropertyWithExplicitThis As Integer ' FN: "Me." not supported (valid when debugging a VB.NET project) - Property FieldWithExplicitThis As Integer ' FN: "this." not supported (valid when debugging a C# project) - Property ContainingInvalidMembers As Integer ' FN: expressions not supported + Property PropertyWithExplicitThis As Integer ' FN: "Me." not supported (valid when debugging a VB.NET project) + Property FieldWithExplicitThis As Integer ' FN: "this." not supported (valid when debugging a C# project) + Property ContainingInvalidMembers As Integer ' FN: expressions not supported End Class - ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} Public Enum TopLevelEnum One Two @@ -52,7 +52,7 @@ End Enum - ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} Public Class TestOnNestedTypes Property SomeProperty As Integer Public SomeField As Integer @@ -96,7 +96,7 @@ Class TestOnIndexers - ' Noncompliant + ' Noncompliant Default Property Item(ByVal i As Integer) As Integer Get Return 1 @@ -106,21 +106,21 @@ Class TestOnIndexers End Property End Class - ' Noncompliant + ' Noncompliant Class TestMultipleAttributes ' ^^^^^^^^^^^^^^^@-1 Property SomeProperty As Integer Public SomeField As Integer = 1 - Property OtherProperty1 As Integer ' Noncompliant + Property OtherProperty1 As Integer ' Noncompliant ' ^^^^^^^^^^^^^^^ - Property OtherProperty2 As Integer + Property OtherProperty2 As Integer ' ^^^^^^^^^^^^^^^^ ' ^^^^^^^^^^^^^^^^@-1 - Property OtherProperty3 As Integer + Property OtherProperty3 As Integer ' ^^^^^^^^^^^^^^^^ ' ^^^^^^^^^^^^^^^^@-1 End Class @@ -148,10 +148,10 @@ Class SupportWhitespaces - ' Noncompliant {{'NonExisting' doesn't exist in this context.}} - ' Noncompliant {{'NonExisting' doesn't exist in this context.}} - ' FN: string concatenation not supported - ' FN: string concatenation not supported + ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' FN: string concatenation not supported + ' FN: string concatenation not supported Property SomeProperty As Integer End Class @@ -160,18 +160,18 @@ Class SupportNq - ' Noncompliant - ' Noncompliant - ' Noncompliant - ' Noncompliant + ' Noncompliant + ' Noncompliant + ' Noncompliant + ' Noncompliant Property SomeProperty As Integer End Class Class SupportOptionalAttributeParameter - ' Noncompliant {{'NonExisting' doesn't exist in this context.}} - ' Noncompliant {{'NonExisting' doesn't exist in this context.}} - ' Noncompliant {{'NonExisting' doesn't exist in this context.}} + ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} Property SomeProperty As Integer ' ^^^^^^^^^^^^^^^@-3 ' ^^^^^^^^^^^^^^^@-3 From 555ae947d600c305b18191f23bdfa6e2c915ad34 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Thu, 9 Feb 2023 16:14:21 +0100 Subject: [PATCH 10/19] Review 2 --- .../DebuggerDisplayUsesExistingMembersTest.cs | 4 --- ...ggerDisplayUsesExistingMembers.CSharp11.cs | 6 ++-- .../DebuggerDisplayUsesExistingMembers.cs | 34 +++++++++++-------- .../DebuggerDisplayUsesExistingMembers.vb | 18 +++++----- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs index d3a4893129b..7584af87d60 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs @@ -33,8 +33,6 @@ public class DebuggerDisplayUsesExistingMembersTest public void DebuggerDisplayUsesExistingMembers_CS() => builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.cs").Verify(); -#if NET - [TestMethod] public void DebuggerDisplayUsesExistingMembers_CSharp8() => builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp8.cs") @@ -59,8 +57,6 @@ public class DebuggerDisplayUsesExistingMembersTest .WithOptions(ParseOptionsHelper.FromCSharp11) .Verify(); -#endif - [TestMethod] public void DebuggerDisplayUsesExistingMembers_VB() => builderVB.AddPaths("DebuggerDisplayUsesExistingMembers.vb").Verify(); diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs index e5a5f89fa50..ad6928a12b6 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs @@ -14,13 +14,13 @@ class SupportRawStringLiterals Some text{SomeField} """"")] int ExistingMultiLineInterpolated => 1; - [DebuggerDisplay("""{Nonexistent}""")] int NonexistentTripleQuotes => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("""{Nonexistent}""")] int NonexistentTripleQuotes => 1; // Noncompliant // ^^^^^^^^^^^^^^^^^^^ - [DebuggerDisplay(""""{Nonexistent}"""")] int NonexistentQuadrupleQuotes => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay(""""{Nonexistent}"""")] int NonexistentQuadrupleQuotes => 1; // Noncompliant // ^^^^^^^^^^^^^^^^^^^^^ [DebuggerDisplay(""" Some text{Nonexistent} - """)] int NonexistentMultiLine1 => 1; // Noncompliant@-2^22#46 {{'Nonexistent' doesn't exist in this context.}} + """)] int NonexistentMultiLine1 => 1; // Noncompliant@-2^22#46 [DebuggerDisplay(""" Some text{Some Property} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs index 119d63c14d3..4e6198a296d 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -53,12 +53,12 @@ class TestOnPropertiesAndFields } [DebuggerDisplay("{this.ToString()}")] -[DebuggerDisplay("{Nonexistent}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} +[DebuggerDisplay("{Nonexistent}")] // Noncompliant public enum TopLevelEnum { One, Two, Three } [DebuggerDisplay("{SomeProperty}")] [DebuggerDisplay("{SomeField}")] -[DebuggerDisplay("{Nonexistent}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} +[DebuggerDisplay("{Nonexistent}")] // Noncompliant public class TestOnNestedTypes { int SomeProperty => 1; @@ -66,8 +66,8 @@ public class TestOnNestedTypes [DebuggerDisplay("{ExistingProperty}")] [DebuggerDisplay("{ExistingField}")] - [DebuggerDisplay("{SomeProperty}")] // Noncompliant {{'SomeProperty' doesn't exist in this context.}} - [DebuggerDisplay("{SomeField}")] // Noncompliant {{'SomeField' doesn't exist in this context.}} + [DebuggerDisplay("{SomeProperty}")] // Noncompliant + [DebuggerDisplay("{SomeField}")] // Noncompliant public class NestedClass { int ExistingProperty => 1; @@ -76,8 +76,8 @@ public class NestedClass [DebuggerDisplay("{ExistingProperty}")] [DebuggerDisplay("{ExistingField}")] - [DebuggerDisplay("{SomeProperty}")] // Noncompliant {{'SomeProperty' doesn't exist in this context.}} - [DebuggerDisplay("{SomeField}")] // Noncompliant {{'SomeField' doesn't exist in this context.}} + [DebuggerDisplay("{SomeProperty}")] // Noncompliant + [DebuggerDisplay("{SomeField}")] // Noncompliant public struct NestedStruct { int ExistingProperty => 1; @@ -136,8 +136,8 @@ public class SupportCaseSensitivity [DebuggerDisplay("{SOMEPROPERTY}")] [DebuggerDisplay("{SomeProperty}")] - [DebuggerDisplay("{someProperty}")] // Noncompliant {{'someProperty' doesn't exist in this context.}} - [DebuggerDisplay("{someproperty}")] // Noncompliant {{'someproperty' doesn't exist in this context.}} + [DebuggerDisplay("{someProperty}")] // Noncompliant + [DebuggerDisplay("{someproperty}")] // Noncompliant int OtherProperty => 1; } @@ -169,10 +169,10 @@ public class SupportNq [DebuggerDisplay("{SomeProperty ,nq}")] [DebuggerDisplay("{SomeProperty, nq}")] [DebuggerDisplay("{SomeProperty,nq }")] - [DebuggerDisplay("{Nonexistent,nq}")] // Noncompliant - [DebuggerDisplay("{Nonexistent ,nq}")] // Noncompliant - [DebuggerDisplay("{Nonexistent, nq}")] // Noncompliant - [DebuggerDisplay("{Nonexistent,nq }")] // Noncompliant + [DebuggerDisplay("{Nonexistent,nq}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent ,nq}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent, nq}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent,nq }")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} int SomeProperty => 1; } @@ -181,9 +181,9 @@ public class SupportOptionalAttributeParameter [DebuggerDisplay("{SomeProperty}", Name = "Any name")] [DebuggerDisplay("{Nonexistent}", Name = "Any name")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^ - [DebuggerDisplay("{Nonexistent}", Name = "Any name", Type = nameof(SupportOptionalAttributeParameter))] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent}", Name = "Any name", Type = nameof(SupportOptionalAttributeParameter))] // Noncompliant // ^^^^^^^^^^^^^^^ - [DebuggerDisplay("{Nonexistent}", Name = "Any name", Target = typeof(SupportOptionalAttributeParameter))] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent}", Name = "Any name", Target = typeof(SupportOptionalAttributeParameter))] // Noncompliant // ^^^^^^^^^^^^^^^ int SomeProperty => 1; } @@ -236,6 +236,12 @@ public class SupportAttributeTargets int SomeProperty => 1; } +public class InvalidAttributes +{ + [DebuggerDisplay] int NoArgs => 1; // Error [CS7036] + [DebuggerDisplay(Type = nameof(InvalidAttributes))] int MissingValue => 1; // Error [CS0029] +} + namespace WithTypeAlias { using DebuggerDisplayAlias = System.Diagnostics.DebuggerDisplayAttribute; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb index 3aa47302806..30e81820995 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -43,7 +43,7 @@ Public Class TestOnPropertiesAndFields End Class - ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' Noncompliant Public Enum TopLevelEnum One Two @@ -52,15 +52,15 @@ End Enum - ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' Noncompliant Public Class TestOnNestedTypes Property SomeProperty As Integer Public SomeField As Integer - ' Noncompliant {{'SomeProperty' doesn't exist in this context.}} - ' Noncompliant {{'SomeField' doesn't exist in this context.}} + ' Noncompliant + ' Noncompliant Public Class NestedClass Property ExistingProperty As Integer Property ExistingField As Integer @@ -68,8 +68,8 @@ Public Class TestOnNestedTypes - ' Noncompliant {{'SomeProperty' doesn't exist in this context.}} - ' Noncompliant {{'SomeField' doesn't exist in this context.}} + ' Noncompliant + ' Noncompliant Public Structure NestedStruct Property ExistingProperty As Integer Property ExistingField As Integer @@ -169,9 +169,9 @@ End Class Class SupportOptionalAttributeParameter - ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} - ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} - ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' Noncompliant + ' Noncompliant + ' Noncompliant Property SomeProperty As Integer ' ^^^^^^^^^^^^^^^@-3 ' ^^^^^^^^^^^^^^^@-3 From d52b7e82f79ff1e3e1704d0e84abcccfbeb045ab Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Thu, 9 Feb 2023 16:41:28 +0100 Subject: [PATCH 11/19] C#9 and C#10 tests only executed in .NET Core --- .../Rules/DebuggerDisplayUsesExistingMembersTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs index 7584af87d60..64af4997c22 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs @@ -39,6 +39,8 @@ public class DebuggerDisplayUsesExistingMembersTest .WithOptions(ParseOptionsHelper.FromCSharp8) .Verify(); +#if NET + [TestMethod] public void DebuggerDisplayUsesExistingMembers_CSharp9() => builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp9.cs") @@ -50,6 +52,7 @@ public class DebuggerDisplayUsesExistingMembersTest builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp10.cs") .WithOptions(ParseOptionsHelper.FromCSharp10) .Verify(); +#endif [TestMethod] public void DebuggerDisplayUsesExistingMembers_CSharp11() => From 1feed5beb504992aaa365b1bdaac87f6216090ff Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Thu, 9 Feb 2023 16:42:11 +0100 Subject: [PATCH 12/19] Missing empty line --- .../Rules/DebuggerDisplayUsesExistingMembersTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs index 64af4997c22..c9386cc0718 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs @@ -52,6 +52,7 @@ public class DebuggerDisplayUsesExistingMembersTest builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp10.cs") .WithOptions(ParseOptionsHelper.FromCSharp10) .Verify(); + #endif [TestMethod] From bd31885e6b653338be111727dc0616fdae0537a4 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Mon, 13 Feb 2023 17:26:35 +0100 Subject: [PATCH 13/19] Review 3 --- .../Facade/CSharpSyntaxFacade.cs | 4 +- .../DebuggerDisplayUsesExistingMembers.cs | 5 +- .../Facade/SyntaxFacade.cs | 2 +- .../DebuggerDisplayUsesExistingMembersBase.cs | 48 ++++++++------ .../Facade/VisualBasicSyntaxFacade.cs | 4 +- .../DebuggerDisplayUsesExistingMembers.cs | 5 +- .../DebuggerDisplayUsesExistingMembersTest.cs | 6 -- ...ggerDisplayUsesExistingMembers.CSharp10.cs | 16 ++++- ...ggerDisplayUsesExistingMembers.CSharp11.cs | 2 +- ...uggerDisplayUsesExistingMembers.CSharp8.cs | 2 +- ...uggerDisplayUsesExistingMembers.CSharp9.cs | 9 --- .../DebuggerDisplayUsesExistingMembers.cs | 64 +++++++++++-------- .../DebuggerDisplayUsesExistingMembers.vb | 32 ++++++---- 13 files changed, 116 insertions(+), 83 deletions(-) delete mode 100644 analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs index 0659799b0ae..21f0560d744 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs @@ -50,8 +50,8 @@ internal sealed class CSharpSyntaxFacade : SyntaxFacade public override bool IsNullLiteral(SyntaxNode node) => node.IsNullLiteral(); - public override bool IsKnownAttribute(SyntaxNode attribute, KnownType knownAttribute, SemanticModel semanticModel) => - AttributeSyntaxExtensions.IsKnownType(Cast(attribute), knownAttribute, semanticModel); + public override bool IsKnownAttributeType(SyntaxNode attribute, KnownType knownType, SemanticModel model) => + AttributeSyntaxExtensions.IsKnownType(Cast(attribute), knownType, model); public override IEnumerable ArgumentExpressions(SyntaxNode node) => node switch diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs index d77039da715..15455c23004 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs @@ -25,10 +25,11 @@ public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExis { protected override ILanguageFacade Language => CSharpFacade.Instance; - protected override SyntaxNode GetAttributeFormatString(AttributeSyntax attribute) => + protected override SyntaxNode AttributeFormatString(AttributeSyntax attribute) => attribute.ArgumentList.Arguments.FirstOrDefault() is { Expression: LiteralExpressionSyntax { RawKind: (int)SyntaxKind.StringLiteralExpression } formatString } ? formatString : null; - protected override bool IsValidMemberName(string memberName) => SyntaxFacts.IsValidIdentifier(memberName); + protected override bool IsValidMemberName(string memberName) => + SyntaxFacts.IsValidIdentifier(memberName); } diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs index 73c2d835a28..f08ceed8ff4 100644 --- a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs @@ -32,7 +32,7 @@ public abstract class SyntaxFacade public abstract bool IsAnyKind(SyntaxNode node, ISet syntaxKinds); public abstract bool IsAnyKind(SyntaxNode node, params TSyntaxKind[] syntaxKinds); public abstract bool IsAnyKind(SyntaxTrivia trivia, params TSyntaxKind[] syntaxKinds); - public abstract bool IsKnownAttribute(SyntaxNode attribute, KnownType knownAttribute, SemanticModel semanticModel); + public abstract bool IsKnownAttributeType(SyntaxNode attribute, KnownType knownType, SemanticModel model); public abstract IEnumerable ArgumentExpressions(SyntaxNode node); public abstract ImmutableArray AssignmentTargets(SyntaxNode assignment); public abstract SyntaxNode AssignmentLeft(SyntaxNode assignment); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs index 89bca7652d0..1453d0d5150 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs @@ -19,6 +19,7 @@ */ using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; namespace SonarAnalyzer.Rules; @@ -28,10 +29,9 @@ public abstract class DebuggerDisplayUsesExistingMembersBase[^\}]+)\}", RegexOptions.Multiline, RegexConstants.DefaultTimeout); + private readonly Regex evaluatedExpressionRegex = new(@"\{(?[^}]+)\}", RegexOptions.Multiline, RegexConstants.DefaultTimeout); - protected abstract SyntaxNode GetAttributeFormatString(TAttributeSyntax attribute); + protected abstract SyntaxNode AttributeFormatString(TAttributeSyntax attribute); protected abstract bool IsValidMemberName(string memberName); protected override string MessageFormat => "'{0}' doesn't exist in this context."; @@ -43,9 +43,10 @@ public abstract class DebuggerDisplayUsesExistingMembersBase { var attribute = (TAttributeSyntax)c.Node; - if (Language.Syntax.IsKnownAttribute(attribute, KnownType.System_Diagnostics_DebuggerDisplayAttribute, c.SemanticModel) - && GetAttributeFormatString(attribute) is { } formatString - && FirstInvalidMemberName(c, formatString.GetFirstToken().ValueText, attribute) is { } firstInvalidMember) + if (Language.Syntax.IsKnownAttributeType(attribute, KnownType.System_Diagnostics_DebuggerDisplayAttribute, c.SemanticModel) + && AttributeFormatString(attribute) is { } formatString + && Language.Syntax.StringValue(formatString, c.SemanticModel) is { } formatStringText + && FirstInvalidMemberName(c, formatStringText, attribute) is { } firstInvalidMember) { c.ReportIssue(Diagnostic.Create(Rule, formatString.GetLocation(), firstInvalidMember)); } @@ -56,14 +57,30 @@ private string FirstInvalidMemberName(SonarSyntaxNodeReportingContext context, s { try { + return (attributeSyntax.Parent?.Parent is { } targetSyntax + && context.SemanticModel.GetDeclaredSymbol(targetSyntax) is { } targetSymbol + && TypeContainingReferencedMembers(targetSymbol) is { } typeSymbol) + ? FirstInvalidMemberName(typeSymbol) + : null; + } + catch (RegexMatchTimeoutException) + { + return null; + } + + string FirstInvalidMemberName(ITypeSymbol typeSymbol) + { + var allMembers = typeSymbol + .GetSelfAndBaseTypes() + .SelectMany(x => x.GetMembers()) + .Select(x => x.Name) + .ToHashSet(Language.NameComparer); + foreach (Match match in evaluatedExpressionRegex.Matches(formatString)) { if (match.Groups["EvaluatedExpression"] is { Success: true, Value: var evaluatedExpression } && ExtractValidMemberName(evaluatedExpression) is { } memberName - && GetAttributeTarget(attributeSyntax) is { } targetSyntax - && context.SemanticModel.GetDeclaredSymbol(targetSyntax) is { } targetSymbol - && GetTypeContainingReferencedMembers(targetSymbol) is { } typeSymbol - && typeSymbol.GetSelfAndBaseTypes().SelectMany(x => x.GetMembers()).All(x => Language.NameComparer.Compare(x.Name, memberName) != 0)) + && !allMembers.Contains(memberName)) { return memberName; } @@ -71,19 +88,14 @@ private string FirstInvalidMemberName(SonarSyntaxNodeReportingContext context, s return null; } - catch (RegexMatchTimeoutException) - { - return null; - } string ExtractValidMemberName(string evaluatedExpression) { - var sanitizedExpression = noQuotesModifierExpressionRegex.Replace(evaluatedExpression, string.Empty).Trim(); + var sanitizedExpression = evaluatedExpression.Split(',')[0].Trim(); return IsValidMemberName(sanitizedExpression) ? sanitizedExpression : null; } - static SyntaxNode GetAttributeTarget(TAttributeSyntax attributeSyntax) => attributeSyntax.Parent?.Parent; - - static ITypeSymbol GetTypeContainingReferencedMembers(ISymbol symbol) => symbol is ITypeSymbol typeSymbol ? typeSymbol : symbol.ContainingType; + static ITypeSymbol TypeContainingReferencedMembers(ISymbol symbol) => + symbol is ITypeSymbol typeSymbol ? typeSymbol : symbol.ContainingType; } } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs index 7e01f5ccb63..66a63dfaa0e 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicSyntaxFacade.cs @@ -50,8 +50,8 @@ internal sealed class VisualBasicSyntaxFacade : SyntaxFacade public override bool IsAnyKind(SyntaxTrivia trivia, params SyntaxKind[] syntaxKinds) => trivia.IsAnyKind(syntaxKinds); - public override bool IsKnownAttribute(SyntaxNode attribute, KnownType knownAttribute, SemanticModel semanticModel) => - AttributeSyntaxExtensions.IsKnownType(Cast(attribute), knownAttribute, semanticModel); + public override bool IsKnownAttributeType(SyntaxNode attribute, KnownType knownType, SemanticModel model) => + AttributeSyntaxExtensions.IsKnownType(Cast(attribute), knownType, model); public override IEnumerable ArgumentExpressions(SyntaxNode node) => node switch diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs index e9f54a448ec..459649f2609 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs @@ -25,10 +25,11 @@ public sealed class DebuggerDisplayUsesExistingMembers : DebuggerDisplayUsesExis { protected override ILanguageFacade Language => VisualBasicFacade.Instance; - protected override SyntaxNode GetAttributeFormatString(AttributeSyntax attribute) => + protected override SyntaxNode AttributeFormatString(AttributeSyntax attribute) => attribute.ArgumentList.Arguments.FirstOrDefault() is SimpleArgumentSyntax { Expression: LiteralExpressionSyntax { RawKind: (int)SyntaxKind.StringLiteralExpression } formatString } ? formatString : null; - protected override bool IsValidMemberName(string memberName) => SyntaxFacts.IsValidIdentifier(memberName); + protected override bool IsValidMemberName(string memberName) => + SyntaxFacts.IsValidIdentifier(memberName); } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs index c9386cc0718..7b98e04ee54 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs @@ -41,12 +41,6 @@ public class DebuggerDisplayUsesExistingMembersTest #if NET - [TestMethod] - public void DebuggerDisplayUsesExistingMembers_CSharp9() => - builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp9.cs") - .WithOptions(ParseOptionsHelper.FromCSharp9) - .Verify(); - [TestMethod] public void DebuggerDisplayUsesExistingMembers_CSharp10() => builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp10.cs") diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs index c7f8bff3cfa..baa330e4c1b 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs @@ -6,6 +6,9 @@ public record SomeRecord(int RecordProperty) { [DebuggerDisplay("{RecordProperty}")] public record struct RecordStruct1(int RecordStructProperty); // Noncompliant [DebuggerDisplay("{RecordStructProperty}")] public record struct RecordStruct2(int RecordStructProperty); // Compliant, RecordStructProperty is a property + + [DebuggerDisplay("{RecordProperty}")] public record NestedRecord1(int NestedRecordProperty); // Noncompliant + [DebuggerDisplay("{NestedRecordProperty}")] public record NestedRecord2(int NestedRecordProperty); // Compliant, NestedRecordProperty is a property } [DebuggerDisplay("{RecordProperty1} bla bla {RecordProperty2}")] @@ -26,7 +29,7 @@ public class NestedClass2 } } -public class SupportConstantInterpolatedStrings +public class ConstantInterpolatedStrings { [DebuggerDisplay($"{{{nameof(SomeProperty)}}}")] [DebuggerDisplay($"{{{nameof(NotAProperty)}}}")] // FN: constant interpolated strings not supported @@ -34,3 +37,14 @@ public class SupportConstantInterpolatedStrings public class NotAProperty { } } + +public interface DefaultInterfaceImplementations +{ + [DebuggerDisplay("{OtherProperty}")] + [DebuggerDisplay("{OtherPropertyImplemented}")] + [DebuggerDisplay("{Nonexistent}")] // Noncompliant + int WithNonexistentProperty => 1; + + string OtherProperty { get; } + string OtherPropertyImplemented => "Something"; +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs index ad6928a12b6..c9e078e9019 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -class SupportRawStringLiterals +class RawStringLiterals { int SomeProperty => 1; int SomeField = 2; diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs index 70e234cca02..96ea4a0d938 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics; -public class SupportAccessModifiers +public class AccessModifiers { public class BaseClass { diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs deleted file mode 100644 index 3222dfd34a0..00000000000 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp9.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; -using System.Diagnostics; - -[DebuggerDisplay("{RecordProperty}")] -public record SomeRecord(int RecordProperty) -{ - [DebuggerDisplay("{RecordProperty}")] public record NestedRecord1(int NestedRecordProperty); // Noncompliant - [DebuggerDisplay("{NestedRecordProperty}")] public record NestedRecord2(int NestedRecordProperty); // Compliant, NestedRecordProperty is a property -} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs index 4e6198a296d..5e3c419fe8e 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -1,9 +1,9 @@ using System; using System.Diagnostics; -class TestOnPropertiesAndFields +class PropertiesAndFields { - const string ConstantWithoutInvalidMembers = "1"; + const string ConstantWithoutInvalidMembers = "Something"; const string ConstantWithInvalidMember = "{Nonexistent}"; const string ConstantFragment1 = "{Non"; const string ConstantFragment2 = "existent}"; @@ -11,9 +11,9 @@ class TestOnPropertiesAndFields int SomeProperty => 1; int SomeField = 2; - [DebuggerDisplayAttribute("1")] int WithSuffix => 1; - [System.Diagnostics.DebuggerDisplay("1")] int WithNamespace => 1; - [DebuggerDisplay(value: "1")] int WithExplicitParameterName => 1; + [DebuggerDisplayAttribute("Hardcoded text")] int WithSuffix => 1; + [System.Diagnostics.DebuggerDisplay("Hardcoded text")] int WithNamespace => 1; + [DebuggerDisplay(value: "Hardcoded text")] int WithExplicitParameterName => 1; [DebuggerDisplay(null)] int WithEmptyArgList => 1; [DebuggerDisplay("")] int WithEmptyFormat => 1; @@ -42,9 +42,9 @@ class TestOnPropertiesAndFields Property}")] int WithNonexistentMemberVerbatimMultiLine2 => 1; // FN@-1: the new line char make the expression within braces not a valid identifier [DebuggerDisplay(ConstantWithInvalidMember)] int WithFormatAsConstant2 => 1; // FN: constants are not checked - [DebuggerDisplay("{Non" + "Existing}")] int WithFormatAsConcatenationOfLiterals => 1; // FN: only simple literal supported + [DebuggerDisplay("{Non" + "Existent}")] int WithFormatAsConcatenationOfLiterals => 1; // FN: only simple literal supported [DebuggerDisplay("{Non" - + "Existing}")] int WithFormatAsConcatenationOfLiteralsMultiLine => 1; // FN: only simple literal supported + + "Existent}")] int WithFormatAsConcatenationOfLiteralsMultiLine => 1; // FN: only simple literal supported [DebuggerDisplay(ConstantFragment1 + ConstantFragment2)] int WithFormatAsConcatenationOfConstants => 1; // FN: only simple literal supported [DebuggerDisplay("{this.NonexistentProperty}")] int PropertyWithExplicitThis => 1; // FN: "this." not supported (valid when debugging a C# project) @@ -53,13 +53,14 @@ class TestOnPropertiesAndFields } [DebuggerDisplay("{this.ToString()}")] +[DebuggerDisplay("{Me.ToString()}")] [DebuggerDisplay("{Nonexistent}")] // Noncompliant public enum TopLevelEnum { One, Two, Three } [DebuggerDisplay("{SomeProperty}")] [DebuggerDisplay("{SomeField}")] [DebuggerDisplay("{Nonexistent}")] // Noncompliant -public class TestOnNestedTypes +public class NestedTypes { int SomeProperty => 1; int SomeField = 1; @@ -84,19 +85,22 @@ public struct NestedStruct int ExistingField => 1; } + [DebuggerDisplay("{42}")] + [DebuggerDisplay("{SomeProperty}")] // Noncompliant + [DebuggerDisplay("{SomeField}")] // Noncompliant public enum NestedEnum { One, Two, Three } } -public class TestOnDelegates +public class Delegates { int ExistingProperty => 1; [DebuggerDisplay("{ExistingProperty}")] // Noncompliant - [DebuggerDisplay("{1}")] + [DebuggerDisplay("{42}")] public delegate void Delegate1(); } -public class TestOnIndexers +public class Indexers { int ExistingProperty => 1; int ExistingField => 1; @@ -109,7 +113,7 @@ public class TestOnIndexers [DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{Nonexistent}")] // Noncompliant // ^^^^^^^^^^^^^^^ -public class TestMultipleAttributes +public class MultipleAttributes { int SomeProperty => 1; int SomeField = 1; @@ -129,7 +133,7 @@ public class TestMultipleAttributes int OtherProperty3 => 1; } -public class SupportCaseSensitivity +public class CaseSensitivity { int SOMEPROPERTY => 1; int SomeProperty => 1; @@ -141,7 +145,7 @@ public class SupportCaseSensitivity int OtherProperty => 1; } -public class SupportNonAlphanumericChars +public class NonAlphanumericChars { int Aa1_뿓 => 1; @@ -150,7 +154,7 @@ public class SupportNonAlphanumericChars int SomeProperty1 => 1; } -public class SupportWhitespaces +public class Whitespaces { [DebuggerDisplay("{ SomeProperty}")] [DebuggerDisplay("{SomeProperty }")] @@ -163,7 +167,7 @@ public class SupportWhitespaces int SomeProperty => 1; } -public class SupportNq +public class NoQuotesModifier { [DebuggerDisplay("{SomeProperty,nq}")] [DebuggerDisplay("{SomeProperty ,nq}")] @@ -176,19 +180,28 @@ public class SupportNq int SomeProperty => 1; } -public class SupportOptionalAttributeParameter +public class InvalidModifier +{ + [DebuggerDisplay("{SomeProperty,asdf}")] + [DebuggerDisplay("{SomeProperty, asdf}")] + [DebuggerDisplay("{Nonexistent,asdf}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent, asdf}")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + int SomeProperty => 1; +} + +public class OptionalAttributeParameter { [DebuggerDisplay("{SomeProperty}", Name = "Any name")] - [DebuggerDisplay("{Nonexistent}", Name = "Any name")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} + [DebuggerDisplay("{Nonexistent}", Name = "Any name")] // Noncompliant {{'Nonexistent' doesn't exist in this context.}} // ^^^^^^^^^^^^^^^ - [DebuggerDisplay("{Nonexistent}", Name = "Any name", Type = nameof(SupportOptionalAttributeParameter))] // Noncompliant + [DebuggerDisplay("{Nonexistent}", Name = "Any name", Type = nameof(OptionalAttributeParameter))] // Noncompliant // ^^^^^^^^^^^^^^^ - [DebuggerDisplay("{Nonexistent}", Name = "Any name", Target = typeof(SupportOptionalAttributeParameter))] // Noncompliant + [DebuggerDisplay("{Nonexistent}", Name = "Any name", Target = typeof(OptionalAttributeParameter))] // Noncompliant // ^^^^^^^^^^^^^^^ int SomeProperty => 1; } -public class SupportInheritance +public class Inheritance { public class BaseClass { @@ -202,7 +215,7 @@ public class SubClass : BaseClass } } -public class SupportAccessModifiers +public class AccessModifiers { public class BaseClass { @@ -228,10 +241,11 @@ public class SubClass : BaseClass } } -public class SupportAttributeTargets +public class AttributeTargets { - [assembly: DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute at assembly level, referencing a non-existing member - [field: DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute ignored, still referencing a non-existing member + [global:DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute at global level, referencing a non-existent member + [assembly: DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute at assembly level, referencing a non-existent member + [field: DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute ignored, still referencing a non-existent member [property: DebuggerDisplay("{Nonexistent}")] // Noncompliant, attribute taken into account int SomeProperty => 1; } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb index 30e81820995..acda269d62b 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -2,16 +2,17 @@ Imports System.Diagnostics Public Class TestOnPropertiesAndFields - Const ConstantWithoutInvalidMembers As String = "1" + Const ConstantWithoutInvalidMembers As String = "Something" Const ConstantWithInvalidMember As String = "{Nonexistent}" Const ConstantFragment1 As String = "{Non" - Const ConstantFragment2 As String = "Existing}" + Const ConstantFragment2 As String = "Existent}" - Property SomeProperty As Integer + Public Property SomeProperty As Integer Public SomeField As Integer - Property WithSuffix As Integer - Property WithNamespace As Integer + Property WithSuffix As Integer + Property WithNamespace As Integer + Property WithGlobal As Integer Property WithEmptyArgList As Integer Property WithEmptyFormat As Integer @@ -24,14 +25,18 @@ Public Class TestOnPropertiesAndFields Property WithNoMemberReferenced1 As Integer Property WithNoMemberReferenced2 As Integer - Property WithNonexistentMember1 As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + Property WithNonexistentMember1 As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^ - Property WithNonexistentMember2 As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + Property WithNonexistentMember2 As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^^^^^ - Property WithMultipleNonexistent As Integer ' Noncompliant {{'Nonexistent1' doesn't exist in this context.}} + Property WithMultipleNonexistent As Integer ' Noncompliant {{'Nonexistent1' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Property WithNonexistentMemberVerbatim As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + Property WithNonexistentMemberVerbatim As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} ' ^^^^^^^^^^^^^^^ + Property WithNamespaceAndNonexistent As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' ^^^^^^^^^^^^^^^ + Property WithGlobalAndNonexistent As Integer ' Noncompliant {{'Nonexistent' doesn't exist in this context.}} + ' ^^^^^^^^^^^^^^^ Property WithFormatAsConstant2 As Integer ' FN: constants are not checked Property WithFormatAsConcatenationOfLiterals As Integer ' FN: only simple literal supported @@ -42,6 +47,7 @@ Public Class TestOnPropertiesAndFields Property ContainingInvalidMembers As Integer ' FN: expressions not supported End Class + ' Noncompliant Public Enum TopLevelEnum @@ -54,7 +60,7 @@ End Enum ' Noncompliant Public Class TestOnNestedTypes - Property SomeProperty As Integer + Public Property SomeProperty As Integer Public SomeField As Integer @@ -86,12 +92,12 @@ Class TestOnDelegates Property ExistingProperty As Integer ' Noncompliant - + Delegate Sub Delegate1() End Class Class TestOnIndexers - Property ExistingProperty As Integer + Public Property ExistingProperty As Integer Public ExistingField As Integer @@ -110,7 +116,7 @@ End Class Class TestMultipleAttributes ' ^^^^^^^^^^^^^^^@-1 - Property SomeProperty As Integer + Public Property SomeProperty As Integer Public SomeField As Integer = 1 Property OtherProperty1 As Integer ' Noncompliant From 0da8962bedf79989052f723a46c048e26671d2df Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Tue, 14 Feb 2023 08:54:41 +0100 Subject: [PATCH 14/19] Empty commit to trigger CI From 68a1463cb3fc2f7a9acdad1c6a4e974da0424a79 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Wed, 15 Feb 2023 17:42:03 +0100 Subject: [PATCH 15/19] Update RSPEC for vbnet rule --- analyzers/rspec/vbnet/S4545_vb.net.html | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/analyzers/rspec/vbnet/S4545_vb.net.html b/analyzers/rspec/vbnet/S4545_vb.net.html index 1ddd69d9df0..80db2002279 100644 --- a/analyzers/rspec/vbnet/S4545_vb.net.html +++ b/analyzers/rspec/vbnet/S4545_vb.net.html @@ -1,23 +1,25 @@

The DebuggerDisplayAttribute is used to determine how an object is displayed in the debugger window.

The DebuggerDisplayAttribute constructor takes a single argument: the string to be displayed in the value column for instances of the type. Any text within curly braces is evaluated as the name of a field, property, or method.

-

Naming a non-existent field, property or method between curly braces will result in a CS0103 error in the debug window when debugging objects. +

Naming a non-existent field, property or method between curly braces will result in a BC30451 error in the debug window when debugging objects. Although there is no impact on the production code, providing a wrong value can lead to difficulties when debugging the application.

This rule raises an issue when text specified between curly braces refers to members that don’t exist in the current context.

Noncompliant Code Example

-[DebuggerDisplay("Name: {Name}")] // Noncompliant - Name doesn't exist in this context
-public class Person
-{
-    public string FullName { get; private set; }
-}
+<DebuggerDisplay("Name: {Name}")> ' Noncompliant - Name doesn't exist in this context
+Public Class Person
+
+    Public Property FullName As String
+
+End Class
 

Compliant Solution

-[DebuggerDisplay("Name: {FullName}")]
-public class Person
-{
-    public string FullName { get; private set; }
-}
+<DebuggerDisplay("Name: {FullName}")>
+Public Class Person
+
+    Public Property FullName As String
+
+End Class
 
From bac7bf477291f12da75bffd851214c6f22642217 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Wed, 15 Feb 2023 17:51:20 +0100 Subject: [PATCH 16/19] Comment explaining C# syntax in VB.NET attribute and vice versa --- .../TestCases/DebuggerDisplayUsesExistingMembers.cs | 4 ++-- .../TestCases/DebuggerDisplayUsesExistingMembers.vb | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs index 5e3c419fe8e..8b8950082a4 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -52,8 +52,8 @@ class PropertiesAndFields [DebuggerDisplay("{1 + NonexistentProperty}")] int ContainingInvalidMembers => 1; // FN: expressions not supported } -[DebuggerDisplay("{this.ToString()}")] -[DebuggerDisplay("{Me.ToString()}")] +[DebuggerDisplay("{this.ToString()}")] // Compliant, it's a method call in C# syntax (valid when debugging a C# project) +[DebuggerDisplay("{Me.ToString()}")] // Compliant, it's a method call in VB.NET syntax (valid when debugging a VB.NET project) [DebuggerDisplay("{Nonexistent}")] // Noncompliant public enum TopLevelEnum { One, Two, Three } diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb index acda269d62b..f8cea9b3789 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -47,9 +47,9 @@ Public Class TestOnPropertiesAndFields Property ContainingInvalidMembers As Integer ' FN: expressions not supported End Class - - - ' Noncompliant + ' Compliant, it's a method call in VB.NET syntax (valid when debugging a VB.NET project) + ' Compliant, it's a method call in C# syntax (valid when debugging a C# project) + ' Noncompliant Public Enum TopLevelEnum One Two From 6c2a52a12bcea724e5f86fc70cc1fd7921b0c2d6 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Tue, 21 Feb 2023 14:00:58 +0100 Subject: [PATCH 17/19] Shorter comment --- .../TestCases/DebuggerDisplayUsesExistingMembers.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs index 8b8950082a4..2de2f760e2d 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs @@ -52,8 +52,8 @@ class PropertiesAndFields [DebuggerDisplay("{1 + NonexistentProperty}")] int ContainingInvalidMembers => 1; // FN: expressions not supported } -[DebuggerDisplay("{this.ToString()}")] // Compliant, it's a method call in C# syntax (valid when debugging a C# project) -[DebuggerDisplay("{Me.ToString()}")] // Compliant, it's a method call in VB.NET syntax (valid when debugging a VB.NET project) +[DebuggerDisplay("{this.ToString()}")] // Compliant, valid when debugging a C# project +[DebuggerDisplay("{Me.ToString()}")] // Compliant, valid when debugging a VB.NET project [DebuggerDisplay("{Nonexistent}")] // Noncompliant public enum TopLevelEnum { One, Two, Three } From a2b1fb90b254310692c0808307f49e257d1d3baf Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Tue, 21 Feb 2023 14:01:41 +0100 Subject: [PATCH 18/19] Shorter comments 2 --- .../TestCases/DebuggerDisplayUsesExistingMembers.vb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb index f8cea9b3789..c89fcc124dd 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb +++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb @@ -47,8 +47,8 @@ Public Class TestOnPropertiesAndFields Property ContainingInvalidMembers As Integer ' FN: expressions not supported End Class - ' Compliant, it's a method call in VB.NET syntax (valid when debugging a VB.NET project) - ' Compliant, it's a method call in C# syntax (valid when debugging a C# project) + ' Compliant, valid when debugging a VB.NET project + ' Compliant, valid when debugging a C# project ' Noncompliant Public Enum TopLevelEnum One From 707a16cf672e4d3eb4d771ca5781a395ac099905 Mon Sep 17 00:00:00 2001 From: Antonio Aversa Date: Tue, 21 Feb 2023 14:07:38 +0100 Subject: [PATCH 19/19] Remove facade methods removed from facade interface --- analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs | 3 --- .../src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs | 3 --- 2 files changed, 6 deletions(-) diff --git a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs index eade4fc7fbc..c0ecf611a64 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpFacade.cs @@ -63,7 +63,4 @@ invocation switch public string GetName(SyntaxNode expression) => expression.GetName(); - - public bool IsValidIdentifier(string name) => - SyntaxFacts.IsValidIdentifier(name); } diff --git a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs index 71d2270b2c6..83d6afc1fab 100644 --- a/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs +++ b/analyzers/src/SonarAnalyzer.VisualBasic/Facade/VisualBasicFacade.cs @@ -61,7 +61,4 @@ invocation switch public string GetName(SyntaxNode expression) => expression.GetName(); - - public bool IsValidIdentifier(string name) => - SyntaxFacts.IsValidIdentifier(name); }