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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,8 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0163](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0163.md)|Usage|UseShellExecute must be false when redirecting standard input or output|⚠️|✔️|❌|
|[MA0164](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0164.md)|Style|Use parentheses to make not pattern clearer|⚠️|✔️|✔️|
|[MA0165](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0165.md)|Usage|Make interpolated string|👻|✔️|✔️|
|[MA0166](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0166.md)|Usage|Forward the TimeProvider to methods that take one|ℹ️|✔️|✔️|
|[MA0167](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0167.md)|Usage|Use an overload with a TimeProvider argument|ℹ️|❌|❌|

<!-- rules -->

Expand Down
14 changes: 14 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@
|[MA0163](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0163.md)|Usage|UseShellExecute must be false when redirecting standard input or output|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0164](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0164.md)|Style|Use parentheses to make not pattern clearer|<span title='Warning'>⚠️</span>|✔️|✔️|
|[MA0165](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0165.md)|Usage|Make interpolated string|<span title='Hidden'>👻</span>|✔️|✔️|
|[MA0166](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0166.md)|Usage|Forward the TimeProvider to methods that take one|<span title='Info'>ℹ️</span>|✔️|✔️|
|[MA0167](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0167.md)|Usage|Use an overload with a TimeProvider argument|<span title='Info'>ℹ️</span>|❌|❌|

|Id|Suppressed rule|Justification|
|--|---------------|-------------|
Expand Down Expand Up @@ -668,6 +670,12 @@ dotnet_diagnostic.MA0164.severity = warning

# MA0165: Make interpolated string
dotnet_diagnostic.MA0165.severity = silent

# MA0166: Forward the TimeProvider to methods that take one
dotnet_diagnostic.MA0166.severity = suggestion

# MA0167: Use an overload with a TimeProvider argument
dotnet_diagnostic.MA0167.severity = none
```

# .editorconfig - all rules disabled
Expand Down Expand Up @@ -1164,4 +1172,10 @@ dotnet_diagnostic.MA0164.severity = none

# MA0165: Make interpolated string
dotnet_diagnostic.MA0165.severity = none

# MA0166: Forward the TimeProvider to methods that take one
dotnet_diagnostic.MA0166.severity = none

# MA0167: Use an overload with a TimeProvider argument
dotnet_diagnostic.MA0167.severity = none
```
16 changes: 16 additions & 0 deletions docs/Rules/MA0166.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# MA0166 - Forward the TimeProvider to methods that take one

You should pass a `System.TimeProvider` when calling a method if there is an overload of the method that supports it.

````csharp
class Test
{
public void A(System.TimeProvider timeProvider)
{
Task.Delay(default(TimeSpan)); // non-compliant
Task.Delay(default(TimeSpan), timeProvider); // compliant
}
}
````

This rule only reports a diagnostic when a `System.TimeProvider` is available in the scope. [MA0167](MA0167.md) detects the same cases, but reports them even if applying a fix would require you to change the calling method's signature.
14 changes: 14 additions & 0 deletions docs/Rules/MA0167.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# MA0167 - Use an overload with a TimeProvider argument


You should pass a `System.TimeProvider` when calling a method if there is an overload of the method that supports it.

````csharp
class Test
{
public void A()
{
Task.Delay(default(TimeSpan)); // non-compliant
}
}
````
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using System.Collections.Immutable;
using System.Composition;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Operations;

namespace Meziantou.Analyzer.Rules;

[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class UseAnOverloadThatHasTimeProviderFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(RuleIdentifiers.UseAnOverloadThatHasTimeProviderWhenAvailable);

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true);
if (nodeToFix is null)
return;

if (nodeToFix.IsKind(SyntaxKind.InvocationExpression))
{
if (!int.TryParse(context.Diagnostics[0].Properties["ParameterIndex"], NumberStyles.None, CultureInfo.InvariantCulture, out var parameterIndex))
return;

if (!context.Diagnostics[0].Properties.TryGetValue("ParameterName", out var parameterName) || parameterName is null)
return;

if (!context.Diagnostics[0].Properties.TryGetValue("Paths", out var paths) || paths is null)
return;

foreach (var cancellationToken in paths.Split(','))
{
var title = "Use TimeProvider: " + cancellationToken;
var codeAction = CodeAction.Create(
title,
ct => FixInvocation(context.Document, (InvocationExpressionSyntax)nodeToFix, parameterIndex, parameterName, cancellationToken, ct),
equivalenceKey: title);

context.RegisterCodeFix(codeAction, context.Diagnostics);
}
}
}

private static async Task<Document> FixInvocation(Document document, InvocationExpressionSyntax nodeToFix, int index, string parameterName, string cancellationTokenText, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var generator = editor.Generator;

var cancellationTokenExpression = SyntaxFactory.ParseExpression(cancellationTokenText);

SyntaxNode newInvocation;
if (index > nodeToFix.ArgumentList.Arguments.Count)
{
var newArguments = nodeToFix.ArgumentList.Arguments.Add((ArgumentSyntax)generator.Argument(parameterName, RefKind.None, cancellationTokenExpression));
newInvocation = nodeToFix.WithArgumentList(SyntaxFactory.ArgumentList(newArguments));
}
else
{
var newArguments = nodeToFix.ArgumentList.Arguments.Insert(index, (ArgumentSyntax)generator.Argument(cancellationTokenExpression));
newInvocation = nodeToFix.WithArgumentList(SyntaxFactory.ArgumentList(newArguments));
}

editor.ReplaceNode(nodeToFix, newInvocation);
return editor.GetChangedDocument();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,9 @@ dotnet_diagnostic.MA0164.severity = warning

# MA0165: Make interpolated string
dotnet_diagnostic.MA0165.severity = silent

# MA0166: Forward the TimeProvider to methods that take one
dotnet_diagnostic.MA0166.severity = suggestion

# MA0167: Use an overload with a TimeProvider argument
dotnet_diagnostic.MA0167.severity = none
6 changes: 6 additions & 0 deletions src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -493,3 +493,9 @@ dotnet_diagnostic.MA0164.severity = none

# MA0165: Make interpolated string
dotnet_diagnostic.MA0165.severity = none

# MA0166: Forward the TimeProvider to methods that take one
dotnet_diagnostic.MA0166.severity = none

# MA0167: Use an overload with a TimeProvider argument
dotnet_diagnostic.MA0167.severity = none
4 changes: 3 additions & 1 deletion src/Meziantou.Analyzer/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Globalization;
using System.Globalization;

namespace Meziantou.Analyzer;

Expand Down Expand Up @@ -168,6 +168,8 @@ internal static class RuleIdentifiers
public const string UseShellExecuteMustBeFalse = "MA0163";
public const string NotPatternShouldBeParenthesized = "MA0164";
public const string MakeInterpolatedString = "MA0165";
public const string UseAnOverloadThatHasTimeProviderWhenAvailable = "MA0166";
public const string UseAnOverloadThatHasTimeProvider = "MA0167";

public static string GetHelpUri(string identifier)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down Expand Up @@ -85,7 +85,6 @@ private sealed class AnalyzerContext(Compilation compilation)

private readonly OverloadFinder _overloadFinder = new(compilation);

public Compilation Compilation { get; } = compilation;
public INamedTypeSymbol CancellationTokenSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Threading.CancellationToken")!; // Not nullable as it is checked before registering the Operation actions
public INamedTypeSymbol? CancellationTokenSourceSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Threading.CancellationTokenSource");
private INamedTypeSymbol? TaskSymbol { get; } = compilation.GetBestTypeByMetadataName("System.Threading.Tasks.Task");
Expand Down
Loading