This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This project uses Microsoft Testing Platform (MTP) with the TUnit testing framework. Test commands differ significantly from traditional VSTest.
See: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-test?tabs=dotnet-test-with-mtp
# Check .NET installation (.NET 8.0, 9.0, and 10.0 required)
dotnet --info
# Restore NuGet packages
cd src
dotnet restore Splat.DI.SourceGenerator.slnxNote: This project uses the modern .slnx (XML-based solution file) format instead of the legacy .sln format. The .slnx format provides better performance, cleaner diffs, and improved tooling support in Visual Studio 2022 17.10+.
CRITICAL: The working folder must be ./src folder. These commands won't function properly without the correct working folder.
# Build the solution
dotnet build Splat.DI.SourceGenerator.slnx -c Release
# Build with warnings as errors (includes StyleCop violations)
dotnet build Splat.DI.SourceGenerator.slnx -c Release -warnaserror
# Clean the solution
dotnet clean Splat.DI.SourceGenerator.slnxCRITICAL: This repository uses MTP configured in testconfig.json. All TUnit-specific arguments must be passed after --:
The working folder must be ./src folder. These commands won't function properly without the correct working folder.
IMPORTANT:
- Do NOT use
--no-buildflag when running tests. Always build before testing to ensure all code changes (including test changes) are compiled. Using--no-buildcan cause tests to run against stale binaries and produce misleading results. - Use
--output Detailedto see Console.WriteLine output from tests. This must be placed BEFORE any--separator:dotnet test --output Detailed -- --treenode-filter "..."
# Run all tests in the solution
dotnet test --solution Splat.DI.SourceGenerator.slnx -c Release
# Run all tests in a specific project
dotnet test --project Splat.DependencyInjection.Analyzer.Tests/Splat.DependencyInjection.Analyzer.Tests.csproj -c Release
dotnet test --project Splat.DependencyInjection.SourceGenerator.Tests/Splat.DependencyInjection.SourceGenerator.Tests.csproj -c Release
# Run a single test method using treenode-filter
# Syntax: /{AssemblyName}/{Namespace}/{ClassName}/{TestMethodName}
dotnet test --project Splat.DependencyInjection.Analyzer.Tests/Splat.DependencyInjection.Analyzer.Tests.csproj -- --treenode-filter "/*/*/*/MyTestMethod"
# Run all tests in a specific class
dotnet test --project Splat.DependencyInjection.Analyzer.Tests/Splat.DependencyInjection.Analyzer.Tests.csproj -- --treenode-filter "/*/*/MyClassName/*"
# Run tests in a specific namespace
dotnet test --project Splat.DependencyInjection.SourceGenerator.Tests/Splat.DependencyInjection.SourceGenerator.Tests.csproj -- --treenode-filter "/*/MyNamespace/*/*"
# Filter by test property (e.g., Category)
dotnet test --solution Splat.DI.SourceGenerator.slnx -- --treenode-filter "/*/*/*/*[Category=Integration]"
# Run tests with code coverage (Microsoft Code Coverage)
dotnet test --solution Splat.DI.SourceGenerator.slnx -- --coverage --coverage-output-format cobertura
# Run tests with detailed output
dotnet test --solution Splat.DI.SourceGenerator.slnx -- --output Detailed
# List all available tests without running them
dotnet test --project Splat.DependencyInjection.Analyzer.Tests/Splat.DependencyInjection.Analyzer.Tests.csproj -- --list-tests
# Fail fast (stop on first failure)
dotnet test --solution Splat.DI.SourceGenerator.slnx -- --fail-fast
# Control parallel test execution
dotnet test --solution Splat.DI.SourceGenerator.slnx -- --maximum-parallel-tests 4
# Generate TRX report
dotnet test --solution Splat.DI.SourceGenerator.slnx -- --report-trx
# Disable logo for cleaner output
dotnet test --project Splat.DependencyInjection.Analyzer.Tests/Splat.DependencyInjection.Analyzer.Tests.csproj -- --disable-logo
# Combine options: coverage + TRX report + detailed output
dotnet test --solution Splat.DI.SourceGenerator.slnx -- --coverage --coverage-output-format cobertura --report-trx --output DetailedAlternative: Using dotnet run for single project
# Run tests using dotnet run (easier for passing flags)
dotnet run --project Splat.DependencyInjection.Analyzer.Tests/Splat.DependencyInjection.Analyzer.Tests.csproj -c Release -- --treenode-filter "/*/*/*/MyTest"
# Disable logo for cleaner output
dotnet run --project Splat.DependencyInjection.SourceGenerator.Tests/Splat.DependencyInjection.SourceGenerator.Tests.csproj -- --disable-logo --treenode-filter "/*/*/*/Test1"The --treenode-filter follows the pattern: /{AssemblyName}/{Namespace}/{ClassName}/{TestMethodName}
Examples:
- Single test:
--treenode-filter "/*/*/*/MyTestMethod" - All tests in class:
--treenode-filter "/*/*/MyClassName/*" - All tests in namespace:
--treenode-filter "/*/MyNamespace/*/*" - Filter by property:
--treenode-filter "/*/*/*/*[Category=Integration]" - Multiple wildcards:
--treenode-filter "/*/*/MyTests*/*"
Note: Use single asterisks (*) to match segments. Double asterisks (/**) are not supported in treenode-filter.
--treenode-filter- Filter tests by path pattern or properties (syntax:/{Assembly}/{Namespace}/{Class}/{Method})--list-tests- Display available tests without running--fail-fast- Stop after first failure--maximum-parallel-tests- Limit concurrent execution (default: processor count)--coverage- Enable Microsoft Code Coverage--coverage-output-format- Set coverage format (cobertura, xml, coverage)--report-trx- Generate TRX format reports--output- Control verbosity (Normal or Detailed)--no-progress- Suppress progress reporting--disable-logo- Remove TUnit logo display--diagnostic- Enable diagnostic logging (Trace level)--timeout- Set global test timeout--reflection- Enable reflection mode instead of source generation
See https://tunit.dev/docs/reference/command-line-flags for complete TUnit flag reference.
src/Splat.DI.SourceGenerator.slnx- Modern XML-based solution file (Visual Studio 2022 17.10+)src/testconfig.json- Configures test execution ("parallel": false) and code coverage (Cobertura format)src/Directory.Build.props- EnablesTestingPlatformDotnetTestSupportfor test projects.github/COPILOT_INSTRUCTIONS.md- Comprehensive development guidelines
Splat.DI.SourceGenerator is a high-performance C# source generator that produces compile-time dependency injection registrations for Splat. It eliminates runtime reflection, provides full native AOT support, and includes intelligent analyzers with automatic code fixes.
Generator Project (Splat.DependencyInjection.SourceGenerator/)
Generator.cs- IIncrementalGenerator entry point with CreateSyntaxProvider pipelineModels/- Value-equatable POCO records (no ISymbol/SyntaxNode references)RegistrationInfo.cs- Base record for all registration typesTransientRegistrationInfo.cs,LazySingletonRegistrationInfo.cs,ConstantRegistrationInfo.csConstructorParameter.cs,PropertyInjection.csEquatableArray.cs- Value-equatable array wrapper for pipeline caching
CodeGeneration/CodeGenerator.cs- String-based code generation (not SyntaxFactory)Constants.cs- Attribute definitions with[Embedded]attributeDiagnosticWarnings.cs- Shared diagnostic descriptors
Analyzer Project (Splat.DependencyInjection.Analyzer/)
Analyzers/ConstructorAnalyzer.cs- Detects multiple constructors without[DependencyInjectionConstructor](SPLATDI001, SPLATDI003, SPLATDI004)Analyzers/PropertyAnalyzer.cs- Validates property injection setters (SPLATDI002)CodeFixes/ConstructorCodeFixProvider.cs- Adds[DependencyInjectionConstructor]attributeCodeFixes/PropertyCodeFixProvider.cs- Fixes property setter accessibility
Test Projects
Splat.DependencyInjection.SourceGenerator.Tests/- 342 snapshot tests using Verify.SourceGeneratorsSplat.DependencyInjection.Analyzer.Tests/- 45 analyzer and code fix tests
Incremental Generator Pipeline (IIncrementalGenerator)
- Predicate functions - Fast syntax-only checks (e.g.,
IsRegisterInvocation) - Transform functions - Semantic analysis + POCO extraction (no ISymbol in output!)
- Generation functions - String-based code output using StringBuilder
Value-Equatable Models (Critical for Caching)
- ALL pipeline models must implement
IEquatable<T> - NEVER include ISymbol or SyntaxNode references in pipeline outputs
- Use
EquatableArray<T>for array equality in records - Extract strings from symbols using
ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
Generic-First API (AOT Compatible)
- Generated code uses
resolver.Register<T>()instead ofRegister(factory, typeof(T)) - Eliminates boxing for value types
- Full Native AOT and trimming support
Code Generation Strategy
- Uses StringBuilder with raw string literals (
""") - 3-5x faster than SyntaxFactory approach
- No dependency on ReactiveMarbles.RoslynHelpers or ILRepack
Analyzer Separation (Roslyn Best Practice)
- Generator focuses on code generation only
- Separate analyzer project provides real-time diagnostics
- Code fix providers offer automatic fixes (Quick Actions)
| ID | Severity | Description | Code Fix Available |
|---|---|---|---|
| SPLATDI001 | Warning | Multiple constructors without [DependencyInjectionConstructor] |
✅ Yes |
| SPLATDI002 | Error | Property lacks accessible setter | ✅ Yes |
| SPLATDI003 | Error | Multiple constructors marked | ❌ Manual fix |
| SPLATDI004 | Error | Constructor not accessible | ✅ Yes |
CRITICAL: All code must comply with ReactiveUI contribution guidelines: https://www.reactiveui.net/contribute/index.html
- EditorConfig rules (
.editorconfig) - comprehensive C# formatting and naming conventions - StyleCop Analyzers - builds fail on violations
- Roslynator Analyzers - additional code quality rules
- Analysis level: latest with enhanced .NET analyzers
- All public APIs require XML documentation comments (including protected methods of public classes)
- Braces: Allman style (each brace on new line)
- Indentation: 4 spaces, no tabs
- Fields:
_camelCasefor private/internal,readonlywhere possible,static readonly(notreadonly static) - Visibility: Always explicit (e.g.,
private string _foonotstring _foo), visibility first modifier - Namespaces: File-scoped preferred, imports outside namespace, sorted (system then third-party)
- Types: Use keywords (
int,string) not BCL types (Int32,String) - Modern C#: Use nullable reference types, pattern matching, switch expressions, records, init setters, target-typed new, collection expressions, file-scoped namespaces, primary constructors
- Avoid
this.unless necessary - Use
nameof()instead of string literals - Use
varwhen it improves readability or aids refactoring
See .github/COPILOT_INSTRUCTIONS.md for complete style guide.
- Unit tests use TUnit framework with Microsoft Testing Platform
- Test projects:
Splat.DependencyInjection.Analyzer.TestsandSplat.DependencyInjection.SourceGenerator.Tests - Coverage configured in
src/testconfig.json(Cobertura format) - Parallel test execution disabled (
"parallel": falsein testconfig.json) - Snapshot testing uses Verify.SourceGenerators with
*.verified.csfiles - Always write unit tests for new features or bug fixes
- Follow existing test patterns in test projects
- Use
TestUtilities.AreEquivalent()for newline-agnostic source code comparison
- Uses
[Test]attribute instead of[Fact]/[Theory] - Uses
[Before(Test)]and[After(Test)]hooks instead of IDisposable - Uses
await Assert.That(x).IsEqualTo(y)instead ofAssert.Equal(y, x) - No
ITestOutputHelper- TUnit uses Microsoft.Testing.Platform for output
- Design incremental pipeline - predicate → transform → generate
- Create value-equatable POCOs - no ISymbol/SyntaxNode references
- Create failing tests first (snapshot tests in SourceGenerator.Tests)
- Implement minimal functionality in Generator.cs
- Update code generation in CodeGenerator.cs (use StringBuilder, not SyntaxFactory)
- Ensure generic-first API usage:
resolver.Register<T>()nottypeof(T) - Verify snapshots match expected output (C# 7.3 compatible)
- Add XML documentation to all public APIs
- Run formatting validation before committing
- Add diagnostic descriptor to
DiagnosticWarnings.cs - Create analyzer in
Splat.DependencyInjection.Analyzer/Analyzers/ - Implement
DiagnosticAnalyzerusingRegisterSymbolActionorRegisterSyntaxNodeAction - Create corresponding code fix provider if fixable
- Add tests in
Splat.DependencyInjection.Analyzer.Tests/ - Use
TestUtilities.AreEquivalent()for source comparison - Update README.md with new diagnostic ID
- Create reproduction test (use Verify snapshots)
- Fix with minimal changes
- Ensure pipeline still caches properly (POCOs value-equatable)
- Verify no regression in existing tests
- Accept snapshot changes if expected
- Modify code generation in
CodeGeneration/CodeGenerator.cs - Use raw string literals (
""") for multi-line code - Ensure generic-first API:
resolver.Register<T>()nottypeof(T) - Run all snapshot tests - expect 342 failing tests
- Review each
.verified.csdiff carefully - Accept snapshots only if changes are correct
- Ensure C# 7.3 compatibility (no file-scoped namespaces, no init properties)
- ISymbol/SyntaxNode in pipeline outputs - breaks incremental caching
- Runtime reflection in generated code - breaks AOT compatibility
- SyntaxFactory for code generation - 3-5x slower than StringBuilder
- Type-based API - use
resolver.Register<T>()notRegister(factory, typeof(T)) - Diagnostics in generator - use separate analyzer project instead
- Heavy dependencies - keep generator lightweight (netstandard2.0 target)
- Breaking changes to generated code format without major version bump
- Non-value-equatable models in pipeline - breaks caching
- Value-Equatable POCOs: CRITICAL for incremental generator caching - never include ISymbol/SyntaxNode
- Generic-First API: All generated code must use
resolver.Register<T>()for AOT compatibility - String-Based Generation: Use StringBuilder with raw string literals, not SyntaxFactory
- Separate Analyzer: Diagnostics in separate project following Roslyn best practices
- No shallow clones: Repository requires full clone for git version information used by Nerdbank.GitVersioning
- Required .NET SDKs: .NET 8.0, 9.0, and 10.0 (all three required for full build)
- Snapshot Testing: Review
.verified.csdiffs carefully before accepting - Comprehensive Instructions:
.github/COPILOT_INSTRUCTIONS.mdcontains detailed development guidelines - Code Formatting: Always run
dotnet format whitespaceanddotnet format stylebefore committing
Philosophy: Generate simple, efficient, AOT-compatible dependency injection code at compile-time. Minimize runtime overhead, maximize build performance with incremental caching, and provide excellent developer experience with real-time diagnostics and automatic code fixes.