Testura / Testura.Code

Testura.Code is a wrapper around the Roslyn API and used for generation, saving and compiling C# code. It provides methods and helpers to generate classes, methods, statements and expressions.
MIT License
297 stars 30 forks source link
code-generation roslyn-api wrapper

Testura Logo

Testura.Code is a wrapper around the Roslyn API and used for generation, saving and compiling C# code. It provides methods and helpers to generate classes, methods, statements and expressions.

It provide helpers to generate:

But also simple statements like:

Install

NuGet NuGet Status

https://www.nuget.org/packages/Testura.Code

PM> Install-Package Testura.Code

Usage

Testura.Code have three different types of helpers:

Documentation

Examples

Hello world

Here is an example on how to generate, save and compile a simple hello world.

Generate

var @class = new ClassBuilder("Program", "HelloWorld")
    .WithUsings("System") 
    .WithModifiers(Modifiers.Public)
    .WithMethods(
        new MethodBuilder("Main")
        .WithModifiers(Modifiers.Public, Modifiers.Static)
        .WithParameters(new Parameter("args", typeof(string[])))
        .WithBody(
            BodyGenerator.Create(
                Statement.Expression.Invoke("Console", "WriteLine", new List<IArgument>() { new ValueArgument("Hello world") }).AsStatement(),
                Statement.Expression.Invoke("Console", "ReadLine").AsStatement()
                ))
        .Build())
    .Build();

This code will generate following code:

using System;

namespace HelloWorld
{
    public class Program
    {
        public static void Main(String[] args)
        {
            Console.WriteLine("Hello world");
            Console.ReadLine();
        }
    }
}

Save

var saver = new CodeSaver();

// As a string
var generatedCode = saver.SaveCodeAsString(@class);

// Or to file
saver.SaveCodeToFile(@class, @"/path/HelloWorld.cs");

Compile

var compiler = new Compiler();

//To a dll

// From string
var result = await compiler.CompileSourceAsync(@"/path/HelloWorld.dll", generatedCode);

// From file
var result = await compiler.CompileFilesAsync(@"/path/HelloWorld.dll",  @"/path/HelloWorld.cs");

//In memory (without creating a dll)

// From string
var result = await compiler.CompileSourceInMemoryAsync(generatedCode);

// From file
var result = await compiler.CompileFilesInMemoryAsync(@"/path/HelloWorld.cs");

More advanced examples

Model class

        var classBuilder = new ClassBuilder("Cat", "Models");
        var @class = classBuilder
            .WithUsings("System")
            .WithConstructor(
                ConstructorGenerator.Create(
                    "Cat",
                    BodyGenerator.Create(
                        Statement.Declaration.Assign("Name", ReferenceGenerator.Create(new VariableReference("name"))),
                        Statement.Declaration.Assign("Age", ReferenceGenerator.Create(new VariableReference("age")))),
                    new List<Parameter> { new Parameter("name", typeof(string)), new Parameter("age", typeof(int)) },
                    new List<Modifiers> { Modifiers.Public }))
            .WithProperties(
                PropertyGenerator.Create(new AutoProperty("Name", typeof(string), PropertyTypes.GetAndSet, new List<Modifiers> { Modifiers.Public })),
                PropertyGenerator.Create(new AutoProperty("Age", typeof(int), PropertyTypes.GetAndSet, new List<Modifiers> { Modifiers.Public })))
            .Build();

This code will generate following code:

using System;

namespace Models
{
    public class Cat
    {
        public Cat(string name, int age)
        {
            Name = name;
            Age = age;
        }

        public string Name { get; set; }
        public int Age { get; set; }
    }
}

Enum

        var @class = new ClassBuilder("Cat", "Models")
            .WithUsings("System")
            .With(new EnumBuildMember("MyEnum", new List<EnumMember> { new("EnumValueOne", 2, new Attribute[] { new Attribute("MyAttribute"), }), new EnumMember("EnumValueTwo") }, new List<Modifiers> { Modifiers.Public }))
            .Build();

This code will generate following code:

using System;

namespace Models
{
    public class Cat
    {
        public enum MyEnum
        {
            [MyAttribute]
            EnumValueOne = 2,
            EnumValueTwo
        }
    }
}

Class with file scoped namespace and body properties

        var @class = new ClassBuilder("Cat", "Models", NamespaceType.FileScoped)
            .WithUsings("System")
            .WithFields(
                new Field("_name", typeof(string), new List<Modifiers>() { Modifiers.Private }),
                new Field("_age", typeof(int), new List<Modifiers>() { Modifiers.Private }))
            .WithConstructor(
                ConstructorGenerator.Create(
                    "Cat",
                    BodyGenerator.Create(
                        Statement.Declaration.Assign("Name", ReferenceGenerator.Create(new VariableReference("name"))),
                        Statement.Declaration.Assign("Age", ReferenceGenerator.Create(new VariableReference("age")))),
                    new List<Parameter> { new Parameter("name", typeof(string)), new Parameter("age", typeof(int)) },
                    new List<Modifiers> { Modifiers.Public }))
            .WithProperties(
                PropertyGenerator.Create(
                    new BodyProperty(
                        "Name",
                        typeof(string),
                        BodyGenerator.Create(Statement.Jump.Return(new VariableReference("_name"))), BodyGenerator.Create(Statement.Declaration.Assign("_name", new ValueKeywordReference())),
                        new List<Modifiers> { Modifiers.Public })),
                PropertyGenerator.Create(
                    new BodyProperty(
                        "Age",
                        typeof(int),
                        BodyGenerator.Create(Statement.Jump.Return(new VariableReference("_age"))), BodyGenerator.Create(Statement.Declaration.Assign("_age", new ValueKeywordReference())),
                        new List<Modifiers> { Modifiers.Public })))
            .Build();

This code will generate following code:

using System;

namespace Models;

public class Cat
{
    private string _name;
    private int _age;

    public Cat(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public string Name
    {
        get
        {
            return _name;
        }

        set
        {
            _name = value;
        }
    }

    public int Age
    {
        get
        {
            return _age;
        }

        set
        {
            _age = value;
        }
    }
}

Record with primary constructor

        var record = new RecordBuilder("Cat", "Models", NamespaceType.FileScoped)
            .WithUsings("System")
            .WithPrimaryConstructor(new Parameter("Age", typeof(int)))
            .Build();

This code will generate following code:

using System;

namespace Models;

public record Cat(int Age);

Class with methods that override operators and comments

        var @class = new ClassBuilder("Cat", "Models")
            .WithUsings("System")
            .WithMethods(new MethodBuilder("MyMethod")
                .WithModifiers(Modifiers.Public, Modifiers.Static)
                .WithOperatorOverloading(Operators.Increment)
                .WithParameters(new Parameter("MyParameter", typeof(string)))
                .WithBody(
                    BodyGenerator.Create(
                        Statement.Declaration.Declare("hello", typeof(int)).WithComment("My comment above").WithComment("hej"),
                        Statement.Declaration.Declare("hello", typeof(int)).WithComment("My comment to the side", CommentPosition.Right)))
                .Build())
            .Build();

This code will generate following code:

using System;

namespace Models
{
    public class Cat
    {
        public static MyMethod operator ++(string MyParameter)
        {
            //hej
            int hello;
            int hello; //My comment to the side
        }
    }
}

Test class with method references

       var @class = new ClassBuilder("NullTest", "MyTest")
            .WithUsings("System", "NUnit.Framework")
            .WithModifiers(Modifiers.Public)
            .WithMethods(
                new MethodBuilder("SetUp")
                    .WithAttributes(new Attribute("SetUp"))
                    .WithModifiers(Modifiers.Public)
                    .Build(),
                new MethodBuilder("Test_WhenAddingNumber_ShouldBeCorrectSum")
                    .WithAttributes(new Attribute("Test"))
                    .WithModifiers(Modifiers.Public)
                    .WithBody(
                        BodyGenerator.Create(
                            Statement.Declaration.Declare("myList", typeof(List<int>)),
                            NunitAssertGenerator.Throws(new VariableReference("myList", new MethodReference("First")), typeof(ArgumentNullException))))
                    .Build())
            .Build();

This code will generate following code:

using System;
using NUnit.Framework;

namespace MyTest
{
    public class NullTest
    {
        [SetUp]
        public void SetUp()
        {
        }

        [Test]
        public void Test_WhenAddingNumber_ShouldBeCorrectSum()
        {
            List<int> myList;
            Assert.Throws<ArgumentNullException>(() => myList.First(), "");
        }
    }
}

Missing anything?

If we miss a feature, syntax or statements - just create an issue or contact us and I'm sure we can add it.

It is also possible for you to contribute with your own feature. Simply add a pull request and we will look at it.

License

This project is licensed under the MIT License. See the LICENSE.md file for details.

Contact

Visit www.testura.net, twitter at @testuranet or email at mille.bostrom@testura.net