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..80db2002279
--- /dev/null
+++ b/analyzers/rspec/vbnet/S4545_vb.net.html
@@ -0,0 +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 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 Property FullName As String
+
+End Class
+
+Compliant Solution
+
+<DebuggerDisplay("Name: {FullName}")>
+Public Class Person
+
+ Public Property FullName As String
+
+End Class
+
+
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/Facade/CSharpSyntaxFacade.cs b/analyzers/src/SonarAnalyzer.CSharp/Facade/CSharpSyntaxFacade.cs
index c26432354f0..21f0560d744 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 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
new file mode 100644
index 00000000000..15455c23004
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DebuggerDisplayUsesExistingMembers.cs
@@ -0,0 +1,35 @@
+/*
+ * 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 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);
+}
diff --git a/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs b/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs
new file mode 100644
index 00000000000..58c78e05571
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.Common/Common/RegexConstants.cs
@@ -0,0 +1,26 @@
+/*
+ * 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.Common;
+
+public static class RegexConstants
+{
+ public static TimeSpan DefaultTimeout => TimeSpan.FromMilliseconds(100);
+}
diff --git a/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs b/analyzers/src/SonarAnalyzer.Common/Facade/SyntaxFacade.cs
index 9144a08af14..f08ceed8ff4 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 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
new file mode 100644
index 00000000000..1453d0d5150
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.Common/Rules/DebuggerDisplayUsesExistingMembersBase.cs
@@ -0,0 +1,101 @@
+/*
+ * 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 System.Text.RegularExpressions;
+using Microsoft.CodeAnalysis;
+
+namespace SonarAnalyzer.Rules;
+
+public abstract class DebuggerDisplayUsesExistingMembersBase : SonarDiagnosticAnalyzer
+ where TAttributeSyntax : SyntaxNode
+ where TSyntaxKind : struct
+{
+ private const string DiagnosticId = "S4545";
+
+ private readonly Regex evaluatedExpressionRegex = new(@"\{(?[^}]+)\}", RegexOptions.Multiline, RegexConstants.DefaultTimeout);
+
+ protected abstract SyntaxNode AttributeFormatString(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;
+ 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));
+ }
+ },
+ Language.SyntaxKind.Attribute);
+
+ private string FirstInvalidMemberName(SonarSyntaxNodeReportingContext context, string formatString, TAttributeSyntax attributeSyntax)
+ {
+ 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
+ && !allMembers.Contains(memberName))
+ {
+ return memberName;
+ }
+ }
+
+ return null;
+ }
+
+ string ExtractValidMemberName(string evaluatedExpression)
+ {
+ var sanitizedExpression = evaluatedExpression.Split(',')[0].Trim();
+ return IsValidMemberName(sanitizedExpression) ? sanitizedExpression : null;
+ }
+
+ 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 c03a1e1f4bd..66a63dfaa0e 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 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
new file mode 100644
index 00000000000..459649f2609
--- /dev/null
+++ b/analyzers/src/SonarAnalyzer.VisualBasic/Rules/DebuggerDisplayUsesExistingMembers.cs
@@ -0,0 +1,35 @@
+/*
+ * 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 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);
+}
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..7b98e04ee54
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/DebuggerDisplayUsesExistingMembersTest.cs
@@ -0,0 +1,61 @@
+/*
+ * 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();
+ private readonly VerifierBuilder builderVB = new VerifierBuilder();
+
+ [TestMethod]
+ public void DebuggerDisplayUsesExistingMembers_CS() =>
+ builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.cs").Verify();
+
+ [TestMethod]
+ public void DebuggerDisplayUsesExistingMembers_CSharp8() =>
+ builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp8.cs")
+ .WithOptions(ParseOptionsHelper.FromCSharp8)
+ .Verify();
+
+#if NET
+
+ [TestMethod]
+ public void DebuggerDisplayUsesExistingMembers_CSharp10() =>
+ builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp10.cs")
+ .WithOptions(ParseOptionsHelper.FromCSharp10)
+ .Verify();
+
+#endif
+
+ [TestMethod]
+ public void DebuggerDisplayUsesExistingMembers_CSharp11() =>
+ builderCS.AddPaths("DebuggerDisplayUsesExistingMembers.CSharp11.cs")
+ .WithOptions(ParseOptionsHelper.FromCSharp11)
+ .Verify();
+
+ [TestMethod]
+ public void DebuggerDisplayUsesExistingMembers_VB() =>
+ builderVB.AddPaths("DebuggerDisplayUsesExistingMembers.vb").Verify();
+}
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..baa330e4c1b
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp10.cs
@@ -0,0 +1,50 @@
+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, 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}")]
+public record struct SomeRecordStruct(int RecordProperty1, string RecordProperty2)
+{
+ [DebuggerDisplay("{RecordProperty}")] // Noncompliant
+ public class NestedClass1
+ {
+ [DebuggerDisplay("{NestedClassProperty}")]
+ public int NestedClassProperty => 1;
+ }
+
+ [DebuggerDisplay("{NestedClassProperty}")]
+ public class NestedClass2
+ {
+ [DebuggerDisplay("{NestedClassProperty}")]
+ public int NestedClassProperty => 1;
+ }
+}
+
+public class ConstantInterpolatedStrings
+{
+ [DebuggerDisplay($"{{{nameof(SomeProperty)}}}")]
+ [DebuggerDisplay($"{{{nameof(NotAProperty)}}}")] // FN: constant interpolated strings not supported
+ public int SomeProperty => 1;
+
+ 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
new file mode 100644
index 00000000000..c9e078e9019
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp11.cs
@@ -0,0 +1,31 @@
+using System.Diagnostics;
+
+class RawStringLiterals
+{
+ 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("""{Nonexistent}""")] int NonexistentTripleQuotes => 1; // Noncompliant
+ // ^^^^^^^^^^^^^^^^^^^
+ [DebuggerDisplay(""""{Nonexistent}"""")] int NonexistentQuadrupleQuotes => 1; // Noncompliant
+ // ^^^^^^^^^^^^^^^^^^^^^
+ [DebuggerDisplay("""
+ Some text{Nonexistent}
+ """)] int NonexistentMultiLine1 => 1; // Noncompliant@-2^22#46
+ [DebuggerDisplay("""
+ Some text{Some
+ Property}
+ """)] int NonexistentMultiLine2 => 1; // FN: the new line char make the expression within braces not a valid identifier
+ [DebuggerDisplay($$"""""
+ Some text{Nonexistent}
+ """"")] int NonexistentMultiLineInterpolated => 1; // FN: interpolated raw string literals strings not supported
+}
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..96ea4a0d938
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.CSharp8.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Diagnostics;
+
+public class AccessModifiers
+{
+ public class BaseClass
+ {
+ private protected int PrivateProtectedProperty => 1;
+
+ [DebuggerDisplay("{PrivateProtectedProperty}")]
+ public int SomeProperty => 1;
+ }
+
+ public class SubClass : BaseClass
+ {
+ [DebuggerDisplay("{PrivateProtectedProperty}")]
+ public int OtherProperty => 1;
+ }
+}
diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs
new file mode 100644
index 00000000000..2de2f760e2d
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.cs
@@ -0,0 +1,267 @@
+using System;
+using System.Diagnostics;
+
+class PropertiesAndFields
+{
+ const string ConstantWithoutInvalidMembers = "Something";
+ const string ConstantWithInvalidMember = "{Nonexistent}";
+ const string ConstantFragment1 = "{Non";
+ const string ConstantFragment2 = "existent}";
+
+ int SomeProperty => 1;
+ int SomeField = 2;
+
+ [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;
+ [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(@"Some text
+ {SomeField}")] int WithExistingFieldVerbatimMultiLine => 1;
+
+ [DebuggerDisplay("{1 + 1}")] int WithNoMemberReferenced1 => 1;
+ [DebuggerDisplay(@"{""1"" + ""1""}")] int WithNoMemberReferenced2 => 1;
+
+ [DebuggerDisplay("{Nonexistent}")] int WithNonexistentMember1 => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}}
+ // ^^^^^^^^^^^^^^^
+ [DebuggerDisplay("1 + {Nonexistent}")] int WithNonexistentMember2 => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}}
+ // ^^^^^^^^^^^^^^^^^^^
+ [DebuggerDisplay("{Nonexistent1} bla bla {Nonexistent2}")] int WithMultipleNonexistent => 1; // Noncompliant {{'Nonexistent1' doesn't exist in this context.}}
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ [DebuggerDisplay(@"{Nonexistent}")] int WithNonexistentMemberVerbatim => 1; // Noncompliant {{'Nonexistent' doesn't exist in this context.}}
+ [DebuggerDisplay(@"Some text
+ {Nonexistent}")] int WithNonexistentMemberVerbatimMultiLine1 => 1; // Noncompliant@-1^22#34 {{'Nonexistent' doesn't exist in this context.}}
+ [DebuggerDisplay(@"Some text {Some
+ 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" + "Existent}")] int WithFormatAsConcatenationOfLiterals => 1; // FN: only simple literal supported
+ [DebuggerDisplay("{Non"
+ + "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)
+ [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()}")] // 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 }
+
+[DebuggerDisplay("{SomeProperty}")]
+[DebuggerDisplay("{SomeField}")]
+[DebuggerDisplay("{Nonexistent}")] // Noncompliant
+public class NestedTypes
+{
+ int SomeProperty => 1;
+ int SomeField = 1;
+
+ [DebuggerDisplay("{ExistingProperty}")]
+ [DebuggerDisplay("{ExistingField}")]
+ [DebuggerDisplay("{SomeProperty}")] // Noncompliant
+ [DebuggerDisplay("{SomeField}")] // Noncompliant
+ public class NestedClass
+ {
+ int ExistingProperty => 1;
+ int ExistingField => 1;
+ }
+
+ [DebuggerDisplay("{ExistingProperty}")]
+ [DebuggerDisplay("{ExistingField}")]
+ [DebuggerDisplay("{SomeProperty}")] // Noncompliant
+ [DebuggerDisplay("{SomeField}")] // Noncompliant
+ public struct NestedStruct
+ {
+ int ExistingProperty => 1;
+ int ExistingField => 1;
+ }
+
+ [DebuggerDisplay("{42}")]
+ [DebuggerDisplay("{SomeProperty}")] // Noncompliant
+ [DebuggerDisplay("{SomeField}")] // Noncompliant
+ public enum NestedEnum { One, Two, Three }
+}
+
+public class Delegates
+{
+ int ExistingProperty => 1;
+
+ [DebuggerDisplay("{ExistingProperty}")] // Noncompliant
+ [DebuggerDisplay("{42}")]
+ public delegate void Delegate1();
+}
+
+public class Indexers
+{
+ int ExistingProperty => 1;
+ int ExistingField => 1;
+
+ [DebuggerDisplay("{ExistingProperty}")]
+ [DebuggerDisplay("{ExistingField}")]
+ [DebuggerDisplay("{Nonexistent}")] // Noncompliant
+ int this[int i] => 1;
+}
+
+[DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{Nonexistent}")] // Noncompliant
+// ^^^^^^^^^^^^^^^
+public class MultipleAttributes
+{
+ int SomeProperty => 1;
+ int SomeField = 1;
+
+ [DebuggerDisplay("{SomeProperty}"), DebuggerDisplay("{SomeField}"), DebuggerDisplay("{Nonexistent}")] // Noncompliant
+ // ^^^^^^^^^^^^^^^
+ int OtherProperty1 => 1;
+
+ [DebuggerDisplay("{Nonexistent1}"), DebuggerDisplay("{Nonexistent2}")]
+ // ^^^^^^^^^^^^^^^^
+ // ^^^^^^^^^^^^^^^^@-1
+ int OtherProperty2 => 1;
+
+ [DebuggerDisplay("{Nonexistent1}")][DebuggerDisplay("{Nonexistent2}")]
+ // ^^^^^^^^^^^^^^^^
+ // ^^^^^^^^^^^^^^^^@-1
+ int OtherProperty3 => 1;
+}
+
+public class CaseSensitivity
+{
+ int SOMEPROPERTY => 1;
+ int SomeProperty => 1;
+
+ [DebuggerDisplay("{SOMEPROPERTY}")]
+ [DebuggerDisplay("{SomeProperty}")]
+ [DebuggerDisplay("{someProperty}")] // Noncompliant
+ [DebuggerDisplay("{someproperty}")] // Noncompliant
+ int OtherProperty => 1;
+}
+
+public class NonAlphanumericChars
+{
+ int Aa1_뿓 => 1;
+
+ [DebuggerDisplay("{Aa1_뿓}")]
+ [DebuggerDisplay("{Aa1_㤬}")] // Noncompliant {{'Aa1_㤬' doesn't exist in this context.}}
+ int SomeProperty1 => 1;
+}
+
+public class Whitespaces
+{
+ [DebuggerDisplay("{ SomeProperty}")]
+ [DebuggerDisplay("{SomeProperty }")]
+ [DebuggerDisplay("{\tSomeProperty}")]
+ [DebuggerDisplay("{\tSomeProperty\t}")]
+ [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;
+}
+
+public class NoQuotesModifier
+{
+ [DebuggerDisplay("{SomeProperty,nq}")]
+ [DebuggerDisplay("{SomeProperty ,nq}")]
+ [DebuggerDisplay("{SomeProperty, nq}")]
+ [DebuggerDisplay("{SomeProperty,nq }")]
+ [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;
+}
+
+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", Type = nameof(OptionalAttributeParameter))] // Noncompliant
+ // ^^^^^^^^^^^^^^^
+ [DebuggerDisplay("{Nonexistent}", Name = "Any name", Target = typeof(OptionalAttributeParameter))] // Noncompliant
+ // ^^^^^^^^^^^^^^^
+ int SomeProperty => 1;
+}
+
+public class Inheritance
+{
+ public class BaseClass
+ {
+ int SomeProperty => 1;
+ }
+
+ public class SubClass : BaseClass
+ {
+ [DebuggerDisplay("{SomeProperty}")] // Compliant, defined in base class
+ int OtherProperty => 1;
+ }
+}
+
+public class AccessModifiers
+{
+ public class BaseClass
+ {
+ public int PublicProperty => 1;
+ internal int InternalProperty => 1;
+ protected int ProtectedProperty => 1;
+ private int PrivateProperty => 1;
+
+ [DebuggerDisplay("{PublicProperty}")]
+ [DebuggerDisplay("{InternalProperty}")]
+ [DebuggerDisplay("{ProtectedProperty}")]
+ [DebuggerDisplay("{PrivateProperty}")]
+ int SomeProperty => 1;
+ }
+
+ public class SubClass : BaseClass
+ {
+ [DebuggerDisplay("{PublicProperty}")]
+ [DebuggerDisplay("{InternalProperty}")]
+ [DebuggerDisplay("{ProtectedProperty}")]
+ [DebuggerDisplay("{PrivateProperty}")]
+ int OtherProperty => 1;
+ }
+}
+
+public class AttributeTargets
+{
+ [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;
+}
+
+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;
+
+ public class Test
+ {
+ [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
new file mode 100644
index 00000000000..c89fcc124dd
--- /dev/null
+++ b/analyzers/tests/SonarAnalyzer.UnitTest/TestCases/DebuggerDisplayUsesExistingMembers.vb
@@ -0,0 +1,224 @@
+Imports System
+Imports System.Diagnostics
+
+Public Class TestOnPropertiesAndFields
+ Const ConstantWithoutInvalidMembers As String = "Something"
+ Const ConstantWithInvalidMember As String = "{Nonexistent}"
+ Const ConstantFragment1 As String = "{Non"
+ Const ConstantFragment2 As String = "Existent}"
+
+ Public Property SomeProperty As Integer
+ Public SomeField As Integer
+
+ Property WithSuffix As Integer
+ Property WithNamespace As Integer
+ Property WithGlobal 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 WithNonexistentMember1 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 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
+ 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
+End Class
+
+ ' Compliant, valid when debugging a VB.NET project
+ ' Compliant, valid when debugging a C# project
+ ' Noncompliant
+Public Enum TopLevelEnum
+ One
+ Two
+ Three
+End Enum
+
+
+
+ ' Noncompliant
+Public Class TestOnNestedTypes
+ Public Property SomeProperty As Integer
+ Public SomeField As Integer
+
+
+
+ ' Noncompliant
+ ' Noncompliant
+ Public Class NestedClass
+ Property ExistingProperty As Integer
+ Property ExistingField As Integer
+ End Class
+
+
+
+ ' Noncompliant
+ ' Noncompliant
+ 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
+
+ Delegate Sub Delegate1()
+End Class
+
+Class TestOnIndexers
+ Public 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
+
+ Public 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
+
+
+
+
+
+ Property OtherProperty As Integer
+End Class
+
+Class SupportNonAlphanumericChars
+ Property Aa1_뿓 As Integer
+
+
+ ' Noncompliant {{'Aa1_㤬' doesn't exist in this context.}}
+ Property SomeProperty1 As Integer
+End Class
+
+Class SupportWhitespaces
+
+
+
+
+ ' 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
+
+Class SupportNq
+
+
+
+
+ ' Noncompliant
+ ' Noncompliant
+ ' Noncompliant
+ ' Noncompliant
+ Property SomeProperty As Integer
+End Class
+
+Class SupportOptionalAttributeParameter
+
+ ' Noncompliant
+ ' Noncompliant
+ ' Noncompliant
+ 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
+