From 2e5242357ac03bfd7d6b3296898b672283b18dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Mon, 27 Apr 2026 19:07:01 +0200 Subject: [PATCH 1/3] coverage: add internal tests for analyzer helpers --- .../Mockolate.Analyzers.csproj | 1 + .../Mockolate.SourceGenerators.csproj | 4 + .../AnalyzerHelpersTests.cs | 123 ++++++++++++++++++ .../Mockolate.Analyzers.Tests.csproj | 1 + Tests/Mockolate.Analyzers.Tests/Polyfills.cs | 38 ++++++ .../Entities/MethodEqualityComparerTests.cs | 108 +++++++++++++++ .../Mockolate.SourceGenerators.Tests.csproj | 1 + .../Polyfills.cs | 38 ++++++ 8 files changed, 314 insertions(+) create mode 100644 Tests/Mockolate.Analyzers.Tests/AnalyzerHelpersTests.cs create mode 100644 Tests/Mockolate.Analyzers.Tests/Polyfills.cs create mode 100644 Tests/Mockolate.SourceGenerators.Tests/Entities/MethodEqualityComparerTests.cs create mode 100644 Tests/Mockolate.SourceGenerators.Tests/Polyfills.cs diff --git a/Source/Mockolate.Analyzers/Mockolate.Analyzers.csproj b/Source/Mockolate.Analyzers/Mockolate.Analyzers.csproj index 7165d003..afa73786 100644 --- a/Source/Mockolate.Analyzers/Mockolate.Analyzers.csproj +++ b/Source/Mockolate.Analyzers/Mockolate.Analyzers.csproj @@ -14,6 +14,7 @@ + diff --git a/Source/Mockolate.SourceGenerators/Mockolate.SourceGenerators.csproj b/Source/Mockolate.SourceGenerators/Mockolate.SourceGenerators.csproj index b2d403c3..bf83e85a 100644 --- a/Source/Mockolate.SourceGenerators/Mockolate.SourceGenerators.csproj +++ b/Source/Mockolate.SourceGenerators/Mockolate.SourceGenerators.csproj @@ -11,6 +11,10 @@ S3776 + + + + diff --git a/Tests/Mockolate.Analyzers.Tests/AnalyzerHelpersTests.cs b/Tests/Mockolate.Analyzers.Tests/AnalyzerHelpersTests.cs new file mode 100644 index 00000000..5292a97c --- /dev/null +++ b/Tests/Mockolate.Analyzers.Tests/AnalyzerHelpersTests.cs @@ -0,0 +1,123 @@ +using System.Linq; +using System.Threading.Tasks; +using aweXpect; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Xunit; +using static aweXpect.Expect; + +namespace Mockolate.Analyzers.Tests; + +public class AnalyzerHelpersTests +{ + [Fact] + public async Task GetSingleInvocationTypeArgumentOrNull_NonGenericMethod_ReturnsNull() + { + const string source = """ + public class C + { + public void Foo() { } + public void Bar() { Foo(); } + } + """; + IMethodSymbol method = GetInvokedMethod(source, "Foo"); + + ITypeSymbol? result = AnalyzerHelpers.GetSingleInvocationTypeArgumentOrNull(method); + + await That(result).IsNull(); + } + + [Fact] + public async Task GetSingleInvocationTypeArgumentOrNull_GenericMethod_ReturnsFirstTypeArgument() + { + const string source = """ + public class C + { + public T Foo() => default!; + public void Bar() { Foo(); } + } + """; + IMethodSymbol method = GetInvokedMethod(source, "Foo"); + + ITypeSymbol? result = AnalyzerHelpers.GetSingleInvocationTypeArgumentOrNull(method); + + await That(result).IsNotNull(); + await That(result!.SpecialType).IsEqualTo(SpecialType.System_Int32); + } + + [Fact] + public async Task GetTypeArgumentLocation_NotInvocationExpression_ReturnsNull() + { + const string source = """ + public class C + { + public int Foo() => 0; + } + """; + SyntaxTree tree = CSharpSyntaxTree.ParseText(source); + CSharpCompilation compilation = CreateCompilation(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + MethodDeclarationSyntax declaration = tree.GetRoot().DescendantNodes() + .OfType() + .Single(); + IMethodSymbol symbol = (IMethodSymbol)model.GetDeclaredSymbol(declaration)!; + + Location? result = AnalyzerHelpers.GetTypeArgumentLocation(declaration, symbol.ReturnType); + + await That(result).IsNull(); + } + + [Fact] + public async Task GetTypeArgumentLocation_MatchingShape_ReturnsLocation() + { + const string source = """ + public static class S + { + public static T Make() => default!; + } + + public class C + { + public void Foo() { S.Make(); } + } + """; + SyntaxTree tree = CSharpSyntaxTree.ParseText(source); + CSharpCompilation compilation = CreateCompilation(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + InvocationExpressionSyntax invocation = tree.GetRoot().DescendantNodes() + .OfType() + .Single(i => i.Expression is MemberAccessExpressionSyntax { Name: GenericNameSyntax, }); + IMethodSymbol method = (IMethodSymbol)model.GetSymbolInfo(invocation).Symbol!; + ITypeSymbol typeArgument = method.TypeArguments[0]; + + Location? result = AnalyzerHelpers.GetTypeArgumentLocation(invocation, typeArgument); + + await That(result).IsNotNull(); + } + + private static IMethodSymbol GetInvokedMethod(string source, string methodName) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(source); + CSharpCompilation compilation = CreateCompilation(tree); + SemanticModel model = compilation.GetSemanticModel(tree); + InvocationExpressionSyntax invocation = tree.GetRoot().DescendantNodes() + .OfType() + .Single(i => InvocationName(i) == methodName); + return (IMethodSymbol)model.GetSymbolInfo(invocation).Symbol!; + } + + private static string? InvocationName(InvocationExpressionSyntax invocation) => invocation.Expression switch + { + IdentifierNameSyntax id => id.Identifier.Text, + GenericNameSyntax generic => generic.Identifier.Text, + MemberAccessExpressionSyntax member => member.Name.Identifier.Text, + _ => null, + }; + + private static CSharpCompilation CreateCompilation(SyntaxTree tree) => CSharpCompilation.Create( + "TestAssembly", + [tree,], + [MetadataReference.CreateFromFile(typeof(object).Assembly.Location),], + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); +} diff --git a/Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj b/Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj index c11c6f4d..4cd7de90 100644 --- a/Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj +++ b/Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj @@ -2,6 +2,7 @@ false net10.0 + $(NoWarn);CS0436 diff --git a/Tests/Mockolate.Analyzers.Tests/Polyfills.cs b/Tests/Mockolate.Analyzers.Tests/Polyfills.cs new file mode 100644 index 00000000..5a807ea2 --- /dev/null +++ b/Tests/Mockolate.Analyzers.Tests/Polyfills.cs @@ -0,0 +1,38 @@ +// Local polyfills that win type resolution over IVT-imported polyfills from +// Mockolate.Analyzers (netstandard2.0). Without these, the imported internal +// nullable-attribute types collide with System.Runtime's public types as +// CS0433. With local definitions present the conflict downgrades to CS0436, +// which is suppressed via NoWarn in the project file. +namespace System.Diagnostics.CodeAnalysis +{ + internal sealed class AllowNullAttribute : Attribute; + internal sealed class DisallowNullAttribute : Attribute; + internal sealed class DoesNotReturnAttribute : Attribute; + internal sealed class DoesNotReturnIfAttribute(bool parameterValue) : Attribute + { + public bool ParameterValue { get; } = parameterValue; + } + internal sealed class MaybeNullAttribute : Attribute; + internal sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute + { + public bool ReturnValue { get; } = returnValue; + } + internal sealed class MemberNotNullAttribute(params string[] members) : Attribute + { + public string[] Members { get; } = members; + } + internal sealed class MemberNotNullWhenAttribute(bool returnValue, params string[] members) : Attribute + { + public bool ReturnValue { get; } = returnValue; + public string[] Members { get; } = members; + } + internal sealed class NotNullAttribute : Attribute; + internal sealed class NotNullIfNotNullAttribute(string parameterName) : Attribute + { + public string ParameterName { get; } = parameterName; + } + internal sealed class NotNullWhenAttribute(bool returnValue) : Attribute + { + public bool ReturnValue { get; } = returnValue; + } +} diff --git a/Tests/Mockolate.SourceGenerators.Tests/Entities/MethodEqualityComparerTests.cs b/Tests/Mockolate.SourceGenerators.Tests/Entities/MethodEqualityComparerTests.cs new file mode 100644 index 00000000..e1d02dc9 --- /dev/null +++ b/Tests/Mockolate.SourceGenerators.Tests/Entities/MethodEqualityComparerTests.cs @@ -0,0 +1,108 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Mockolate.SourceGenerators.Entities; +using Mockolate.SourceGenerators.Internals; + +namespace Mockolate.SourceGenerators.Tests.Entities; + +public class MethodEqualityComparerTests +{ + [Fact] + public async Task BothNull_ReturnsTrue() + { + IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; + + bool result = comparer.Equals(null, null); + + await That(result).IsTrue(); + } + + [Fact] + public async Task LeftNull_ReturnsFalse() + { + IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; + Method right = CreateMethod("public class C { public void Foo() {} }", "Foo"); + + bool result = comparer.Equals(null, right); + + await That(result).IsFalse(); + } + + [Fact] + public async Task RightNull_ReturnsFalse() + { + IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; + Method left = CreateMethod("public class C { public void Foo() {} }", "Foo"); + + bool result = comparer.Equals(left, null); + + await That(result).IsFalse(); + } + + [Fact] + public async Task DifferentNames_ReturnsFalse() + { + IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; + Method left = CreateMethod("public class C { public void Foo() {} }", "Foo"); + Method right = CreateMethod("public class C { public void Bar() {} }", "Bar"); + + bool result = comparer.Equals(left, right); + + await That(result).IsFalse(); + } + + [Fact] + public async Task DifferentParameterCount_ReturnsFalse() + { + IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; + Method left = CreateMethod("public class C { public void Foo() {} }", "Foo"); + Method right = CreateMethod("public class C { public void Foo(int x) {} }", "Foo"); + + bool result = comparer.Equals(left, right); + + await That(result).IsFalse(); + } + + [Fact] + public async Task BothMethodsHaveDefaultParameters_ReturnsTrue() + { + IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; + Method left = CreateMethodWithDefaultParameters("public class C { public void Foo() {} }", "Foo"); + Method right = CreateMethodWithDefaultParameters("public class C { public void Foo() {} }", "Foo"); + + bool result = comparer.Equals(left, right); + + await That(result).IsTrue(); + await That(left.Parameters.AsArray()).IsNull(); + await That(right.Parameters.AsArray()).IsNull(); + } + + private static Method CreateMethod(string source, string methodName) + { + SyntaxTree tree = CSharpSyntaxTree.ParseText(source); + CSharpCompilation compilation = CSharpCompilation.Create( + "TestAssembly", + [tree,], + [MetadataReference.CreateFromFile(typeof(object).Assembly.Location),], + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + SemanticModel model = compilation.GetSemanticModel(tree); + MethodDeclarationSyntax declaration = tree.GetRoot().DescendantNodes() + .OfType() + .First(m => m.Identifier.Text == methodName); + IMethodSymbol symbol = (IMethodSymbol)model.GetDeclaredSymbol(declaration)!; + return new Method(symbol, null); + } + + private static Method CreateMethodWithDefaultParameters(string source, string methodName) + { + Method method = CreateMethod(source, methodName); + FieldInfo backingField = typeof(Method) + .GetField("k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)!; + backingField.SetValue(method, default(EquatableArray)); + return method; + } +} diff --git a/Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj b/Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj index 743b7fa6..61b3fac4 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj +++ b/Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj @@ -2,6 +2,7 @@ net10.0 + $(NoWarn);CS0436 diff --git a/Tests/Mockolate.SourceGenerators.Tests/Polyfills.cs b/Tests/Mockolate.SourceGenerators.Tests/Polyfills.cs new file mode 100644 index 00000000..8a9baf47 --- /dev/null +++ b/Tests/Mockolate.SourceGenerators.Tests/Polyfills.cs @@ -0,0 +1,38 @@ +// Local polyfills that win type resolution over IVT-imported polyfills from +// Mockolate.SourceGenerators (netstandard2.0). Without these, the imported +// internal nullable-attribute types collide with System.Runtime's public +// types as CS0433. With local definitions present the conflict downgrades +// to CS0436, which is suppressed via NoWarn in the project file. +namespace System.Diagnostics.CodeAnalysis +{ + internal sealed class AllowNullAttribute : Attribute; + internal sealed class DisallowNullAttribute : Attribute; + internal sealed class DoesNotReturnAttribute : Attribute; + internal sealed class DoesNotReturnIfAttribute(bool parameterValue) : Attribute + { + public bool ParameterValue { get; } = parameterValue; + } + internal sealed class MaybeNullAttribute : Attribute; + internal sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute + { + public bool ReturnValue { get; } = returnValue; + } + internal sealed class MemberNotNullAttribute(params string[] members) : Attribute + { + public string[] Members { get; } = members; + } + internal sealed class MemberNotNullWhenAttribute(bool returnValue, params string[] members) : Attribute + { + public bool ReturnValue { get; } = returnValue; + public string[] Members { get; } = members; + } + internal sealed class NotNullAttribute : Attribute; + internal sealed class NotNullIfNotNullAttribute(string parameterName) : Attribute + { + public string ParameterName { get; } = parameterName; + } + internal sealed class NotNullWhenAttribute(bool returnValue) : Attribute + { + public bool ReturnValue { get; } = returnValue; + } +} From 962d442082c100a08a84d09c57b1940ad0f27a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Tue, 28 Apr 2026 08:55:06 +0200 Subject: [PATCH 2/3] Use manual polyfills in sources --- .../Mockolate.Analyzers.csproj | 4 ++ .../Polyfills/NotNullWhenAttribute.cs | 71 +++++++++++++++++++ .../Mockolate.SourceGenerators.csproj | 4 ++ .../Polyfills/NotNullWhenAttribute.cs | 71 +++++++++++++++++++ .../Mockolate.Analyzers.Tests.csproj | 1 - Tests/Mockolate.Analyzers.Tests/Polyfills.cs | 38 ---------- .../Mockolate.SourceGenerators.Tests.csproj | 1 - .../Polyfills.cs | 38 ---------- 8 files changed, 150 insertions(+), 78 deletions(-) create mode 100644 Source/Mockolate.Analyzers/Polyfills/NotNullWhenAttribute.cs create mode 100644 Source/Mockolate.SourceGenerators/Polyfills/NotNullWhenAttribute.cs delete mode 100644 Tests/Mockolate.Analyzers.Tests/Polyfills.cs delete mode 100644 Tests/Mockolate.SourceGenerators.Tests/Polyfills.cs diff --git a/Source/Mockolate.Analyzers/Mockolate.Analyzers.csproj b/Source/Mockolate.Analyzers/Mockolate.Analyzers.csproj index afa73786..ae7c3f2c 100644 --- a/Source/Mockolate.Analyzers/Mockolate.Analyzers.csproj +++ b/Source/Mockolate.Analyzers/Mockolate.Analyzers.csproj @@ -41,4 +41,8 @@ + + + + diff --git a/Source/Mockolate.Analyzers/Polyfills/NotNullWhenAttribute.cs b/Source/Mockolate.Analyzers/Polyfills/NotNullWhenAttribute.cs new file mode 100644 index 00000000..a39569f5 --- /dev/null +++ b/Source/Mockolate.Analyzers/Polyfills/NotNullWhenAttribute.cs @@ -0,0 +1,71 @@ +#region License +// MIT License +// +// Copyright (c) Manuel Römer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#endregion + +#if !NULLABLE_ATTRIBUTES_DISABLE +#nullable enable +#pragma warning disable + +namespace System.Diagnostics.CodeAnalysis +{ + using global::System; + +#if DEBUG + /// + /// Specifies that when a method returns , + /// the parameter will not be even if the corresponding type allows it. + /// +#endif + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if !NULLABLE_ATTRIBUTES_INCLUDE_IN_CODE_COVERAGE + [ExcludeFromCodeCoverage, DebuggerNonUserCode] +#endif + internal sealed class NotNullWhenAttribute : Attribute + { +#if DEBUG + /// + /// Gets the return value condition. + /// If the method returns this value, the associated parameter will not be . + /// +#endif + public bool ReturnValue { get; } + +#if DEBUG + /// + /// Initializes the attribute with the specified return value condition. + /// + /// + /// The return value condition. + /// If the method returns this value, the associated parameter will not be . + /// +#endif + public NotNullWhenAttribute(bool returnValue) + { + ReturnValue = returnValue; + } + } +} + +#pragma warning restore +#nullable restore +#endif // NULLABLE_ATTRIBUTES_DISABLE diff --git a/Source/Mockolate.SourceGenerators/Mockolate.SourceGenerators.csproj b/Source/Mockolate.SourceGenerators/Mockolate.SourceGenerators.csproj index bf83e85a..454c4e27 100644 --- a/Source/Mockolate.SourceGenerators/Mockolate.SourceGenerators.csproj +++ b/Source/Mockolate.SourceGenerators/Mockolate.SourceGenerators.csproj @@ -24,4 +24,8 @@ + + + + diff --git a/Source/Mockolate.SourceGenerators/Polyfills/NotNullWhenAttribute.cs b/Source/Mockolate.SourceGenerators/Polyfills/NotNullWhenAttribute.cs new file mode 100644 index 00000000..a39569f5 --- /dev/null +++ b/Source/Mockolate.SourceGenerators/Polyfills/NotNullWhenAttribute.cs @@ -0,0 +1,71 @@ +#region License +// MIT License +// +// Copyright (c) Manuel Römer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#endregion + +#if !NULLABLE_ATTRIBUTES_DISABLE +#nullable enable +#pragma warning disable + +namespace System.Diagnostics.CodeAnalysis +{ + using global::System; + +#if DEBUG + /// + /// Specifies that when a method returns , + /// the parameter will not be even if the corresponding type allows it. + /// +#endif + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if !NULLABLE_ATTRIBUTES_INCLUDE_IN_CODE_COVERAGE + [ExcludeFromCodeCoverage, DebuggerNonUserCode] +#endif + internal sealed class NotNullWhenAttribute : Attribute + { +#if DEBUG + /// + /// Gets the return value condition. + /// If the method returns this value, the associated parameter will not be . + /// +#endif + public bool ReturnValue { get; } + +#if DEBUG + /// + /// Initializes the attribute with the specified return value condition. + /// + /// + /// The return value condition. + /// If the method returns this value, the associated parameter will not be . + /// +#endif + public NotNullWhenAttribute(bool returnValue) + { + ReturnValue = returnValue; + } + } +} + +#pragma warning restore +#nullable restore +#endif // NULLABLE_ATTRIBUTES_DISABLE diff --git a/Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj b/Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj index 4cd7de90..c11c6f4d 100644 --- a/Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj +++ b/Tests/Mockolate.Analyzers.Tests/Mockolate.Analyzers.Tests.csproj @@ -2,7 +2,6 @@ false net10.0 - $(NoWarn);CS0436 diff --git a/Tests/Mockolate.Analyzers.Tests/Polyfills.cs b/Tests/Mockolate.Analyzers.Tests/Polyfills.cs deleted file mode 100644 index 5a807ea2..00000000 --- a/Tests/Mockolate.Analyzers.Tests/Polyfills.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Local polyfills that win type resolution over IVT-imported polyfills from -// Mockolate.Analyzers (netstandard2.0). Without these, the imported internal -// nullable-attribute types collide with System.Runtime's public types as -// CS0433. With local definitions present the conflict downgrades to CS0436, -// which is suppressed via NoWarn in the project file. -namespace System.Diagnostics.CodeAnalysis -{ - internal sealed class AllowNullAttribute : Attribute; - internal sealed class DisallowNullAttribute : Attribute; - internal sealed class DoesNotReturnAttribute : Attribute; - internal sealed class DoesNotReturnIfAttribute(bool parameterValue) : Attribute - { - public bool ParameterValue { get; } = parameterValue; - } - internal sealed class MaybeNullAttribute : Attribute; - internal sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute - { - public bool ReturnValue { get; } = returnValue; - } - internal sealed class MemberNotNullAttribute(params string[] members) : Attribute - { - public string[] Members { get; } = members; - } - internal sealed class MemberNotNullWhenAttribute(bool returnValue, params string[] members) : Attribute - { - public bool ReturnValue { get; } = returnValue; - public string[] Members { get; } = members; - } - internal sealed class NotNullAttribute : Attribute; - internal sealed class NotNullIfNotNullAttribute(string parameterName) : Attribute - { - public string ParameterName { get; } = parameterName; - } - internal sealed class NotNullWhenAttribute(bool returnValue) : Attribute - { - public bool ReturnValue { get; } = returnValue; - } -} diff --git a/Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj b/Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj index 61b3fac4..743b7fa6 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj +++ b/Tests/Mockolate.SourceGenerators.Tests/Mockolate.SourceGenerators.Tests.csproj @@ -2,7 +2,6 @@ net10.0 - $(NoWarn);CS0436 diff --git a/Tests/Mockolate.SourceGenerators.Tests/Polyfills.cs b/Tests/Mockolate.SourceGenerators.Tests/Polyfills.cs deleted file mode 100644 index 8a9baf47..00000000 --- a/Tests/Mockolate.SourceGenerators.Tests/Polyfills.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Local polyfills that win type resolution over IVT-imported polyfills from -// Mockolate.SourceGenerators (netstandard2.0). Without these, the imported -// internal nullable-attribute types collide with System.Runtime's public -// types as CS0433. With local definitions present the conflict downgrades -// to CS0436, which is suppressed via NoWarn in the project file. -namespace System.Diagnostics.CodeAnalysis -{ - internal sealed class AllowNullAttribute : Attribute; - internal sealed class DisallowNullAttribute : Attribute; - internal sealed class DoesNotReturnAttribute : Attribute; - internal sealed class DoesNotReturnIfAttribute(bool parameterValue) : Attribute - { - public bool ParameterValue { get; } = parameterValue; - } - internal sealed class MaybeNullAttribute : Attribute; - internal sealed class MaybeNullWhenAttribute(bool returnValue) : Attribute - { - public bool ReturnValue { get; } = returnValue; - } - internal sealed class MemberNotNullAttribute(params string[] members) : Attribute - { - public string[] Members { get; } = members; - } - internal sealed class MemberNotNullWhenAttribute(bool returnValue, params string[] members) : Attribute - { - public bool ReturnValue { get; } = returnValue; - public string[] Members { get; } = members; - } - internal sealed class NotNullAttribute : Attribute; - internal sealed class NotNullIfNotNullAttribute(string parameterName) : Attribute - { - public string ParameterName { get; } = parameterName; - } - internal sealed class NotNullWhenAttribute(bool returnValue) : Attribute - { - public bool ReturnValue { get; } = returnValue; - } -} From 7b5c4a5aeb0291d029324a0ff9909ab7b23e3de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Breu=C3=9F=20Valentin?= Date: Tue, 28 Apr 2026 09:25:13 +0200 Subject: [PATCH 3/3] Fix review issues --- .../Entities/Method.cs | 9 ++--- .../AnalyzerHelpersTests.cs | 8 ++--- .../Entities/MethodEqualityComparerTests.cs | 35 +++---------------- 3 files changed, 11 insertions(+), 41 deletions(-) diff --git a/Source/Mockolate.SourceGenerators/Entities/Method.cs b/Source/Mockolate.SourceGenerators/Entities/Method.cs index a400f278..6b169e38 100644 --- a/Source/Mockolate.SourceGenerators/Entities/Method.cs +++ b/Source/Mockolate.SourceGenerators/Entities/Method.cs @@ -131,13 +131,8 @@ public bool Equals(Method? x, Method? y) } // Compare parameters ignoring nullability annotations - MethodParameter[]? xParams = x.Parameters.AsArray(); - MethodParameter[]? yParams = y.Parameters.AsArray(); - - if (xParams is null || yParams is null) - { - return xParams is null && yParams is null; - } + MethodParameter[] xParams = x.Parameters.AsArray()!; + MethodParameter[] yParams = y.Parameters.AsArray()!; for (int i = 0; i < xParams.Length; i++) { diff --git a/Tests/Mockolate.Analyzers.Tests/AnalyzerHelpersTests.cs b/Tests/Mockolate.Analyzers.Tests/AnalyzerHelpersTests.cs index 5292a97c..48a8ed7d 100644 --- a/Tests/Mockolate.Analyzers.Tests/AnalyzerHelpersTests.cs +++ b/Tests/Mockolate.Analyzers.Tests/AnalyzerHelpersTests.cs @@ -12,7 +12,7 @@ namespace Mockolate.Analyzers.Tests; public class AnalyzerHelpersTests { [Fact] - public async Task GetSingleInvocationTypeArgumentOrNull_NonGenericMethod_ReturnsNull() + public async Task WhenInvokedMethodIsNotGeneric_ShouldNotReturnAnyTypeArgument() { const string source = """ public class C @@ -29,7 +29,7 @@ public void Foo() { } } [Fact] - public async Task GetSingleInvocationTypeArgumentOrNull_GenericMethod_ReturnsFirstTypeArgument() + public async Task WhenInvokedMethodIsGeneric_ShouldReturnFirstTypeArgument() { const string source = """ public class C @@ -47,7 +47,7 @@ public class C } [Fact] - public async Task GetTypeArgumentLocation_NotInvocationExpression_ReturnsNull() + public async Task WhenSyntaxIsNotInvocationExpression_ShouldNotReturnAnyLocation() { const string source = """ public class C @@ -69,7 +69,7 @@ public class C } [Fact] - public async Task GetTypeArgumentLocation_MatchingShape_ReturnsLocation() + public async Task WhenInvocationHasGenericNameSyntax_ShouldReturnTypeArgumentLocation() { const string source = """ public static class S diff --git a/Tests/Mockolate.SourceGenerators.Tests/Entities/MethodEqualityComparerTests.cs b/Tests/Mockolate.SourceGenerators.Tests/Entities/MethodEqualityComparerTests.cs index e1d02dc9..b7bbb438 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Entities/MethodEqualityComparerTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Entities/MethodEqualityComparerTests.cs @@ -1,18 +1,16 @@ using System.Collections.Generic; using System.Linq; -using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Mockolate.SourceGenerators.Entities; -using Mockolate.SourceGenerators.Internals; namespace Mockolate.SourceGenerators.Tests.Entities; public class MethodEqualityComparerTests { [Fact] - public async Task BothNull_ReturnsTrue() + public async Task WhenBothMethodsAreNull_ShouldReturnTrue() { IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; @@ -22,7 +20,7 @@ public async Task BothNull_ReturnsTrue() } [Fact] - public async Task LeftNull_ReturnsFalse() + public async Task WhenLeftMethodIsNull_ShouldReturnFalse() { IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; Method right = CreateMethod("public class C { public void Foo() {} }", "Foo"); @@ -33,7 +31,7 @@ public async Task LeftNull_ReturnsFalse() } [Fact] - public async Task RightNull_ReturnsFalse() + public async Task WhenRightMethodIsNull_ShouldReturnFalse() { IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; Method left = CreateMethod("public class C { public void Foo() {} }", "Foo"); @@ -44,7 +42,7 @@ public async Task RightNull_ReturnsFalse() } [Fact] - public async Task DifferentNames_ReturnsFalse() + public async Task WhenMethodsHaveDifferentNames_ShouldReturnFalse() { IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; Method left = CreateMethod("public class C { public void Foo() {} }", "Foo"); @@ -56,7 +54,7 @@ public async Task DifferentNames_ReturnsFalse() } [Fact] - public async Task DifferentParameterCount_ReturnsFalse() + public async Task WhenMethodsHaveDifferentParameterCount_ShouldReturnFalse() { IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; Method left = CreateMethod("public class C { public void Foo() {} }", "Foo"); @@ -67,20 +65,6 @@ public async Task DifferentParameterCount_ReturnsFalse() await That(result).IsFalse(); } - [Fact] - public async Task BothMethodsHaveDefaultParameters_ReturnsTrue() - { - IEqualityComparer comparer = Method.ContainingTypeIndependentEqualityComparer; - Method left = CreateMethodWithDefaultParameters("public class C { public void Foo() {} }", "Foo"); - Method right = CreateMethodWithDefaultParameters("public class C { public void Foo() {} }", "Foo"); - - bool result = comparer.Equals(left, right); - - await That(result).IsTrue(); - await That(left.Parameters.AsArray()).IsNull(); - await That(right.Parameters.AsArray()).IsNull(); - } - private static Method CreateMethod(string source, string methodName) { SyntaxTree tree = CSharpSyntaxTree.ParseText(source); @@ -96,13 +80,4 @@ private static Method CreateMethod(string source, string methodName) IMethodSymbol symbol = (IMethodSymbol)model.GetDeclaredSymbol(declaration)!; return new Method(symbol, null); } - - private static Method CreateMethodWithDefaultParameters(string source, string methodName) - { - Method method = CreateMethod(source, methodName); - FieldInfo backingField = typeof(Method) - .GetField("k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic)!; - backingField.SetValue(method, default(EquatableArray)); - return method; - } }