Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0170](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0170.md)|Design|Type cannot be used as an attribute argument|⚠️|❌|❌|
|[MA0171](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0171.md)|Usage|Use pattern matching instead of inequality operators for discrete value|ℹ️|❌|✔️|
|[MA0172](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0172.md)|Usage|Both sides of the logical operation are identical|⚠️|❌|❌|
|[MA0173](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0173.md)|Design|Use LazyInitializer.EnsureInitialize|ℹ️|✔️|❌|

<!-- rules -->

Expand Down
7 changes: 7 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@
|[MA0170](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0170.md)|Design|Type cannot be used as an attribute argument|<span title='Warning'>⚠️</span>|❌|❌|
|[MA0171](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0171.md)|Usage|Use pattern matching instead of inequality operators for discrete value|<span title='Info'>ℹ️</span>|❌|✔️|
|[MA0172](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0172.md)|Usage|Both sides of the logical operation are identical|<span title='Warning'>⚠️</span>|❌|❌|
|[MA0173](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0173.md)|Design|Use LazyInitializer.EnsureInitialize|<span title='Info'>ℹ️</span>|✔️|❌|

|Id|Suppressed rule|Justification|
|--|---------------|-------------|
Expand Down Expand Up @@ -696,6 +697,9 @@ dotnet_diagnostic.MA0171.severity = none

# MA0172: Both sides of the logical operation are identical
dotnet_diagnostic.MA0172.severity = none

# MA0173: Use LazyInitializer.EnsureInitialize
dotnet_diagnostic.MA0173.severity = suggestion
```

# .editorconfig - all rules disabled
Expand Down Expand Up @@ -1213,4 +1217,7 @@ dotnet_diagnostic.MA0171.severity = none

# MA0172: Both sides of the logical operation are identical
dotnet_diagnostic.MA0172.severity = none

# MA0173: Use LazyInitializer.EnsureInitialize
dotnet_diagnostic.MA0173.severity = none
```
7 changes: 7 additions & 0 deletions docs/Rules/MA0173.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# MA0173 - Use LazyInitializer.EnsureInitialize

```c#
Interlocked.CompareExchange(ref _field, new Sample(), null); // non-compliant
LazyInitializer.EnsureInitialized(ref _field, () => new Sample()); // compliant
```

Original file line number Diff line number Diff line change
Expand Up @@ -514,3 +514,6 @@ dotnet_diagnostic.MA0171.severity = none

# MA0172: Both sides of the logical operation are identical
dotnet_diagnostic.MA0172.severity = none

# MA0173: Use LazyInitializer.EnsureInitialize
dotnet_diagnostic.MA0173.severity = suggestion
3 changes: 3 additions & 0 deletions src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -514,3 +514,6 @@ dotnet_diagnostic.MA0171.severity = none

# MA0172: Both sides of the logical operation are identical
dotnet_diagnostic.MA0172.severity = none

# MA0173: Use LazyInitializer.EnsureInitialize
dotnet_diagnostic.MA0173.severity = none
1 change: 1 addition & 0 deletions src/Meziantou.Analyzer/Internals/OperationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,5 @@ static bool IsValid(Location location, int operationLocation, int? staticContext
}

public static bool IsConstantZero(this IOperation operation) => operation is { ConstantValue: { HasValue: true, Value: 0 or 0L or 0u or 0uL or 0f or 0d or 0m } };
public static bool IsNull(this IOperation operation) => operation is { ConstantValue: { HasValue: true, Value: null } };
}
1 change: 1 addition & 0 deletions src/Meziantou.Analyzer/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ internal static class RuleIdentifiers
public const string TypeCannotBeUsedInAnAttributeParameter = "MA0170";
public const string UsePatternMatchingInsteadOfHasvalue = "MA0171";
public const string BothSideOfTheConditionAreIdentical = "MA0172";
public const string UseLazyInitializerEnsureInitialize = "MA0173";

public static string GetHelpUri(string identifier)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Collections.Immutable;
using Meziantou.Analyzer.Internals;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Meziantou.Analyzer.Rules;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class UseLazyInitializerEnsureInitializeAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new(
RuleIdentifiers.UseLazyInitializerEnsureInitialize,
title: "Use LazyInitializer.EnsureInitialize",
messageFormat: "Use LazyInitializer.EnsureInitialize",
RuleCategories.Design,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: "",
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.UseLazyInitializerEnsureInitialize));

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

context.RegisterCompilationStartAction(compilationContext =>
{
var interlockedType = compilationContext.Compilation.GetBestTypeByMetadataName("System.Threading.Interlocked");

compilationContext.RegisterOperationAction(context =>
{
var operation = (IInvocationOperation)context.Operation;
var targetMethod = operation.TargetMethod;

// Interlocked.CompareExchange(ref _instance, new Sample(), null)
if (operation.Arguments.Length is 3 && targetMethod.Name is "CompareExchange" && targetMethod.ContainingType.IsEqualTo(interlockedType))
{
if (operation.Arguments[2].Value.IsNull() && operation.Arguments[1].Value is IObjectCreationOperation)
{
context.ReportDiagnostic(Rule, operation);
}
}
}, OperationKind.Invocation);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using Meziantou.Analyzer.Rules;
using Meziantou.Analyzer.Test.Helpers;
using TestHelper;
using Xunit;

namespace Meziantou.Analyzer.Test.Rules;

public sealed class UseLazyInitializerEnsureInitializeAnalyzerTests
{
private static ProjectBuilder CreateProjectBuilder()
{
return new ProjectBuilder()
.WithAnalyzer<UseLazyInitializerEnsureInitializeAnalyzer>()
.WithTargetFramework(TargetFramework.NetLatest)
.WithOutputKind(Microsoft.CodeAnalysis.OutputKind.ConsoleApplication);
}

[Fact]
public async Task NewObject_Null()
{
await CreateProjectBuilder()
.WithSourceCode("""
object a = default;
[|System.Threading.Interlocked.CompareExchange(ref a, new object(), null)|];
""")
.ValidateAsync();
}

[Fact]
public async Task NewCustomClass_Null()
{
await CreateProjectBuilder()
.WithSourceCode("""
Sample a = default;
[|System.Threading.Interlocked.CompareExchange(ref a, new Sample(), null)|];
class Sample { };
""")
.ValidateAsync();
}

[Fact]
public async Task NewCustomClass_Default()
{
await CreateProjectBuilder()
.WithSourceCode("""
Sample a = default;
[|System.Threading.Interlocked.CompareExchange(ref a, new Sample(), default)|];
class Sample { };
""")
.ValidateAsync();
}

[Fact]
public async Task NewCustomStruct()
{
await CreateProjectBuilder()
.WithSourceCode("""
Sample a = default;
System.Threading.Interlocked.CompareExchange(ref a, new Sample(), default);
struct Sample { };
""")
.ValidateAsync();
}

[Fact]
public async Task NewInt32_Zero()
{
await CreateProjectBuilder()
.WithSourceCode("""
int a = default;
System.Threading.Interlocked.CompareExchange(ref a, 0, 0);
""")
.ValidateAsync();
}
}