diff --git a/Sybil.IntegrationTests/FlowTests.cs b/Sybil.IntegrationTests/FlowTests.cs index 7bdd48c..206dc76 100644 --- a/Sybil.IntegrationTests/FlowTests.cs +++ b/Sybil.IntegrationTests/FlowTests.cs @@ -13,7 +13,7 @@ public sealed class FlowTests [InternalsVisibleTo(""FlowTests"")] namespace TestNamespace; [SomeAttribute(SomeIntProperty = 1, SomeStringProperty = ""Hello"", SomeEnumProperty = MyEnum.Value, SomeTypeProperty = typeof(String), SomeNullProperty = null)] -public sealed class TestClass : BaseTestClass, IInterface +public sealed class TestClass : BaseTestClass, IInterface where T : new(), class { [SomeAttribute5(null)] TestClass(string fieldString) : base(fieldString) @@ -29,7 +29,7 @@ public sealed class TestClass : BaseTestClass, IInterface public string PropertyString { private set; get; } [SomeAttribute4(0.5F)] - public string GetFieldString() + public string GetFieldString() { this.fieldString = 0; return this.fieldString; @@ -49,6 +49,11 @@ public sealed class TestClass : BaseTestClass, IInterface SyntaxBuilder.CreateClass("TestClass") .WithBaseClass("BaseTestClass") .WithInterface("IInterface") + .WithTypeParameter(SyntaxBuilder.CreateTypeParameter("T")) + .WithTypeParameterConstraint( + SyntaxBuilder.CreateTypeParameterConstraint("T") + .WithParameterlessConstructor() + .WithClass()) .WithAttribute(SyntaxBuilder.CreateAttribute("SomeAttribute") .WithArgument("SomeIntProperty", 1) .WithArgument("SomeStringProperty", "Hello") @@ -87,6 +92,7 @@ public sealed class TestClass : BaseTestClass, IInterface .WithMethod( SyntaxBuilder.CreateMethod("string", "GetFieldString") .WithModifier("public") + .WithTypeParameter(SyntaxBuilder.CreateTypeParameter("T")) .WithAttribute(SyntaxBuilder.CreateAttribute("SomeAttribute4") .WithArgument(0.5f)) .WithBody("this.fieldString = 0;\r\nreturn this.fieldString;")))) diff --git a/Sybil.UnitTests/TypeParameterBuilderTests.cs b/Sybil.UnitTests/TypeParameterBuilderTests.cs new file mode 100644 index 0000000..df5820d --- /dev/null +++ b/Sybil.UnitTests/TypeParameterBuilderTests.cs @@ -0,0 +1,38 @@ +using FluentAssertions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Sybil.Tests; + +[TestClass] +public class TypeParameterBuilderTests +{ + private const string TypeName = "T"; + + private readonly TypeParameterBuilder builder; + + public TypeParameterBuilderTests() + { + this.builder = new TypeParameterBuilder(TypeName); + } + + [TestMethod] + public void Constructor_NullType_ThrowsArgumentNullException() + { + var action = () => + { + _ = new TypeParameterBuilder(null); + }; + + action.Should().Throw(); + } + + [TestMethod] + public void Build_ReturnsPropertyDeclarationSyntax() + { + var syntax = this.builder.Build(); + + syntax.Should().NotBeNull().And.Subject.Should().BeOfType(); + } +} \ No newline at end of file diff --git a/Sybil.UnitTests/TypeParameterConstraintBuilderTests.cs b/Sybil.UnitTests/TypeParameterConstraintBuilderTests.cs new file mode 100644 index 0000000..7b90569 --- /dev/null +++ b/Sybil.UnitTests/TypeParameterConstraintBuilderTests.cs @@ -0,0 +1,91 @@ +using FluentAssertions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Sybil.Tests; + +[TestClass] +public class TypeParameterConstraintBuilderTests +{ + private const string TypeName = "T"; + private const string ConstraintType = "TType"; + private const string Expected = "where T : class, struct, new(), TType"; + + private readonly TypeParameterConstraintBuilder builder; + + public TypeParameterConstraintBuilderTests() + { + this.builder = new TypeParameterConstraintBuilder(TypeName); + } + + [TestMethod] + public void Constructor_NullType_ThrowsArgumentNullException() + { + var action = () => + { + _ = new TypeParameterConstraintBuilder(null); + }; + + action.Should().Throw(); + } + + [TestMethod] + public void WithClass_ReturnsBuilder() + { + this.builder.WithClass().Should().BeOfType(); + } + + [TestMethod] + public void WithStruct_ReturnsBuilder() + { + this.builder.WithStruct().Should().BeOfType(); + } + + [TestMethod] + public void WithParameterlessConstructor_ReturnsBuilder() + { + this.builder.WithParameterlessConstructor().Should().BeOfType(); + } + + [TestMethod] + public void WithType_ReturnsBuilder() + { + this.builder.WithType(ConstraintType).Should().BeOfType(); + } + + [TestMethod] + public void WithType_NullType_ThrowsArgumentNullException() + { + var action = () => + { + _ = this.builder.WithType(null); + }; + + action.Should().Throw(); + } + + [TestMethod] + public void Build_ReturnsPropertyDeclarationSyntax() + { + var syntax = this.builder.Build(); + + syntax.Should().NotBeNull().And.Subject.Should().BeOfType(); + } + + [TestMethod] + public void Build_ReturnsExpectedString() + { + var syntax = this.builder + .WithClass() + .WithStruct() + .WithParameterlessConstructor() + .WithType(ConstraintType) + .Build(); + + var result = syntax.NormalizeWhitespace().ToFullString(); + + result.Should().Be(Expected); + } +} \ No newline at end of file diff --git a/Sybil/ClassBuilder.cs b/Sybil/ClassBuilder.cs index 4eeff36..4391e26 100644 --- a/Sybil/ClassBuilder.cs +++ b/Sybil/ClassBuilder.cs @@ -16,6 +16,8 @@ namespace Sybil private readonly List Fields = new List(); private readonly List Properties = new List(); private readonly List Methods = new List(); + private readonly List TypeParameters = new List(); + private readonly List TypeParameterConstraints = new List(); internal ClassBuilder( string className) @@ -98,6 +100,20 @@ namespace Sybil return this; } + public ClassBuilder WithTypeParameter(TypeParameterBuilder typeParameterBuilder) + { + this.TypeParameters.Add(typeParameterBuilder ?? throw new ArgumentNullException(nameof(typeParameterBuilder))); + + return this; + } + + public ClassBuilder WithTypeParameterConstraint(TypeParameterConstraintBuilder typeConstraintBuilder) + { + this.TypeParameterConstraints.Add(typeConstraintBuilder ?? throw new ArgumentNullException(nameof(typeConstraintBuilder))); + + return this; + } + public ClassDeclarationSyntax Build() { if (this.Attributes.Count > 0) @@ -105,6 +121,15 @@ namespace Sybil this.ClassDeclaration = this.ClassDeclaration.AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(this.Attributes.Select(p => p.Build()).ToArray()))); } + if (this.TypeParameters.Count > 0) + { + this.ClassDeclaration = this.ClassDeclaration.AddTypeParameterListParameters(this.TypeParameters.Select(t => t.Build()).ToArray()); + if (this.TypeParameterConstraints.Count > 0) + { + this.ClassDeclaration = this.ClassDeclaration.AddConstraintClauses(this.TypeParameterConstraints.Select(t => t.Build()).ToArray()); + } + } + return this.ClassDeclaration .AddMembers(this.Constructors.Select(p => p.Build()).ToArray()) .AddMembers(this.Fields.Select(p => p.Build()).ToArray()) diff --git a/Sybil/InterfaceBuilder.cs b/Sybil/InterfaceBuilder.cs index 8a82449..3243915 100644 --- a/Sybil/InterfaceBuilder.cs +++ b/Sybil/InterfaceBuilder.cs @@ -13,6 +13,8 @@ namespace Sybil private readonly List Properties = new List(); private readonly List Methods = new List(); + private readonly List TypeParameters = new List(); + private readonly List TypeParameterConstraints = new List(); internal InterfaceBuilder( string interfaceName) @@ -54,8 +56,31 @@ namespace Sybil return this; } + public InterfaceBuilder WithTypeParameter(TypeParameterBuilder typeParameterBuilder) + { + this.TypeParameters.Add(typeParameterBuilder ?? throw new ArgumentNullException(nameof(typeParameterBuilder))); + + return this; + } + + public InterfaceBuilder WithTypeParameterConstraint(TypeParameterConstraintBuilder typeConstraintBuilder) + { + this.TypeParameterConstraints.Add(typeConstraintBuilder ?? throw new ArgumentNullException(nameof(typeConstraintBuilder))); + + return this; + } + public InterfaceDeclarationSyntax Build() { + if (this.TypeParameters.Count > 0) + { + this.InterfaceDeclaration = this.InterfaceDeclaration.AddTypeParameterListParameters(this.TypeParameters.Select(t => t.Build()).ToArray()); + if (this.TypeParameterConstraints.Count > 0) + { + this.InterfaceDeclaration = this.InterfaceDeclaration.AddConstraintClauses(this.TypeParameterConstraints.Select(t => t.Build()).ToArray()); + } + } + return this.InterfaceDeclaration .AddMembers(this.Properties.Select(p => p.Build()).ToArray()) .AddMembers(this.Methods.Select(p => p.Build()).ToArray()) diff --git a/Sybil/MethodBuilder.cs b/Sybil/MethodBuilder.cs index d5145f2..630811b 100644 --- a/Sybil/MethodBuilder.cs +++ b/Sybil/MethodBuilder.cs @@ -14,6 +14,8 @@ namespace Sybil private BlockSyntax BlockBody { get; set; } private ArrowExpressionClauseSyntax ArrowExpression { get; set; } private MethodDeclarationSyntax MethodDeclarationSyntax { get; set; } + private readonly List TypeParameters = new List(); + private readonly List TypeParameterConstraints = new List(); internal MethodBuilder( string returnType, string name) @@ -115,6 +117,20 @@ namespace Sybil return this; } + public MethodBuilder WithTypeParameter(TypeParameterBuilder typeParameterBuilder) + { + this.TypeParameters.Add(typeParameterBuilder ?? throw new ArgumentNullException(nameof(typeParameterBuilder))); + + return this; + } + + public MethodBuilder WithTypeParameterConstraint(TypeParameterConstraintBuilder typeConstraintBuilder) + { + this.TypeParameterConstraints.Add(typeConstraintBuilder ?? throw new ArgumentNullException(nameof(typeConstraintBuilder))); + + return this; + } + public MethodDeclarationSyntax Build() { if (this.attributeBuilders.Count > 0) @@ -122,13 +138,22 @@ namespace Sybil this.MethodDeclarationSyntax = this.MethodDeclarationSyntax.AddAttributeLists(SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(this.attributeBuilders.Select(p => p.Build()).ToArray()))); } + if (this.TypeParameters.Count > 0) + { + this.MethodDeclarationSyntax = this.MethodDeclarationSyntax.AddTypeParameterListParameters(this.TypeParameters.Select(t => t.Build()).ToArray()); + if (this.TypeParameterConstraints.Count > 0) + { + this.MethodDeclarationSyntax = this.MethodDeclarationSyntax.AddConstraintClauses(this.TypeParameterConstraints.Select(t => t.Build()).ToArray()); + } + } + if (this.BlockBody is null is false) { return this.MethodDeclarationSyntax .WithBody(this.BlockBody) .NormalizeWhitespace(); } - + return this.MethodDeclarationSyntax .WithExpressionBody(this.ArrowExpression) .NormalizeWhitespace(); diff --git a/Sybil/Sybil.csproj b/Sybil/Sybil.csproj index 82aa816..49f10b9 100644 --- a/Sybil/Sybil.csproj +++ b/Sybil/Sybil.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 0.7.0 + 0.8.0 diff --git a/Sybil/SyntaxBuilder.cs b/Sybil/SyntaxBuilder.cs index 75ff2aa..feccd68 100644 --- a/Sybil/SyntaxBuilder.cs +++ b/Sybil/SyntaxBuilder.cs @@ -16,6 +16,8 @@ namespace Sybil public static FieldBuilder CreateField(string fieldType, string fieldName) => new FieldBuilder(fieldType, fieldName); public static MethodBuilder CreateMethod(string returnType, string methodName) => new MethodBuilder(returnType, methodName); public static AttributeBuilder CreateAttribute(string attributeName) => new AttributeBuilder(attributeName); + public static TypeParameterBuilder CreateTypeParameter(string typeName) => new TypeParameterBuilder(typeName); + public static TypeParameterConstraintBuilder CreateTypeParameterConstraint(string typeName) => new TypeParameterConstraintBuilder(typeName); public static CompilationUnitBuilder CreateCompilationUnit() => new CompilationUnitBuilder(); } } diff --git a/Sybil/TypeParameterBuilder.cs b/Sybil/TypeParameterBuilder.cs new file mode 100644 index 0000000..3e91767 --- /dev/null +++ b/Sybil/TypeParameterBuilder.cs @@ -0,0 +1,23 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; + +namespace Sybil +{ + public sealed class TypeParameterBuilder : IBuilder + { + private readonly string identifier; + + internal TypeParameterBuilder(string identifier) + { + _ = identifier ?? throw new ArgumentNullException(nameof(identifier)); + + this.identifier = identifier; + } + + public TypeParameterSyntax Build() + { + return SyntaxFactory.TypeParameter(this.identifier); + } + } +} diff --git a/Sybil/TypeParameterConstraintBuilder.cs b/Sybil/TypeParameterConstraintBuilder.cs new file mode 100644 index 0000000..9d8f960 --- /dev/null +++ b/Sybil/TypeParameterConstraintBuilder.cs @@ -0,0 +1,46 @@ +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; + +namespace Sybil +{ + public sealed class TypeParameterConstraintBuilder : IBuilder + { + private TypeParameterConstraintClauseSyntax TypeParameterConstraintClauseSyntax { get; set; } + + internal TypeParameterConstraintBuilder(string typeIdentifier) + { + _ = typeIdentifier ?? throw new ArgumentNullException(nameof(typeIdentifier)); + this.TypeParameterConstraintClauseSyntax = SyntaxFactory.TypeParameterConstraintClause(typeIdentifier); + } + + public TypeParameterConstraintBuilder WithClass() + { + this.TypeParameterConstraintClauseSyntax = this.TypeParameterConstraintClauseSyntax.AddConstraints(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.ClassConstraint)); + return this; + } + + public TypeParameterConstraintBuilder WithStruct() + { + this.TypeParameterConstraintClauseSyntax = this.TypeParameterConstraintClauseSyntax.AddConstraints(SyntaxFactory.ClassOrStructConstraint(SyntaxKind.StructConstraint)); + return this; + } + + public TypeParameterConstraintBuilder WithParameterlessConstructor() + { + this.TypeParameterConstraintClauseSyntax = this.TypeParameterConstraintClauseSyntax.AddConstraints(SyntaxFactory.ConstructorConstraint()); + return this; + } + + public TypeParameterConstraintBuilder WithType(string typeIdentifier) + { + this.TypeParameterConstraintClauseSyntax = this.TypeParameterConstraintClauseSyntax.AddConstraints(SyntaxFactory.TypeConstraint(SyntaxFactory.ParseTypeName(typeIdentifier ?? throw new ArgumentNullException(nameof(typeIdentifier))))); + return this; + } + + public TypeParameterConstraintClauseSyntax Build() + { + return this.TypeParameterConstraintClauseSyntax; + } + } +}