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:
- Classes
- Methods
- Parameters
- Arguments
- Attributes
- Fields
- Properties
But also simple statements like:
- Declaration statements (for example declare and assign variables)
- Iterations statements (for example for-loop)
- Jump statements (for example return)
- Selection statement (for example if-statements)
- Expression statements (for example invoke methods)
https://www.nuget.org/packages/Testura.Code
PM> Install-Package Testura.Code
Testura.Code have three different types of helpers:
Generators
- The most basic kinds of code generators, for example fields, properties and modifiers.Statement
- Helpers for regular statements and expressions, for example declare and assign a variable or invoke a method.Builders
- Currently we have two builder - One class builder and one method builder. These have the highest abstraction and are easy to use.
Here is an example on how to generate, save and compile a simple hello world.
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();
}
}
}
var saver = new CodeSaver();
// As a string
var generatedCode = saver.SaveCodeAsString(@class);
// Or to file
saver.SaveCodeToFile(@class, @"/path/HelloWorld.cs");
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");
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; }
}
}
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
}
}
}
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;
}
}
}
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);
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
}
}
}
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(), "");
}
}
}
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.
This project is licensed under the MIT License. See the LICENSE.md file for details.
Visit www.testura.net, twitter at @testuranet or email at mille.bostrom@testura.net