Skip to content

Commit 605228e

Browse files
Fix S1104 FP: Do not report in Unity serializable classes (#9514)
1 parent bc2307e commit 605228e

File tree

3 files changed

+85
-41
lines changed

3 files changed

+85
-41
lines changed

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

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,54 +18,59 @@
1818
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
*/
2020

21-
namespace SonarAnalyzer.Rules.CSharp
21+
namespace SonarAnalyzer.Rules.CSharp;
22+
23+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
24+
public sealed class FieldsShouldBeEncapsulatedInProperties : SonarDiagnosticAnalyzer
2225
{
23-
[DiagnosticAnalyzer(LanguageNames.CSharp)]
24-
public sealed class FieldsShouldBeEncapsulatedInProperties : SonarDiagnosticAnalyzer
25-
{
26-
private const string DiagnosticId = "S1104";
27-
private const string MessageFormat = "Make this field 'private' and encapsulate it in a 'public' property.";
26+
private const string DiagnosticId = "S1104";
27+
private const string MessageFormat = "Make this field 'private' and encapsulate it in a 'public' property.";
28+
29+
private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat);
2830

29-
private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat);
31+
private static readonly ISet<SyntaxKind> ValidModifiers = new HashSet<SyntaxKind>
32+
{
33+
SyntaxKind.PrivateKeyword,
34+
SyntaxKind.ProtectedKeyword,
35+
SyntaxKind.InternalKeyword,
36+
SyntaxKind.ReadOnlyKeyword,
37+
SyntaxKind.ConstKeyword
38+
};
3039

31-
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
40+
private static readonly ImmutableArray<KnownType> IgnoredTypes = ImmutableArray.Create(
41+
KnownType.UnityEngine_MonoBehaviour,
42+
KnownType.UnityEngine_ScriptableObject);
3243

33-
private static readonly ISet<SyntaxKind> ValidModifiers = new HashSet<SyntaxKind>
34-
{
35-
SyntaxKind.PrivateKeyword,
36-
SyntaxKind.ProtectedKeyword,
37-
SyntaxKind.InternalKeyword,
38-
SyntaxKind.ReadOnlyKeyword,
39-
SyntaxKind.ConstKeyword
40-
};
44+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule);
4145

42-
protected override void Initialize(SonarAnalysisContext context) =>
43-
context.RegisterNodeAction(
44-
c =>
46+
protected override void Initialize(SonarAnalysisContext context) =>
47+
context.RegisterNodeAction(
48+
c =>
49+
{
50+
var fieldDeclaration = (FieldDeclarationSyntax)c.Node;
51+
if (fieldDeclaration.Modifiers.Any(m => ValidModifiers.Contains(m.Kind())))
4552
{
46-
var fieldDeclaration = (FieldDeclarationSyntax)c.Node;
47-
if (fieldDeclaration.Modifiers.Any(m => ValidModifiers.Contains(m.Kind())))
48-
{
49-
return;
50-
}
53+
return;
54+
}
5155

52-
var firstVariable = fieldDeclaration.Declaration.Variables[0];
53-
var symbol = c.SemanticModel.GetDeclaredSymbol(firstVariable);
54-
var parentSymbol = c.SemanticModel.GetDeclaredSymbol(fieldDeclaration.Parent);
55-
if (parentSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_StructLayoutAttribute) || Serializable(symbol, parentSymbol))
56-
{
57-
return;
58-
}
56+
var firstVariable = fieldDeclaration.Declaration.Variables[0];
57+
var symbol = c.SemanticModel.GetDeclaredSymbol(firstVariable);
58+
var parentSymbol = c.SemanticModel.GetDeclaredSymbol(fieldDeclaration.Parent);
59+
if (symbol.ContainingType.DerivesFromAny(IgnoredTypes)
60+
|| parentSymbol.HasAttribute(KnownType.System_Runtime_InteropServices_StructLayoutAttribute)
61+
|| Serializable(symbol, parentSymbol))
62+
{
63+
return;
64+
}
5965

60-
if (symbol.GetEffectiveAccessibility() == Accessibility.Public)
61-
{
62-
c.ReportIssue(Rule, firstVariable);
63-
}
64-
},
65-
SyntaxKind.FieldDeclaration);
66+
if (symbol.GetEffectiveAccessibility() == Accessibility.Public)
67+
{
68+
c.ReportIssue(Rule, firstVariable);
69+
}
70+
},
71+
SyntaxKind.FieldDeclaration);
6672

67-
private static bool Serializable(ISymbol symbol, ISymbol parentSymbol) =>
68-
parentSymbol.HasAttribute(KnownType.System_SerializableAttribute)
69-
&& !symbol.HasAttribute(KnownType.System_NonSerializedAttribute);
70-
}
73+
private static bool Serializable(ISymbol symbol, ISymbol parentSymbol) =>
74+
parentSymbol.HasAttribute(KnownType.System_SerializableAttribute)
75+
&& !symbol.HasAttribute(KnownType.System_NonSerializedAttribute);
7176
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ public class FieldsShouldBeEncapsulatedInPropertiesTest
3131
public void FieldsShouldBeEncapsulatedInProperties() =>
3232
builder.AddPaths("FieldsShouldBeEncapsulatedInProperties.cs").Verify();
3333

34+
[TestMethod]
35+
public void FieldsShouldBeEncapsulatedInProperties_Unity3D_Ignored() =>
36+
builder.AddPaths("FieldsShouldBeEncapsulatedInProperties.Unity3D.cs")
37+
// Concurrent analysis puts fake Unity3D class into the Concurrent namespace
38+
.WithConcurrentAnalysis(false)
39+
.Verify();
40+
3441
#if NET
3542

3643
[TestMethod]
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
3+
// https://github.com/SonarSource/sonar-dotnet/issues/7522
4+
public class UnityMonoBehaviour : UnityEngine.MonoBehaviour
5+
{
6+
public int Field1; // Compliant
7+
}
8+
9+
public class UnityScriptableObject : UnityEngine.ScriptableObject
10+
{
11+
public int Field1; // Compliant
12+
}
13+
14+
public class InvalidCustomSerializableClass1 : UnityEngine.Object
15+
{
16+
public int Field1; // Noncompliant
17+
}
18+
19+
[Serializable]
20+
public class ValidCustomSerializableClass : UnityEngine.Object
21+
{
22+
public int Field1; // Compliant
23+
}
24+
25+
// Unity3D does not seem to be available as a nuget package and we cannot use the original classes
26+
// Cannot run this test case in Concurrent mode because of the Concurrent namespace
27+
namespace UnityEngine
28+
{
29+
public class MonoBehaviour { }
30+
public class ScriptableObject { }
31+
public class Object { }
32+
}

0 commit comments

Comments
 (0)