Skip to content

Commit ac721fe

Browse files
josefpihrtJochemHarmes
authored andcommitted
Do not remove parameterless constructor (RCS1074) (dotnet#1021)
1 parent a86b19e commit ac721fe

File tree

3 files changed

+135
-40
lines changed

3 files changed

+135
-40
lines changed

ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- [CLI] Add nullable reference type modifier when creating a list of symbols (`list-symbols` command) ([#1013](https://github.com/josefpihrt/roslynator/pull/1013)).
1515
- Add/remove blank line after file scoped namespace declaration ([RCS0060](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS0060.md)) ([#1014](https://github.com/josefpihrt/roslynator/pull/1014)).
1616
- Do not remove overriding member in record ([RCS1132](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1132.md)) ([#1015](https://github.com/josefpihrt/roslynator/pull/1015)).
17+
- Do not remove parameterless empty constructor in a struct with field initializers ([RCS1074](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1074.md)) ([#1020](https://github.com/josefpihrt/roslynator/pull/1020)).
1718

1819
## [4.2.0] - 2022-11-27
1920

src/Analyzers/CSharp/Analysis/RemoveRedundantConstructorAnalyzer.cs

Lines changed: 74 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ namespace Roslynator.CSharp.Analysis;
1212
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1313
public sealed class RemoveRedundantConstructorAnalyzer : BaseDiagnosticAnalyzer
1414
{
15-
private static readonly MetadataName _usedImplicitlyAttribute = MetadataName.Parse("JetBrains.Annotations.UsedImplicitlyAttribute");
16-
1715
private static ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics;
1816

1917
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
@@ -38,48 +36,84 @@ private static void AnalyzeConstructorDeclaration(SyntaxNodeAnalysisContext cont
3836
{
3937
var constructor = (ConstructorDeclarationSyntax)context.Node;
4038

41-
if (constructor.ContainsDiagnostics)
42-
return;
43-
44-
if (constructor.ParameterList?.Parameters.Any() != false)
45-
return;
46-
47-
if (constructor.Body?.Statements.Any() != false)
48-
return;
49-
50-
SyntaxTokenList modifiers = constructor.Modifiers;
51-
52-
if (!modifiers.Contains(SyntaxKind.PublicKeyword))
53-
return;
54-
55-
if (modifiers.Contains(SyntaxKind.StaticKeyword))
56-
return;
57-
58-
ConstructorInitializerSyntax initializer = constructor.Initializer;
59-
60-
if (initializer is not null
61-
&& initializer.ArgumentList?.Arguments.Any() != false)
39+
if (!constructor.ContainsDiagnostics
40+
&& constructor.ParameterList?.Parameters.Any() == false
41+
&& constructor.Body?.Statements.Any() == false)
6242
{
63-
return;
43+
SyntaxTokenList modifiers = constructor.Modifiers;
44+
45+
if (modifiers.Contains(SyntaxKind.PublicKeyword)
46+
&& !modifiers.Contains(SyntaxKind.StaticKeyword))
47+
{
48+
ConstructorInitializerSyntax initializer = constructor.Initializer;
49+
50+
if (initializer is null
51+
|| initializer.ArgumentList?.Arguments.Any() == false)
52+
{
53+
if (!constructor.AttributeLists.Any(attributeList => attributeList.Attributes.Any())
54+
&& !constructor.HasDocumentationComment()
55+
&& CheckStructWithFieldInitializer(constructor))
56+
{
57+
IMethodSymbol symbol = context.SemanticModel.GetDeclaredSymbol(constructor, context.CancellationToken);
58+
59+
if (symbol is not null
60+
&& SymbolEqualityComparer.Default.Equals(symbol, symbol.ContainingType.InstanceConstructors.SingleOrDefault(shouldThrow: false))
61+
&& constructor.DescendantTrivia(constructor.Span).All(f => f.IsWhitespaceOrEndOfLineTrivia()))
62+
{
63+
DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.RemoveRedundantConstructor, constructor);
64+
}
65+
}
66+
}
67+
}
6468
}
69+
}
6570

66-
if (constructor.HasDocumentationComment())
67-
return;
68-
69-
IMethodSymbol symbol = context.SemanticModel.GetDeclaredSymbol(constructor, context.CancellationToken);
70-
71-
if (symbol?.Kind != SymbolKind.Method)
72-
return;
73-
74-
if (symbol.ContainingType.InstanceConstructors.SingleOrDefault(shouldThrow: false) != symbol)
75-
return;
76-
77-
if (symbol.HasAttribute(_usedImplicitlyAttribute))
78-
return;
71+
private static bool CheckStructWithFieldInitializer(ConstructorDeclarationSyntax constructor)
72+
{
73+
var memberDeclaration = constructor.Parent as BaseTypeDeclarationSyntax;
7974

80-
if (!constructor.DescendantTrivia(constructor.Span).All(f => f.IsWhitespaceOrEndOfLineTrivia()))
81-
return;
75+
if (memberDeclaration is not null)
76+
{
77+
SyntaxList<MemberDeclarationSyntax> members;
78+
79+
if (memberDeclaration is StructDeclarationSyntax structDeclaration)
80+
{
81+
if (memberDeclaration.Modifiers.Contains(SyntaxKind.PartialKeyword))
82+
return false;
83+
84+
members = structDeclaration.Members;
85+
}
86+
else if (memberDeclaration is RecordDeclarationSyntax recordDeclaration
87+
&& recordDeclaration.ClassOrStructKeyword.IsKind(SyntaxKind.StructKeyword))
88+
{
89+
if (memberDeclaration.Modifiers.Contains(SyntaxKind.PartialKeyword))
90+
return false;
91+
92+
members = recordDeclaration.Members;
93+
}
94+
95+
foreach (MemberDeclarationSyntax member in members)
96+
{
97+
switch (member)
98+
{
99+
case PropertyDeclarationSyntax property:
100+
{
101+
return property.Initializer is null;
102+
}
103+
case FieldDeclarationSyntax field:
104+
{
105+
foreach (VariableDeclaratorSyntax declarator in field.Declaration.Variables)
106+
{
107+
if (declarator.Initializer is not null)
108+
return false;
109+
}
110+
111+
break;
112+
}
113+
}
114+
}
115+
}
82116

83-
DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.RemoveRedundantConstructor, constructor);
117+
return true;
84118
}
85119
}

src/Tests/Analyzers.Tests/RCS1074RemoveRedundantConstructorTests.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,66 @@ public class UsedImplicitlyAttribute : System.Attribute
4747
{
4848
}
4949
}
50+
");
51+
}
52+
53+
[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveRedundantConstructor)]
54+
public async Task TestNoDiagnostic_StructWithFieldInitializer()
55+
{
56+
await VerifyNoDiagnosticAsync(@"
57+
struct C
58+
{
59+
private string _f = """";
60+
61+
public C()
62+
{
63+
}
64+
}
65+
");
66+
}
67+
68+
[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveRedundantConstructor)]
69+
public async Task TestNoDiagnostic_StructWithPropertyInitializer()
70+
{
71+
await VerifyNoDiagnosticAsync(@"
72+
struct C
73+
{
74+
public string P { get; init; } = """";
75+
76+
public C()
77+
{
78+
}
79+
}
80+
");
81+
}
82+
83+
[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveRedundantConstructor)]
84+
public async Task TestNoDiagnostic_RecordStructWithFieldInitializer()
85+
{
86+
await VerifyNoDiagnosticAsync(@"
87+
record struct C
88+
{
89+
private string _f = """";
90+
91+
public C()
92+
{
93+
}
94+
}
95+
");
96+
}
97+
98+
[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.RemoveRedundantConstructor)]
99+
public async Task TestNoDiagnostic_RecordStructWithPropertyInitializer()
100+
{
101+
await VerifyNoDiagnosticAsync(@"
102+
record struct C
103+
{
104+
public string P { get; init; } = """";
105+
106+
public C()
107+
{
108+
}
109+
}
50110
");
51111
}
52112
}

0 commit comments

Comments
 (0)