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 Arguments.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
</Folder>
<Folder Name="/src/">
<Project Path="src/NetEvolve.Arguments/NetEvolve.Arguments.csproj" />
<Project Path="src/NetEvolve.Arguments.Analyser/NetEvolve.Arguments.Analyser.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/NetEvolve.Arguments.Tests.Unit/NetEvolve.Arguments.Tests.Unit.csproj" />
<Project Path="tests/NetEvolve.Arguments.Analyser.Tests.Unit/NetEvolve.Arguments.Analyser.Tests.Unit.csproj" />
</Folder>
</Solution>
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="10.29.0.143774" />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.6.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.6.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.6.0" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.9.0" />
<PackageVersion Include="NetEvolve.Extensions.TUnit" Version="3.5.348" />
<PackageVersion Include="Polyfill" Version="11.0.1" />
Expand Down
93 changes: 93 additions & 0 deletions docs/analysers/NEA0001.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# NEA0001: Use ArgumentNullException.ThrowIfNull

| Property | Value |
| ----------------------------------- | ------------------------------------- |
| **Rule ID** | NEA0001 |
| **Title** | Use ArgumentNullException.ThrowIfNull |
| **Category** | Maintainability |
| **Fix is breaking or non-breaking** | Non-breaking |
| **Enabled by default** | Yes, as suggestion |
| **Applicable languages** | C# |

## Cause

Code checks whether an argument is `null` and then conditionally throws an [ArgumentNullException](https://learn.microsoft.com/dotnet/api/system.argumentnullexception).

## Rule description

This rule mirrors the built-in [CA1510](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1510), but fires on every target framework supported by [NetEvolve.Arguments](https://www.nuget.org/packages/NetEvolve.Arguments/) — including .NET Standard 2.0 and .NET Framework 4.7.2+, which predate the real `ArgumentNullException.ThrowIfNull` member. NEA0001 stays silent whenever the compilation already exposes the real BCL member (.NET 6+), since CA1510 already covers that case.

Recognized shapes:

- `if (arg is null) throw new ArgumentNullException(nameof(arg));`
- `if (arg == null) throw ...` / `if (null == arg) throw ...`
- `if (ReferenceEquals(arg, null)) throw ...` / `if (ReferenceEquals(null, arg)) throw ...`
- Any of the above negated, e.g. `if (!(arg != null)) throw ...`, `if (!(arg is not null)) throw ...`
- Null-coalescing throw: `arg ?? throw new ArgumentNullException(nameof(arg));`

Only fires when the thrown exception takes no arguments, or a single `paramName` argument that matches the checked expression (`nameof(arg)` or a matching string literal). Violations with a custom message argument are not flagged, since `ThrowIfNull` doesn't support one.

## Example

```csharp
void M(string arg)
{
if (arg is null)
throw new ArgumentNullException(nameof(arg));
}
```

```csharp
class C
{
private readonly string _value;

public C(string? arg)
{
_value = arg ?? throw new ArgumentNullException(nameof(arg));
}
}
```

## Fix

```csharp
void M(string arg)
{
ArgumentNullException.ThrowIfNull(arg);
}
```

```csharp
class C
{
private readonly string _value;

public C(string? arg)
{
ArgumentNullException.ThrowIfNull(arg);
_value = arg;
}
}
```

## How to fix violations

Use the code fix (lightbulb menu), or replace the `if` block / coalescing throw manually with a call to `ArgumentNullException.ThrowIfNull`.

## When to suppress warnings

It's safe to suppress a violation of this rule if you're not concerned about the maintainability of your code, or if the finding is a false positive.

## Suppress a warning

```csharp
#pragma warning disable NEA0001
// The code that's violating the rule is on this line.
#pragma warning restore NEA0001
```

```ini
[*.cs]
dotnet_diagnostic.NEA0001.severity = none
```
65 changes: 65 additions & 0 deletions docs/analysers/NEA0002.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# NEA0002: Use ArgumentException throw helper

| Property | Value |
| ----------------------------------- | ---------------------------------- |
| **Rule ID** | NEA0002 |
| **Title** | Use ArgumentException throw helper |
| **Category** | Maintainability |
| **Fix is breaking or non-breaking** | Non-breaking |
| **Enabled by default** | Yes, as suggestion |
| **Applicable languages** | C# |

## Cause

Code checks whether a string is `null`/empty or `null`/empty/whitespace-only and then conditionally throws an [ArgumentException](https://learn.microsoft.com/dotnet/api/system.argumentexception).

## Rule description

This rule mirrors the built-in [CA1511](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1511), but fires on every target framework supported by [NetEvolve.Arguments](https://www.nuget.org/packages/NetEvolve.Arguments/) — including frameworks that predate the real `ArgumentException.ThrowIfNullOrEmpty`/`ThrowIfNullOrWhiteSpace` members (.NET 8). NEA0002 stays silent whenever the compilation already exposes the real BCL member.

Recognized shapes:

- `if (string.IsNullOrEmpty(arg)) throw new ArgumentException(...);` → `ArgumentException.ThrowIfNullOrEmpty(arg)`
- `if (string.IsNullOrWhiteSpace(arg)) throw new ArgumentException(...);` → `ArgumentException.ThrowIfNullOrWhiteSpace(arg)`

Any exception constructor arguments (including a custom message) are dropped by the fix, matching the upstream CA1511 fixer behavior.

## Example

```csharp
void M(string arg)
{
if (string.IsNullOrEmpty(arg))
throw new ArgumentException("", nameof(arg));
}
```

## Fix

```csharp
void M(string arg)
{
ArgumentException.ThrowIfNullOrEmpty(arg);
}
```

## How to fix violations

Use the code fix (lightbulb menu), or replace the `if` block manually with a call to `ArgumentException.ThrowIfNullOrEmpty` or `ArgumentException.ThrowIfNullOrWhiteSpace`.

## When to suppress warnings

It's safe to suppress a violation of this rule if you're not concerned about the maintainability of your code, or if the finding is a false positive.

## Suppress a warning

```csharp
#pragma warning disable NEA0002
// The code that's violating the rule is on this line.
#pragma warning restore NEA0002
```

```ini
[*.cs]
dotnet_diagnostic.NEA0002.severity = none
```
80 changes: 80 additions & 0 deletions docs/analysers/NEA0003.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# NEA0003: Use ArgumentOutOfRangeException throw helper

| Property | Value |
| ----------------------------------- | -------------------------------------------- |
| **Rule ID** | NEA0003 |
| **Title** | Use ArgumentOutOfRangeException throw helper |
| **Category** | Maintainability |
| **Fix is breaking or non-breaking** | Non-breaking |
| **Enabled by default** | Yes, as suggestion |
| **Applicable languages** | C# |

## Cause

Code checks whether an argument is less than, greater than, or equal to a given value (or falls outside a range) and then conditionally throws an [ArgumentOutOfRangeException](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception).

## Rule description

This rule mirrors the built-in [CA1512](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1512), but fires on every target framework supported by [NetEvolve.Arguments](https://www.nuget.org/packages/NetEvolve.Arguments/) — including frameworks that predate the real BCL throw-helper members (.NET 8). NEA0003 stays silent whenever the compilation already exposes the real BCL member.

Recognized shapes (`value` on the left, `other`/`min`/`max` on the right):

| Condition | Fix |
| ------------------------------ | ----------------------------------------- |
| `value < 0` | `ThrowIfNegative(value)` |
| `value <= 0` | `ThrowIfNegativeOrZero(value)` |
| `value == 0` | `ThrowIfZero(value)` |
| `value < other` | `ThrowIfLessThan(value, other)` |
| `value <= other` | `ThrowIfLessThanOrEqual(value, other)` |
| `value > other` | `ThrowIfGreaterThan(value, other)` |
| `value >= other` | `ThrowIfGreaterThanOrEqual(value, other)` |
| `value == other` | `ThrowIfEqual(value, other)` |
| `value != other` | `ThrowIfNotEqual(value, other)` |
| `value < min \|\| value > max` | `ThrowIfOutOfRange(value, min, max)` |

Only the `value OP other` operand ordering is recognized (not the reversed `other OP value` form).

## Example

```csharp
void M(int arg)
{
if (arg < 0)
throw new ArgumentOutOfRangeException(nameof(arg));

if (arg < 5 || arg > 100)
throw new ArgumentOutOfRangeException(nameof(arg));
}
```

## Fix

```csharp
void M(int arg)
{
ArgumentOutOfRangeException.ThrowIfNegative(arg);

ArgumentOutOfRangeException.ThrowIfOutOfRange(arg, 5, 100);
}
```

## How to fix violations

Use the code fix (lightbulb menu), or replace the `if` block manually with the matching `ArgumentOutOfRangeException` throw-helper.

## When to suppress warnings

It's safe to suppress a violation of this rule if you're not concerned about the maintainability of your code, or if the finding is a false positive.

## Suppress a warning

```csharp
#pragma warning disable NEA0003
// The code that's violating the rule is on this line.
#pragma warning restore NEA0003
```

```ini
[*.cs]
dotnet_diagnostic.NEA0003.severity = none
```
64 changes: 64 additions & 0 deletions docs/analysers/NEA0004.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# NEA0004: Use ArgumentException.ThrowIfDefault

| Property | Value |
| ----------------------------------- | ------------------------------------ |
| **Rule ID** | NEA0004 |
| **Title** | Use ArgumentException.ThrowIfDefault |
| **Category** | Maintainability |
| **Fix is breaking or non-breaking** | Non-breaking |
| **Enabled by default** | Yes, as suggestion |
| **Applicable languages** | C# |

## Cause

Code checks whether a value-type argument equals its default value and then conditionally throws an [ArgumentException](https://learn.microsoft.com/dotnet/api/system.argumentexception).

## Rule description

There is no built-in Roslyn analyzer for this pattern — `ArgumentException.ThrowIfDefault` is a throw-helper provided by [NetEvolve.Arguments](https://www.nuget.org/packages/NetEvolve.Arguments/) itself, not the BCL, so this rule always fires regardless of target framework.

Recognized shapes:

- `if (arg.Equals(default)) throw new ArgumentException(...);`
- `if (arg.Equals(default(T))) throw new ArgumentException(...);`
- `if (arg == default) throw ...` / `if (default == arg) throw ...` (and the `default(T)` variants)

## Example

```csharp
void M(Guid arg)
{
if (arg.Equals(default))
throw new ArgumentException(nameof(arg));
}
```

## Fix

```csharp
void M(Guid arg)
{
ArgumentException.ThrowIfDefault(arg);
}
```

## How to fix violations

Use the code fix (lightbulb menu), or replace the `if` block manually with a call to `ArgumentException.ThrowIfDefault`.

## When to suppress warnings

It's safe to suppress a violation of this rule if you're not concerned about the maintainability of your code, or if the finding is a false positive.

## Suppress a warning

```csharp
#pragma warning disable NEA0004
// The code that's violating the rule is on this line.
#pragma warning restore NEA0004
```

```ini
[*.cs]
dotnet_diagnostic.NEA0004.severity = none
```
70 changes: 70 additions & 0 deletions docs/analysers/NEA0005.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# NEA0005: Use ObjectDisposedException.ThrowIf

| Property | Value |
| ----------------------------------- | ----------------------------------- |
| **Rule ID** | NEA0005 |
| **Title** | Use ObjectDisposedException.ThrowIf |
| **Category** | Maintainability |
| **Fix is breaking or non-breaking** | Non-breaking |
| **Enabled by default** | Yes, as suggestion |
| **Applicable languages** | C# |

## Cause

Code checks whether an object is disposed and then conditionally throws an [ObjectDisposedException](https://learn.microsoft.com/dotnet/api/system.objectdisposedexception).

## Rule description

This rule mirrors the built-in [CA1513](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1513), but fires on every target framework supported by [NetEvolve.Arguments](https://www.nuget.org/packages/NetEvolve.Arguments/) — including frameworks that predate the real `ObjectDisposedException.ThrowIf` member (.NET 7). NEA0005 stays silent whenever the compilation already exposes the real BCL member, and only fires inside instance members (the fix needs `this`).

## Example

```csharp
class C
{
private bool _disposed;

void M()
{
if (_disposed)
throw new ObjectDisposedException(GetType().Name);
}
}
```

## Fix

```csharp
class C
{
private bool _disposed;

void M()
{
ObjectDisposedException.ThrowIf(_disposed, this);
}
}
```

The fix always passes `this` as the instance, dropping whatever argument the original exception constructor used — matching the upstream CA1513 fixer behavior.

## How to fix violations

Use the code fix (lightbulb menu), or replace the `if` block manually with a call to `ObjectDisposedException.ThrowIf`.

## When to suppress warnings

It's safe to suppress a violation of this rule if you're not concerned about the maintainability of your code, or if the finding is a false positive.

## Suppress a warning

```csharp
#pragma warning disable NEA0005
// The code that's violating the rule is on this line.
#pragma warning restore NEA0005
```

```ini
[*.cs]
dotnet_diagnostic.NEA0005.severity = none
```
Loading