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 @@ -185,6 +185,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0167](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0167.md)|Usage|Use an overload with a TimeProvider argument|ℹ️|❌|❌|
|[MA0168](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0168.md)|Performance|Use readonly struct for in or ref readonly parameter|ℹ️|❌|❌|
|[MA0169](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0169.md)|Design|Use Equals method instead of operator|⚠️|✔️|❌|
|[MA0170](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0170.md)|Design|Type cannot be used as an attribute argument|⚠️|❌|❌|

<!-- rules -->

Expand Down
7 changes: 7 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
|[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>|❌|❌|
|[MA0168](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0168.md)|Performance|Use readonly struct for in or ref readonly parameter|<span title='Info'>ℹ️</span>|❌|❌|
|[MA0169](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0169.md)|Design|Use Equals method instead of operator|<span title='Warning'>⚠️</span>|✔️|❌|
|[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>|❌|❌|

|Id|Suppressed rule|Justification|
|--|---------------|-------------|
Expand Down Expand Up @@ -684,6 +685,9 @@ dotnet_diagnostic.MA0168.severity = none

# MA0169: Use Equals method instead of operator
dotnet_diagnostic.MA0169.severity = warning

# MA0170: Type cannot be used as an attribute argument
dotnet_diagnostic.MA0170.severity = none
```

# .editorconfig - all rules disabled
Expand Down Expand Up @@ -1192,4 +1196,7 @@ dotnet_diagnostic.MA0168.severity = none

# MA0169: Use Equals method instead of operator
dotnet_diagnostic.MA0169.severity = none

# MA0170: Type cannot be used as an attribute argument
dotnet_diagnostic.MA0170.severity = none
```
20 changes: 20 additions & 0 deletions docs/Rules/MA0170.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# MA0170 - Type cannot be used as an attribute argument

Report any constructor parameters, fields, or properties that are not supported by the C# language as attribute arguments as defined in https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/attributes?WT.mc_id=DT-MVP-5003978#2224-attribute-parameter-types.

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

- One of the following types: `bool`, `byte`, `char`, `double`, `float`, `int`, `long`, `sbyte`, `short`, `string`, `uint`, `ulong`, `ushort`.
- The type `object`.
- The type `System.Type`.
- Enum types.
- Single-dimensional arrays of the above types.


```c#
class SampleAttribute : Attribute
{
public SampleAttribute(int value) { } // ok
public SampleAttribute(System.Action value) { } // non-compliant
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,6 @@ dotnet_diagnostic.MA0168.severity = none

# MA0169: Use Equals method instead of operator
dotnet_diagnostic.MA0169.severity = warning

# MA0170: Type cannot be used as an attribute argument
dotnet_diagnostic.MA0170.severity = none
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 @@ -505,3 +505,6 @@ dotnet_diagnostic.MA0168.severity = none

# MA0169: Use Equals method instead of operator
dotnet_diagnostic.MA0169.severity = none

# MA0170: Type cannot be used as an attribute argument
dotnet_diagnostic.MA0170.severity = none
1 change: 1 addition & 0 deletions src/Meziantou.Analyzer/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ internal static class RuleIdentifiers
public const string UseAnOverloadThatHasTimeProvider = "MA0167";
public const string UseReadOnlyStructForRefReadOnlyParameters = "MA0168";
public const string UseEqualsMethodInsteadOfOperator = "MA0169";
public const string TypeCannotBeUsedInAnAttributeParameter = "MA0170";

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

namespace Meziantou.Analyzer.Rules;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class TypeCannotBeUsedInAnAttributeParameterAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new(
RuleIdentifiers.TypeCannotBeUsedInAnAttributeParameter,
title: "Type cannot be used as an attribute argument",
messageFormat: "Type cannot be used as an attribute argument",
RuleCategories.Design,
DiagnosticSeverity.Warning,
isEnabledByDefault: false,
description: "",
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.TypeCannotBeUsedInAnAttributeParameter));

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

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(context =>
{
var analyzerContext = new AnalyzerContext(context.Compilation);
if (!analyzerContext.IsValid)
return;

context.RegisterSymbolAction(context =>
{
var method = (IMethodSymbol)context.Symbol;
if (method.MethodKind is MethodKind.Constructor && method.DeclaredAccessibility is Accessibility.Public or Accessibility.Internal && analyzerContext.IsAttribute(method))
{
foreach (var parameter in method.Parameters)
{
if (!analyzerContext.IsTypeValid(parameter.Type))
{
context.ReportDiagnostic(Rule, parameter);
}
}
}
}, SymbolKind.Method);

context.RegisterSymbolAction(context =>
{
var field = (IFieldSymbol)context.Symbol;
if (field.DeclaredAccessibility is Accessibility.Public && !field.IsStatic && !field.IsReadOnly && !field.IsConst && analyzerContext.IsAttribute(field))
{
if (!analyzerContext.IsTypeValid(field.Type))
{
context.ReportDiagnostic(Rule, field);
}
}
}, SymbolKind.Field);

context.RegisterSymbolAction(context =>
{
var property = (IPropertySymbol)context.Symbol;
if (property.DeclaredAccessibility is Accessibility.Public && property.SetMethod is not null && !property.IsStatic && analyzerContext.IsAttribute(property))
{
if (!analyzerContext.IsTypeValid(property.Type))
{
context.ReportDiagnostic(Rule, property);
}
}
}, SymbolKind.Property);
});
}

private sealed class AnalyzerContext(Compilation compilation)
{
private readonly ITypeSymbol? _attributeSymbol = compilation.GetBestTypeByMetadataName("System.Attribute");
private readonly ITypeSymbol? _typeSymbol = compilation.GetBestTypeByMetadataName("System.Type");
private readonly ITypeSymbol? _enumSymbol = compilation.GetBestTypeByMetadataName("System.Enum");

public bool IsValid => _attributeSymbol is not null;

public bool IsAttribute(ISymbol methodSymbol)
{
return methodSymbol.ContainingType.IsOrInheritFrom(_attributeSymbol);
}

public bool IsTypeValid(ITypeSymbol type)
{
return IsTypeValid(type, allowArray: true);

bool IsTypeValid(ITypeSymbol type, bool allowArray)
{
switch (type.SpecialType)
{
case SpecialType.System_Boolean:
case SpecialType.System_Char:
case SpecialType.System_Byte:
case SpecialType.System_SByte:
case SpecialType.System_Int16:
case SpecialType.System_Int32:
case SpecialType.System_Int64:
case SpecialType.System_String:
case SpecialType.System_UInt32:
case SpecialType.System_UInt64:
case SpecialType.System_UInt16:
case SpecialType.System_Single:
case SpecialType.System_Double:
case SpecialType.System_Object:
case SpecialType.System_Enum:
return true;
}

if (type.IsEqualTo(_typeSymbol))
return true;

if (type.IsOrInheritFrom(_enumSymbol))
return true;

if (allowArray && type is IArrayTypeSymbol array && array.Rank is 1 && IsTypeValid(array.ElementType, allowArray: false))
return true;

return false;
}
}
}
}
Loading