Skip to content

Commit

Permalink
New Rule T0012: Use null instead of default for reference types (#9140)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastien-marichal committed Apr 24, 2024
1 parent 96b281e commit 993671c
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2024 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Rules.CSharp.Styling;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseNullInsteadOfDefault : StylingAnalyzer
{
public UseNullInsteadOfDefault() : base("T0012", "Use 'null' instead of 'default' for reference types.") { }

protected override void Initialize(SonarAnalysisContext context) =>
context.RegisterNodeAction(c =>
{
if (IsReferenceType(c.Node, c.SemanticModel))
{
c.ReportIssue(Rule, c.Node);
}
},
SyntaxKind.DefaultLiteralExpression, SyntaxKind.DefaultExpression);

private static bool IsReferenceType(SyntaxNode node, SemanticModel model)
{
var type = model.GetTypeInfo(node).Type;
return (type.IsReferenceType && type is not IErrorTypeSymbol) || type.Is(KnownType.System_Nullable_T);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

namespace SonarAnalyzer.Helpers;

internal static class CSharpSyntaxHelper
public static class CSharpSyntaxHelper
{
public static readonly ExpressionSyntax NullLiteralExpression =
SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression);
Expand Down Expand Up @@ -221,7 +221,7 @@ public static bool IsStringEmpty(this ExpressionSyntax expression, SemanticModel
public static bool NameIs(this SyntaxNode node, string name, params string[] orNames) =>
node.GetName() is { } nodeName
&& (nodeName.Equals(name, StringComparison.Ordinal)
|| orNames.Any(x => nodeName.Equals(x, StringComparison.Ordinal)));
|| Array.Exists(orNames, x => nodeName.Equals(x, StringComparison.Ordinal)));

public static bool HasConstantValue(this ExpressionSyntax expression, SemanticModel semanticModel) =>
expression.RemoveParentheses().IsAnyKind(LiteralSyntaxKinds) || expression.FindConstantValue(semanticModel) != null;
Expand Down
5 changes: 1 addition & 4 deletions analyzers/src/SonarAnalyzer.Common/Helpers/TypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using Microsoft.CodeAnalysis;
using static Google.Protobuf.WellKnownTypes.Field;

namespace SonarAnalyzer.Helpers;

internal static class TypeHelper
public static class TypeHelper
{
#region TypeKind

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2024 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.CSharp.Styling.Test.Rules;

[TestClass]
public class UseNullInsteadOfDefaultTest
{
private readonly VerifierBuilder builder = StylingVerifierBuilder.Create<UseNullInsteadOfDefault>();

[TestMethod]
public void UseNullInsteadOfDefault() =>
builder.AddPaths("UseNullInsteadOfDefault.cs").Verify();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;

class UseNullInsteadOfDefault
{
void Method()
{
_ = default; // Error [CS8716]
var discarded = default; // Error [CS8716]
int integer = default;
int x = default, y = 0, z = default(int);
var integer2 = default(Int32);
object objectReference = default; // Noncompliant {{Use 'null' instead of 'default' for reference types.}}
// ^^^^^^^
var objectReference2 = default(object); // Noncompliant {{Use 'null' instead of 'default' for reference types.}}
// ^^^^^^^^^^^^^^^
object obj1 = default, obj2 = null, obj3 = default(object);
// ^^^^^^^
// ^^^^^^^^^^^^^^^@-1
IEnumerable<string> collection = null;
IEnumerable<string> collection2 = default; // Noncompliant
IEnumerable<string> collection3 = true ? null : default; // Noncompliant
collection = null;
collection2 = default; // Noncompliant
collection2 = true ? null : default; // Noncompliant

_ = default(object) is null; // Noncompliant FP - Do we care?
_ = collection is default(IEnumerable<string>); // Noncompliant


int? nullableInt = default; // Noncompliant

_ = collection == default; // Noncompliant

Use(null);
Use(default); // Noncompliant

switch (collection)
{
case default(IEnumerable<string>): // Noncompliant
break;
default:
break;
}

switch (integer)
{
case default(int):
break;
default:
break;
}
}

void Use(string s) { }
void OptionalWithNull(string name = null) { }
void OptionalWithDefault(string name = default) { } // Noncompliant
void OptionalWithNull(int? name = null) { }
void OptionalWithDefault(int name = default) { }
void OptionalWithDefault(int? name = default) { } // Noncompliant

void GenericMethod<T>()
{
T t = default;
var t2 = default(T);
T? t3 = default;
T? t4 = default(T);

t = default;
t = default(T);
t3 = default;
t3 = default(T);

_ = t == default; // Error [CS8761]
_ = t3 == default; // Error [CS8761]
_ = t is default(T); // Error [CS0150]
_ = t3 is default(T); // Error [CS0150]

switch (t)
{
case default(T): // Error [CS0150]
break;
default:
break;
}

switch (t3)
{
case default(T): // Error [CS0150]
break;
default:
break;
}
}

void GenericMethodClassConstraint<T>()
where T : class
{
T t = default; // Noncompliant
var t2 = default(T); // Noncompliant
T? t3 = default; // Noncompliant
T? t4 = default(T); // Noncompliant

t = default; // Noncompliant
t = default(T); // Noncompliant
t3 = default; // Noncompliant
t3 = default(T); // Noncompliant

_ = t == default; // Noncompliant
_ = t3 == default; // Noncompliant
_ = t3 != default; // Noncompliant
_ = t is default(T); // Noncompliant
_ = t3 is default(T); // Noncompliant
_ = t is not default(T); // Noncompliant
_ = t3 is not default(T); // Noncompliant

switch (t)
{
case default(T): // Noncompliant
break;
default:
break;
}

switch (t3)
{
case default(T): // Noncompliant
break;
default:
break;
}
}

void GenericMethodStructConstraint<T>()
where T : struct
{
T t = default;
var t2 = default(T);

T? t3 = default; // Noncompliant
}

void GenericMethodEnumConstraint<T>()
where T : Enum
{
T t = default;
var t2 = default(T);

T? t3 = default; // FN
}

void GenericMethodStructEnumConstraint<T>()
where T : struct, Enum
{
T t = default;
var t2 = default(T);
T? t3 = default; // Noncompliant
T? t4 = default(T); // FN

t = default;
t = default(T);
t3 = default; // Noncompliant
t3 = default(T);

_ = t == default; // Error [CS8761]
_ = t3 == default; // Error [CS0019]
_ = t is default(T); // Error [CS0150]
_ = t3 is default(T); // Error [CS0150]

switch (t)
{
case default(T): // Error [CS0150]
break;
default:
break;
}

switch (t3)
{
case default(T): // Error [CS0150]
break;
default:
break;
}
}
}

0 comments on commit 993671c

Please sign in to comment.