diff --git a/CompositeKey.slnx b/CompositeKey.slnx
index 8dcb9fa..1c5d121 100644
--- a/CompositeKey.slnx
+++ b/CompositeKey.slnx
@@ -8,6 +8,7 @@
+
diff --git a/src/CompositeKey.Analyzers.Common.UnitTests/CompositeKey.Analyzers.Common.UnitTests.csproj b/src/CompositeKey.Analyzers.Common.UnitTests/CompositeKey.Analyzers.Common.UnitTests.csproj
new file mode 100644
index 0000000..bebdef8
--- /dev/null
+++ b/src/CompositeKey.Analyzers.Common.UnitTests/CompositeKey.Analyzers.Common.UnitTests.csproj
@@ -0,0 +1,40 @@
+
+
+
+ net9.0;net8.0
+ true
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
diff --git a/src/CompositeKey.Analyzers.Common.UnitTests/TestHelpers/CompilationTestHelper.cs b/src/CompositeKey.Analyzers.Common.UnitTests/TestHelpers/CompilationTestHelper.cs
new file mode 100644
index 0000000..a67b240
--- /dev/null
+++ b/src/CompositeKey.Analyzers.Common.UnitTests/TestHelpers/CompilationTestHelper.cs
@@ -0,0 +1,63 @@
+using System.Reflection;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace CompositeKey.Analyzers.Common.UnitTests.TestHelpers;
+
+///
+/// Test helper for creating Roslyn compilation objects for testing validation logic.
+///
+public static class CompilationTestHelper
+{
+ private static readonly Assembly SystemRuntimeAssembly = Assembly.Load(new AssemblyName("System.Runtime"));
+ private static readonly CSharpParseOptions DefaultParseOptions = new(LanguageVersion.CSharp11);
+
+ ///
+ /// Creates a C# compilation from source code.
+ ///
+ public static CSharpCompilation CreateCompilation(string source, string assemblyName = "TestAssembly")
+ {
+ List references =
+ [
+ MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Guid).Assembly.Location),
+ MetadataReference.CreateFromFile(SystemRuntimeAssembly.Location),
+ MetadataReference.CreateFromFile(typeof(CompositeKeyAttribute).Assembly.Location),
+ ];
+
+ return CSharpCompilation.Create(
+ assemblyName,
+ [CSharpSyntaxTree.ParseText(source, DefaultParseOptions)],
+ references,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+ }
+
+ ///
+ /// Gets the semantic model and type declaration syntax for the first type in the compilation.
+ ///
+ public static (SemanticModel SemanticModel, TypeDeclarationSyntax TypeDeclaration, INamedTypeSymbol TypeSymbol)
+ GetFirstTypeInfo(CSharpCompilation compilation, string typeName)
+ {
+ var syntaxTree = compilation.SyntaxTrees.First();
+ var semanticModel = compilation.GetSemanticModel(syntaxTree);
+
+ var typeDeclaration = syntaxTree.GetRoot()
+ .DescendantNodes()
+ .OfType()
+ .First(t => t.Identifier.ValueText == typeName);
+
+ var typeSymbol = semanticModel.GetDeclaredSymbol(typeDeclaration)
+ ?? throw new InvalidOperationException($"Could not get type symbol for {typeName}");
+
+ return (semanticModel, typeDeclaration, typeSymbol);
+ }
+
+ ///
+ /// Gets the CompositeKeyConstructorAttribute type symbol from the compilation.
+ ///
+ public static INamedTypeSymbol? GetCompositeKeyConstructorAttributeSymbol(CSharpCompilation compilation)
+ {
+ return compilation.GetTypeByMetadataName("CompositeKey.CompositeKeyConstructorAttribute");
+ }
+}
diff --git a/src/CompositeKey.Analyzers.Common.UnitTests/Validation/TypeValidationTests.cs b/src/CompositeKey.Analyzers.Common.UnitTests/Validation/TypeValidationTests.cs
new file mode 100644
index 0000000..4b287b0
--- /dev/null
+++ b/src/CompositeKey.Analyzers.Common.UnitTests/Validation/TypeValidationTests.cs
@@ -0,0 +1,572 @@
+using CompositeKey.Analyzers.Common.Diagnostics;
+using CompositeKey.Analyzers.Common.UnitTests.TestHelpers;
+using CompositeKey.Analyzers.Common.Validation;
+
+namespace CompositeKey.Analyzers.Common.UnitTests.Validation;
+
+public static class TypeValidationTests
+{
+ public class ValidateTypeForCompositeKeyTests
+ {
+ [Fact]
+ public void ValidRecord_ShouldReturnSuccess()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ [CompositeKey("{Id}")]
+ public partial record TestKey(Guid Id);
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (semanticModel, typeDeclaration, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ var result = TypeValidation.ValidateTypeForCompositeKey(
+ typeSymbol,
+ typeDeclaration,
+ semanticModel,
+ attributeSymbol,
+ CancellationToken.None);
+
+ // Assert
+ result.IsSuccess.ShouldBeTrue();
+ result.Constructor.ShouldNotBeNull();
+ result.TargetTypeDeclarations.ShouldNotBeNull();
+ result.TargetTypeDeclarations.ShouldContain("public partial record TestKey");
+ }
+
+ [Theory]
+ [InlineData("class", "TestClass")]
+ [InlineData("struct", "TestStruct")]
+ [InlineData("interface", "TestInterface")]
+ public void NonRecordType_ShouldReturnFailureWithUnsupportedCompositeType(string typeKind, string typeName)
+ {
+ // Arrange
+ string source = typeKind switch
+ {
+ "interface" => $$"""
+ using System;
+ using CompositeKey;
+
+ [CompositeKey("{Id}")]
+ public partial {{typeKind}} {{typeName}}
+ {
+ Guid Id { get; set; }
+ }
+ """,
+ _ => $$"""
+ using System;
+ using CompositeKey;
+
+ [CompositeKey("{Id}")]
+ public partial {{typeKind}} {{typeName}}
+ {
+ public Guid Id { get; set; }
+ }
+ """
+ };
+
+ var compilation = CompilationTestHelper.CreateCompilation(source);
+ var (semanticModel, typeDeclaration, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, typeName);
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ var result = TypeValidation.ValidateTypeForCompositeKey(
+ typeSymbol,
+ typeDeclaration,
+ semanticModel,
+ attributeSymbol,
+ CancellationToken.None);
+
+ // Assert
+ result.IsSuccess.ShouldBeFalse();
+ result.Descriptor.ShouldBe(DiagnosticDescriptors.UnsupportedCompositeType);
+ result.MessageArgs.ShouldNotBeNull();
+ result.MessageArgs![0].ShouldBe(typeName);
+ }
+
+ [Fact]
+ public void NonPartialRecord_ShouldReturnFailureWithCompositeTypeMustBePartial()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ [CompositeKey("{Id}")]
+ public record TestKey(Guid Id);
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (semanticModel, typeDeclaration, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ var result = TypeValidation.ValidateTypeForCompositeKey(
+ typeSymbol,
+ typeDeclaration,
+ semanticModel,
+ attributeSymbol,
+ CancellationToken.None);
+
+ // Assert
+ result.IsSuccess.ShouldBeFalse();
+ result.Descriptor.ShouldBe(DiagnosticDescriptors.CompositeTypeMustBePartial);
+ result.MessageArgs.ShouldNotBeNull();
+ result.MessageArgs![0].ShouldBe("TestKey");
+ }
+
+ [Fact]
+ public void RecordWithMultiplePublicConstructorsAndNoParameterless_ShouldReturnFailureWithNoObviousDefaultConstructor()
+ {
+ // Arrange - Multiple constructors but none parameterless and none attributed
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ [CompositeKey("{Id}")]
+ public partial record TestKey
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+
+ public TestKey(Guid id) { Id = id; Name = ""; }
+ public TestKey(Guid id, string name) { Id = id; Name = name; }
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (semanticModel, typeDeclaration, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ var result = TypeValidation.ValidateTypeForCompositeKey(
+ typeSymbol,
+ typeDeclaration,
+ semanticModel,
+ attributeSymbol,
+ CancellationToken.None);
+
+ // Assert
+ result.IsSuccess.ShouldBeFalse();
+ result.Descriptor.ShouldBe(DiagnosticDescriptors.NoObviousDefaultConstructor);
+ result.MessageArgs.ShouldNotBeNull();
+ result.MessageArgs![0].ShouldBe("TestKey");
+ }
+
+ [Fact]
+ public void NestedPartialRecord_ShouldReturnSuccessWithAllDeclarations()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial class Outer
+ {
+ [CompositeKey("{Id}")]
+ public partial record InnerKey(Guid Id);
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (semanticModel, typeDeclaration, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "InnerKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ var result = TypeValidation.ValidateTypeForCompositeKey(
+ typeSymbol,
+ typeDeclaration,
+ semanticModel,
+ attributeSymbol,
+ CancellationToken.None);
+
+ // Assert
+ result.IsSuccess.ShouldBeTrue();
+ result.TargetTypeDeclarations.ShouldNotBeNull();
+ result.TargetTypeDeclarations.Count.ShouldBe(2);
+ result.TargetTypeDeclarations.ShouldContain("public partial record InnerKey");
+ result.TargetTypeDeclarations.ShouldContain("public partial class Outer");
+ }
+
+ [Fact]
+ public void RecordStruct_ShouldReturnSuccessIfValid()
+ {
+ // Arrange - Record structs are supported
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ [CompositeKey("{Id}")]
+ public partial record struct TestRecordStruct(Guid Id);
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (semanticModel, typeDeclaration, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestRecordStruct");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ var result = TypeValidation.ValidateTypeForCompositeKey(
+ typeSymbol,
+ typeDeclaration,
+ semanticModel,
+ attributeSymbol,
+ CancellationToken.None);
+
+ // Assert
+ // Record structs are valid composite key types
+ result.IsSuccess.ShouldBeTrue();
+ result.Constructor.ShouldNotBeNull();
+ result.TargetTypeDeclarations.ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void NestedTypeWithNonPartialParent_ShouldReturnFailureWithCompositeTypeMustBePartial()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public class NonPartialOuter
+ {
+ [CompositeKey("{Id}")]
+ public partial record InnerKey(Guid Id);
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (semanticModel, typeDeclaration, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "InnerKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ var result = TypeValidation.ValidateTypeForCompositeKey(
+ typeSymbol,
+ typeDeclaration,
+ semanticModel,
+ attributeSymbol,
+ CancellationToken.None);
+
+ // Assert
+ result.IsSuccess.ShouldBeFalse();
+ result.Descriptor.ShouldBe(DiagnosticDescriptors.CompositeTypeMustBePartial);
+ result.MessageArgs.ShouldNotBeNull();
+ result.MessageArgs![0].ShouldBe("InnerKey");
+ }
+ }
+
+ public class TryGetObviousOrExplicitlyMarkedConstructorTests
+ {
+ [Fact]
+ public void RecordWithPrimaryConstructor_ShouldReturnPrimaryConstructor()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey(Guid Id, string Name);
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ attributeSymbol,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeTrue();
+ constructor.ShouldNotBeNull();
+ constructor.Parameters.Length.ShouldBe(2);
+ }
+
+ [Fact]
+ public void RecordWithParameterlessConstructor_ShouldReturnParameterlessConstructor()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey
+ {
+ public Guid Id { get; set; }
+ public TestKey() { }
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ attributeSymbol,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeTrue();
+ constructor.ShouldNotBeNull();
+ constructor.Parameters.Length.ShouldBe(0);
+ }
+
+ [Fact]
+ public void RecordWithMultipleParameterizedConstructors_ShouldReturnFalse()
+ {
+ // Arrange - Multiple constructors, no parameterless, no attributed
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+
+ public TestKey(Guid id) { Id = id; Name = ""; }
+ public TestKey(Guid id, string name) { Id = id; Name = name; }
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ attributeSymbol,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeFalse();
+ constructor.ShouldBeNull();
+ }
+
+ [Fact]
+ public void RecordWithSinglePublicConstructor_ShouldReturnThatConstructor()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+
+ public TestKey(Guid id, string name)
+ {
+ Id = id;
+ Name = name;
+ }
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ attributeSymbol,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeTrue();
+ constructor.ShouldNotBeNull();
+ constructor.Parameters.Length.ShouldBe(2);
+ }
+
+ [Fact]
+ public void RecordWithExplicitAttributeMarkedConstructor_ShouldReturnAttributedConstructor()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+
+ public TestKey() { }
+
+ [CompositeKeyConstructor]
+ public TestKey(Guid id, string name)
+ {
+ Id = id;
+ Name = name;
+ }
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ attributeSymbol,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeTrue();
+ constructor.ShouldNotBeNull();
+ constructor.Parameters.Length.ShouldBe(2);
+ constructor.GetAttributes().Any(a => a.AttributeClass?.Name == "CompositeKeyConstructorAttribute").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void RecordWithMultipleAttributedConstructors_ShouldReturnFalse()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+
+ [CompositeKeyConstructor]
+ public TestKey(Guid id)
+ {
+ Id = id;
+ Name = "";
+ }
+
+ [CompositeKeyConstructor]
+ public TestKey(Guid id, string name)
+ {
+ Id = id;
+ Name = name;
+ }
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ attributeSymbol,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeFalse();
+ constructor.ShouldBeNull();
+ }
+
+ [Fact]
+ public void RecordWithCopyConstructor_ShouldIgnoreCopyConstructor()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey(Guid Id)
+ {
+ // Copy constructor should be ignored
+ public TestKey(TestKey original) : this(original.Id) { }
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ attributeSymbol,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeTrue();
+ constructor.ShouldNotBeNull();
+ // Should return primary constructor, not copy constructor
+ constructor.Parameters.Length.ShouldBe(1);
+ constructor.Parameters[0].Type.Name.ShouldBe("Guid");
+ }
+
+ [Fact]
+ public void RecordWithNullAttributeSymbol_ShouldStillWork()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey(Guid Id);
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+
+ // Act - Pass null for attribute symbol
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ null,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeTrue();
+ constructor.ShouldNotBeNull();
+ constructor.Parameters.Length.ShouldBe(1);
+ }
+
+ [Fact]
+ public void RecordWithMultipleConstructorsIncludingParameterless_ShouldPreferParameterless()
+ {
+ // Arrange
+ const string Source = """
+ using System;
+ using CompositeKey;
+
+ public partial record TestKey
+ {
+ public Guid Id { get; set; }
+ public string Name { get; set; }
+
+ public TestKey() { }
+ public TestKey(Guid id) { Id = id; Name = ""; }
+ public TestKey(Guid id, string name) { Id = id; Name = name; }
+ }
+ """;
+
+ var compilation = CompilationTestHelper.CreateCompilation(Source);
+ var (_, _, typeSymbol) = CompilationTestHelper.GetFirstTypeInfo(compilation, "TestKey");
+ var attributeSymbol = CompilationTestHelper.GetCompositeKeyConstructorAttributeSymbol(compilation);
+
+ // Act
+ bool result = TypeValidation.TryGetObviousOrExplicitlyMarkedConstructor(
+ typeSymbol,
+ attributeSymbol,
+ out var constructor);
+
+ // Assert
+ result.ShouldBeTrue();
+ constructor.ShouldNotBeNull();
+ // Should prefer the parameterless constructor
+ constructor.Parameters.Length.ShouldBe(0);
+ }
+ }
+}
diff --git a/src/CompositeKey.Analyzers.Common.UnitTests/packages.lock.json b/src/CompositeKey.Analyzers.Common.UnitTests/packages.lock.json
new file mode 100644
index 0000000..9e002f8
--- /dev/null
+++ b/src/CompositeKey.Analyzers.Common.UnitTests/packages.lock.json
@@ -0,0 +1,645 @@
+{
+ "version": 2,
+ "dependencies": {
+ "net8.0": {
+ "coverlet.collector": {
+ "type": "Direct",
+ "requested": "[6.0.4, )",
+ "resolved": "6.0.4",
+ "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg=="
+ },
+ "DotNet.ReproducibleBuilds": {
+ "type": "Direct",
+ "requested": "[1.2.25, )",
+ "resolved": "1.2.25",
+ "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg=="
+ },
+ "JunitXml.TestLogger": {
+ "type": "Direct",
+ "requested": "[6.1.0, )",
+ "resolved": "6.1.0",
+ "contentHash": "a3ciawoHOzqcry7yS5z9DerNyF9QZi6fEZZJPILSy6Noj6+r8Ydma+cENA6wvivXDCblpXxw72wWT9QApNy/0w=="
+ },
+ "Microsoft.CodeAnalysis": {
+ "type": "Direct",
+ "requested": "[4.8.0, )",
+ "resolved": "4.8.0",
+ "contentHash": "g5eTgZVyBr4k1zxvJeVrJ1nDvBHrDt7XX2Uo7UWRoF9GdzOv9od4WtOeL1/e86ifgwX/H7H1Vs5u2OCdv0HYpQ==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.CSharp.Workspaces": "[4.8.0]",
+ "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[4.8.0]"
+ }
+ },
+ "Microsoft.NET.Test.Sdk": {
+ "type": "Direct",
+ "requested": "[17.14.1, )",
+ "resolved": "17.14.1",
+ "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==",
+ "dependencies": {
+ "Microsoft.CodeCoverage": "17.14.1",
+ "Microsoft.TestPlatform.TestHost": "17.14.1"
+ }
+ },
+ "Shouldly": {
+ "type": "Direct",
+ "requested": "[4.3.0, )",
+ "resolved": "4.3.0",
+ "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==",
+ "dependencies": {
+ "DiffEngine": "11.3.0",
+ "EmptyFiles": "4.4.0"
+ }
+ },
+ "xunit": {
+ "type": "Direct",
+ "requested": "[2.9.3, )",
+ "resolved": "2.9.3",
+ "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
+ "dependencies": {
+ "xunit.analyzers": "1.18.0",
+ "xunit.assert": "2.9.3",
+ "xunit.core": "[2.9.3]"
+ }
+ },
+ "xunit.runner.visualstudio": {
+ "type": "Direct",
+ "requested": "[3.1.3, )",
+ "resolved": "3.1.3",
+ "contentHash": "go7e81n/UI3LeNqoJIJ3thkS4JfJtiQnDbAxLh09JkJqoHthnfbLS5p68s4/Bm12B9umkoYSB5SaDr68hZNleg=="
+ },
+ "DiffEngine": {
+ "type": "Transitive",
+ "resolved": "11.3.0",
+ "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==",
+ "dependencies": {
+ "EmptyFiles": "4.4.0",
+ "System.Management": "6.0.1"
+ }
+ },
+ "EmptyFiles": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw=="
+ },
+ "Humanizer.Core": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
+ },
+ "Microsoft.Bcl.AsyncInterfaces": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg=="
+ },
+ "Microsoft.CodeAnalysis.Analyzers": {
+ "type": "Transitive",
+ "resolved": "3.3.4",
+ "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g=="
+ },
+ "Microsoft.CodeAnalysis.Common": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Analyzers": "3.3.4",
+ "System.Collections.Immutable": "7.0.0",
+ "System.Reflection.Metadata": "7.0.0",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ }
+ },
+ "Microsoft.CodeAnalysis.CSharp": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]"
+ }
+ },
+ "Microsoft.CodeAnalysis.VisualBasic": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "kfHPh/etcWypMDYfHxgfitgJMhi986OFCICb76RPcA1Toordf6bBYEJytWr2L5CNdkXFWuw5qTkrlsktBav4VA==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]"
+ }
+ },
+ "Microsoft.CodeAnalysis.VisualBasic.Workspaces": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "4fNpQX8LRV0ZCfB6+rr9s61zdhNpN6Bgow/kmvsO2Gm5KtzbOUPijbUZex26wJwRHyW+ZYoatTRd449A7+D3Wg==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]",
+ "Microsoft.CodeAnalysis.VisualBasic": "[4.8.0]",
+ "Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]"
+ }
+ },
+ "Microsoft.CodeAnalysis.Workspaces.Common": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "LXyV+MJKsKRu3FGJA3OmSk40OUIa/dQCFLOnm5X8MNcujx7hzGu8o+zjXlb/cy5xUdZK2UKYb9YaQ2E8m9QehQ==",
+ "dependencies": {
+ "Humanizer.Core": "2.14.1",
+ "Microsoft.Bcl.AsyncInterfaces": "7.0.0",
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]",
+ "System.Composition": "7.0.0",
+ "System.IO.Pipelines": "7.0.0",
+ "System.Threading.Channels": "7.0.0"
+ }
+ },
+ "Microsoft.CodeCoverage": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg=="
+ },
+ "Microsoft.TestPlatform.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==",
+ "dependencies": {
+ "System.Reflection.Metadata": "8.0.0"
+ }
+ },
+ "Microsoft.TestPlatform.TestHost": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==",
+ "dependencies": {
+ "Microsoft.TestPlatform.ObjectModel": "17.14.1",
+ "Newtonsoft.Json": "13.0.3"
+ }
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.3",
+ "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
+ },
+ "System.CodeDom": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
+ },
+ "System.Collections.Immutable": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg=="
+ },
+ "System.Composition": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "tRwgcAkDd85O8Aq6zHDANzQaq380cek9lbMg5Qma46u5BZXq/G+XvIYmu+UI+BIIZ9zssXLYrkTykEqxxvhcmg==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "7.0.0",
+ "System.Composition.Convention": "7.0.0",
+ "System.Composition.Hosting": "7.0.0",
+ "System.Composition.Runtime": "7.0.0",
+ "System.Composition.TypedParts": "7.0.0"
+ }
+ },
+ "System.Composition.AttributedModel": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "2QzClqjElKxgI1jK1Jztnq44/8DmSuTSGGahXqQ4TdEV0h9s2KikQZIgcEqVzR7OuWDFPGLHIprBJGQEPr8fAQ=="
+ },
+ "System.Composition.Convention": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "IMhTlpCs4HmlD8B+J8/kWfwX7vrBBOs6xyjSTzBlYSs7W4OET4tlkR/Sg9NG8jkdJH9Mymq0qGdYS1VPqRTBnQ==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "7.0.0"
+ }
+ },
+ "System.Composition.Hosting": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "eB6gwN9S+54jCTBJ5bpwMOVerKeUfGGTYCzz3QgDr1P55Gg/Wb27ShfPIhLMjmZ3MoAKu8uUSv6fcCdYJTN7Bg==",
+ "dependencies": {
+ "System.Composition.Runtime": "7.0.0"
+ }
+ },
+ "System.Composition.Runtime": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "aZJ1Zr5Txe925rbo4742XifEyW0MIni1eiUebmcrP3HwLXZ3IbXUj4MFMUH/RmnJOAQiS401leg/2Sz1MkApDw=="
+ },
+ "System.Composition.TypedParts": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "ZK0KNPfbtxVceTwh+oHNGUOYV2WNOHReX2AXipuvkURC7s/jPwoWfsu3SnDBDgofqbiWr96geofdQ2erm/KTHg==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "7.0.0",
+ "System.Composition.Hosting": "7.0.0",
+ "System.Composition.Runtime": "7.0.0"
+ }
+ },
+ "System.IO.Pipelines": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg=="
+ },
+ "System.Management": {
+ "type": "Transitive",
+ "resolved": "6.0.1",
+ "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==",
+ "dependencies": {
+ "System.CodeDom": "6.0.0"
+ }
+ },
+ "System.Reflection.Metadata": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==",
+ "dependencies": {
+ "System.Collections.Immutable": "8.0.0"
+ }
+ },
+ "System.Runtime.CompilerServices.Unsafe": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
+ },
+ "System.Threading.Channels": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA=="
+ },
+ "xunit.abstractions": {
+ "type": "Transitive",
+ "resolved": "2.0.3",
+ "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
+ },
+ "xunit.analyzers": {
+ "type": "Transitive",
+ "resolved": "1.18.0",
+ "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
+ },
+ "xunit.assert": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
+ },
+ "xunit.core": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
+ "dependencies": {
+ "xunit.extensibility.core": "[2.9.3]",
+ "xunit.extensibility.execution": "[2.9.3]"
+ }
+ },
+ "xunit.extensibility.core": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
+ "dependencies": {
+ "xunit.abstractions": "2.0.3"
+ }
+ },
+ "xunit.extensibility.execution": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
+ "dependencies": {
+ "xunit.extensibility.core": "[2.9.3]"
+ }
+ },
+ "compositekey": {
+ "type": "Project"
+ },
+ "compositekey.analyzers.common": {
+ "type": "Project"
+ },
+ "Microsoft.CodeAnalysis.CSharp.Workspaces": {
+ "type": "CentralTransitive",
+ "requested": "[4.8.0, )",
+ "resolved": "4.8.0",
+ "contentHash": "3amm4tq4Lo8/BGvg9p3BJh3S9nKq2wqCXfS7138i69TUpo/bD+XvD0hNurpEBtcNZhi1FyutiomKJqVF39ugYA==",
+ "dependencies": {
+ "Humanizer.Core": "2.14.1",
+ "Microsoft.CodeAnalysis.CSharp": "[4.8.0]",
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]",
+ "Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]"
+ }
+ }
+ },
+ "net9.0": {
+ "coverlet.collector": {
+ "type": "Direct",
+ "requested": "[6.0.4, )",
+ "resolved": "6.0.4",
+ "contentHash": "lkhqpF8Pu2Y7IiN7OntbsTtdbpR1syMsm2F3IgX6ootA4ffRqWL5jF7XipHuZQTdVuWG/gVAAcf8mjk8Tz0xPg=="
+ },
+ "DotNet.ReproducibleBuilds": {
+ "type": "Direct",
+ "requested": "[1.2.25, )",
+ "resolved": "1.2.25",
+ "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg=="
+ },
+ "JunitXml.TestLogger": {
+ "type": "Direct",
+ "requested": "[6.1.0, )",
+ "resolved": "6.1.0",
+ "contentHash": "a3ciawoHOzqcry7yS5z9DerNyF9QZi6fEZZJPILSy6Noj6+r8Ydma+cENA6wvivXDCblpXxw72wWT9QApNy/0w=="
+ },
+ "Microsoft.CodeAnalysis": {
+ "type": "Direct",
+ "requested": "[4.8.0, )",
+ "resolved": "4.8.0",
+ "contentHash": "g5eTgZVyBr4k1zxvJeVrJ1nDvBHrDt7XX2Uo7UWRoF9GdzOv9od4WtOeL1/e86ifgwX/H7H1Vs5u2OCdv0HYpQ==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.CSharp.Workspaces": "[4.8.0]",
+ "Microsoft.CodeAnalysis.VisualBasic.Workspaces": "[4.8.0]"
+ }
+ },
+ "Microsoft.NET.Test.Sdk": {
+ "type": "Direct",
+ "requested": "[17.14.1, )",
+ "resolved": "17.14.1",
+ "contentHash": "HJKqKOE+vshXra2aEHpi2TlxYX7Z9VFYkr+E5rwEvHC8eIXiyO+K9kNm8vmNom3e2rA56WqxU+/N9NJlLGXsJQ==",
+ "dependencies": {
+ "Microsoft.CodeCoverage": "17.14.1",
+ "Microsoft.TestPlatform.TestHost": "17.14.1"
+ }
+ },
+ "Shouldly": {
+ "type": "Direct",
+ "requested": "[4.3.0, )",
+ "resolved": "4.3.0",
+ "contentHash": "sDetrWXrl6YXZ4HeLsdBoNk3uIa7K+V4uvIJ+cqdRa5DrFxeTED7VkjoxCuU1kJWpUuBDZz2QXFzSxBtVXLwRQ==",
+ "dependencies": {
+ "DiffEngine": "11.3.0",
+ "EmptyFiles": "4.4.0"
+ }
+ },
+ "xunit": {
+ "type": "Direct",
+ "requested": "[2.9.3, )",
+ "resolved": "2.9.3",
+ "contentHash": "TlXQBinK35LpOPKHAqbLY4xlEen9TBafjs0V5KnA4wZsoQLQJiirCR4CbIXvOH8NzkW4YeJKP5P/Bnrodm0h9Q==",
+ "dependencies": {
+ "xunit.analyzers": "1.18.0",
+ "xunit.assert": "2.9.3",
+ "xunit.core": "[2.9.3]"
+ }
+ },
+ "xunit.runner.visualstudio": {
+ "type": "Direct",
+ "requested": "[3.1.3, )",
+ "resolved": "3.1.3",
+ "contentHash": "go7e81n/UI3LeNqoJIJ3thkS4JfJtiQnDbAxLh09JkJqoHthnfbLS5p68s4/Bm12B9umkoYSB5SaDr68hZNleg=="
+ },
+ "DiffEngine": {
+ "type": "Transitive",
+ "resolved": "11.3.0",
+ "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==",
+ "dependencies": {
+ "EmptyFiles": "4.4.0",
+ "System.Management": "6.0.1"
+ }
+ },
+ "EmptyFiles": {
+ "type": "Transitive",
+ "resolved": "4.4.0",
+ "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw=="
+ },
+ "Humanizer.Core": {
+ "type": "Transitive",
+ "resolved": "2.14.1",
+ "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw=="
+ },
+ "Microsoft.Bcl.AsyncInterfaces": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "3aeMZ1N0lJoSyzqiP03hqemtb1BijhsJADdobn/4nsMJ8V1H+CrpuduUe4hlRdx+ikBQju1VGjMD1GJ3Sk05Eg=="
+ },
+ "Microsoft.CodeAnalysis.Analyzers": {
+ "type": "Transitive",
+ "resolved": "3.3.4",
+ "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g=="
+ },
+ "Microsoft.CodeAnalysis.Common": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "/jR+e/9aT+BApoQJABlVCKnnggGQbvGh7BKq2/wI1LamxC+LbzhcLj4Vj7gXCofl1n4E521YfF9w0WcASGg/KA==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Analyzers": "3.3.4",
+ "System.Collections.Immutable": "7.0.0",
+ "System.Reflection.Metadata": "7.0.0",
+ "System.Runtime.CompilerServices.Unsafe": "6.0.0"
+ }
+ },
+ "Microsoft.CodeAnalysis.CSharp": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "+3+qfdb/aaGD8PZRCrsdobbzGs1m9u119SkkJt8e/mk3xLJz/udLtS2T6nY27OTXxBBw10HzAbC8Z9w08VyP/g==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]"
+ }
+ },
+ "Microsoft.CodeAnalysis.VisualBasic": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "kfHPh/etcWypMDYfHxgfitgJMhi986OFCICb76RPcA1Toordf6bBYEJytWr2L5CNdkXFWuw5qTkrlsktBav4VA==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]"
+ }
+ },
+ "Microsoft.CodeAnalysis.VisualBasic.Workspaces": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "4fNpQX8LRV0ZCfB6+rr9s61zdhNpN6Bgow/kmvsO2Gm5KtzbOUPijbUZex26wJwRHyW+ZYoatTRd449A7+D3Wg==",
+ "dependencies": {
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]",
+ "Microsoft.CodeAnalysis.VisualBasic": "[4.8.0]",
+ "Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]"
+ }
+ },
+ "Microsoft.CodeAnalysis.Workspaces.Common": {
+ "type": "Transitive",
+ "resolved": "4.8.0",
+ "contentHash": "LXyV+MJKsKRu3FGJA3OmSk40OUIa/dQCFLOnm5X8MNcujx7hzGu8o+zjXlb/cy5xUdZK2UKYb9YaQ2E8m9QehQ==",
+ "dependencies": {
+ "Humanizer.Core": "2.14.1",
+ "Microsoft.Bcl.AsyncInterfaces": "7.0.0",
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]",
+ "System.Composition": "7.0.0",
+ "System.IO.Pipelines": "7.0.0",
+ "System.Threading.Channels": "7.0.0"
+ }
+ },
+ "Microsoft.CodeCoverage": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "pmTrhfFIoplzFVbhVwUquT+77CbGH+h4/3mBpdmIlYtBi9nAB+kKI6dN3A/nV4DFi3wLLx/BlHIPK+MkbQ6Tpg=="
+ },
+ "Microsoft.TestPlatform.ObjectModel": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "xTP1W6Mi6SWmuxd3a+jj9G9UoC850WGwZUps1Wah9r1ZxgXhdJfj1QqDLJkFjHDCvN42qDL2Ps5KjQYWUU0zcQ==",
+ "dependencies": {
+ "System.Reflection.Metadata": "8.0.0"
+ }
+ },
+ "Microsoft.TestPlatform.TestHost": {
+ "type": "Transitive",
+ "resolved": "17.14.1",
+ "contentHash": "d78LPzGKkJwsJXAQwsbJJ7LE7D1wB+rAyhHHAaODF+RDSQ0NgMjDFkSA1Djw18VrxO76GlKAjRUhl+H8NL8Z+Q==",
+ "dependencies": {
+ "Microsoft.TestPlatform.ObjectModel": "17.14.1",
+ "Newtonsoft.Json": "13.0.3"
+ }
+ },
+ "Newtonsoft.Json": {
+ "type": "Transitive",
+ "resolved": "13.0.3",
+ "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
+ },
+ "System.CodeDom": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA=="
+ },
+ "System.Collections.Immutable": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg=="
+ },
+ "System.Composition": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "tRwgcAkDd85O8Aq6zHDANzQaq380cek9lbMg5Qma46u5BZXq/G+XvIYmu+UI+BIIZ9zssXLYrkTykEqxxvhcmg==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "7.0.0",
+ "System.Composition.Convention": "7.0.0",
+ "System.Composition.Hosting": "7.0.0",
+ "System.Composition.Runtime": "7.0.0",
+ "System.Composition.TypedParts": "7.0.0"
+ }
+ },
+ "System.Composition.AttributedModel": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "2QzClqjElKxgI1jK1Jztnq44/8DmSuTSGGahXqQ4TdEV0h9s2KikQZIgcEqVzR7OuWDFPGLHIprBJGQEPr8fAQ=="
+ },
+ "System.Composition.Convention": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "IMhTlpCs4HmlD8B+J8/kWfwX7vrBBOs6xyjSTzBlYSs7W4OET4tlkR/Sg9NG8jkdJH9Mymq0qGdYS1VPqRTBnQ==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "7.0.0"
+ }
+ },
+ "System.Composition.Hosting": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "eB6gwN9S+54jCTBJ5bpwMOVerKeUfGGTYCzz3QgDr1P55Gg/Wb27ShfPIhLMjmZ3MoAKu8uUSv6fcCdYJTN7Bg==",
+ "dependencies": {
+ "System.Composition.Runtime": "7.0.0"
+ }
+ },
+ "System.Composition.Runtime": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "aZJ1Zr5Txe925rbo4742XifEyW0MIni1eiUebmcrP3HwLXZ3IbXUj4MFMUH/RmnJOAQiS401leg/2Sz1MkApDw=="
+ },
+ "System.Composition.TypedParts": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "ZK0KNPfbtxVceTwh+oHNGUOYV2WNOHReX2AXipuvkURC7s/jPwoWfsu3SnDBDgofqbiWr96geofdQ2erm/KTHg==",
+ "dependencies": {
+ "System.Composition.AttributedModel": "7.0.0",
+ "System.Composition.Hosting": "7.0.0",
+ "System.Composition.Runtime": "7.0.0"
+ }
+ },
+ "System.IO.Pipelines": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "jRn6JYnNPW6xgQazROBLSfpdoczRw694vO5kKvMcNnpXuolEixUyw6IBuBs2Y2mlSX/LdLvyyWmfXhaI3ND1Yg=="
+ },
+ "System.Management": {
+ "type": "Transitive",
+ "resolved": "6.0.1",
+ "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==",
+ "dependencies": {
+ "System.CodeDom": "6.0.0"
+ }
+ },
+ "System.Reflection.Metadata": {
+ "type": "Transitive",
+ "resolved": "8.0.0",
+ "contentHash": "ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==",
+ "dependencies": {
+ "System.Collections.Immutable": "8.0.0"
+ }
+ },
+ "System.Runtime.CompilerServices.Unsafe": {
+ "type": "Transitive",
+ "resolved": "6.0.0",
+ "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
+ },
+ "System.Threading.Channels": {
+ "type": "Transitive",
+ "resolved": "7.0.0",
+ "contentHash": "qmeeYNROMsONF6ndEZcIQ+VxR4Q/TX/7uIVLJqtwIWL7dDWeh0l1UIqgo4wYyjG//5lUNhwkLDSFl+pAWO6oiA=="
+ },
+ "xunit.abstractions": {
+ "type": "Transitive",
+ "resolved": "2.0.3",
+ "contentHash": "pot1I4YOxlWjIb5jmwvvQNbTrZ3lJQ+jUGkGjWE3hEFM0l5gOnBWS+H3qsex68s5cO52g+44vpGzhAt+42vwKg=="
+ },
+ "xunit.analyzers": {
+ "type": "Transitive",
+ "resolved": "1.18.0",
+ "contentHash": "OtFMHN8yqIcYP9wcVIgJrq01AfTxijjAqVDy/WeQVSyrDC1RzBWeQPztL49DN2syXRah8TYnfvk035s7L95EZQ=="
+ },
+ "xunit.assert": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "/Kq28fCE7MjOV42YLVRAJzRF0WmEqsmflm0cfpMjGtzQ2lR5mYVj1/i0Y8uDAOLczkL3/jArrwehfMD0YogMAA=="
+ },
+ "xunit.core": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "BiAEvqGvyme19wE0wTKdADH+NloYqikiU0mcnmiNyXaF9HyHmE6sr/3DC5vnBkgsWaE6yPyWszKSPSApWdRVeQ==",
+ "dependencies": {
+ "xunit.extensibility.core": "[2.9.3]",
+ "xunit.extensibility.execution": "[2.9.3]"
+ }
+ },
+ "xunit.extensibility.core": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "kf3si0YTn2a8J8eZNb+zFpwfoyvIrQ7ivNk5ZYA5yuYk1bEtMe4DxJ2CF/qsRgmEnDr7MnW1mxylBaHTZ4qErA==",
+ "dependencies": {
+ "xunit.abstractions": "2.0.3"
+ }
+ },
+ "xunit.extensibility.execution": {
+ "type": "Transitive",
+ "resolved": "2.9.3",
+ "contentHash": "yMb6vMESlSrE3Wfj7V6cjQ3S4TXdXpRqYeNEI3zsX31uTsGMJjEw6oD5F5u1cHnMptjhEECnmZSsPxB6ChZHDQ==",
+ "dependencies": {
+ "xunit.extensibility.core": "[2.9.3]"
+ }
+ },
+ "compositekey": {
+ "type": "Project"
+ },
+ "compositekey.analyzers.common": {
+ "type": "Project"
+ },
+ "Microsoft.CodeAnalysis.CSharp.Workspaces": {
+ "type": "CentralTransitive",
+ "requested": "[4.8.0, )",
+ "resolved": "4.8.0",
+ "contentHash": "3amm4tq4Lo8/BGvg9p3BJh3S9nKq2wqCXfS7138i69TUpo/bD+XvD0hNurpEBtcNZhi1FyutiomKJqVF39ugYA==",
+ "dependencies": {
+ "Humanizer.Core": "2.14.1",
+ "Microsoft.CodeAnalysis.CSharp": "[4.8.0]",
+ "Microsoft.CodeAnalysis.Common": "[4.8.0]",
+ "Microsoft.CodeAnalysis.Workspaces.Common": "[4.8.0]"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CompositeKey.Analyzers.Common/Validation/TypeValidation.cs b/src/CompositeKey.Analyzers.Common/Validation/TypeValidation.cs
new file mode 100644
index 0000000..6863e8f
--- /dev/null
+++ b/src/CompositeKey.Analyzers.Common/Validation/TypeValidation.cs
@@ -0,0 +1,150 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using CompositeKey.Analyzers.Common.Diagnostics;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace CompositeKey.Analyzers.Common.Validation;
+
+public static class TypeValidation
+{
+ public static TypeValidationResult ValidateTypeForCompositeKey(
+ INamedTypeSymbol targetTypeSymbol,
+ TypeDeclarationSyntax typeDeclarationSyntax,
+ SemanticModel semanticModel,
+ INamedTypeSymbol? compositeKeyConstructorAttributeType,
+ CancellationToken cancellationToken)
+ {
+ if (!targetTypeSymbol.IsRecord)
+ {
+ return TypeValidationResult.Failure(DiagnosticDescriptors.UnsupportedCompositeType, targetTypeSymbol.Name);
+ }
+
+ if (!TryGetTargetTypeDeclarations(typeDeclarationSyntax, semanticModel, out var targetTypeDeclarations, cancellationToken))
+ {
+ return TypeValidationResult.Failure(DiagnosticDescriptors.CompositeTypeMustBePartial, targetTypeSymbol.Name);
+ }
+
+ if (!TryGetObviousOrExplicitlyMarkedConstructor(targetTypeSymbol, compositeKeyConstructorAttributeType, out var constructor))
+ {
+ return TypeValidationResult.Failure(DiagnosticDescriptors.NoObviousDefaultConstructor, targetTypeSymbol.Name);
+ }
+
+ return TypeValidationResult.Success(constructor, targetTypeDeclarations);
+ }
+
+ public static bool TryGetObviousOrExplicitlyMarkedConstructor(
+ INamedTypeSymbol typeSymbol,
+ INamedTypeSymbol? compositeKeyConstructorAttributeType,
+ [NotNullWhen(true)] out IMethodSymbol? constructor)
+ {
+ constructor = null;
+
+ var publicConstructors = typeSymbol.Constructors
+ .Where(c => !c.IsStatic && !(c.IsImplicitlyDeclared && typeSymbol.IsValueType && c.Parameters.Length == 0))
+ .Where(c => !(c.Parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(c.Parameters[0].Type, typeSymbol)))
+ .ToArray();
+
+ var lonePublicConstructor = publicConstructors.Length == 1 ? publicConstructors[0] : null;
+ IMethodSymbol? constructorWithAttribute = null;
+ IMethodSymbol? publicParameterlessConstructor = null;
+
+ foreach (var ctor in publicConstructors)
+ {
+ if (compositeKeyConstructorAttributeType != null &&
+ ctor.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, compositeKeyConstructorAttributeType)))
+ {
+ if (constructorWithAttribute is not null)
+ return false; // Multiple constructors with attribute
+
+ constructorWithAttribute = ctor;
+ }
+ else if (ctor.Parameters.Length == 0)
+ {
+ publicParameterlessConstructor = ctor;
+ }
+ }
+
+ constructor = constructorWithAttribute ?? publicParameterlessConstructor ?? lonePublicConstructor;
+ return constructor is not null;
+ }
+
+ private static bool TryGetTargetTypeDeclarations(
+ TypeDeclarationSyntax typeDeclarationSyntax,
+ SemanticModel semanticModel,
+ [NotNullWhen(true)] out List? targetTypeDeclarations,
+ CancellationToken cancellationToken)
+ {
+ targetTypeDeclarations = null;
+
+ for (var current = typeDeclarationSyntax; current != null; current = current.Parent as TypeDeclarationSyntax)
+ {
+ var stringBuilder = new StringBuilder();
+
+ bool isPartialType = false;
+ foreach (var modifier in current.Modifiers)
+ {
+ stringBuilder.Append(modifier.Text);
+ stringBuilder.Append(' ');
+
+ isPartialType |= modifier.IsKind(SyntaxKind.PartialKeyword);
+ }
+
+ if (!isPartialType)
+ return false;
+
+ stringBuilder.Append(GetTypeKindKeyword(current));
+ stringBuilder.Append(' ');
+
+ var typeSymbol = semanticModel.GetDeclaredSymbol(current, cancellationToken);
+ if (typeSymbol is null)
+ return false;
+
+ stringBuilder.Append(typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
+
+ (targetTypeDeclarations ??= []).Add(stringBuilder.ToString());
+ }
+
+ return targetTypeDeclarations?.Count > 0;
+ }
+
+ private static string GetTypeKindKeyword(TypeDeclarationSyntax typeDeclarationSyntax) =>
+ typeDeclarationSyntax.Kind() switch
+ {
+ SyntaxKind.ClassDeclaration => "class",
+ SyntaxKind.InterfaceDeclaration => "interface",
+ SyntaxKind.StructDeclaration => "struct",
+ SyntaxKind.RecordDeclaration => "record",
+ SyntaxKind.RecordStructDeclaration => "record struct",
+ SyntaxKind.EnumDeclaration => "enum",
+ SyntaxKind.DelegateDeclaration => "delegate",
+ _ => throw new ArgumentOutOfRangeException(nameof(typeDeclarationSyntax))
+ };
+}
+
+public record TypeValidationResult
+{
+ [MemberNotNullWhen(true, nameof(Constructor), nameof(TargetTypeDeclarations))]
+ [MemberNotNullWhen(false, nameof(Descriptor), nameof(MessageArgs))]
+ public required bool IsSuccess { get; init; }
+
+ public DiagnosticDescriptor? Descriptor { get; init; }
+ public object?[]? MessageArgs { get; init; }
+ public IMethodSymbol? Constructor { get; init; }
+ public IReadOnlyList? TargetTypeDeclarations { get; init; }
+
+ public static TypeValidationResult Success(IMethodSymbol constructor, IReadOnlyList targetTypeDeclarations) => new()
+ {
+ IsSuccess = true,
+ Constructor = constructor,
+ TargetTypeDeclarations = targetTypeDeclarations
+ };
+
+ public static TypeValidationResult Failure(DiagnosticDescriptor descriptor, params object?[]? messageArgs) => new()
+ {
+ IsSuccess = false,
+ Descriptor = descriptor,
+ MessageArgs = messageArgs
+ };
+}
diff --git a/src/CompositeKey.SourceGeneration/SourceGenerator.Parser.cs b/src/CompositeKey.SourceGeneration/SourceGenerator.Parser.cs
index b6e358a..45227ac 100644
--- a/src/CompositeKey.SourceGeneration/SourceGenerator.Parser.cs
+++ b/src/CompositeKey.SourceGeneration/SourceGenerator.Parser.cs
@@ -1,7 +1,6 @@
using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.Text;
using CompositeKey.Analyzers.Common.Diagnostics;
+using CompositeKey.Analyzers.Common.Validation;
using CompositeKey.SourceGeneration.Core;
using CompositeKey.SourceGeneration.Core.Extensions;
using CompositeKey.SourceGeneration.Core.Tokenization;
@@ -56,27 +55,27 @@ public void ReportDiagnostic(DiagnosticDescriptor descriptor, Location? location
return null;
}
- if (!targetTypeSymbol.IsRecord)
- {
- ReportDiagnostic(DiagnosticDescriptors.UnsupportedCompositeType, _location, targetTypeSymbol.Name);
- return null;
- }
+ // Validate type structure using comprehensive shared validation
+ var validationResult = TypeValidation.ValidateTypeForCompositeKey(
+ targetTypeSymbol,
+ typeDeclarationSyntax,
+ semanticModel,
+ _knownTypeSymbols.CompositeKeyConstructorAttributeType,
+ cancellationToken);
- if (!TryGetTargetTypeDeclarations(typeDeclarationSyntax, semanticModel, out var targetTypeDeclarations, cancellationToken))
+ if (!validationResult.IsSuccess)
{
- ReportDiagnostic(DiagnosticDescriptors.CompositeTypeMustBePartial, _location, targetTypeSymbol.Name);
+ ReportDiagnostic(validationResult.Descriptor, _location, validationResult.MessageArgs);
return null;
}
+ // Use validated data from the validation result (guaranteed non-null due to MemberNotNullWhen on IsSuccess)
+ var targetTypeDeclarations = validationResult.TargetTypeDeclarations;
+ var constructor = validationResult.Constructor;
+
var compositeKeyAttributeValues = ParseCompositeKeyAttributeValues(targetTypeSymbol);
Debug.Assert(compositeKeyAttributeValues is not null);
- if (TryGetObviousOrExplicitlyMarkedConstructor(targetTypeSymbol) is not { } constructor)
- {
- ReportDiagnostic(DiagnosticDescriptors.NoObviousDefaultConstructor, _location, targetTypeSymbol.Name);
- return null;
- }
-
var constructorParameters = ParseConstructorParameters(constructor, out var constructionStrategy, out bool constructorSetsRequiredMembers);
var properties = ParseProperties(targetTypeSymbol);
var propertyInitializers = ParsePropertyInitializers(constructorParameters, properties.Select(p => p.Spec).ToList(), ref constructionStrategy, constructorSetsRequiredMembers);
@@ -404,33 +403,6 @@ private ConstructorParameterSpec[] ParseConstructorParameters(
return constructorParameters;
}
- private IMethodSymbol? TryGetObviousOrExplicitlyMarkedConstructor(INamedTypeSymbol typeSymbol)
- {
- var publicConstructors = typeSymbol.Constructors
- .Where(c => !c.IsStatic && !(c.IsImplicitlyDeclared && typeSymbol.IsValueType && c.Parameters.Length == 0))
- .Where(c => !(c.Parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(c.Parameters[0].Type, typeSymbol)))
- .ToArray();
-
- var lonePublicConstructor = publicConstructors.Length == 1 ? publicConstructors[0] : null;
- IMethodSymbol? constructorWithAttribute = null, publicParameterlessConstructor = null;
-
- foreach (var constructor in publicConstructors)
- {
- if (constructor.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _knownTypeSymbols.CompositeKeyConstructorAttributeType)))
- {
- if (constructorWithAttribute is not null)
- return null; // Somehow we found a duplicate so let's just return null so the diagnostic is emitted
-
- constructorWithAttribute = constructor;
- }
- else if (constructor.Parameters.Length == 0)
- {
- publicParameterlessConstructor = constructor;
- }
- }
-
- return constructorWithAttribute ?? publicParameterlessConstructor ?? lonePublicConstructor;
- }
private CompositeKeyAttributeValues? ParseCompositeKeyAttributeValues(INamedTypeSymbol targetTypeSymbol)
{
@@ -481,55 +453,5 @@ private ConstructorParameterSpec[] ParseConstructorParameters(
}
}
- private static bool TryGetTargetTypeDeclarations(
- TypeDeclarationSyntax typeDeclarationSyntax,
- SemanticModel semanticModel,
- [NotNullWhen(true)] out List? targetTypeDeclarations,
- CancellationToken cancellationToken)
- {
- targetTypeDeclarations = null;
-
- for (var current = typeDeclarationSyntax; current != null; current = current.Parent as TypeDeclarationSyntax)
- {
- StringBuilder stringBuilder = new();
-
- bool isPartialType = false;
- foreach (var modifier in current.Modifiers)
- {
- stringBuilder.Append(modifier.Text);
- stringBuilder.Append(' ');
-
- isPartialType |= modifier.IsKind(SyntaxKind.PartialKeyword);
- }
-
- if (!isPartialType)
- return false;
-
- stringBuilder.Append(GetTypeKindKeyword(current));
- stringBuilder.Append(' ');
-
- var typeSymbol = semanticModel.GetDeclaredSymbol(current, cancellationToken);
- Debug.Assert(typeSymbol is not null);
-
- stringBuilder.Append(typeSymbol!.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
-
- (targetTypeDeclarations ??= []).Add(stringBuilder.ToString());
- }
-
- return targetTypeDeclarations?.Count > 0;
-
- static string GetTypeKindKeyword(TypeDeclarationSyntax typeDeclarationSyntax) =>
- typeDeclarationSyntax.Kind() switch
- {
- SyntaxKind.ClassDeclaration => "class",
- SyntaxKind.InterfaceDeclaration => "interface",
- SyntaxKind.StructDeclaration => "struct",
- SyntaxKind.RecordDeclaration => "record",
- SyntaxKind.RecordStructDeclaration => "record struct",
- SyntaxKind.EnumDeclaration => "enum",
- SyntaxKind.DelegateDeclaration => "delegate",
- _ => throw new ArgumentOutOfRangeException(nameof(typeDeclarationSyntax))
- };
- }
}
}