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
11 changes: 10 additions & 1 deletion docs/Rules/MA0042.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ The attribute supports:
- a documentation ID (`M:...` for methods, `P:...` for properties) for exact matching
- `Type` + member name (+ optional parameter types) for direct signature-based matching

You can also exclude `await` / `await using` recommendations for specific types using `NonAwaitableTypeAttribute`:
You can also exclude `await` recommendations for specific types using `NonAwaitableTypeAttribute`:
To use `Meziantou.Analyzer.Annotations.NonAwaitableTypeAttribute`, add the `Meziantou.Analyzer.Annotations` NuGet package (or copy the attribute source into your project). See [Meziantou.Analyzer.Annotations README](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/README.md).

```csharp
Expand All @@ -132,6 +132,15 @@ To use `Meziantou.Analyzer.Annotations.NonAwaitableTypeAttribute`, add the `Mezi

The match is exact-type only. Derived types are not excluded unless explicitly listed.

You can also exclude `await using` recommendations for specific types using `NonAsyncDisposableTypeAttribute`:
To use `Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute`, add the `Meziantou.Analyzer.Annotations` NuGet package (or copy the attribute source into your project). See [Meziantou.Analyzer.Annotations README](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/README.md).

```csharp
[assembly: Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute(typeof(System.Data.Common.DbCommand))]
```

The match is exact-type only. Derived types are not excluded unless explicitly listed.

This is useful for cases such as:

```csharp
Expand Down
11 changes: 10 additions & 1 deletion docs/Rules/MA0045.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ The attribute supports:
- a documentation ID (`M:...` for methods, `P:...` for properties) for exact matching
- `Type` + member name (+ optional parameter types) for direct signature-based matching

You can also exclude `await` / `await using` recommendations for specific types using `NonAwaitableTypeAttribute`:
You can also exclude `await` recommendations for specific types using `NonAwaitableTypeAttribute`:
To use `Meziantou.Analyzer.Annotations.NonAwaitableTypeAttribute`, add the `Meziantou.Analyzer.Annotations` NuGet package (or copy the attribute source into your project). See [Meziantou.Analyzer.Annotations README](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/README.md).

```csharp
Expand All @@ -75,6 +75,15 @@ To use `Meziantou.Analyzer.Annotations.NonAwaitableTypeAttribute`, add the `Mezi

The match is exact-type only. Derived types are not excluded unless explicitly listed.

You can also exclude `await using` recommendations for specific types using `NonAsyncDisposableTypeAttribute`:
To use `Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute`, add the `Meziantou.Analyzer.Annotations` NuGet package (or copy the attribute source into your project). See [Meziantou.Analyzer.Annotations README](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/README.md).

```csharp
[assembly: Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute(typeof(System.Data.Common.DbCommand))]
```

The match is exact-type only. Derived types are not excluded unless explicitly listed.

This is useful for cases such as:

```csharp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<Version>1.4.0</Version>
<Version>1.5.0</Version>
<Description>Annotations to configure Meziantou.Analyzer</Description>
<PackageTags>Meziantou.Analyzer, analyzers</PackageTags>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#pragma warning disable CS1591
#pragma warning disable IDE0060
#pragma warning disable CA1019

namespace Meziantou.Analyzer.Annotations;

[System.Diagnostics.Conditional("MEZIANTOU_ANALYZER_ANNOTATIONS")]
[System.AttributeUsage(System.AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public sealed class NonAsyncDisposableTypeAttribute : System.Attribute
{
public NonAsyncDisposableTypeAttribute(System.Type type) { }
}
14 changes: 12 additions & 2 deletions src/Meziantou.Analyzer.Annotations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ If you want to keep these attributes in the metadata (for example, for reflectio
| Attribute | Purpose | Related rules |
| --- | --- | --- |
| `CultureInsensitiveTypeAttribute` | Marks a type (or a specific format) as culture-insensitive. | [MA0011](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0011.md), [MA0075](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0075.md), [MA0076](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0076.md), [MA0185](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0185.md) |
| `NonAwaitableTypeAttribute` | Excludes await/await-using recommendations for specific types. | [MA0042](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0042.md), [MA0045](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0045.md), [MA0134](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0134.md), [MA0137](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0137.md), [MA0138](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0138.md) |
| `NonAwaitableTypeAttribute` | Excludes await recommendations for specific types. | [MA0042](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0042.md), [MA0045](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0045.md), [MA0134](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0134.md), [MA0137](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0137.md), [MA0138](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0138.md) |
| `NonAsyncDisposableTypeAttribute` | Excludes `await using` recommendations for specific types. | [MA0042](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0042.md), [MA0045](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0045.md) |
| `ExcludeFromBlockingCallAnalysisAttribute` | Excludes specific methods/properties from blocking-call diagnostics. | [MA0042](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0042.md), [MA0045](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0045.md) |
| `RequireNamedArgumentAttribute` | Requires named arguments for decorated parameters. | [MA0003](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0003.md) |
| `StructuredLogFieldAttribute` | Declares allowed types for named log properties in an assembly. | [MA0124](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0124.md), [MA0139](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0139.md) |
Expand All @@ -35,9 +36,18 @@ Use `ExcludeFromBlockingCallAnalysisAttribute` to exclude specific MA0042/MA0045

## NonAwaitableTypeAttribute

Use `NonAwaitableTypeAttribute` to exclude MA0042/MA0045 `await`/`await using` recommendations for specific types at the assembly level.
Use `NonAwaitableTypeAttribute` to exclude MA0042/MA0045 `await` recommendations for specific types at the assembly level.
The match is exact-type only. Derived types are not excluded unless explicitly listed.

```csharp
[assembly: Meziantou.Analyzer.Annotations.NonAwaitableTypeAttribute(typeof(System.Data.Common.DbCommand))]
```

## NonAsyncDisposableTypeAttribute

Use `NonAsyncDisposableTypeAttribute` to exclude MA0042/MA0045 `await using` recommendations for specific types at the assembly level.
The match is exact-type only. Derived types are not excluded unless explicitly listed.

```csharp
[assembly: Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute(typeof(System.Data.Common.DbCommand))]
```
22 changes: 22 additions & 0 deletions src/Meziantou.Analyzer/Internals/AnnotationAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,26 @@ public static bool IsNonAwaitableTypeAttributeSymbol(ITypeSymbol? symbol)
}
};
}

public static bool IsNonAsyncDisposableTypeAttributeSymbol(ITypeSymbol? symbol)
{
// Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute
return symbol is INamedTypeSymbol
{
Name: "NonAsyncDisposableTypeAttribute",
ContainingSymbol: INamespaceSymbol
{
Name: "Annotations",
ContainingSymbol: INamespaceSymbol
{
Name: "Analyzer",
ContainingSymbol: INamespaceSymbol
{
Name: "Meziantou",
ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true }
}
}
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private sealed class Context

private readonly INamedTypeSymbol[] _taskAwaiterLikeSymbols;
private readonly HashSet<ISymbol> _excludedDiagnosticSymbols;
private readonly HashSet<INamedTypeSymbol> _nonAsyncDisposableTypes;
private readonly ConcurrentHashSet<IMethodSymbol> _symbolsWithNoAsyncOverloads = new(SymbolEqualityComparer.Default);

public Context(Compilation compilation)
Expand Down Expand Up @@ -132,6 +133,7 @@ public Context(Compilation compilation)
taskAwaiterLikeSymbols.AddIfNotNull(ValueTaskAwaiterOfTSymbol);
_taskAwaiterLikeSymbols = [.. taskAwaiterLikeSymbols];
_excludedDiagnosticSymbols = CreateExcludedDiagnosticSymbols(compilation);
_nonAsyncDisposableTypes = CreateNonAsyncDisposableTypes(compilation);
}

private ISymbol? StreamSymbol { get; }
Expand Down Expand Up @@ -419,6 +421,47 @@ static void AddExcludedSymbol(HashSet<ISymbol> symbols, ISymbol symbol)
}
}

private bool IsNonAsyncDisposableType(ITypeSymbol? symbol)
{
if (_nonAsyncDisposableTypes.Count == 0 || symbol is not INamedTypeSymbol namedType)
return false;

return IsConfiguredType(namedType, _nonAsyncDisposableTypes);
}

private static HashSet<INamedTypeSymbol> CreateNonAsyncDisposableTypes(Compilation compilation)
{
var result = new HashSet<INamedTypeSymbol>(SymbolEqualityComparer.Default);
foreach (var attribute in compilation.Assembly.GetAttributes())
{
if (!AnnotationAttributes.IsNonAsyncDisposableTypeAttributeSymbol(attribute.AttributeClass))
continue;

var constructorArguments = attribute.ConstructorArguments;
if (constructorArguments is [{ Value: INamedTypeSymbol type }])
{
result.Add(type);
if (!ReferenceEquals(type, type.OriginalDefinition))
{
result.Add(type.OriginalDefinition);
}
}
}

return result;
}

private static bool IsConfiguredType(INamedTypeSymbol type, HashSet<INamedTypeSymbol> configuredTypes)
{
if (configuredTypes.Contains(type))
return true;

if (!ReferenceEquals(type, type.OriginalDefinition) && configuredTypes.Contains(type.OriginalDefinition))
return true;

return false;
}

private bool IsExcludedDiagnosticSymbol(ISymbol symbol)
{
if (_excludedDiagnosticSymbols.Count == 0)
Expand Down Expand Up @@ -662,7 +705,7 @@ private bool CanBeAwaitUsing(IOperation operation, bool sqliteSpecialCasesEnable
if (operation.GetActualType() is not INamedTypeSymbol type)
return false;

if (_awaitableTypes.IsNonAwaitableType(type))
if (IsNonAsyncDisposableType(type))
return false;

var isSqliteSpecialCaseType = IsSqliteSpecialCaseType(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3617,7 +3617,7 @@ public void Dispose() { }
}

[Fact]
public async Task NonAwaitableTypeAttribute_DoesAffectAwaitUsing()
public async Task NonAwaitableTypeAttribute_DoesNotAffectAwaitUsing()
{
await CreateProjectBuilder()
.AddMeziantouAttributes()
Expand All @@ -3626,6 +3626,33 @@ await CreateProjectBuilder()
using System.Threading.Tasks;
[assembly: Meziantou.Analyzer.Annotations.NonAwaitableTypeAttribute(typeof(AsyncDisposable))]

class Test
{
public async Task A()
{
[|using var value = new AsyncDisposable();|]
}
}

class AsyncDisposable : IDisposable, IAsyncDisposable
{
public void Dispose() { }
public ValueTask DisposeAsync() => default;
}
""")
.ValidateAsync();
}

[Fact]
public async Task NonAsyncDisposableTypeAttribute_DoesAffectAwaitUsing()
{
await CreateProjectBuilder()
.AddMeziantouAttributes()
.WithSourceCode("""
using System;
using System.Threading.Tasks;
[assembly: Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute(typeof(AsyncDisposable))]

class Test
{
public async Task A()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ public void Dispose() { }
}

[Fact]
public async Task NonAwaitableTypeAttribute_DoesAffectAwaitUsing()
public async Task NonAwaitableTypeAttribute_DoesNotAffectAwaitUsing()
{
await CreateProjectBuilder()
.AddMeziantouAttributes()
Expand All @@ -428,6 +428,33 @@ await CreateProjectBuilder()
using System.Threading.Tasks;
[assembly: Meziantou.Analyzer.Annotations.NonAwaitableTypeAttribute(typeof(AsyncDisposable))]

class Test
{
private void A()
{
[|using var value = new AsyncDisposable();|]
}
}

class AsyncDisposable : IDisposable, IAsyncDisposable
{
public void Dispose() { }
public ValueTask DisposeAsync() => default;
}
""")
.ValidateAsync();
}

[Fact]
public async Task NonAsyncDisposableTypeAttribute_DoesAffectAwaitUsing()
{
await CreateProjectBuilder()
.AddMeziantouAttributes()
.WithSourceCode("""
using System;
using System.Threading.Tasks;
[assembly: Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute(typeof(AsyncDisposable))]

class Test
{
private void A()
Expand Down
Loading