Skip to content

Commit ed1d110

Browse files
sebastien-marichalsonartech
authored andcommitted
NET-1253 Fix S1699 FN: Adds support for complex inheritance
1 parent 6101985 commit ed1d110

File tree

6 files changed

+107
-54
lines changed

6 files changed

+107
-54
lines changed

analyzers/src/SonarAnalyzer.CSharp/Rules/ConstructorOverridableCall.cs

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,61 +14,44 @@
1414
* along with this program; if not, see https://sonarsource.com/license/ssal/
1515
*/
1616

17-
namespace SonarAnalyzer.CSharp.Rules
18-
{
19-
[DiagnosticAnalyzer(LanguageNames.CSharp)]
20-
public sealed class ConstructorOverridableCall : SonarDiagnosticAnalyzer
21-
{
22-
internal const string DiagnosticId = "S1699";
23-
private const string MessageFormat = "Remove this call from a constructor to the overridable '{0}' method.";
17+
namespace SonarAnalyzer.CSharp.Rules;
2418

25-
private static readonly DiagnosticDescriptor rule =
26-
DescriptorFactory.Create(DiagnosticId, MessageFormat);
19+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
20+
public sealed class ConstructorOverridableCall : SonarDiagnosticAnalyzer
21+
{
22+
private const string DiagnosticId = "S1699";
23+
private const string MessageFormat = "Remove this call from a constructor to the overridable '{0}' method.";
2724

28-
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(rule);
25+
private static readonly DiagnosticDescriptor Rule =
26+
DescriptorFactory.Create(DiagnosticId, MessageFormat);
2927

30-
protected override void Initialize(SonarAnalysisContext context)
31-
{
32-
context.RegisterNodeAction(
33-
CheckOverridableCallInConstructor,
34-
SyntaxKind.InvocationExpression);
35-
}
28+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
3629

37-
private static void CheckOverridableCallInConstructor(SonarSyntaxNodeReportingContext context)
30+
protected override void Initialize(SonarAnalysisContext context) =>
31+
context.RegisterSymbolStartAction(c =>
3832
{
39-
var invocationExpression = (InvocationExpressionSyntax)context.Node;
40-
41-
var calledOn = (invocationExpression.Expression as MemberAccessExpressionSyntax)?.Expression;
42-
var isCalledOnThis = calledOn == null || calledOn is ThisExpressionSyntax;
43-
if (!isCalledOnThis)
44-
{
45-
return;
46-
}
47-
48-
var enclosingSymbol = context.Model.GetEnclosingSymbol(invocationExpression.SpanStart) as IMethodSymbol;
49-
if (!IsMethodConstructor(enclosingSymbol))
33+
if (c.Symbol is IMethodSymbol { MethodKind: MethodKind.Constructor, ContainingType.IsSealed: false } constructor
34+
&& !constructor.ContainingType.DerivesFrom(KnownType.Nancy_NancyModule))
5035
{
51-
return;
36+
c.RegisterSyntaxNodeAction(cc => CheckOverridableCallInConstructor(cc, constructor), SyntaxKind.InvocationExpression);
5237
}
38+
}, SymbolKind.Method);
5339

40+
private static void CheckOverridableCallInConstructor(SonarSyntaxNodeReportingContext context, IMethodSymbol constructor)
41+
{
42+
var invocationExpression = (InvocationExpressionSyntax)context.Node;
5443

55-
if (context.Model.GetSymbolInfo(invocationExpression.Expression).Symbol is IMethodSymbol methodSymbol &&
56-
IsMethodOverridable(methodSymbol) &&
57-
enclosingSymbol.IsInType(methodSymbol.ContainingType))
58-
{
59-
context.ReportIssue(rule, invocationExpression.Expression, methodSymbol.Name);
60-
}
61-
}
62-
63-
private static bool IsMethodOverridable(IMethodSymbol methodSymbol)
64-
{
65-
return methodSymbol.IsVirtual || methodSymbol.IsAbstract;
66-
}
67-
68-
private static bool IsMethodConstructor(IMethodSymbol methodSymbol)
44+
if (invocationExpression.Expression is IdentifierNameSyntax or MemberAccessExpressionSyntax { Expression: ThisExpressionSyntax }
45+
&& context.Model.GetEnclosingSymbol(invocationExpression.SpanStart).Equals(constructor)
46+
&& context.Model.GetSymbolInfo(invocationExpression.Expression).Symbol is IMethodSymbol methodSymbol
47+
&& IsMethodOverridable(methodSymbol))
6948
{
70-
return methodSymbol != null &&
71-
methodSymbol.MethodKind == MethodKind.Constructor;
49+
context.ReportIssue(Rule, invocationExpression.Expression, methodSymbol.Name);
7250
}
7351
}
52+
53+
private static bool IsMethodOverridable(IMethodSymbol methodSymbol) =>
54+
methodSymbol.IsVirtual
55+
|| methodSymbol.IsAbstract
56+
|| (methodSymbol.IsOverride && !methodSymbol.IsSealed);
7457
}

analyzers/src/SonarAnalyzer.Core/AnalysisContext/SonarAnalysisContext.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ public void RegisterSymbolAction(Action<SonarSymbolReportingContext> action, par
8282
RegisterCompilationStartAction(
8383
x => x.RegisterSymbolAction(action, symbolKinds));
8484

85+
public void RegisterSymbolStartAction(Action<SonarSymbolStartAnalysisContext> action, SymbolKind symbolKind) =>
86+
RegisterCompilationStartAction(
87+
x => x.RegisterSymbolStartAction(action, symbolKind));
88+
8589
public void RegisterNodeAction<TSyntaxKind>(GeneratedCodeRecognizer generatedCodeRecognizer, Action<SonarSyntaxNodeReportingContext> action, params TSyntaxKind[] syntaxKinds)
8690
where TSyntaxKind : struct =>
8791
RegisterCompilationStartAction(

analyzers/src/SonarAnalyzer.Core/Semantics/KnownType.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ public sealed partial class KnownType
194194
public static readonly KnownType MySql_Data_MySqlClient_MySqlHelper = new("MySql.Data.MySqlClient.MySqlHelper");
195195
public static readonly KnownType MySql_Data_MySqlClient_MySqlScript = new("MySql.Data.MySqlClient.MySqlScript");
196196
public static readonly KnownType Nancy_Cookies_NancyCookie = new("Nancy.Cookies.NancyCookie");
197+
public static readonly KnownType Nancy_NancyModule = new("Nancy.NancyModule");
197198
public static readonly KnownType NFluent_Check = new("NFluent.Check");
198199
public static readonly KnownType NFluent_FluentCheckException = new("NFluent.FluentCheckException");
199200
public static readonly KnownType NFluent_Kernel_FluentCheckException = new("NFluent.Kernel.FluentCheckException");

analyzers/tests/SonarAnalyzer.Test/Rules/ConstructorOverridableCallTest.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,21 @@
1616

1717
using SonarAnalyzer.CSharp.Rules;
1818

19-
namespace SonarAnalyzer.Test.Rules
19+
namespace SonarAnalyzer.Test.Rules;
20+
21+
[TestClass]
22+
public class ConstructorOverridableCallTest
2023
{
21-
[TestClass]
22-
public class ConstructorOverridableCallTest
23-
{
24-
[TestMethod]
25-
public void ConstructorOverridableCall() =>
26-
new VerifierBuilder<ConstructorOverridableCall>().AddPaths("ConstructorOverridableCall.cs").Verify();
27-
}
24+
private readonly VerifierBuilder builder = new VerifierBuilder<ConstructorOverridableCall>();
25+
26+
[TestMethod]
27+
public void ConstructorOverridableCall() =>
28+
builder.AddPaths("ConstructorOverridableCall.cs").Verify();
29+
30+
[TestMethod]
31+
public void ConstructorOverridableCall_Nancy() =>
32+
builder
33+
.WithConcurrentAnalysis(false)
34+
.AddPaths("ConstructorOverridableCall.Nancy.cs")
35+
.VerifyNoIssues();
2836
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
3+
namespace Nancy
4+
{
5+
// Shim of a Nancy module
6+
// https://github.com/NancyFx/Nancy/wiki/Exploring-the-nancy-module
7+
public class NancyModule
8+
{
9+
public virtual void Get<T>(string path, Func<dynamic, T> action) { }
10+
}
11+
}
12+
13+
public class SampleModule : Nancy.NancyModule
14+
{
15+
public SampleModule()
16+
{
17+
Get("/", _ => "Hello World!"); // Compliant
18+
}
19+
}

analyzers/tests/SonarAnalyzer.Test/TestCases/ConstructorOverridableCall.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,58 @@ public override void DoSomething()
5555
Console.WriteLine(this.foo.Length);
5656
}
5757
}
58+
59+
public class BaseClass
60+
{
61+
public BaseClass(int a)
62+
{
63+
}
64+
65+
public virtual int DoSomething() { return 0; }
66+
public virtual int DoSomething(int a) { return 0; }
67+
}
68+
69+
public class InlineBaseThisCall : BaseClass
70+
{
71+
// Error@+1 [CS0120]
72+
public InlineBaseThisCall() : this(DoSomething()) { } // Noncompliant
73+
// Error@+1 [CS0120]
74+
public InlineBaseThisCall(int a) : base(DoSomething(a)) { } // Noncompliant
75+
}
76+
77+
// https://sonarsource.atlassian.net/browse/NET-1318
78+
public class LocalFuncion : BaseClass
79+
{
80+
public LocalFuncion() : base(0)
81+
{
82+
Local();
83+
84+
void Local()
85+
{
86+
DoSomething(); // FN
87+
}
88+
}
89+
}
5890
}
5991

6092
// https://github.com/SonarSource/sonar-dotnet/issues/6245
6193
namespace Tests.Diagnostics.Repro_6245
6294
{
6395
public class Class1
6496
{
97+
public Class1()
98+
{
99+
DoSomething(); // Noncompliant
100+
}
101+
65102
public virtual void DoSomething() { }
66103
}
67104

68105
public class ClassA2 : Class1
69106
{
70107
public ClassA2()
71108
{
72-
DoSomething(); // FN
109+
DoSomething(); // Noncompliant
73110
}
74111
}
75112

@@ -80,6 +117,7 @@ public class ClassA3 : ClassA2
80117
public ClassA3(string name)
81118
{
82119
Name = name;
120+
DoSomething(); // Noncompliant
83121
}
84122

85123
public override void DoSomething()

0 commit comments

Comments
 (0)