diff --git a/Sybil.IntegrationTests/FlowTests.cs b/Sybil.IntegrationTests/FlowTests.cs index a8fbdcd..95b89c1 100644 --- a/Sybil.IntegrationTests/FlowTests.cs +++ b/Sybil.IntegrationTests/FlowTests.cs @@ -7,44 +7,44 @@ namespace Sybil.IntegrationTests; [TestClass] public sealed class FlowTests { - private const string ExpectedNamespace = -@"[InternalsVisibleTo(""FlowTests"")] -namespace TestNamespace + private const string ExpectedSyntax = +@"using System; + +[InternalsVisibleTo(""FlowTests"")] +namespace TestNamespace; +[SomeAttribute(SomeIntProperty = 1, SomeStringProperty = ""Hello"", SomeEnumProperty = MyEnum.Value, SomeTypeProperty = typeof(String), SomeNullProperty = null)] +public sealed class TestClass : BaseTestClass { - using System; - - [SomeAttribute(SomeIntProperty = 1, SomeStringProperty = ""Hello"", SomeEnumProperty = MyEnum.Value, SomeTypeProperty = typeof(String), SomeNullProperty = null)] - public sealed class TestClass : BaseTestClass + [SomeAttribute5(null)] + TestClass(string fieldString) : base(fieldString) { - [SomeAttribute5(null)] - TestClass(string fieldString) : base(fieldString) - { - this.fieldString = fieldString ?? throw new ArgumentNullException(); - this.PropertyString = string.Empty; - } + this.fieldString = fieldString ?? throw new ArgumentNullException(); + this.PropertyString = string.Empty; + } - private string someTypeString = $""{typeof(string)}""; - [SomeAttribute2] - private string fieldString; - [SomeAttribute3(typeof(String))] - public string PropertyString { private set; get; } + private string someTypeString = $""{typeof(string)}""; + [SomeAttribute2] + private string fieldString; + [SomeAttribute3(typeof(String))] + public string PropertyString { private set; get; } - [SomeAttribute4(0.5F)] - public string GetFieldString() - { - this.fieldString = 0; - return this.fieldString; - } + [SomeAttribute4(0.5F)] + public string GetFieldString() + { + this.fieldString = 0; + return this.fieldString; } }"; [TestMethod] public void NewNamespace_GeneratesExpected() { - var namespaceSyntax = SyntaxBuilder.CreateNamespace("TestNamespace") + var compilationUnitSyntax = SyntaxBuilder.CreateCompilationUnit() + .WithUsing("System") + .WithNamespace( + SyntaxBuilder.CreateFileScopedNamespace("TestNamespace") .WithAttribute(SyntaxBuilder.CreateAttribute("InternalsVisibleTo") .WithArgument("FlowTests")) - .WithUsing("System") .WithClass( SyntaxBuilder.CreateClass("TestClass") .WithBaseClass("BaseTestClass") @@ -88,10 +88,10 @@ namespace TestNamespace .WithModifier("public") .WithAttribute(SyntaxBuilder.CreateAttribute("SomeAttribute4") .WithArgument(0.5f)) - .WithBody("this.fieldString = 0;\r\nreturn this.fieldString;"))) + .WithBody("this.fieldString = 0;\r\nreturn this.fieldString;")))) .Build(); - var namespaceString = namespaceSyntax.ToFullString(); - namespaceString.Should().Be(ExpectedNamespace); + var compilationUnit = compilationUnitSyntax.ToFullString(); + compilationUnit.Should().Be(ExpectedSyntax); } } diff --git a/Sybil.UnitTests/CompilationUnitTests.cs b/Sybil.UnitTests/CompilationUnitTests.cs new file mode 100644 index 0000000..641f76b --- /dev/null +++ b/Sybil.UnitTests/CompilationUnitTests.cs @@ -0,0 +1,44 @@ +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace Sybil.Tests +{ + [TestClass] + public class CompilationUnitTests + { + private const string UsingSystemWindows = "using System.Windows;"; + + private readonly CompilationUnitBuilder compilationUnitBuilder = new(); + + [TestMethod] + public void WithUsing_NullUsing_ThrowsArgumentNullException() + { + var action = () => + { + this.compilationUnitBuilder.WithUsing(null); + }; + + action.Should().Throw(); + } + + [TestMethod] + public void WithNamespace_NullNamespaceBuilder_ThrowsArgumentNullException() + { + var action = () => + { + this.compilationUnitBuilder.WithNamespace(null); + }; + + action.Should().Throw(); + } + + [TestMethod] + public void WithUsing_ReturnsExpected() + { + var result = this.compilationUnitBuilder.WithUsing("System.Windows").Build().ToFullString(); + + result.Should().Be(UsingSystemWindows); + } + } +} diff --git a/Sybil.UnitTests/NamespaceBuilderTests.cs b/Sybil.UnitTests/NamespaceBuilderTests.cs index 426a3ab..7d1cc14 100644 --- a/Sybil.UnitTests/NamespaceBuilderTests.cs +++ b/Sybil.UnitTests/NamespaceBuilderTests.cs @@ -15,6 +15,9 @@ public class NamespaceBuilderTests { using System; }"; + private const string FileScopedNamespaceWithUsing = +@"namespace Test; +using System;"; private readonly NamespaceBuilder builder; @@ -65,21 +68,40 @@ public class NamespaceBuilderTests } [TestMethod] - public void Build_ReturnsNamespaceDeclarationSyntax() + public void Build_ReturnsFileScopedNamespaceDeclarationSyntax() { var syntax = this.builder.Build(); + syntax.Should().NotBeNull().And.Subject.Should().BeOfType(); + } + + [TestMethod] + public void Build_ReturnsNamespaceDeclarationSyntax() + { + var syntax = new NamespaceBuilder(Namespace, false).Build(); + syntax.Should().NotBeNull().And.Subject.Should().BeOfType(); } [TestMethod] - public void WithUsing_ReturnsExpectedString() + public void FileScoped_WithUsing_ReturnsExpectedString() { var result = this.builder .WithUsing(Using) .Build() .ToFullString(); + result.Should().Be(FileScopedNamespaceWithUsing); + } + + [TestMethod] + public void Enclosed_WithUsing_ReturnsExpectedString() + { + var result = new NamespaceBuilder(Namespace, false) + .WithUsing(Using) + .Build() + .ToFullString(); + result.Should().Be(NamespaceWithUsing); } } \ No newline at end of file diff --git a/Sybil/CompilationUnitBuilder.cs b/Sybil/CompilationUnitBuilder.cs new file mode 100644 index 0000000..9d0a9c1 --- /dev/null +++ b/Sybil/CompilationUnitBuilder.cs @@ -0,0 +1,44 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Sybil +{ + public sealed class CompilationUnitBuilder : IBuilder + { + private readonly List namespaceBuilders = new List(); + + private CompilationUnitSyntax CompilationUnitSyntax { get; set; } + + internal CompilationUnitBuilder() + { + this.CompilationUnitSyntax = SyntaxFactory.CompilationUnit(); + } + + public CompilationUnitBuilder WithUsing(string usingName) + { + if (string.IsNullOrWhiteSpace(usingName)) + { + throw new ArgumentNullException(nameof(usingName)); + } + + this.CompilationUnitSyntax = this.CompilationUnitSyntax.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(usingName))); + return this; + } + + public CompilationUnitBuilder WithNamespace(NamespaceBuilder namespaceBuilder) + { + this.namespaceBuilders.Add(namespaceBuilder ?? throw new ArgumentNullException(nameof(namespaceBuilder))); + return this; + } + + public CompilationUnitSyntax Build() + { + this.CompilationUnitSyntax = this.CompilationUnitSyntax.AddMembers(this.namespaceBuilders.Select(n => n.Build()).ToArray()); + return this.CompilationUnitSyntax.NormalizeWhitespace(); + } + } +} diff --git a/Sybil/NamespaceBuilder.cs b/Sybil/NamespaceBuilder.cs index c5d6347..6935dfc 100644 --- a/Sybil/NamespaceBuilder.cs +++ b/Sybil/NamespaceBuilder.cs @@ -7,18 +7,20 @@ using System.Linq; namespace Sybil { - public sealed class NamespaceBuilder : IBuilder + public sealed class NamespaceBuilder : IBuilder { private readonly List attributeBuilders = new List(); private readonly List classBuilders = new List(); - private NamespaceDeclarationSyntax NamespaceDeclaration { get; set; } + private BaseNamespaceDeclarationSyntax NamespaceDeclaration { get; set; } internal NamespaceBuilder( - string @namespace) + string @namespace, bool fileScoped = true) { _ = string.IsNullOrWhiteSpace(@namespace) ? throw new ArgumentNullException(nameof(@namespace)) : @namespace; - this.NamespaceDeclaration = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(@namespace)); + this.NamespaceDeclaration = fileScoped ? + (BaseNamespaceDeclarationSyntax)SyntaxFactory.FileScopedNamespaceDeclaration(SyntaxFactory.ParseName(@namespace)) : + (BaseNamespaceDeclarationSyntax)SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(@namespace)); } public NamespaceBuilder WithUsing(string usingName) @@ -48,7 +50,7 @@ namespace Sybil return this; } - public NamespaceDeclarationSyntax Build() + public BaseNamespaceDeclarationSyntax Build() { if (this.attributeBuilders.Count > 0) { diff --git a/Sybil/Sybil.csproj b/Sybil/Sybil.csproj index e888a3e..df118b1 100644 --- a/Sybil/Sybil.csproj +++ b/Sybil/Sybil.csproj @@ -2,7 +2,7 @@ netstandard2.0 - 0.5.0 + 0.6.0 diff --git a/Sybil/SyntaxBuilder.cs b/Sybil/SyntaxBuilder.cs index 298a6a0..a9b6bea 100644 --- a/Sybil/SyntaxBuilder.cs +++ b/Sybil/SyntaxBuilder.cs @@ -4,7 +4,8 @@ namespace Sybil { public static class SyntaxBuilder { - public static NamespaceBuilder CreateNamespace(string @namespace) => new NamespaceBuilder(@namespace); + public static NamespaceBuilder CreateNamespace(string @namespace) => new NamespaceBuilder(@namespace, false); + public static NamespaceBuilder CreateFileScopedNamespace(string @namespace) => new NamespaceBuilder(@namespace, true); public static ClassBuilder CreateClass(string className) => new ClassBuilder(className); public static ConstructorBuilder CreateConstructor(string className) => new ConstructorBuilder(className); public static BaseConstructorBuilder CreateBaseConstructor() => new BaseConstructorBuilder(); @@ -14,5 +15,6 @@ 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 CompilationUnitBuilder CreateCompilationUnit() => new CompilationUnitBuilder(); } }