A comprehensive framework for programmatic code generation and analysis with language-agnostic interfaces and C# implementations using Roslyn. Designed for building source generators, code analysis tools, and testing generated code.
The CodeBuilder framework follows a layered architecture with language-agnostic interfaces and specific implementations:
graph TB
subgraph "Abstractions Layer"
A[FractalDataWorks.CodeBuilder.Abstractions<br/>Language-agnostic interfaces]
end
subgraph "Analysis Layer"
B[FractalDataWorks.CodeBuilder.Analysis<br/>Testing interfaces]
C[FractalDataWorks.CodeBuilder.Analysis.CSharp<br/>Roslyn-based testing implementation]
end
subgraph "Implementation Layer"
D[FractalDataWorks.CodeBuilder.CSharp<br/>Roslyn-based code generation]
end
A -.-> B
B --> C
A --> D
C --> D
| Project | Description | Purpose |
|---|---|---|
| FractalDataWorks.CodeBuilder.Abstractions | Language-agnostic interfaces | Defines contracts for code builders, generators, and parsers |
| FractalDataWorks.CodeBuilder.CSharp | C# code generation | Fluent API for generating C# code using Roslyn |
| FractalDataWorks.CodeBuilder.Analysis | Testing interfaces | Language-agnostic interfaces for compilation verification and syntax expectations |
| FractalDataWorks.CodeBuilder.Analysis.CSharp | C# testing implementation | Roslyn-based implementation of testing interfaces |
| Project | Description |
|---|---|
| FractalDataWorks.CodeBuilder.Tests | Unit tests for core functionality |
# For C# code generation
Install-Package FractalDataWorks.CodeBuilder.CSharp
# For testing generated code
Install-Package FractalDataWorks.CodeBuilder.Analysis.CSharp
# For language-agnostic abstractions only
Install-Package FractalDataWorks.CodeBuilder.Abstractions# For C# code generation
dotnet add package FractalDataWorks.CodeBuilder.CSharp
# For testing generated code
dotnet add package FractalDataWorks.CodeBuilder.Analysis.CSharp
# For language-agnostic abstractions only
dotnet add package FractalDataWorks.CodeBuilder.Abstractionsusing FractalDataWorks.CodeBuilder.CSharp;
// Create a simple class
var code = new ClassBuilder("Person")
.MakePublic()
.AddProperty("string", "Name", p => p
.MakePublic()
.WithGetter()
.WithSetter())
.AddProperty("int", "Age", p => p
.MakePublic()
.WithGetter()
.WithSetter())
.AddMethod("string", "GetDisplayName", m => m
.MakePublic()
.WithBody("return $\"{Name} ({Age})\";"))
.Build();
Console.WriteLine(code);Generated Output:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string GetDisplayName()
{
return $"{Name} ({Age})";
}
}using FractalDataWorks.CodeBuilder.Testing.CSharp;
using FractalDataWorks.CodeBuilder.Testing;
// Test that generated code compiles and has expected structure
var verifier = new CSharpCompilationVerifier();
var syntaxExpectations = new CSharpSyntaxExpectations();
// Verify compilation
var result = verifier.CompileAndVerify(generatedCode);
Assert.True(result.Success);
// Verify structure
syntaxExpectations
.ExpectCode(generatedCode)
.HasClass("Person", c => c
.HasAccessModifier("public")
.HasProperty("Name", p => p.HasType("string"))
.HasProperty("Age", p => p.HasType("int"))
.HasMethod("GetDisplayName", m => m
.HasReturnType("string")
.IsPublic()))
.Compiles();- Fluent API: Intuitive builder pattern for constructing code elements
- Type-Safe: Compile-time safety with strongly-typed builders
- Roslyn Integration: Built on Microsoft's Roslyn compiler platform
- Rich Support: Classes, interfaces, methods, properties, fields, constructors, enums, records
- Modern C# Features: Support for nullable reference types, init-only properties, records, and more
- Language-Agnostic Interfaces: Define testing contracts independent of implementation
- Compilation Verification: Verify that generated code compiles without errors
- Syntax Expectations: Fluent API for asserting code structure and content
- Method Invocation: Execute methods from compiled assemblies for integration testing
- Diagnostic Analysis: Detailed compilation error and warning reporting
var repositoryClass = new ClassBuilder("UserRepository")
.MakePublic()
.AddGenericParameter("T")
.AddGenericConstraint("T", "class, IEntity")
.WithInterface("IRepository<T>")
.WithNamespace("MyApp.Data")
// Fields
.AddField("DbContext", "_context", f => f
.MakePrivate()
.MakeReadOnly())
// Constructor
.AddConstructor(c => c
.MakePublic()
.AddParameter("DbContext", "context")
.WithBody("_context = context;"))
// Properties
.AddProperty("IQueryable<T>", "Items", p => p
.MakePublic()
.WithGetter("_context.Set<T>().AsQueryable()"))
// Methods
.AddMethod("Task<T?>", "FindAsync", m => m
.MakePublic()
.MakeAsync()
.AddParameter("int", "id")
.AddParameter("CancellationToken", "cancellationToken", "default")
.WithBody(@"
return await _context.Set<T>()
.FindAsync(new object[] { id }, cancellationToken);"))
.Build();// Test compilation with custom options
var options = new AnalysisOptions
{
OutputKind = OutputKind.DynamicallyLinkedLibrary,
OptimizationLevel = OptimizationLevel.Release,
AllowUnsafe = false,
References = new[]
{
typeof(System.Collections.Generic.IEnumerable<>).Assembly.Location,
typeof(System.Runtime.CompilerServices.CompilerGeneratedAttribute).Assembly.Location
}
};
var result = verifier.CompileWithOptions(new[] { generatedCode }, options);
Assert.True(result.Success);
// Execute compiled method
var displayName = result.InvokeMethod("Person", "GetDisplayName");
Assert.Equal("John Doe (30)", displayName);// Test a source generator
public class MySourceGeneratorTests
{
[Fact]
public void Generator_ProducesExpectedOutput()
{
// Input source code
var inputSource = @"
[GenerateBuilder]
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}";
// Run your source generator
var generatedSources = RunSourceGenerator<ProductBuilderGenerator>(inputSource);
// Verify generated code structure
var syntaxExpectations = new CSharpSyntaxExpectations();
syntaxExpectations
.ExpectCode(generatedSources.First().SourceText.ToString())
.HasClass("ProductBuilder", c => c
.IsPublic()
.HasMethod("WithId", m => m
.HasParameter("id", "int")
.HasReturnType("ProductBuilder"))
.HasMethod("WithName", m => m
.HasParameter("name", "string")
.HasReturnType("ProductBuilder"))
.HasMethod("Build", m => m
.HasReturnType("Product")))
.Compiles();
}
}Provides language-agnostic compilation verification:
public interface ICompilationVerifier
{
ICompilationResult CompileAndVerify(params string[] sources);
ICompilationResult CompileWithOptions(string[] sources, ICompilationOptions options);
}Key Features:
- Compilation Results: Success status, diagnostics, and assembly bytes
- Method Invocation: Execute methods from compiled assemblies
- Custom Options: Control output type, optimization level, and references
- Diagnostic Reporting: Detailed error and warning information
Provides fluent syntax verification:
public interface ISyntaxExpectations
{
ISyntaxTreeExpectations ExpectCode(string generatedCode);
ISyntaxTreeExpectations ExpectSyntaxTree(object syntaxTree);
}Key Features:
- Structure Verification: Assert namespaces, classes, interfaces, enums, records
- Member Verification: Assert methods, properties, fields, constructors
- Modifier Verification: Assert access modifiers, static, abstract, sealed, etc.
- Type Verification: Assert parameter types, return types, property types
- Compilation Verification: Ensure generated code compiles successfully
The framework supports extensibility through a language registry system:
// Register C# language support
var registry = new LanguageRegistry();
registry.RegisterLanguage<CSharpCodeGenerator>("csharp");
registry.RegisterLanguage<CSharpCodeGenerator>("cs");
// Get generator for a language
var generator = registry.GetGenerator("csharp");- Use Fluent API: Chain builder methods for readable code construction
- Namespace Organization: Always specify appropriate namespaces
- Type Safety: Leverage compile-time checking with strongly-typed builders
- Documentation: Add XML documentation to generated code
- Formatting: Use consistent indentation and formatting
- Verify Compilation: Always ensure generated code compiles
- Test Structure: Use syntax expectations to verify code structure
- Test Behavior: Use method invocation to test runtime behavior
- Edge Cases: Test error scenarios and edge cases
- Integration Tests: Test complete source generator workflows
If you're migrating from the previous TestUtilities project:
- Namespace Changes: Update from
FractalDataWorks.SmartGenerators.TestUtilitiestoFractalDataWorks.CodeBuilder.Testing.CSharp - Interface Adoption: Use the new language-agnostic interfaces where possible
- Compilation Verification: Switch to
ICompilationVerifierfor testing generated code - Syntax Expectations: Use
ISyntaxExpectationsfor structural assertions
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
For questions, issues, or contributions:
- GitHub Issues: Report bugs or request features
- Documentation: API Documentation
- Samples: Check the
testsdirectory for comprehensive examples