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
13 changes: 13 additions & 0 deletions analyzers/its/expected/Automapper/AutoMapper--net461-S4049.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@
},
{
"id": "S4049",
"message": "Consider making method 'GetTypeDefinitionIfGeneric' a property.",
"location": {
"uri": "sources\Automapper\src\AutoMapper\Internal\TypePair.cs",
"region": {
"startLine": 56,
"startColumn": 25,
"endLine": 56,
"endColumn": 51
}
}
},
{
"id": "S4049",
"message": "Consider making method 'GetCurrentPath' a property.",
"location": {
"uri": "sources\Automapper\src\AutoMapper\QueryableExtensions\ProjectionBuilder.cs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@
},
{
"id": "S4049",
"message": "Consider making method 'GetTypeDefinitionIfGeneric' a property.",
"location": {
"uri": "sources\Automapper\src\AutoMapper\Internal\TypePair.cs",
"region": {
"startLine": 56,
"startColumn": 25,
"endLine": 56,
"endColumn": 51
}
}
},
{
"id": "S4049",
"message": "Consider making method 'GetCurrentPath' a property.",
"location": {
"uri": "sources\Automapper\src\AutoMapper\QueryableExtensions\ProjectionBuilder.cs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using SonarAnalyzer.CFG.Helpers;
using SonarAnalyzer.CFG.Sonar;
using SonarAnalyzer.Helpers;

namespace SonarAnalyzer.CFG
{
Expand Down
22 changes: 12 additions & 10 deletions analyzers/src/SonarAnalyzer.CFG/Helpers/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
using System.Collections.Generic;
using System.Text;

namespace SonarAnalyzer.CFG.Helpers
namespace SonarAnalyzer.Helpers
{
public static class StringExtensions
{
Expand All @@ -31,19 +31,19 @@ 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="table">
/// <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>
public static IEnumerable<string> SplitCamelCaseToWords(this string name)
{
bool IsFollowedByLower(int i) => i + 1 < name.Length && char.IsLower(name[i + 1]);

if (name == null)
{
yield break;
Expand Down Expand Up @@ -83,6 +83,8 @@ public static IEnumerable<string> SplitCamelCaseToWords(this string name)
{
yield return currentWord.ToString();
}

bool IsFollowedByLower(int i) => i + 1 < name.Length && char.IsLower(name[i + 1]);
}
}
}
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,22 +43,18 @@ 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;
}

var propertyCandidates = typeSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.Where(HasCandidateName)
.Where(HasCandidateReturnType)
.Where(HasCandidateSignature)
.Where(UsageAttributesAllowProperties);
.Where(x => HasCandidateName(x) && HasCandidateReturnType(x) && HasCandidateSignature(x) && UsageAttributesAllowProperties(x));

foreach (var candidate in propertyCandidates)
{
Expand All @@ -67,7 +66,9 @@ protected override void Initialize(SonarAnalysisContext context) =>
},
SyntaxKind.ClassDeclaration,
SyntaxKind.InterfaceDeclaration,
SyntaxKindEx.RecordClassDeclaration);
SyntaxKind.StructDeclaration,
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 +86,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
88 changes: 0 additions & 88 deletions analyzers/src/SonarAnalyzer.Common/Helpers/StringExtensions.cs

This file was deleted.

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>();

[TestMethod]
public void PropertiesShouldBePreferred() =>
OldVerifier.VerifyAnalyzer(@"TestCases\PropertiesShouldBePreferred.cs",
new PropertiesShouldBePreferred(),
MetadataReferenceFacade.SystemThreadingTasks);
builder.AddPaths("PropertiesShouldBePreferred.cs").AddReferences(MetadataReferenceFacade.SystemThreadingTasks).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; } = "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public interface IBase

public struct SomeStruct
{
string GetStuff() { return ""; } // FN
public string GetStuff() { return ""; } // Noncompliant
}

public interface IFoo
Expand Down