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
89 changes: 89 additions & 0 deletions docs/CultureInsensitiveAttribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# CultureInsensitiveAttribute

The `CultureInsensitiveAttribute` is used to mark the value of a property, a field, or a parameter as culture-insensitive, even when the type of the value is culture-sensitive. This attribute can be used to suppress culture-related analyzer rules ([MA0011](Rules/MA0011.md), [MA0075](Rules/MA0075.md), [MA0076](Rules/MA0076.md)) for that value.

Use [CultureInsensitiveTypeAttribute](CultureInsensitiveTypeAttribute.md) when a whole type is culture-insensitive, and `CultureInsensitiveAttribute` when only some values of a culture-sensitive type are known to be culture-insensitive.

## Usage

The attribute is available through the [`Meziantou.Analyzer.Annotations`](https://www.nuget.org/packages/Meziantou.Analyzer.Annotations/) NuGet package.

Alternatively, you can define the attribute in your own assembly instead of using the package. The analyzer only looks for the attribute by name and namespace, so you can copy the [attribute definition](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/CultureInsensitiveAttribute.cs) into your project.

### Marking a Property or a Field

```csharp
using Meziantou.Analyzer.Annotations;

class Sample
{
[CultureInsensitive]
public double Value { get; set; }

[CultureInsensitive]
public double Field;

public double OtherValue { get; set; }
}

// Usage
_ = $"{sample.Value} {sample.Field}"; // OK - Both values are marked as culture-insensitive
_ = "value: " + sample.OtherValue; // Warning - MA0075
```

Methods cannot be annotated: the attribute marks a value, not the way a method computes it. When a method returns a culture-insensitive value of a culture-sensitive type, mark the type with [CultureInsensitiveTypeAttribute](CultureInsensitiveTypeAttribute.md), or assign the result to an annotated property or field.

### Marking a Parameter

A parameter marked with the attribute is culture-insensitive in both directions: reading it in the method does not report a diagnostic, and [MA0075](Rules/MA0075.md) and [MA0076](Rules/MA0076.md) are not reported for the arguments provided by the callers. The latter is useful for a method that formats its argument with a fixed culture, such as a wrapper around an interpolated string handler.

```csharp
using Meziantou.Analyzer.Annotations;

class Sample
{
public static void Write([CultureInsensitive] double value)
{
_ = $"{value}"; // OK - The parameter is marked as culture-insensitive
}

public static void Log([CultureInsensitive] string message) { }

public static string Format(string message) => message;
}

// Usage
Sample.Log($"Value: {1.5}"); // OK - The argument is passed to a culture-insensitive parameter
Sample.Log(Sample.Format($"{1.5}")); // Warning - The interpolated string is an argument of Format
```

Only the closest argument is considered, so a value nested in another invocation is still reported.

### Assembly-Level Annotation for External Members

When you cannot modify the source member (e.g., third-party libraries), use the assembly-level attribute with the [XML documentation id](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/#id-strings) of the property or the field:

```csharp
using Meziantou.Analyzer.Annotations;

[assembly: CultureInsensitive("P:Sample.StringHelper.InvariantValue")]
[assembly: CultureInsensitive("F:Sample.StringHelper.InvariantField")]
```

## Constructors

| Constructor | Description |
|-------------|-------------|
| `CultureInsensitive()` | Marks the value of the property, the field, or the parameter on which the attribute is applied as culture-insensitive |
| `CultureInsensitive(string xmlDocumentationId)` | Assembly-level: marks the value of the property or the field matching the XML documentation id as culture-insensitive |

## Related Rules

- [MA0011](Rules/MA0011.md) - IFormatProvider is missing
- [MA0075](Rules/MA0075.md) - Do not use implicit culture-sensitive ToString
- [MA0076](Rules/MA0076.md) - Do not use implicit culture-sensitive ToString in interpolated strings
- [MA0185](Rules/MA0185.md) - Simplify string.Create when all parameters are culture invariant

## Additional Information

The attribute is marked with `[Conditional("MEZIANTOU_ANALYZER_ANNOTATIONS")]`, which means it is only compiled into your assembly when the `MEZIANTOU_ANALYZER_ANNOTATIONS` compilation symbol is defined. This keeps the attribute metadata in your assembly for use by analyzers without affecting runtime behavior.
2 changes: 2 additions & 0 deletions docs/Rules/MA0011.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ MA0011.treat_unsealed_types_as_culture_sensitive=false
````

You can also annotate a type with `[Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute]` to disable the rule for this type. See [CultureInsensitiveTypeAttribute](../CultureInsensitiveTypeAttribute.md) for details and [Meziantou.Analyzer.Annotations README](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/README.md) for installation options.

You can annotate a property, a field, or a parameter with `[Meziantou.Analyzer.Annotations.CultureInsensitiveAttribute]` to disable the rule for this value. See [CultureInsensitiveAttribute](../CultureInsensitiveAttribute.md) for details.
2 changes: 2 additions & 0 deletions docs/Rules/MA0075.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ MA0075.treat_unsealed_types_as_culture_sensitive=false
````

You can also annotate a type with `[Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute]` to disable the rule for this type. See [CultureInsensitiveTypeAttribute](../CultureInsensitiveTypeAttribute.md) for details and [Meziantou.Analyzer.Annotations README](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/README.md) for installation options.

You can annotate a property, a field, or a parameter with `[Meziantou.Analyzer.Annotations.CultureInsensitiveAttribute]` to disable the rule for this value. See [CultureInsensitiveAttribute](../CultureInsensitiveAttribute.md) for details.
2 changes: 2 additions & 0 deletions docs/Rules/MA0076.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ MA0076.treat_unsealed_types_as_culture_sensitive=false
````

You can also annotate a type with `[Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute]` to disable the rule for this type. See [CultureInsensitiveTypeAttribute](../CultureInsensitiveTypeAttribute.md) for details and [Meziantou.Analyzer.Annotations README](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/README.md) for installation options.

You can annotate a property, a field, or a parameter with `[Meziantou.Analyzer.Annotations.CultureInsensitiveAttribute]` to disable the rule for this value. See [CultureInsensitiveAttribute](../CultureInsensitiveAttribute.md) for details.
2 changes: 2 additions & 0 deletions docs/Rules/MA0185.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ The analyzer detects when all interpolated values are culture-invariant, such as

You can annotate custom types with `[Meziantou.Analyzer.Annotations.CultureInsensitiveTypeAttribute]` to mark them as culture-insensitive and make additional `string.Create(CultureInfo.InvariantCulture, ...)` usages eligible for simplification. See [CultureInsensitiveTypeAttribute](../CultureInsensitiveTypeAttribute.md) for details and [Meziantou.Analyzer.Annotations README](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.Annotations/README.md) for installation options.

You can also annotate a property, a field, or a parameter with `[Meziantou.Analyzer.Annotations.CultureInsensitiveAttribute]` to mark its value as culture-insensitive. See [CultureInsensitiveAttribute](../CultureInsensitiveAttribute.md) for details.

The analyzer will NOT suggest simplification when:
- Any parameter is culture-sensitive (e.g., `double`, `DateTime` with culture-sensitive format)
- A parameter is an opaque runtime type and `MA0185.treat_opaque_runtime_types_as_culture_sensitive` is enabled
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma warning disable CS1591
#pragma warning disable IDE0060

namespace Meziantou.Analyzer.Annotations;

/// <summary>
/// Indicates that the value of a property, a field, or a parameter is culture insensitive, even when its type is
/// culture sensitive. This can be used to suppress rules such as <c>MA0011</c>, <c>MA0075</c>, <c>MA0076</c>.
/// <para><code>[CultureInsensitive]double Value { get; }</code></para>
/// <para><code>[assembly: CultureInsensitive("P:Sample.StringHelper.InvariantValue")]</code></para>
/// </summary>
[System.Diagnostics.Conditional("MEZIANTOU_ANALYZER_ANNOTATIONS")]
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter | System.AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)]
public sealed class CultureInsensitiveAttribute : System.Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="CultureInsensitiveAttribute"/> class.
/// </summary>
/// <remarks>
/// This can be applied on a property, a field, or a parameter to mark its value as culture insensitive.
/// </remarks>
public CultureInsensitiveAttribute() { }

/// <summary>
/// Initializes a new instance of the <see cref="CultureInsensitiveAttribute"/> class with the specified XML documentation id.
/// </summary>
/// <param name="xmlDocumentationId">The XML documentation id of the property or the field to mark as culture insensitive, such as <c>P:System.DateTime.Now</c>.</param>
/// <remarks>
/// This can be applied on an <see cref="System.Reflection.Assembly"/> to mark the value of a property or a field of another assembly as culture insensitive.
/// </remarks>
public CultureInsensitiveAttribute(string xmlDocumentationId) => XmlDocumentationId = xmlDocumentationId;

/// <summary>
/// Gets the XML documentation id of the property or the field annotated at assembly level.
/// </summary>
/// <value>
/// The XML documentation id of the property or the field annotated at assembly level, or <see langword="null"/> when
/// the attribute is applied to the member itself.
/// </value>
public string? XmlDocumentationId { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<Version>1.6.0</Version>
<Version>1.7.0</Version>
<Description>Annotations to configure Meziantou.Analyzer</Description>
<PackageTags>Meziantou.Analyzer, analyzers</PackageTags>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
Expand Down
29 changes: 29 additions & 0 deletions src/Meziantou.Analyzer.Annotations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ If you want to keep these attributes in the metadata (for example, for reflectio
| --- | --- | --- |
| `DoNotIgnoreAttribute` | Marks a return value or `out` parameter as must-not-be-ignored. | [MA0060](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0060.md) |
| `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) |
| `CultureInsensitiveAttribute` | Marks the value of a property, a field, or a parameter 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 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) |
Expand Down Expand Up @@ -52,3 +53,31 @@ The match is exact-type only. Derived types are not excluded unless explicitly l
```csharp
[assembly: Meziantou.Analyzer.Annotations.NonAsyncDisposableTypeAttribute(typeof(System.Data.Common.DbCommand))]
```

## CultureInsensitiveAttribute

Use `CultureInsensitiveAttribute` to mark the value of a property, a field, or a parameter as culture-insensitive, even when its type is culture-sensitive. Methods cannot be annotated: the attribute marks a value, not the way a method computes it.

```csharp
class Sample
{
[Meziantou.Analyzer.Annotations.CultureInsensitive]
public static double InvariantValue => 0;
}

_ = $"{Sample.InvariantValue}"; // No MA0076 diagnostic
```

A parameter marked with the attribute is culture-insensitive in both directions: reading it in the method does not report a diagnostic, and MA0075/MA0076 are not reported for the arguments provided by the callers.

```csharp
static void Log([Meziantou.Analyzer.Annotations.CultureInsensitive] string message) { }

Log($"Value: {1.5}"); // No MA0076 diagnostic
```

Properties and fields of another assembly can be annotated at the assembly level using their XML documentation id.

```csharp
[assembly: Meziantou.Analyzer.Annotations.CultureInsensitiveAttribute("P:Sample.InvariantValue")]
```
22 changes: 22 additions & 0 deletions src/Meziantou.Analyzer/Internals/AnnotationAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ public static bool IsCultureSensitiveAttributeSymbol(ITypeSymbol? symbol)
};
}

public static bool IsCultureInsensitiveAttributeSymbol(ITypeSymbol? symbol)
{
// Meziantou.Analyzer.Annotations.CultureInsensitiveAttribute
return symbol is INamedTypeSymbol
{
Name: "CultureInsensitiveAttribute",
ContainingSymbol: INamespaceSymbol
{
Name: "Annotations",
ContainingSymbol: INamespaceSymbol
{
Name: "Analyzer",
ContainingSymbol: INamespaceSymbol
{
Name: "Meziantou",
ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true }
}
}
}
};
}

public static bool IsRequireNamedArgumentAttributeSymbol(ITypeSymbol? symbol)
{
// Meziantou.Analyzer.Annotations.RequireNamedArgumentAttribute
Expand Down
Loading
Loading