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
23 changes: 23 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,16 @@ The project implements two separate analyzers for enforcing global usings, follo
- Best for: Library projects, projects with many third-party dependencies
- Implementation: `GlobalUsingsCommonAnalyzer.cs` + `GlobalUsingsCommonCodeFixProvider.cs`

**Automatic Mutual Exclusivity:**

By default, ATC220 excludes namespaces that ATC221 handles (configured via `dotnet_diagnostic.ATC220.exclude_common_namespaces`, default `true`). This means both rules can be enabled simultaneously without producing duplicate warnings:
- ATC220 flags **non-common** namespaces (third-party, custom)
- ATC221 flags **common** namespaces (System, Microsoft, Atc)

ATC220 reads ATC221's `namespace_prefixes` setting to determine which namespaces to skip, so custom prefix configurations stay in sync.

To make ATC220 flag ALL namespaces (original behavior), set `exclude_common_namespaces = false` in `.editorconfig`.

**Why Two Separate Analyzers?**

While these analyzers could theoretically be merged into one with different rule IDs, they are kept separate to:
Expand Down Expand Up @@ -493,6 +503,19 @@ dotnet_diagnostic.ATC210.max_line_length = 100

The configuration uses the standard Roslyn `.editorconfig` option naming convention: `dotnet_diagnostic.{RULE_ID}.option_name`

#### Exclude Common Namespaces (ATC220)

ATC220 supports excluding common namespaces to avoid overlap with ATC221:

```editorconfig
[*.cs]
# Exclude common namespaces handled by ATC221 (default: true)
dotnet_diagnostic.ATC220.exclude_common_namespaces = false
```

- **Default value**: true (common namespaces are excluded)
- When true, ATC220 reads ATC221's `namespace_prefixes` to determine which namespaces to skip

#### Reading Configuration in Analyzers

To read custom `.editorconfig` options in an analyzer, use the `AnalyzerConfigOptionsExtensions` helper:
Expand Down
63 changes: 34 additions & 29 deletions docs/rules/ATC220.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,52 +34,44 @@ Centralizing all namespace imports in GlobalUsings.cs provides several benefits:

This is the most aggressive global usings policy, suitable for projects where you want absolutely minimal boilerplate.

## 🔀 ATC220 vs ATC221: Which Rule to Use?
## 🔀 ATC220 and ATC221: Working Together

Both ATC220 and ATC221 enforce global usings, but they differ in scope and strictness:
Both ATC220 and ATC221 enforce global usings, but they differ in scope:

| Aspect | ATC220 (Strict) | ATC221 (Lenient) |
|--------|----------------|------------------|
| **Policy** | All namespaces | Only System/Microsoft/Atc |
| **Flags** | Every using directive | Only common framework usings |
| **Policy** | Non-common namespaces (by default) | Only System/Microsoft/Atc |
| **Flags** | Third-party and custom using directives | Only common framework usings |
| **Third-party libs** | Must be in GlobalUsings.cs | Can stay in individual files |
| **Use when** | You want zero using statements in code files | You want flexibility for third-party dependencies |
| **Best for** | Highly controlled codebases, internal projects | Projects with many third-party libraries |
| **Best for** | Catching all remaining non-common usings | Centralizing common framework usings |

### When to Use ATC220 (This Rule)
### Automatic Mutual Exclusivity

Choose ATC220 if you want:
By default, both rules can be enabled simultaneously **without producing duplicate warnings**. ATC220 automatically excludes namespaces that ATC221 handles (System, Microsoft, Atc), so each using directive triggers at most one warning.

- **Maximum consistency**: Every file looks identical (no using statements at all)
- **Zero exceptions**: All usings centralized, no local decisions needed
- **Internal projects**: Where you control all dependencies
- **Minimal boilerplate**: Absolutely cleanest possible code files
| Scenario | ATC220 fires on | ATC221 fires on |
|----------|----------------|----------------|
| Both enabled (default) | Third-party/custom namespaces | System/Microsoft/Atc |
| Only ATC220 (`exclude_common_namespaces = false`) | Everything | n/a |
| Only ATC221 | n/a | System/Microsoft/Atc |

### When to Use ATC221 Instead

Choose ATC221 if you want:

- **Flexibility**: Allow third-party usings to remain in individual files
- **Context clarity**: See third-party dependencies where they're used
- **Migration friendliness**: Easier to adopt incrementally
- **Library projects**: Where different files may use different third-party packages

### Configuration Example

You can enable only one rule at a time in your `.editorconfig`:
### Configuration Examples

```ini
# Use strict policy (all usings must be global)
# Both enabled (default) - no overlap, each rule covers its own scope
dotnet_diagnostic.ATC220.severity = warning
dotnet_diagnostic.ATC221.severity = warning

# Use ATC220 for ALL namespaces (disable ATC221 and the exclusion)
dotnet_diagnostic.ATC220.severity = warning
dotnet_diagnostic.ATC220.exclude_common_namespaces = false
dotnet_diagnostic.ATC221.severity = none

# OR use lenient policy (only System/Microsoft/Atc must be global)
# Use only ATC221 for common namespaces
dotnet_diagnostic.ATC220.severity = none
dotnet_diagnostic.ATC221.severity = warning
```

**Note**: It's recommended to enable only one of these rules to avoid conflicting guidance.

## 📝 Examples

### Scenario 1: All namespaces should be in GlobalUsings.cs
Expand Down Expand Up @@ -113,7 +105,20 @@ public class Sample

## ⚙️ Configuration

This rule is not configurable. It applies to all using directives.
Configure in `.editorconfig`:

```ini
[*.cs]
# Exclude common namespaces handled by ATC221 (default: true)
# Set to false if you want ATC220 to flag ALL namespaces (use with ATC221 disabled)
dotnet_diagnostic.ATC220.exclude_common_namespaces = true
```

| Option | Default | Description |
|--------|---------|-------------|
| `exclude_common_namespaces` | `true` | When true, skips namespaces matching ATC221's configured prefixes (System, Microsoft, Atc) to avoid duplicate diagnostics |

When `exclude_common_namespaces` is `true` (default), ATC220 reads ATC221's `namespace_prefixes` setting to determine which namespaces to skip. If you customize ATC221's prefixes, ATC220 automatically respects that.

## 🔧 Code Fix

Expand Down
49 changes: 20 additions & 29 deletions docs/rules/ATC221.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,53 +37,44 @@ Centralizing common namespace imports in GlobalUsings.cs provides several benefi

This is particularly useful for large projects where the same namespaces (like `System.Linq`, `System.Collections.Generic`) are used throughout the codebase.

## 🔀 ATC220 vs ATC221: Which Rule to Use?
## 🔀 ATC220 and ATC221: Working Together

Both ATC220 and ATC221 enforce global usings, but they differ in scope and strictness:
Both ATC220 and ATC221 enforce global usings, but they differ in scope:

| Aspect | ATC220 (Strict) | ATC221 (Lenient) |
|--------|----------------|------------------|
| **Policy** | All namespaces | Only System/Microsoft/Atc |
| **Flags** | Every using directive | Only common framework usings |
| **Policy** | Non-common namespaces (by default) | Only System/Microsoft/Atc |
| **Flags** | Third-party and custom using directives | Only common framework usings |
| **Third-party libs** | Must be in GlobalUsings.cs | Can stay in individual files |
| **Use when** | You want zero using statements in code files | You want flexibility for third-party dependencies |
| **Best for** | Highly controlled codebases, internal projects | Projects with many third-party libraries |
| **Best for** | Catching all remaining non-common usings | Centralizing common framework usings |

### When to Use ATC221 (This Rule)
### Automatic Mutual Exclusivity

Choose ATC221 if you want:
By default, both rules can be enabled simultaneously **without producing duplicate warnings**. ATC220 automatically excludes namespaces that ATC221 handles (System, Microsoft, Atc), so each using directive triggers at most one warning.

- **Flexibility**: Allow third-party usings to remain in individual files
- **Context clarity**: See third-party dependencies where they're used (Xunit, FluentAssertions, etc.)
- **Migration friendliness**: Easier to adopt incrementally in existing projects
- **Library projects**: Where different files may use different third-party packages
- **Balanced approach**: Centralize framework usings while keeping context for external dependencies
| Scenario | ATC220 fires on | ATC221 fires on |
|----------|----------------|----------------|
| Both enabled (default) | Third-party/custom namespaces | System/Microsoft/Atc |
| Only ATC220 (`exclude_common_namespaces = false`) | Everything | n/a |
| Only ATC221 | n/a | System/Microsoft/Atc |

### When to Use ATC220 Instead

Choose ATC220 if you want:

- **Maximum consistency**: Every file looks identical (no using statements at all)
- **Zero exceptions**: All usings centralized, no local decisions needed
- **Internal projects**: Where you control all dependencies
- **Minimal boilerplate**: Absolutely cleanest possible code files

### Configuration Example

You can enable only one rule at a time in your `.editorconfig`:
### Configuration Examples

```ini
# Use lenient policy (only System/Microsoft/Atc must be global)
# Both enabled (default) - no overlap, each rule covers its own scope
dotnet_diagnostic.ATC220.severity = warning
dotnet_diagnostic.ATC221.severity = warning

# Use only ATC221 for common namespaces
dotnet_diagnostic.ATC220.severity = none
dotnet_diagnostic.ATC221.severity = warning

# OR use strict policy (all usings must be global)
# Use ATC220 for ALL namespaces (disable ATC221 and the exclusion)
dotnet_diagnostic.ATC220.severity = warning
dotnet_diagnostic.ATC220.exclude_common_namespaces = false
dotnet_diagnostic.ATC221.severity = none
```

**Note**: It's recommended to enable only one of these rules to avoid conflicting guidance.

## 📝 Examples

### Scenario 1: System namespaces should be in GlobalUsings.cs
Expand Down
10 changes: 6 additions & 4 deletions sample/Atc.Analyzer.Sample.Atc220/InvalidExamples.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
namespace Atc.Analyzer.Sample.Atc220;

// These usings SHOULD be moved to GlobalUsings.cs and will trigger ATC220
// These usings SHOULD be moved to GlobalUsings.cs
// By default, ATC220 skips common namespaces (System/Microsoft/Atc) to avoid overlap with ATC221
// System.Linq and System.Text are flagged by ATC221, while Xunit is flagged by ATC220
#pragma warning disable ATC220, ATC221

using System.Linq; // ATC220: Should be in GlobalUsings.cs
using System.Text; // ATC220: Should be in GlobalUsings.cs
using Xunit; // ATC220: Should be in GlobalUsings.cs (ALL namespaces)
using System.Linq; // ATC221: Should be in GlobalUsings.cs (common namespace)
using System.Text; // ATC221: Should be in GlobalUsings.cs (common namespace)
using Xunit; // ATC220: Should be in GlobalUsings.cs (non-common namespace)

#pragma warning disable CA1303
#pragma warning disable CA1822
Expand Down
3 changes: 2 additions & 1 deletion sample/Atc.Analyzer.Sample.Atc221/InvalidExamples.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
namespace Atc.Analyzer.Sample.Atc221;

// These usings SHOULD be moved to GlobalUsings.cs and will trigger ATC221
#pragma warning disable ATC220, ATC221
// ATC220 automatically skips these common namespaces (System/Microsoft/Atc) by default
#pragma warning disable ATC221

using System.IO; // ATC221: Should be in GlobalUsings.cs
using System.Linq; // ATC221: Should be in GlobalUsings.cs
Expand Down
4 changes: 2 additions & 2 deletions sample/Atc.Analyzer.Sample.Atc221/ValidExamples.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
namespace Atc.Analyzer.Sample.Atc221;

#pragma warning disable ATC220
#pragma warning disable ATC220 // ATC220 still flags third-party namespaces like Xunit

// No usings from System/Microsoft/Atc here - they're in GlobalUsings.cs
using Xunit; // Third-party namespace - this is OK
using Xunit; // Third-party namespace - OK for ATC221, but ATC220 flags non-common namespaces

#pragma warning disable CA1303
#pragma warning disable CA1822
Expand Down
32 changes: 32 additions & 0 deletions src/Atc.Analyzer/Extensions/AnalyzerConfigOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,38 @@ public int GetMinChainLength(string ruleId)
return Math.Max(MinMinCount, Math.Min(MaxMinCount, length));
}

/// <summary>
/// Gets whether common namespaces should be excluded from the specified rule.
/// When true, namespaces matching ATC221's configured prefixes are skipped to avoid duplicate diagnostics.
/// </summary>
/// <param name="ruleId">The rule identifier (e.g., "ATC220").</param>
/// <returns>
/// True if common namespaces should be excluded (default), false otherwise.
/// </returns>
/// <remarks>
/// Configure in .editorconfig using:
/// <code>
/// dotnet_diagnostic.ATC220.exclude_common_namespaces = false
/// </code>
/// </remarks>
public bool GetExcludeCommonNamespaces(string ruleId)
{
if (options is null)
{
return true;
}

var key = $"dotnet_diagnostic.{ruleId}.exclude_common_namespaces";

if (!options.TryGetValue(key, out var value) ||
string.IsNullOrWhiteSpace(value))
{
return true;
}

return !value.Equals("false", StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Gets the configured namespace prefixes for the specified rule from .editorconfig.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions src/Atc.Analyzer/Rules/Style/GlobalUsingsAllAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public override void Initialize(AnalysisContext context)
context.RegisterSyntaxNodeAction(AnalyzeUsingDirective, SyntaxKind.UsingDirective);
}

[SuppressMessage("Design", "MA0051:Method is too long", Justification = "OK.")]
private static void AnalyzeUsingDirective(SyntaxNodeAnalysisContext context)
{
if (context.Node.SyntaxTree.IsAutoGeneratedFile())
Expand Down Expand Up @@ -76,6 +77,19 @@ private static void AnalyzeUsingDirective(SyntaxNodeAnalysisContext context)
return;
}

// Check if common namespaces should be excluded (deferred to ATC221)
var options = context.Options.AnalyzerConfigOptionsProvider.GetOptions(context.Node.SyntaxTree);
var excludeCommon = options.GetExcludeCommonNamespaces(RuleIdentifierConstants.Style.GlobalUsingsAll);

if (excludeCommon)
{
var commonPrefixes = options.GetNamespacePrefixes(RuleIdentifierConstants.Style.GlobalUsingsCommon);
if (commonPrefixes.Any(prefix => namespaceName!.StartsWith(prefix, StringComparison.Ordinal)))
{
return;
}
}

// All non-static, non-alias using directives should be global
var diagnostic = Diagnostic.Create(
Rule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,49 @@ public static void DoSomething()

await AnalyzerVerifier.VerifyAnalyzerAsync(code);
}

[Fact]
public async Task NoDiagnostic_SystemNamespace_DefaultExcludesCommon()
{
const string code = """
using System;

public class Sample
{
public static void DoSomething()
{
}
}
""";

await AnalyzerVerifier.VerifyAnalyzerAsync(code);
}

[Fact]
public async Task NoDiagnostic_MicrosoftNamespace_DefaultExcludesCommon()
{
const string code = """
using Microsoft.Extensions.Logging;

public class Sample
{
}
""";

await AnalyzerVerifier.VerifyAnalyzerAsync(code);
}

[Fact]
public async Task NoDiagnostic_AtcNamespace_DefaultExcludesCommon()
{
const string code = """
using Atc.Utilities;

public class Sample
{
}
""";

await AnalyzerVerifier.VerifyAnalyzerAsync(code);
}
}
Loading