Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix HasValueX incorrectly returning true #676

Merged
merged 4 commits into from
Dec 17, 2023
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
31 changes: 31 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ spelling_checkable_types = # NEW
# Default Severity for all .NET Code Style rules below
dotnet_analyzer_diagnostic.severity = warning

# VSSpell001: Spell Check
dotnet_diagnostic.VSSpell001.severity = suggestion
dotnet_diagnostic.VSSpell002.severity = suggestion

##########################################
# Language Rules - .NET
# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/language-rules
Expand Down Expand Up @@ -162,6 +166,7 @@ dotnet_diagnostic.IDE0063.severity = suggestion
csharp_style_namespace_declarations = file_scoped
csharp_style_prefer_method_group_conversion = true # NEW
csharp_style_prefer_top_level_statements = true # NEW
dotnet_diagnostic.IDE0290.severity = suggestion # NEW - Use primary constructor

# Expression-bodied members
csharp_style_expression_bodied_constructors = true
Expand Down Expand Up @@ -189,6 +194,11 @@ csharp_style_implicit_object_creation_when_type_is_apparent = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_tuple_swap = true # NEW
csharp_style_prefer_utf8_string_literals = true # NEW
dotnet_diagnostic.IDE0028.severity = suggestion # NEW - Use collection initializers
dotnet_diagnostic.IDE0090.severity = suggestion # NEW - Simplify new expression
dotnet_diagnostic.IDE0300.severity = suggestion # NEW - Collection initialization can be simplified
dotnet_diagnostic.IDE0301.severity = suggestion # NEW - Collection initialization can be simplified
dotnet_diagnostic.IDE0305.severity = suggestion # NEW - Collection initialization can be simplified

# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
Expand Down Expand Up @@ -294,6 +304,27 @@ csharp_space_between_square_brackets = false
csharp_preserve_single_line_statements = false
csharp_preserve_single_line_blocks = true

##########################################
# Code Quality Rules - C#
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/
##########################################

[*.{cs,csx,cake}]

# Usage rules
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/usage-warnings
dotnet_diagnostic.CA2249.severity = suggestion # NEW - Consider using String.Contains instead of String.IndexOf

# Performance rules
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/performance-warnings
dotnet_diagnostic.CA1854.severity = suggestion # NEW - Prefer the IDictionary.TryGetValue(TKey, out TValue) method
dotnet_diagnostic.CA1859.severity = suggestion # NEW - Use concrete types when possible for improved performance
dotnet_diagnostic.CA1861.severity = suggestion # NEW - Avoid constant arrays as arguments
dotnet_diagnostic.CA1865.severity = suggestion # NEW - Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char
dotnet_diagnostic.CA1866.severity = suggestion # NEW - Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char
dotnet_diagnostic.CA1867.severity = suggestion # NEW - Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char
dotnet_diagnostic.CA1869.severity = suggestion # NEW - Cache and reuse 'JsonSerializerOptions' instances

##########################################
# .NET Naming Rules
# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/naming-rules
Expand Down
16 changes: 8 additions & 8 deletions Source/Common/Values{T1,T2,T3,T4,T5,T6,T7}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -245,21 +245,21 @@ public Values(IEnumerable<object?> items)
}
}

this.HasValue1 = items1?.Count > 0;
this.HasValue2 = items2?.Count > 0;
this.HasValue3 = items3?.Count > 0;
this.HasValue4 = items4?.Count > 0;
this.HasValue5 = items5?.Count > 0;
this.HasValue6 = items6?.Count > 0;
this.HasValue7 = items7?.Count > 0;

this.Value1 = items1 == null ? default : (OneOrMany<T1>)items1;
this.Value2 = items2 == null ? default : (OneOrMany<T2>)items2;
this.Value3 = items3 == null ? default : (OneOrMany<T3>)items3;
this.Value4 = items4 == null ? default : (OneOrMany<T4>)items4;
this.Value5 = items5 == null ? default : (OneOrMany<T5>)items5;
this.Value6 = items6 == null ? default : (OneOrMany<T6>)items6;
this.Value7 = items7 == null ? default : (OneOrMany<T7>)items7;

this.HasValue1 = this.Value1.Count > 0;
this.HasValue2 = this.Value2.Count > 0;
this.HasValue3 = this.Value3.Count > 0;
this.HasValue4 = this.Value4.Count > 0;
this.HasValue5 = this.Value5.Count > 0;
this.HasValue6 = this.Value6.Count > 0;
this.HasValue7 = this.Value7.Count > 0;
}

/// <summary>
Expand Down
14 changes: 7 additions & 7 deletions Source/Common/Values{T1,T2,T3,T4,T5,T6}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,19 +204,19 @@ public Values(IEnumerable<object?> items)
}
}

this.HasValue1 = items1?.Count > 0;
this.HasValue2 = items2?.Count > 0;
this.HasValue3 = items3?.Count > 0;
this.HasValue4 = items4?.Count > 0;
this.HasValue5 = items5?.Count > 0;
this.HasValue6 = items6?.Count > 0;

this.Value1 = items1 == null ? default : (OneOrMany<T1>)items1;
this.Value2 = items2 == null ? default : (OneOrMany<T2>)items2;
this.Value3 = items3 == null ? default : (OneOrMany<T3>)items3;
this.Value4 = items4 == null ? default : (OneOrMany<T4>)items4;
this.Value5 = items5 == null ? default : (OneOrMany<T5>)items5;
this.Value6 = items6 == null ? default : (OneOrMany<T6>)items6;

this.HasValue1 = this.Value1.Count > 0;
this.HasValue2 = this.Value2.Count > 0;
this.HasValue3 = this.Value3.Count > 0;
this.HasValue4 = this.Value4.Count > 0;
this.HasValue5 = this.Value5.Count > 0;
this.HasValue6 = this.Value6.Count > 0;
}

/// <summary>
Expand Down
12 changes: 6 additions & 6 deletions Source/Common/Values{T1,T2,T3,T4,T5}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,17 +167,17 @@ public Values(IEnumerable<object?> items)
}
}

this.HasValue1 = items1?.Count > 0;
this.HasValue2 = items2?.Count > 0;
this.HasValue3 = items3?.Count > 0;
this.HasValue4 = items4?.Count > 0;
this.HasValue5 = items5?.Count > 0;

this.Value1 = items1 == null ? default : (OneOrMany<T1>)items1;
this.Value2 = items2 == null ? default : (OneOrMany<T2>)items2;
this.Value3 = items3 == null ? default : (OneOrMany<T3>)items3;
this.Value4 = items4 == null ? default : (OneOrMany<T4>)items4;
this.Value5 = items5 == null ? default : (OneOrMany<T5>)items5;

this.HasValue1 = this.Value1.Count > 0;
this.HasValue2 = this.Value2.Count > 0;
this.HasValue3 = this.Value3.Count > 0;
this.HasValue4 = this.Value4.Count > 0;
this.HasValue5 = this.Value5.Count > 0;
}

/// <summary>
Expand Down
10 changes: 5 additions & 5 deletions Source/Common/Values{T1,T2,T3,T4}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,15 @@ public Values(IEnumerable<object?> items)
}
}

this.HasValue1 = items1?.Count > 0;
this.HasValue2 = items2?.Count > 0;
this.HasValue3 = items3?.Count > 0;
this.HasValue4 = items4?.Count > 0;

this.Value1 = items1 == null ? default : (OneOrMany<T1>)items1;
this.Value2 = items2 == null ? default : (OneOrMany<T2>)items2;
this.Value3 = items3 == null ? default : (OneOrMany<T3>)items3;
this.Value4 = items4 == null ? default : (OneOrMany<T4>)items4;

this.HasValue1 = this.Value1.Count > 0;
this.HasValue2 = this.Value2.Count > 0;
this.HasValue3 = this.Value3.Count > 0;
this.HasValue4 = this.Value4.Count > 0;
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions Source/Common/Values{T1,T2,T3}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ public Values(IEnumerable<object> items)
}
}

this.HasValue1 = items1?.Count > 0;
this.HasValue2 = items2?.Count > 0;
this.HasValue3 = items3?.Count > 0;

this.Value1 = items1 == null ? default : (OneOrMany<T1>)items1;
this.Value2 = items2 == null ? default : (OneOrMany<T2>)items2;
this.Value3 = items3 == null ? default : (OneOrMany<T3>)items3;

this.HasValue1 = this.Value1.Count > 0;
this.HasValue2 = this.Value2.Count > 0;
this.HasValue3 = this.Value3.Count > 0;
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions Source/Common/Values{T1,T2}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ public Values(IEnumerable<object?> items)
}
}

this.HasValue1 = items1?.Count > 0;
this.HasValue2 = items2?.Count > 0;

this.Value1 = items1 == null ? default : (OneOrMany<T1>)items1;
this.Value2 = items2 == null ? default : (OneOrMany<T2>)items2;

this.HasValue1 = this.Value1.Count > 0;
this.HasValue2 = this.Value2.Count > 0;
}

/// <summary>
Expand Down
18 changes: 18 additions & 0 deletions Tests/Schema.NET.Test/AssertEx.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Schema.NET.Test;

using System.Collections;
using Xunit;

/// <summary>
/// Provides extended methods and helpers to the Xunit.Assert class.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Name needs to be short to ensure readability of consuming code.")]
public static class AssertEx
{
/// <summary>
/// Verifies that a <see cref="OneOrMany{T}"/> collection is empty.
/// </summary>
/// <typeparam name="T">The type of the values.</typeparam>
/// <inheritdoc cref="Assert.Empty(IEnumerable)"/>
public static void Empty<T>(OneOrMany<T> collection) => Assert.Empty(collection);
}
2 changes: 1 addition & 1 deletion Tests/Schema.NET.Test/OneOrManyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void Constructor_NullList_ThrowsArgumentNullException() =>
public void Count_DefaultStructConstructor_ReturnsZero() => Assert.Empty(default(OneOrMany<int>));

[Fact]
public void Count_DefaultClassConstructor_ReturnsZero() => Assert.Empty(default(OneOrMany<string>)!);
public void Count_DefaultClassConstructor_ReturnsZero() => AssertEx.Empty(default(OneOrMany<string>));

[Fact]
public void Count_NullItem_ReturnsZero() => Assert.Empty(new OneOrMany<int?>((int?)null));
Expand Down
23 changes: 20 additions & 3 deletions Tests/Schema.NET.Test/Values2Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void Constructor_Value1Passed_OnlyValue1HasValue()
Assert.True(values.HasValue1);
Assert.Single(values.Value1);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.Equal(new List<object>() { 1 }, values.Cast<object>().ToList());
}

Expand Down Expand Up @@ -43,6 +43,23 @@ public void Constructor_Items_HasAllItems()
Assert.Equal(new List<object>() { 1, "Foo" }, values.Cast<object>().ToList());
}

[Fact]
public void Constructor_StringItems_NullOrWhitespaceDoesntHaveValue()
{
object[] nullOrWhitespaceValues = new[]
{
string.Empty,
null!,
"\u2028 \u2029 \u0009 \u000A \u000B \u000C \u000D \u0085",
};
var values = new Values<int, string>(nullOrWhitespaceValues);

Assert.False(values.HasValue1);
Assert.Empty(values.Value1);
Assert.False(values.HasValue2, $"{nameof(values.HasValue2)}: Expected: False, Actual: True");
AssertEx.Empty(values.Value2);
}

[Fact]
public void Constructor_NullList_ThrowsArgumentNullException() =>
Assert.Throws<ArgumentNullException>(() => new Values<int, string>((List<object>)null!));
Expand Down Expand Up @@ -88,7 +105,7 @@ public void ImplicitConversionOperator_Value1Passed_OnlyValue1HasValue()
Assert.True(values.HasValue1);
Assert.Single(values.Value1);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.Equal(new List<object>() { 1 }, values.Cast<object>().ToList());
}

Expand All @@ -112,7 +129,7 @@ public void ImplicitConversionOperator_Value1ListPassed_OnlyValue1HasValue()
Assert.True(values.HasValue1);
Assert.Equal(2, values.Value1.Count);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.Equal(new List<object>() { 1, 2 }, values.Cast<object>().ToList());
}

Expand Down
31 changes: 25 additions & 6 deletions Tests/Schema.NET.Test/Values3Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public void Constructor_Value1Passed_OnlyValue1HasValue()
Assert.True(values.HasValue1);
Assert.Single(values.Value1);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.Equal(new List<object>() { 1 }, values.Cast<object>().ToList());
}

Expand All @@ -39,7 +39,7 @@ public void Constructor_Value3Passed_OnlyValue3HasValue()
Assert.False(values.HasValue1);
Assert.Empty(values.Value1);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.True(values.HasValue3);
Assert.Single(values.Value3);
Assert.Equal(new List<object>() { DayOfWeek.Friday }, values.Cast<object>().ToList());
Expand All @@ -59,6 +59,25 @@ public void Constructor_Items_HasAllItems()
Assert.Equal(new List<object>() { 1, "Foo", DayOfWeek.Friday }, values.Cast<object>().ToList());
}

[Fact]
public void Constructor_StringItems_NullOrWhitespaceDoesntHaveValue()
{
object[] nullOrWhitespaceValues = new[]
{
string.Empty,
null!,
"\u2028 \u2029 \u0009 \u000A \u000B \u000C \u000D \u0085",
};
var values = new Values<int, string, DayOfWeek>(nullOrWhitespaceValues);

Assert.False(values.HasValue1);
Assert.Empty(values.Value1);
Assert.False(values.HasValue2, $"{nameof(values.HasValue2)}: Expected: False, Actual: True");
AssertEx.Empty(values.Value2);
Assert.False(values.HasValue3);
Assert.Empty(values.Value3);
}

[Fact]
public void Constructor_NullList_ThrowsArgumentNullException() =>
Assert.Throws<ArgumentNullException>(() => new Values<int, string, DayOfWeek>((List<object>)null!));
Expand Down Expand Up @@ -119,7 +138,7 @@ public void ImplicitConversionOperator_Value1Passed_OnlyValue1HasValue()
Assert.True(values.HasValue1);
Assert.Single(values.Value1);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.Equal(new List<object>() { 1 }, values.Cast<object>().ToList());
}

Expand All @@ -143,7 +162,7 @@ public void ImplicitConversionOperator_Value3Passed_OnlyValue3HasValue()
Assert.False(values.HasValue1);
Assert.Empty(values.Value1);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.True(values.HasValue3);
Assert.Single(values.Value3);
Assert.Equal(new List<object>() { DayOfWeek.Friday }, values.Cast<object>().ToList());
Expand All @@ -157,7 +176,7 @@ public void ImplicitConversionOperator_Value1CollectionPassed_OnlyValue1HasValue
Assert.True(values.HasValue1);
Assert.Equal(2, values.Value1.Count);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.Equal(new List<object>() { 1, 2 }, values.Cast<object>().ToList());
}

Expand All @@ -181,7 +200,7 @@ public void ImplicitConversionOperator_Value3CollectionPassed_OnlyValue3HasValue
Assert.False(values.HasValue1);
Assert.Empty(values.Value1);
Assert.False(values.HasValue2);
Assert.Empty(values.Value2!);
AssertEx.Empty(values.Value2);
Assert.True(values.HasValue3);
Assert.Equal(2, values.Value3.Count);
Assert.Equal(new List<object>() { DayOfWeek.Friday, DayOfWeek.Monday }, values.Cast<object>().ToList());
Expand Down
Loading