Strongbars is a compile-time, type-safe .NET source generator that transforms text templates (HTML, JSON, SQL, etc.) containing {{variable}} placeholder syntax into strongly-typed C# classes. The key design goal is zero runtime overhead and build-time validation of template parameters.
Published on NuGet as two packages:
Strongbars— main package users referenceStrongbars.Abstractions— base types that generated classes inherit from
Strongbars/
├── Strongbars/ # NuGet package shell (no real C# code)
│ ├── Strongbars.csproj # Version, NuGet metadata, package bundling
│ └── build/
│ └── Strongbars.props # MSBuild props injected into consumer projects
├── Strongbars.Abstractions/ # Base types for generated classes
│ ├── Template.cs # Base class: Template : TemplateArgument
│ ├── Variable.cs # Variable metadata (name, type, optional, array)
│ └── VariableType.cs # Enum: String, IFormattable, TemplateArgument, Bool
├── Strongbars.Generator/ # The actual source generator
│ ├── FileGenerator.cs # IIncrementalGenerator entry point
│ ├── ClassGenerator.cs # C# class code generation from AST
│ ├── Parser.cs # Template parser → ITemplateNode AST + ParserError
│ ├── TemplateToken.cs # ITemplateNode implementations (AST node types)
│ ├── ProviderExtensions.cs # MSBuild property/metadata helpers
│ └── EnumerableExtensions.cs # DistinctBy polyfill for netstandard2.0
├── Strongbars.Tests/ # NUnit test suite
│ ├── FileGeneratorTests.cs # End-to-end generator tests + error diagnostic tests
│ ├── ParserTests.cs # Standalone Parser unit tests (AST structure + errors)
│ ├── sample/ # HTML template fixtures used in generator tests
│ └── Utils/ # Test infrastructure (mocks for analyzer APIs)
├── Strongbars.Benchmarks/ # BenchmarkDotNet performance comparison
│ ├── AllTemplatesBenchmark.cs # Benchmark class (net10.0, RuntimeMoniker.Net10_0)
│ ├── Templates/ # HTML templates for benchmarks (auto-discovered)
│ └── Scenarios/ # Scenario wrappers + competitor engine adapters
├── examples/
│ └── ExampleConsoleApp/ # Working usage example
├── .github/workflows/
│ ├── dotnet.yml # CI: format check + tests on push/PR
│ └── nuget.yml # CD: publish to NuGet on version tags
├── .config/dotnet-tools.json # Pins CSharpier version
├── Directory.Build.props # Global C# settings for all projects
├── Strongbars.slnx # Solution file (new .slnx format)
├── check_version.nu # NuShell script: validates tag matches csproj version
└── README.md # User-facing documentation
# Restore dependencies and local tools
dotnet restore
dotnet tool restore
# Check code formatting (must pass in CI)
dotnet csharpier check .
# Auto-format code
dotnet csharpier format .
# Build all projects
dotnet build
# Run tests
dotnet test
# Run benchmarks (must be Release mode)
dotnet run -c Release --project Strongbars.Benchmarks
# Build release + create NuGet packages
dotnet build --configuration Release /p:Version=<version>
dotnet pack --output .Strongbars templates use these constructs:
| Syntax | Meaning |
|---|---|
{{foo}} |
Required string variable |
{{foo?}} |
Optional string variable |
{{..foo}} |
Iterable variable (generates IEnumerable<string> + IEnumerable<TemplateArgument> overloads) |
{% if foo %}...{% end %} |
Conditional block |
{% if foo %}...{% else %}...{% end %} |
Conditional with else |
{% unless foo %}...{% end %} |
Inverted conditional |
{% unless foo %}...{% else %}...{% end %} |
Inverted conditional with else |
Variables can hold string, IFormattable, TemplateArgument, or bool — the generator detects the required type from usage context.
Known limitation: Each variable name must appear at most once per template. The generator does not deduplicate variables, so using {{name}} in two places generates a duplicate constructor parameter. See EnumerableExtensions.DistinctBy for the intended deduplication hook.
- Implements
IIncrementalGenerator(the modern incremental API, not legacyISourceGenerator) - Reads template files registered as
AdditionalFileswithStrongbarsNamespacemetadata FileGeneratorhandles MSBuild plumbing and diagnostic reporting (SB001–SB003)ClassGeneratorgenerates the C# class source from the parsed AST- Emits one C# class per template file; class name = filename without extension
- For
{{..array}}variables, generates two constructor overloads:IEnumerable<string>andIEnumerable<TemplateArgument>
Parserconverts raw template text into anITemplateNodetree via regex- AST node types:
LiteralTemplateNode,VariableTemplateNode,CompositeTemplateNode,ConditionalTemplateNode - Each node implements
ITemplateNodewith:GenerateRenderExpression()— returns a C# expression string that computes the rendered outputGetVariables()— yields allVariableinstances referenced by this subtree
- Parser errors throw
ParserError(caught byFileGeneratorand reported as SB003 diagnostics)
- All generated classes inherit from
Template : TemplateArgument TemplateArgumenthas implicit conversions fromstringandint, enabling templates to be nested inside other templates- Contains the canonical regex patterns used by both the generator (for parsing) and the generated classes (for rendering)
- Generated classes are
internalby default - Consumers set
<StrongbarsVisibility>public</StrongbarsVisibility>in their.csprojto make them public - This is exposed via
Strongbars.propsas a compiler-visible property
- Targets
net10.0withRuntimeMoniker.Net10_0 ReflectedScenarioauto-discovers generated template classes and creates benchmark scenarios- Converts Strongbars template syntax to each competitor's dialect (Scriban, Fluid/Liquid, Handlebars, Stubble/Mustache)
- Add new
.htmlfiles toTemplates/to add new benchmark scenarios automatically - Constraint: Benchmark templates must not repeat the same variable name (generator limitation)
- Formatter: CSharpier (enforced in CI — must pass before merging)
- C# version: LangVersion 10 (file-scoped namespaces, records if needed)
- Nullable:
#nullable enableeverywhere;TreatWarningsAsErrors: true - Naming: PascalCase for types and public members;
_camelCasefor private fields
- Framework: NUnit 4.x with
Assert.That(...)fluent assertions FileGeneratorTests.cs: end-to-end tests viaOutputGenerator(full generator pipeline)ParserTests.cs: unit tests that callParser.Parse()directly, checking AST node types/structure- Template fixtures live in
Strongbars.Tests/sample/as real.htmlfiles - Every public feature of the generator must have test coverage
- Test names are descriptive: e.g.,
ConditionalRendersContentWhenTrue
- Warnings are errors — fix all nullable warnings, don't suppress with
!unless unavoidable - The MSBuild property
<MSBuildWarningsAsErrors>CS8785</MSBuildWarningsAsErrors>catches source generator exceptions at build time
| Code | Severity | Trigger |
|---|---|---|
| SB001 | Error | Template file could not be read |
| SB002 | Error | Template file name could not be determined |
| SB003 | Error | Parser failed (invalid variable name, invalid expression, unclosed conditional) |
- format job:
dotnet csharpier check .— fails if any file is not formatted - test job:
dotnet test
- Verifies commit is on
main - Runs
check_version.nuto assert tag matches version inStrongbars.csproj - Builds in Release mode
- Runs tests
- Packs and pushes
Strongbars+Strongbars.Abstractionsto NuGet.org (usesNUGET_KEYsecret) - Creates a GitHub Release with auto-generated notes
To release a new version:
- Bump
<Version>inStrongbars/Strongbars.csproj - Commit and merge to
main - Create a git tag:
git tag v<version> && git push origin v<version>
| Setting | Value | Where |
|---|---|---|
| C# version | 10 | Directory.Build.props |
| Nullable | enabled | Directory.Build.props |
| Warnings as errors | true | Directory.Build.props |
| Target framework (lib) | netstandard2.0 |
individual .csproj |
| Target framework (tests) | net10.0 |
Strongbars.Tests.csproj |
| Target framework (benchmarks) | net10.0 |
Strongbars.Benchmarks.csproj |
| Code formatter | CSharpier 1.2.5 | .config/dotnet-tools.json |
| Current version | 1.4.0 | Strongbars/Strongbars.csproj |
- Add sample
.htmlfixture toStrongbars.Tests/sample/ - Write failing tests in
FileGeneratorTests.cscovering the new syntax - Add direct
Parsertests inParserTests.csfor the new AST structure - Update regex patterns in
Parser.csif needed - Add new
ITemplateNodeimplementation inTemplateToken.csif needed - Implement code generation in
ClassGenerator.cs - Run
dotnet csharpier format .to format - Run
dotnet testto verify all tests pass - Update
README.mdwith the new syntax
- Do not use
ISourceGenerator— the project uses the incrementalIIncrementalGeneratorAPI for performance - Do not add runtime logic to the main
Strongbarspackage — it contains no C# source, only the NuGet packaging shell; runtime logic belongs inStrongbars.Abstractions - Always run
dotnet csharpier format .before committing — the CI check will fail otherwise - Version and tag must match — the
check_version.nuscript enforces this during the publish pipeline netstandard2.0target — the library projects must remain onnetstandard2.0for broad compatibility; only tests, benchmarks, and examples usenet10.0- Variables must be unique per template — the generator does not deduplicate; using the same variable name twice causes a duplicate-parameter compile error in the generated code
- Benchmark templates must use
{% end %}not{% endif %}— the Strongbars parser uses{% end %}as the generic block closer; the benchmark's syntax converters map this to each engine's specific closing tag