From dc04be559b21751a5fc5b4c944e1f5878cb00b65 Mon Sep 17 00:00:00 2001 From: Paul Martins <50200071+MooVC@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:49:47 +0000 Subject: [PATCH 1/2] Fix nested generator ordering for multi-level classes --- .../Classes/Nested.MultiLevel.Declarations.cs | 31 + .../Classes/Nested.MultiLevel.Expected.cs | 544 ++++++++++++++++++ .../Declarations/Classes/Nested.MultiLevel.cs | 48 ++ .../TypeGeneratorTests/WhenExecuted.cs | 12 +- src/Monify/TypeGenerator.cs | 22 +- 5 files changed, 639 insertions(+), 18 deletions(-) create mode 100644 src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.Declarations.cs create mode 100644 src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.Expected.cs create mode 100644 src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.cs diff --git a/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.Declarations.cs b/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.Declarations.cs new file mode 100644 index 0000000..5970cf8 --- /dev/null +++ b/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.Declarations.cs @@ -0,0 +1,31 @@ +namespace Monify.Snippets.Declarations.Classes; + +using Microsoft.CodeAnalysis.CSharp; +using static Monify.Snippets.Snippets; + +internal static partial class Nested +{ + public static partial class MultiLevel + { + public static class Declarations + { + public static readonly Content Main = new( + $$""" + namespace Monify.Testing.Classes + { + public partial class Snippet + { + public partial class BlockOptions + { + [{{BodyTag}}] + public sealed partial class InlineStyle + { + } + } + } + } + """, + LanguageVersion.CSharp2); + } + } +} \ No newline at end of file diff --git a/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.Expected.cs b/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.Expected.cs new file mode 100644 index 0000000..9d46c2b --- /dev/null +++ b/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.Expected.cs @@ -0,0 +1,544 @@ +namespace Monify.Snippets.Declarations.Classes; + +internal static partial class Nested +{ + public static partial class MultiLevel + { + public static class Expected + { + public static readonly Generated ConstructorForEncapsulatedValue = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public InlineStyle(int value) + { + _value = value; + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasConstructorForEncapsulatedValue, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.ctor"); + + public static readonly Generated ConversionFromValue = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public static implicit operator int(InlineStyle subject) + { + if (ReferenceEquals(subject, null)) + { + throw new ArgumentNullException("subject"); + } + + return subject._value; + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasConversionFrom, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.ConvertFrom"); + + public static readonly Generated ConversionToValue = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public static implicit operator InlineStyle(int value) + { + return new InlineStyle(value); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasConversionTo, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.ConvertTo"); + + public static readonly Generated EquatableForSelf = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle : IEquatable + { + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasEquatableForSelf, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.IEquatable.Self"); + + public static readonly Generated EquatableForValue = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle : IEquatable + { + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasEquatableForValue, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.IEquatable.Value"); + + public static readonly Generated EqualityOperatorForSelf = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public static bool operator ==(InlineStyle left, InlineStyle right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (ReferenceEquals(left, null) || ReferenceEquals(right, null)) + { + return false; + } + + return left.Equals(right); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasEqualityOperatorForSelf, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.Equality.Self"); + + public static readonly Generated EqualityOperatorForValue = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public static bool operator ==(InlineStyle left, int right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (ReferenceEquals(left, null) || ReferenceEquals(right, null)) + { + return false; + } + + return left.Equals(right); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasEqualityOperatorForValue, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.Equality.Value"); + + public static readonly Generated EqualsOverride = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public override bool Equals(object other) + { + if (other is InlineStyle) + { + return Equals((InlineStyle)other); + } + + return false; + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasEqualsOverride, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.Equals"); + + public static readonly Generated FieldForEncapsulatedValue = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + private readonly int _value; + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasFieldForEncapsulatedValue, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle._value"); + + public static new readonly Generated GetHashCode = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public override int GetHashCode() + { + return global::Monify.Internal.HashCode.Combine(_value); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasGetHashCodeOverride, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.GetHashCode"); + + public static readonly Generated InequalityOperatorForSelf = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public static bool operator !=(InlineStyle left, InlineStyle right) + { + return !(left == right); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasInequalityOperatorForSelf, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.Inequality.Self"); + + public static readonly Generated InequalityOperatorForValue = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public static bool operator !=(InlineStyle left, int right) + { + return !(left == right); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasInequalityOperatorForValue, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.Inequality.Value"); + + public static new readonly Generated ToString = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public override string ToString() + { + return string.Format("InlineStyle {{ {0} }}", _value); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.HasToStringOverride, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.ToString"); + + public static readonly Generated EquatableToSelf = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public bool Equals(InlineStyle other) + { + if (ReferenceEquals(this, other)) + { + return true; + } + + if (ReferenceEquals(other, null)) + { + return false; + } + + return Equals(other._value); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.IsEquatableToSelf, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.IEquatable.Self.Equals"); + + public static readonly Generated EquatableToValue = new( + """ + namespace Monify.Testing.Classes + { + using System; + using System.Collections.Generic; + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable disable + #endif + + partial class Snippet + { + partial class BlockOptions + { + sealed partial class InlineStyle + { + public bool Equals(int other) + { + if (ReferenceEquals(this, other)) + { + return true; + } + + if (ReferenceEquals(other, null)) + { + return false; + } + + return global::System.Collections.Generic.EqualityComparer.Default.Equals(_value, other); + } + } + } + } + + #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + #nullable restore + #endif + } + """, + Extensions.IsEquatableToValue, + "Monify.Testing.Classes.Snippet.BlockOptions.InlineStyle.IEquatable.Value.Equals"); + } + } +} \ No newline at end of file diff --git a/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.cs b/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.cs new file mode 100644 index 0000000..05465b7 --- /dev/null +++ b/src/Monify.Tests/Snippets/Declarations/Classes/Nested.MultiLevel.cs @@ -0,0 +1,48 @@ +namespace Monify.Snippets.Declarations.Classes; + +using static Monify.Snippets.Declarations.Attributes.Annotations; + +internal static partial class Nested +{ + public static partial class MultiLevel + { + public static readonly Snippets Declaration = new( + [NonGeneric], + Declarations.Main, + [ + Expected.ConstructorForEncapsulatedValue, + Expected.ConversionFromValue, + Expected.ConversionToValue, + Expected.EqualityOperatorForSelf, + Expected.EqualityOperatorForValue, + Expected.EqualsOverride, + Expected.EquatableForSelf, + Expected.EquatableToSelf, + Expected.EquatableForValue, + Expected.EquatableToValue, + Expected.FieldForEncapsulatedValue, + Expected.GetHashCode, + Expected.InequalityOperatorForSelf, + Expected.InequalityOperatorForValue, + Expected.ToString, + ], + [ + new(Expected.ConstructorForEncapsulatedValue.Content, Extensions.HasConstructorForEncapsulatedValue), + new(Expected.ConversionFromValue.Content, Extensions.HasConversionFrom), + new(Expected.ConversionToValue.Content, Extensions.HasConversionTo), + new(Expected.EqualityOperatorForSelf.Content, Extensions.HasEqualityOperatorForSelf), + new(Expected.EqualityOperatorForValue.Content, Extensions.HasEqualityOperatorForValue), + new(Expected.EqualsOverride.Content, Extensions.HasEqualsOverride), + new(Expected.EquatableForSelf.Content, Extensions.HasEquatableForSelf), + new(Expected.EquatableToSelf.Content, Extensions.IsEquatableToSelf), + new(Expected.EquatableForValue.Content, Extensions.HasEquatableForValue), + new(Expected.EquatableToValue.Content, Extensions.IsEquatableToValue), + new(Expected.FieldForEncapsulatedValue.Content, Extensions.HasFieldForEncapsulatedValue), + new(Expected.GetHashCode.Content, Extensions.HasGetHashCodeOverride), + new(Expected.InequalityOperatorForSelf.Content, Extensions.HasInequalityOperatorForSelf), + new(Expected.InequalityOperatorForValue.Content, Extensions.HasInequalityOperatorForValue), + new(Expected.ToString.Content, Extensions.HasToStringOverride), + ], + nameof(MultiLevel)); + } +} \ No newline at end of file diff --git a/src/Monify.Tests/TypeGeneratorTests/WhenExecuted.cs b/src/Monify.Tests/TypeGeneratorTests/WhenExecuted.cs index 8a9980c..6aa82e8 100644 --- a/src/Monify.Tests/TypeGeneratorTests/WhenExecuted.cs +++ b/src/Monify.Tests/TypeGeneratorTests/WhenExecuted.cs @@ -21,12 +21,12 @@ public sealed class WhenExecuted public async Task GivenATypeTheExpectedSourceIsGenerated(ReferenceAssemblies assembly, Expectations expectations, LanguageVersion language) { // Arrange - var test = new GeneratorTest(assembly, language, _generators); - - Attributes.IsExpectedIn(test.TestState, language); - expectations.IsDeclaredIn(test.TestState); - Internal.HashCode.IsExpectedIn(test.TestState); - Internal.SequenceEqualityComparer.IsExpectedIn(test.TestState); + var test = new GeneratorTest(assembly, language, _generators); + + Attributes.IsExpectedIn(test.TestState, language); + Internal.HashCode.IsExpectedIn(test.TestState); + Internal.SequenceEqualityComparer.IsExpectedIn(test.TestState); + expectations.IsDeclaredIn(test.TestState); // Act Func act = () => test.RunAsync(); diff --git a/src/Monify/TypeGenerator.cs b/src/Monify/TypeGenerator.cs index 35b0313..745ebbc 100644 --- a/src/Monify/TypeGenerator.cs +++ b/src/Monify/TypeGenerator.cs @@ -1,6 +1,7 @@ namespace Monify; using System; +using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Monify.Model; @@ -76,17 +77,14 @@ private static void Generate(SourceProductionContext context, Subject? subject) private static string GetHint(Source source, Subject subject) { - string name = subject.Name; - - if (subject.Nesting.Length > 0) - { - IEnumerable names = subject.Nesting - .Reverse() - .Select(parent => parent.Name) - .Union(new[] { name }); - - name = string.Join(".", names); - } + string name = subject.Nesting + .Select(parent => parent.Name) + .Concat(new[] { subject.Name }) + .Aggregate( + string.Empty, + (current, next) => string.IsNullOrEmpty(current) + ? next + : $"{current}.{next}"); string separator = source.Hint.StartsWith(".") ? string.Empty @@ -112,7 +110,7 @@ private static bool IsMatch(SyntaxNode node, CancellationToken cancellationToken private static string Nest(string code, Subject subject) { - foreach (Nesting parent in subject.Nesting) + foreach (Nesting parent in subject.Nesting.Reverse()) { code = code.Indent(); From 6e6159e04462c7d6eb83d091ddf1e0c5e72769e6 Mon Sep 17 00:00:00 2001 From: Paul Martins Date: Sun, 7 Dec 2025 15:43:50 +0000 Subject: [PATCH 2/2] Fixed whitespace --- .github/ISSUE_TEMPLATE/bug_report.md | 8 +- CHANGELOG.md | 8 +- .../TypeGeneratorTests/WhenExecuted.cs | 13 +- .../INamedTypeSymbolExtensions.IsStateless.cs | 118 +++++++++--------- 4 files changed, 76 insertions(+), 71 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c0ad098..9d825a5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -20,9 +20,9 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Environment (please complete the following information):** -- Monify Version: [e.g. 1.0.0] -- .NET Version (Your Project): [e.g. NET 8] - - IDE: [e.g. Visual Studio 2022] +- Monify Version: [1.3.1] +- .NET Version (Your Project): [NET 10] + - IDE: [Visual Studio 2022 v18.0.2] **Additional context** -Add any other context about the problem here. +Add any other context about the problem here. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a11a99..adfa5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,13 @@ All notable changes to Monify will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -# [1.3.0] - TBC +# [1.3.1] - 2025-12-07 + +## Fixed + +- Nested partial classes are now ordered correctly (#49). + +# [1.3.0] - 2025-11-29 ## Added diff --git a/src/Monify.Tests/TypeGeneratorTests/WhenExecuted.cs b/src/Monify.Tests/TypeGeneratorTests/WhenExecuted.cs index 6aa82e8..67f51e9 100644 --- a/src/Monify.Tests/TypeGeneratorTests/WhenExecuted.cs +++ b/src/Monify.Tests/TypeGeneratorTests/WhenExecuted.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis.Testing; using Monify.Snippets; using Monify.Snippets.Declarations; -using Monify.Snippets.Declarations.Structs; public sealed class WhenExecuted { @@ -21,12 +20,12 @@ public sealed class WhenExecuted public async Task GivenATypeTheExpectedSourceIsGenerated(ReferenceAssemblies assembly, Expectations expectations, LanguageVersion language) { // Arrange - var test = new GeneratorTest(assembly, language, _generators); - - Attributes.IsExpectedIn(test.TestState, language); - Internal.HashCode.IsExpectedIn(test.TestState); - Internal.SequenceEqualityComparer.IsExpectedIn(test.TestState); - expectations.IsDeclaredIn(test.TestState); + var test = new GeneratorTest(assembly, language, _generators); + + Attributes.IsExpectedIn(test.TestState, language); + Internal.HashCode.IsExpectedIn(test.TestState); + Internal.SequenceEqualityComparer.IsExpectedIn(test.TestState); + expectations.IsDeclaredIn(test.TestState); // Act Func act = () => test.RunAsync(); diff --git a/src/Monify/Semantics/INamedTypeSymbolExtensions.IsStateless.cs b/src/Monify/Semantics/INamedTypeSymbolExtensions.IsStateless.cs index cf16a3e..a70c116 100644 --- a/src/Monify/Semantics/INamedTypeSymbolExtensions.IsStateless.cs +++ b/src/Monify/Semantics/INamedTypeSymbolExtensions.IsStateless.cs @@ -1,64 +1,64 @@ -namespace Monify.Semantics; - -using Microsoft.CodeAnalysis; +namespace Monify.Semantics; + +using Microsoft.CodeAnalysis; using Monify.Strategies; -/// -/// Provides extensions relating to . -/// -internal static partial class INamedTypeSymbolExtensions -{ - private const int ExpectedFieldsForStatelessType = 0; - private const int ExpectedFieldsWhenFieldIsAlreadyDefined = 1; - private const int OffsetForFieldWhenAlreadyDefined = 0; - - /// - /// Determines whether or not the is stateless. - /// - /// - /// The subject for which the state holding capability is to be determined. - /// - /// - /// The type associated with the encapsulated value. - /// - /// - /// if the already defines a field for the encapsulated value, otherwise . - /// - /// - /// if the is stateless, otherwise . - /// - /// - /// If the class holds state, then it points to a design issue, as the class is intended to represent a single state. - /// - public static bool IsStateless(this INamedTypeSymbol subject, ITypeSymbol? value, out bool hasFieldForEncapsulatedValue) +/// +/// Provides extensions relating to . +/// +internal static partial class INamedTypeSymbolExtensions +{ + private const int ExpectedFieldsForStatelessType = 0; + private const int ExpectedFieldsWhenFieldIsAlreadyDefined = 1; + private const int OffsetForFieldWhenAlreadyDefined = 0; + + /// + /// Determines whether or not the is stateless. + /// + /// + /// The subject for which the state holding capability is to be determined. + /// + /// + /// The type associated with the encapsulated value. + /// + /// + /// if the already defines a field for the encapsulated value, otherwise . + /// + /// + /// if the is stateless, otherwise . + /// + /// + /// If the class holds state, then it points to a design issue, as the class is intended to represent a single state. + /// + public static bool IsStateless(this INamedTypeSymbol subject, ITypeSymbol? value, out bool hasFieldForEncapsulatedValue) { - IFieldSymbol[] fields = subject - .GetMembers() - .Where(member => member.Kind == SymbolKind.Field && !member.IsStatic) - .OfType() - .Where(field => !field.IsImplicitlyDeclared || IsExplicitlyDeclaredBackingField(field)) - .ToArray(); - - hasFieldForEncapsulatedValue = false; - - if (fields.Length == ExpectedFieldsForStatelessType) - { - return true; - } - - if (value is not null && fields.Length == ExpectedFieldsWhenFieldIsAlreadyDefined) - { - IFieldSymbol field = fields[OffsetForFieldWhenAlreadyDefined]; - + IFieldSymbol[] fields = subject + .GetMembers() + .Where(member => member.Kind == SymbolKind.Field && !member.IsStatic) + .OfType() + .Where(field => !field.IsImplicitlyDeclared || IsExplicitlyDeclaredBackingField(field)) + .ToArray(); + + hasFieldForEncapsulatedValue = false; + + if (fields.Length == ExpectedFieldsForStatelessType) + { + return true; + } + + if (value is not null && fields.Length == ExpectedFieldsWhenFieldIsAlreadyDefined) + { + IFieldSymbol field = fields[OffsetForFieldWhenAlreadyDefined]; + hasFieldForEncapsulatedValue = field.Name.Equals(FieldStrategy.Name) - && SymbolEqualityComparer.IncludeNullability.Equals(field.Type, value); - } - - return hasFieldForEncapsulatedValue; - } - - private static bool IsExplicitlyDeclaredBackingField(IFieldSymbol field) - { - return field.AssociatedSymbol is IPropertySymbol property && !property.IsImplicitlyDeclared; - } + && SymbolEqualityComparer.IncludeNullability.Equals(field.Type, value); + } + + return hasFieldForEncapsulatedValue; + } + + private static bool IsExplicitlyDeclaredBackingField(IFieldSymbol field) + { + return field.AssociatedSymbol is IPropertySymbol property && !property.IsImplicitlyDeclared; + } } \ No newline at end of file