Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

S4049: Add support for record struct #5593

Merged
merged 10 commits into from
May 3, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
*/

using System;
using System.Collections;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using SonarAnalyzer.Extensions;
using SonarAnalyzer.Helpers;
using StyleCop.Analyzers.Lightup;

Expand All @@ -40,11 +43,10 @@ public sealed class PropertiesShouldBePreferred : SonarDiagnosticAnalyzer
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);

protected override void Initialize(SonarAnalysisContext context) =>
context.RegisterSyntaxNodeActionInNonGenerated(
c =>
context.RegisterSyntaxNodeActionInNonGenerated(c =>
{
if (c.ContainingSymbol.Kind != SymbolKind.NamedType
|| !(c.SemanticModel.GetDeclaredSymbol(c.Node) is INamedTypeSymbol typeSymbol))
if (c.IsRedundantPositionalRecordContext()
|| c.SemanticModel.GetDeclaredSymbol(c.Node) is not INamedTypeSymbol typeSymbol)
{
return;
}
Expand All @@ -67,7 +69,8 @@ protected override void Initialize(SonarAnalysisContext context) =>
},
SyntaxKind.ClassDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKindEx.RecordClassDeclaration);
SyntaxKindEx.RecordClassDeclaration,
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
SyntaxKindEx.RecordStructDeclaration);

private static bool HasCandidateSignature(IMethodSymbol method) =>
method.IsPubliclyAccessible()
Expand All @@ -85,7 +88,7 @@ private static bool HasCandidateReturnType(IMethodSymbol method) =>

private static bool HasCandidateName(IMethodSymbol method)
{
if (method.Name == "GetEnumerator" || method.Name == "GetAwaiter")
if (method.Name is nameof(IEnumerable.GetEnumerator) or nameof(Task.GetAwaiter))
{
return false;
}
Expand Down
16 changes: 9 additions & 7 deletions analyzers/src/SonarAnalyzer.Common/Helpers/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ public static class StringExtensions
/// Sequence of upper case letters is considered as single word.
///
/// For example:
/// thisIsAName => this is a name
/// ThisIsSMTPName => this is smtp name
/// bin2hex => bin hex
/// HTML => html
/// SOME_value => some value
/// PEHeader => pe header
/// <list type="bullet">
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
/// <item>"thisIsAName" => ["THIS", "IS", "A", "NAME"]</item>
/// <item>"ThisIsSMTPName" => ["THIS", "IS", "SMTP", "NAME"]</item>
/// <item>"bin2hex" => ["BIN", "HEX"]</item>
/// <item>"HTML" => ["HTML"]</item>
/// <item>"SOME_value" => ["SOME", "VALUE"]</item>
/// <item>"PEHeader" => ["PE", "HEADER"]</item>
/// </list>
/// </summary>
/// <param name="name">A string containing words.</param>
/// <returns>A list of words (all lowercase) contained in the string.</returns>
/// <returns>A list of words (all uppercase) contained in the string.</returns>
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
public static IEnumerable<string> SplitCamelCaseToWords(this string name)
{
bool IsFollowedByLower(int i) => i + 1 < name.Length && char.IsLower(name[i + 1]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,27 @@ namespace SonarAnalyzer.UnitTest.Rules
[TestClass]
public class PropertiesShouldBePreferredTest
{
private readonly VerifierBuilder builder = new VerifierBuilder<PropertiesShouldBePreferred>().AddReferences(MetadataReferenceFacade.SystemThreadingTasks);
pavel-mikula-sonarsource marked this conversation as resolved.
Show resolved Hide resolved

[TestMethod]
public void PropertiesShouldBePreferred() =>
OldVerifier.VerifyAnalyzer(@"TestCases\PropertiesShouldBePreferred.cs",
new PropertiesShouldBePreferred(),
MetadataReferenceFacade.SystemThreadingTasks);
builder.AddPaths("PropertiesShouldBePreferred.cs").Verify();

#if NET

[TestMethod]
public void PropertiesShouldBePreferred_CSharp9() =>
OldVerifier.VerifyAnalyzerFromCSharp9Library(@"TestCases\PropertiesShouldBePreferred.CSharp9.cs", new PropertiesShouldBePreferred());
builder.AddPaths("PropertiesShouldBePreferred.CSharp9.cs").WithOptions(ParseOptionsHelper.FromCSharp9).Verify();

[TestMethod]
public void PropertiesShouldBePreferred_CSharp10() =>
OldVerifier.VerifyAnalyzerFromCSharp10Library(@"TestCases\PropertiesShouldBePreferred.CSharp10.cs", new PropertiesShouldBePreferred());
builder.AddPaths("PropertiesShouldBePreferred.CSharp10.cs").WithOptions(ParseOptionsHelper.FromCSharp10).Verify();

[TestMethod]
public void PropertiesShouldBePreferred_CSharpPreview() =>
OldVerifier.VerifyAnalyzerCSharpPreviewLibrary(@"TestCases\PropertiesShouldBePreferred.CSharpPreview.cs", new PropertiesShouldBePreferred());
builder.AddPaths("PropertiesShouldBePreferred.CSharpPreview.cs").WithOptions(ParseOptionsHelper.CSharpPreview).Verify();

#endif

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,27 @@
{
private string name = "";

public string GetName() // FN
public string GetName() // Noncompliant {{Consider making method 'GetName' a property.}}
// ^^^^^^^
{
return name;
}

public object GetName2() => null; // FN

public object GetName2() => null; // Noncompliant
private string GetName3() => null; // Compliant

public string Property { get; set; } = "";
}

public record struct PositionalRecordStruct(string Parameter)
{
private string name = "";

public string GetName() // FN
public string GetName() // Noncompliant
{
return name;
}

public object GetName2() => null; // FN

public object GetName2() => null; // Noncompliant
private string GetName3() => null; // Compliant

public string Property { get; set; } = "";
}