Skip to content

Commit d788e51

Browse files
committed
Close #755: Simplify the ValuesAttribute
1 parent 1a135a9 commit d788e51

File tree

9 files changed

+717
-0
lines changed

9 files changed

+717
-0
lines changed

documentation/NUnit4001.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# NUnit4001
2+
3+
## Simplify the Values attribute
4+
5+
| Topic | Value
6+
| :-- | :--
7+
| Id | NUnit4001
8+
| Severity | Info
9+
| Enabled | True
10+
| Category | Style
11+
| Code | [SimplifyValuesAnalyzer](https://github.com/nunit/nunit.analyzers/blob/master/src/nunit.analyzers/SimplifyValues/SimplifyValuesAnalyzer.cs)
12+
13+
## Description
14+
15+
Consider simplifying the ValuesAttribute when you're using the CombinatorialAttribute.
16+
17+
## Motivation
18+
19+
When used without any arguments, the [Values] attribute on a (nullable) boolean or an (nullable) enum parameter
20+
will automatically include all possible values.
21+
22+
Therefore the `Values` attribute like
23+
24+
```csharp
25+
[Test]
26+
public void MyBoolTest([Values(true, false)] bool value) { /* ... */ }
27+
```
28+
29+
can be simplified to
30+
31+
```csharp
32+
[Test]
33+
public void MyBoolTest([Values] bool value) { /* ... */ }
34+
```
35+
36+
## How to fix violations
37+
38+
Remove all arguments of the `Values` attribute.
39+
40+
<!-- start generated config severity -->
41+
## Configure severity
42+
43+
### Via ruleset file
44+
45+
Configure the severity per project, for more info see
46+
[MSDN](https://learn.microsoft.com/en-us/visualstudio/code-quality/using-rule-sets-to-group-code-analysis-rules?view=vs-2022).
47+
48+
### Via .editorconfig file
49+
50+
```ini
51+
# NUnit4001: Simplify the Values attribute
52+
dotnet_diagnostic.NUnit4001.severity = chosenSeverity
53+
```
54+
55+
where `chosenSeverity` can be one of `none`, `silent`, `suggestion`, `warning`, or `error`.
56+
57+
### Via #pragma directive
58+
59+
```csharp
60+
#pragma warning disable NUnit4001 // Simplify the Values attribute
61+
Code violating the rule here
62+
#pragma warning restore NUnit4001 // Simplify the Values attribute
63+
```
64+
65+
Or put this at the top of the file to disable all instances.
66+
67+
```csharp
68+
#pragma warning disable NUnit4001 // Simplify the Values attribute
69+
```
70+
71+
### Via attribute `[SuppressMessage]`
72+
73+
```csharp
74+
[System.Diagnostics.CodeAnalysis.SuppressMessage("Assertion",
75+
"NUnit4001: Simplify the Values attribute",
76+
Justification = "Reason...")]
77+
```
78+
<!-- end generated config severity -->

documentation/index.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,11 @@ builds (version 3.0.0 and above) which require Visual Studio 2019 (version 16.3)
120120
| [NUnit3002](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit3002.md) | Field/Property is initialized in SetUp or OneTimeSetUp method | :white_check_mark: | :information_source: | :x: |
121121
| [NUnit3003](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit3003.md) | Class is an NUnit TestFixture and is instantiated using reflection | :white_check_mark: | :information_source: | :x: |
122122
| [NUnit3004](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit3004.md) | Field should be Disposed in TearDown or OneTimeTearDown method | :white_check_mark: | :information_source: | :x: |
123+
124+
## Style Rules (NUnit4001 - )
125+
126+
Rules which help you write concise and readable NUnit test code.
127+
128+
| Id | Title | :mag: | :memo: | :bulb: |
129+
| :-- | :-- | :--: | :--: | :--: |
130+
| [NUnit4001](https://github.com/nunit/nunit.analyzers/tree/master/documentation/NUnit4001.md) | Simplify the Values attribute | :white_check_mark: | :information_source: | :x: |
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
using System.Globalization;
2+
using Gu.Roslyn.Asserts;
3+
using Microsoft.CodeAnalysis;
4+
using NUnit.Analyzers.Constants;
5+
using NUnit.Analyzers.SimplifyValues;
6+
using NUnit.Framework;
7+
8+
namespace NUnit.Analyzers.Tests.SimplifyValues;
9+
10+
public class SimplifyValuesAnalyzerTests
11+
{
12+
private readonly SimplifyValuesAnalyzer analyzer = new();
13+
14+
[Test]
15+
public void VerifySupportedDiagnostics()
16+
{
17+
var diagnostics = this.analyzer.SupportedDiagnostics;
18+
19+
Assert.That(diagnostics, Has.Length.EqualTo(1));
20+
var diagnostic = diagnostics[0];
21+
Assert.Multiple(() =>
22+
{
23+
Assert.That(diagnostic.Id, Is.EqualTo(AnalyzerIdentifiers.SimplifyValues));
24+
Assert.That(diagnostic.Title.ToString(CultureInfo.InvariantCulture), Is.Not.Empty);
25+
Assert.That(diagnostic.Category, Is.EqualTo(Categories.Style));
26+
Assert.That(diagnostic.DefaultSeverity, Is.EqualTo(DiagnosticSeverity.Info));
27+
});
28+
}
29+
30+
[Test]
31+
public void AnalyzeWhenAttributeIsNotInNUnit()
32+
{
33+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
34+
public class AnalyzeWhenAttributeIsNotInNUnit
35+
{
36+
[Test]
37+
public void ATest([Values] bool b) { }
38+
39+
private sealed class ValuesAttribute : Attribute
40+
{ }
41+
}");
42+
RoslynAssert.Valid(this.analyzer, testCode);
43+
}
44+
45+
[Test]
46+
public void AnalyzeWhenCombinatorialStrategyIsNotUsed(
47+
[Values("Sequential", "Pairwise")] string nonCombinatorialAttribute,
48+
[Values] bool fullyQualify,
49+
[Values] bool omitAttribute)
50+
{
51+
var prefix = fullyQualify ? "NUnit.Framework." : string.Empty;
52+
var suffix = omitAttribute ? string.Empty : "Attribute";
53+
var attribute = $"{prefix}{nonCombinatorialAttribute}{suffix}";
54+
55+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing($@"
56+
public class AnalyzeWhenCombinatorialStrategyIsNotUsed
57+
{{
58+
public enum TestEnum {{ A, B, C }}
59+
60+
[Test]
61+
[{attribute}]
62+
public void Test([Values(TestEnum.A, TestEnum.B, TestEnum.C)] TestEnum e) {{ }}
63+
}}");
64+
RoslynAssert.Valid(this.analyzer, testCode);
65+
}
66+
67+
[Test]
68+
public void AnalyzeWhenAttributeHasNoArguments()
69+
{
70+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
71+
public class AnalyzeWhenAttributeHasNoArguments
72+
{
73+
[Test]
74+
public void ATest([Values] bool b) { }
75+
}");
76+
RoslynAssert.Valid(this.analyzer, testCode);
77+
}
78+
79+
[Test]
80+
public void AnalyzeWhenOneBooleanWasUsed()
81+
{
82+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
83+
public class AnalyzeWhenOneBooleanWasUsed
84+
{
85+
[Test]
86+
public void ATest([Values(true)] bool b) { }
87+
}");
88+
RoslynAssert.Valid(this.analyzer, testCode);
89+
}
90+
91+
[Test]
92+
public void AnalyzeWhenTwoBooleanWereUsedForNullableBoolean()
93+
{
94+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
95+
public class AnalyzeWhenTwoBooleanWereUsedForNullableBoolean
96+
{
97+
[Test]
98+
public void ATest([Values(true, false)] bool? b) { }
99+
}");
100+
RoslynAssert.Valid(this.analyzer, testCode);
101+
}
102+
103+
[Test]
104+
public void AnalyzeWhenNotAllEnumValuesWereUsed()
105+
{
106+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
107+
public class AnalyzeWhenNotAllEnumValuesWereUsed
108+
{
109+
public enum TestEnum { A, B, C }
110+
111+
[Test]
112+
public void Test([Values(TestEnum.A, TestEnum.B)] TestEnum e) { }
113+
}");
114+
RoslynAssert.Valid(this.analyzer, testCode);
115+
}
116+
117+
[Test]
118+
public void AnalyzeWhenNotAllEnumValuesWereUsedForNullableBoolean()
119+
{
120+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
121+
public class AnalyzeWhenNotAllEnumValuesWereUsedForNullableBoolean
122+
{
123+
public enum TestEnum { A, B, C }
124+
125+
[Test]
126+
public void Test([Values(TestEnum.A, TestEnum.B, TestEnum.C)] TestEnum? e) { }
127+
}");
128+
RoslynAssert.Valid(this.analyzer, testCode);
129+
}
130+
131+
[Test]
132+
public void AnalyzeNotAllBooleanValuesAreInParameters()
133+
{
134+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
135+
public class AnalyzeNotAllBooleanValuesAreInParameters
136+
{
137+
public void Test([Values(new object[] { true })] bool b) { }
138+
}");
139+
RoslynAssert.Valid(this.analyzer, testCode);
140+
}
141+
142+
[Test]
143+
public void AnalyzeNotAllNullableBooleanValuesAreInParameters()
144+
{
145+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
146+
public class AnalyzeNotAllNullableBooleanValuesAreInParameters
147+
{
148+
public void Test([Values(new object[] { true, false })] bool? b) { }
149+
}");
150+
RoslynAssert.Valid(this.analyzer, testCode);
151+
}
152+
153+
[Test]
154+
public void AnalyzeNotAllEnumValuesAreInParameters()
155+
{
156+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
157+
public class AnalyzeNotAllEnumValuesAreInParameters
158+
{
159+
public enum TestEnum { A, B, C }
160+
161+
public void Test([Values(new object[] { TestEnum.A, TestEnum.B })] TestEnum e) { }
162+
}");
163+
RoslynAssert.Valid(this.analyzer, testCode);
164+
}
165+
166+
[Test]
167+
public void AnalyzeNotAllNullableEnumValuesAreInParameters()
168+
{
169+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
170+
public class AnalyzeNotAllNullableEnumValuesAreInParameters
171+
{
172+
public enum TestEnum { A, B, C }
173+
174+
public void Test([Values(new object[] { TestEnum.A, TestEnum.B, TestEnum.C })] TestEnum? e) { }
175+
}");
176+
RoslynAssert.Valid(this.analyzer, testCode);
177+
}
178+
179+
[Test]
180+
public void AnalyzeWhenAllBooleanValuesWereUsed()
181+
{
182+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
183+
public class AnalyzeWhenAllBooleanValuesWereUsed
184+
{
185+
[Test]
186+
public void Test([↓Values(true, false)] bool b) { }
187+
}");
188+
RoslynAssert.Diagnostics(this.analyzer,
189+
ExpectedDiagnostic.Create(AnalyzerIdentifiers.SimplifyValues),
190+
testCode);
191+
}
192+
193+
[Test]
194+
public void AnalyzeWhenAllNullableBooleanValuesWereUsed()
195+
{
196+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
197+
public class AnalyzeWhenAllNullableBooleanValuesWereUsed
198+
{
199+
[Test]
200+
public void Test([↓Values(true, false, null)] bool? b) { }
201+
}");
202+
RoslynAssert.Diagnostics(this.analyzer,
203+
ExpectedDiagnostic.Create(AnalyzerIdentifiers.SimplifyValues),
204+
testCode);
205+
}
206+
207+
[Test]
208+
public void AnalyzeWhenAllEnumValuesWereUsed()
209+
{
210+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
211+
public class AnalyzeWhenAllEnumValuesWereUsed
212+
{
213+
public enum TestEnum { A, B, C }
214+
215+
[Test]
216+
public void Test([↓Values(TestEnum.A, TestEnum.B, TestEnum.C)] TestEnum e) { }
217+
}");
218+
RoslynAssert.Diagnostics(this.analyzer,
219+
ExpectedDiagnostic.Create(AnalyzerIdentifiers.SimplifyValues),
220+
testCode);
221+
}
222+
223+
[Test]
224+
public void AnalyzeWhenAllNullableEnumValuesWereUsed()
225+
{
226+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
227+
public class AnalyzeWhenAllNullableEnumValuesWereUsed
228+
{
229+
public enum TestEnum { A, B, C }
230+
231+
[Test]
232+
public void Test([↓Values(TestEnum.A, TestEnum.B, TestEnum.C, null)] TestEnum? e) { }
233+
}");
234+
RoslynAssert.Diagnostics(this.analyzer,
235+
ExpectedDiagnostic.Create(AnalyzerIdentifiers.SimplifyValues),
236+
testCode);
237+
}
238+
239+
[Test]
240+
public void AnalyzeAllEnumValuesAreInParameters()
241+
{
242+
var testCode = TestUtility.WrapClassInNamespaceAndAddUsing(@"
243+
public class AnalyzeAllEnumValuesAreInParameters
244+
{
245+
public enum TestEnum { A, B, C }
246+
247+
public void Test([↓Values(new object[] { TestEnum.A, TestEnum.B, TestEnum.C })] TestEnum e) { }
248+
}");
249+
RoslynAssert.Diagnostics(this.analyzer,
250+
ExpectedDiagnostic.Create(AnalyzerIdentifiers.SimplifyValues),
251+
testCode);
252+
}
253+
}

0 commit comments

Comments
 (0)