Skip to content

Conversation

@thomhurst
Copy link
Owner

No description provided.

@claude
Copy link
Contributor

claude bot commented Dec 22, 2025

Pull Request Review: TUnit.AspNetCore Analyzers

Thank you for this contribution! This PR adds important compile-time safety checks for the TUnit.AspNetCore testing framework. Here's my comprehensive review:


Strengths

1. Excellent Code Quality

  • Modern C# patterns throughout (collection expressions, file-scoped namespaces, pattern matching)
  • Clean separation of concerns with ConcurrentDiagnosticAnalyzer base class
  • Proper use of immutable collections for rule definitions
  • Well-structured test organization

2. Strong Test Coverage

  • Comprehensive test suite (440+ lines) covering all analyzer scenarios
  • Both positive and negative test cases
  • Edge cases properly tested (unrelated factories, inheritance hierarchy)
  • Cross-platform line ending support via LineEndingNormalizingVerifier

3. Clear Documentation

  • Excellent diagnostic messages with actionable guidance
  • Resource strings properly externalized
  • Good use of XML documentation in resources

4. Good Architecture

  • Multi-targeting Roslyn versions (4.4, 4.14, 4.7) follows TUnit patterns
  • Proper analyzer versioning with release tracking
  • CI/CD integration with pipeline modules

🔍 Issues & Recommendations

CRITICAL: Missing Dual-Mode Verification

Per CLAUDE.md Rule 1, all features must work in both source-generated AND reflection modes. This PR adds analyzers but doesn't verify behavior in both execution modes.

Required Action:
Add tests that verify the analyzer warnings don't break functionality in both modes:

[Test]
[Arguments(ExecutionMode.SourceGenerated)]
[Arguments(ExecutionMode.Reflection)]
public async Task Factory_Access_In_Test_Works_In_Both_Modes(ExecutionMode mode)
{
    // Verify no false positives that would break actual test execution
}

CRITICAL: Missing Public API Snapshot Tests

Per CLAUDE.md Rule 2, changes to public APIs require snapshot testing.

Required Action:

cd TUnit.PublicAPI
dotnet test
# Review .received.txt files
# If changes are intentional:
for f in *.received.txt; do mv "$f" "${f%.received.txt}.verified.txt"; done
git add *.verified.txt

📝 Code-Specific Feedback

TUnit.AspNetCore.Analyzers/WebApplicationFactoryAccessAnalyzer.cs

Line 145-169: Type checking logic

  • ✅ Good: Walks inheritance hierarchy correctly
  • ⚠️ Consider: Cache type lookups for performance (hot path optimization per CLAUDE.md Rule 4)
// Suggestion: Add caching
private static readonly ConcurrentDictionary<INamedTypeSymbol, bool> TypeCache = new();

private static bool IsWebApplicationTestType(INamedTypeSymbol? type)
{
    if (type == null) return false;
    return TypeCache.GetOrAdd(type, CheckTypeHierarchy);
}

private static bool CheckTypeHierarchy(INamedTypeSymbol type)
{
    // Current logic here
}

Line 171-201: GetContainingMethod

  • ✅ Correct implementation
  • 💡 Minor: Could use SyntaxNode.AncestorsAndSelf() for cleaner traversal:
private static IMethodSymbol? GetContainingMethod(IOperation operation)
{
    var syntax = operation.Syntax;
    var methodNode = syntax.AncestorsAndSelf()
        .OfType<MethodDeclarationSyntax>()
        .FirstOrDefault() 
        ?? syntax.AncestorsAndSelf().OfType<ConstructorDeclarationSyntax>().FirstOrDefault();
    
    if (methodNode == null) return null;
    
    return operation.SemanticModel?.GetDeclaredSymbol(methodNode) as IMethodSymbol;
}

TUnit.AspNetCore/TestWebApplicationFactory.cs

Lines 44-45: Exception throwing

  • ✅ Excellent design! Prevents accidental shared state access
  • ✅ Clear error messages guide users to correct pattern
  • 💡 Consider: These could be compile-time errors via analyzers (future enhancement)

TUnit.AspNetCore.Analyzers.Tests/Verifiers/LineEndingNormalizingVerifier.cs

Lines 1-101: Cross-platform testing

  • ✅ Well-documented purpose
  • ✅ Comprehensive implementation covering all IVerifier methods
  • ⚠️ Line 88: PushContext creates new verifier but doesn't delegate properly

Bug Fix:

public IVerifier PushContext(string context)
{
    return new LineEndingNormalizingVerifierWithContext(_defaultVerifier.PushContext(context));
}

private class LineEndingNormalizingVerifierWithContext : LineEndingNormalizingVerifier
{
    public LineEndingNormalizingVerifierWithContext(IVerifier wrappedVerifier)
    {
        _defaultVerifier = (DefaultVerifier)wrappedVerifier; // This cast may fail!
    }
}

Issue: The wrapped verifier might not be a DefaultVerifier, causing potential cast exceptions.

Recommended Fix:

private readonly IVerifier _innerVerifier;

public LineEndingNormalizingVerifier(IVerifier? innerVerifier = null)
{
    _innerVerifier = innerVerifier ?? new DefaultVerifier();
}

public IVerifier PushContext(string context)
{
    return new LineEndingNormalizingVerifier(_innerVerifier.PushContext(context));
}

// Update all methods to use _innerVerifier instead of _defaultVerifier

TUnit.AspNetCore.Analyzers.Tests/WebApplicationFactoryAccessAnalyzerTests.cs

Lines 427-440: Test organization

  • ✅ Excellent naming conventions
  • ✅ Good use of test stubs for isolation
  • 💡 Consider extracting common stubs to a shared file to reduce duplication

🔒 Security & Performance

Security

  • ✅ No security concerns identified
  • ✅ Analyzer prevents common misuse patterns that could lead to test isolation bugs
  • ✅ No reflection without AOT annotations needed (analyzers are compile-time only)

Performance

  • ⚠️ Minor: Type hierarchy walking (line 145) could benefit from caching for large test suites
  • ⚠️ Minor: Syntax tree traversal (line 171) called frequently; consider memoization
  • ✅ Proper use of ImmutableHashSet for O(1) lookups

Impact: Low priority - analyzers run at compile-time, not runtime


🧪 Missing Test Coverage

  1. Nested class scenarios

    public class OuterTests : WebApplicationTest<MyFactory, Program>
    {
        public class InnerTests // Does analyzer work here?
  2. Lambda/local function access

    public MyTests()
    {
        Action a = () => { var f = Factory; }; // Should this be caught?
    }
  3. Expression-bodied members

    private object MyProp => Factory; // In constructor context

📦 Build & Integration

Concerns:

  1. AspNetCore.Analyzer.props - Good pattern following existing Roslyn.props
  2. Directory.Build.props - Proper multi-targeting setup
  3. Pipeline modules - ✅ Well integrated

Missing:

  • No mention of analyzer packaging in .csproj files
  • Verify analyzers are included in NuGet package with proper <PackageReference> integration

Pre-Commit Checklist (from CLAUDE.md)

Based on CLAUDE.md requirements:

  • Public API changed: Need to run TUnit.PublicAPI tests and accept snapshots
  • Dual-mode feature: Need tests verifying both execution modes
  • Code follows style guide: Modern C#, proper patterns
  • No breaking changes: This is additive
  • ⚠️ Performance: Minor optimization opportunities (non-blocking)
  • AOT compatibility: N/A (analyzers are compile-time)

📋 Action Items

Before Merge:

  1. REQUIRED: Run TUnit.PublicAPI tests and commit snapshots
  2. REQUIRED: Add dual-mode execution verification tests
  3. REQUIRED: Fix LineEndingNormalizingVerifier.PushContext potential bug
  4. RECOMMENDED: Add caching to IsWebApplicationTestType for performance
  5. RECOMMENDED: Add test coverage for nested classes, lambdas, expression-bodied members
  6. NICE-TO-HAVE: Refactor GetContainingMethod to use AncestorsAndSelf()

Future Enhancements:

  • Consider making TestWebApplicationFactory.Services/Server throw compile errors via analyzers
  • Add code fixes to suggest Factory when GlobalFactory.Services is used
  • Performance profiling for large test suites

🎯 Overall Assessment

Rating: ⭐⭐⭐⭐ (4/5) - Excellent work with minor required fixes

This is a high-quality contribution that adds important safety guardrails to TUnit.AspNetCore. The analyzer design is sound, test coverage is comprehensive, and the code follows modern C# best practices.

Blocking Issues: Snapshot testing and dual-mode verification per TUnit development guidelines.

Once addressed, this will be ready to merge! Great work on improving developer experience with compile-time safety.


Review conducted following TUnit CLAUDE.md development guidelines

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Roslyn analyzers for TUnit.AspNetCore to catch common misuse patterns at compile-time. The analyzers enforce proper usage of the WebApplicationTest pattern, preventing developers from accessing properties before they're initialized and ensuring test isolation by preventing direct access to GlobalFactory's shared members.

Key changes:

  • Introduces two new analyzer rules (TUnit0062 and TUnit0063) to detect premature property access and test isolation violations
  • Implements multi-version Roslyn support (4.4, 4.7, 4.14) following TUnit's existing analyzer patterns
  • Adds comprehensive analyzer tests with cross-platform line ending normalization
  • Updates TUnit.AspNetCore package to include analyzers in NuGet distribution
  • Adds NuGet integration tests to validate the analyzers work when consumed as a package

Reviewed changes

Copilot reviewed 34 out of 35 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
TUnit.AspNetCore.Analyzers/WebApplicationFactoryAccessAnalyzer.cs Core analyzer detecting premature access to Factory/Services/HttpCapture and misuse of GlobalFactory
TUnit.AspNetCore.Analyzers/Rules.cs Diagnostic rule definitions for TUnit0062 and TUnit0063
TUnit.AspNetCore.Analyzers/ConcurrentDiagnosticAnalyzer.cs Base class for thread-safe analyzers, consistent with existing TUnit patterns
TUnit.AspNetCore.Analyzers/Resources.resx Localized error messages and descriptions for analyzer diagnostics
TUnit.AspNetCore.Analyzers/Resources.Designer.cs Auto-generated resource accessor class
TUnit.AspNetCore.Analyzers/TUnit.AspNetCore.Analyzers.csproj Main analyzer project configuration
TUnit.AspNetCore.Analyzers/AnalyzerReleases.*.md Analyzer release tracking files
TUnit.AspNetCore.Analyzers.Roslyn44/TUnit.AspNetCore.Analyzers.Roslyn44.csproj Roslyn 4.4 compatibility project
TUnit.AspNetCore.Analyzers.Roslyn47/TUnit.AspNetCore.Analyzers.Roslyn47.csproj Roslyn 4.7 compatibility project
TUnit.AspNetCore.Analyzers.Roslyn414/TUnit.AspNetCore.Analyzers.Roslyn414.csproj Roslyn 4.14 compatibility project
TUnit.AspNetCore.Analyzers.Tests/WebApplicationFactoryAccessAnalyzerTests.cs Comprehensive analyzer tests covering all scenarios
TUnit.AspNetCore.Analyzers.Tests/Verifiers/*.cs Test infrastructure with line-ending normalization for cross-platform compatibility
TUnit.AspNetCore.Analyzers.Tests/TUnit.AspNetCore.Analyzers.Tests.csproj Test project configuration
TUnit.AspNetCore/TestWebApplicationFactory.cs Adds defensive properties throwing NotSupportedException to enforce analyzer patterns at runtime
TUnit.AspNetCore/TUnit.AspNetCore.csproj Configures analyzer packaging for all Roslyn versions
tools/tunit-nuget-tester/TUnit.AspNetCore.NugetTester/* Integration tests validating analyzer behavior when consumed as NuGet package
TUnit.Pipeline/Modules/TestAspNetCoreNugetPackageModule.cs Pipeline module for testing NuGet package
TUnit.Pipeline/Modules/RunAspNetCoreAnalyzersTestsModule.cs Pipeline module for running analyzer tests
TUnit.sln Adds new analyzer and test projects to solution
Directory.Build.props Configures common properties for AspNetCore.Analyzers.Roslyn* projects
Directory.Packages.props Adds TUnit.AspNetCore package version
AspNetCore.Analyzer.props Shared props file for AspNetCore analyzer projects
Files not reviewed (1)
  • TUnit.AspNetCore.Analyzers/Resources.Designer.cs: Language not supported

public Task InitializeAsync()
{
// Eagerly start the server to catch configuration errors early
_ = Server;
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The InitializeAsync method attempts to access the Server property, but the base class TestWebApplicationFactory explicitly throws NotSupportedException when Server is accessed (see TestWebApplicationFactory.cs line 45). This will cause a runtime exception when the factory is initialized.

The GlobalFactory (which is a TestWebApplicationFactory) should not have its Server or Services properties accessed directly. Instead, the pattern is to call GetIsolatedFactory() which returns a regular WebApplicationFactory where Server is accessible. If you need to eagerly start the server in InitializeAsync, you should either remove this code or rethink the initialization pattern.

Copilot uses AI. Check for mistakes.
{
// Verifies that POST request body is captured
var client = Factory.CreateClient();
var content = new StringContent("test payload", Encoding.UTF8, "text/plain");
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disposable 'StringContent' is created but not disposed.

Copilot uses AI. Check for mistakes.
// Verifies that filtering by HTTP method works
var client = Factory.CreateClient();
await client.GetAsync("/ping");
await client.PostAsync("/echo", new StringContent("test"));
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disposable 'StringContent' is created but not disposed.

Copilot uses AI. Check for mistakes.
Comment on lines +89 to +96
else if (containingMethod.Name == "SetupAsync" && containingMethod.IsOverride)
{
// Only Factory/Services/HttpCapture are invalid in SetupAsync
// GlobalFactory IS available in SetupAsync
if (isRestrictedInBoth)
{
contextName = "SetupAsync";
}
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 'if' statements can be combined.

Suggested change
else if (containingMethod.Name == "SetupAsync" && containingMethod.IsOverride)
{
// Only Factory/Services/HttpCapture are invalid in SetupAsync
// GlobalFactory IS available in SetupAsync
if (isRestrictedInBoth)
{
contextName = "SetupAsync";
}
else if (containingMethod.Name == "SetupAsync" && containingMethod.IsOverride && isRestrictedInBoth)
{
// Only Factory/Services/HttpCapture are invalid in SetupAsync
// GlobalFactory IS available in SetupAsync
contextName = "SetupAsync";

Copilot uses AI. Check for mistakes.
@claude
Copy link
Contributor

claude bot commented Dec 22, 2025

Pull Request Review: TUnit.AspNetCore Analyzers

Thank you for adding analyzers to TUnit.AspNetCore! This is a valuable addition that will help developers catch common mistakes at compile-time. Here's my comprehensive review:


Strengths

1. Excellent Test Coverage

  • Comprehensive test suite with 547 lines in WebApplicationFactoryAccessAnalyzerTests.cs
  • Tests cover all major scenarios: constructors, SetupAsync, GlobalFactory access, etc.
  • Good use of both positive (no error) and negative (error expected) test cases
  • Cross-platform line ending handling via LineEndingNormalizingVerifier (TUnit.AspNetCore.Analyzers.Tests/Verifiers/LineEndingNormalizingVerifier.cs:1)

2. Well-Designed Analyzer Logic

  • Clear separation of restricted properties:
    • RestrictedInConstructorAndSetup: Factory, Services, HttpCapture
    • RestrictedInConstructorOnly: GlobalFactory
    • RestrictedGlobalFactoryMembers: Services, Server, CreateClient, CreateDefaultClient
  • Proper use of immutable collections for performance
  • Correct inheritance checking with IsWebApplicationTestType (TUnit.AspNetCore.Analyzers/WebApplicationFactoryAccessAnalyzer.cs:146-170)

3. Good Error Messages

  • Descriptive diagnostic IDs: TUnit0062, TUnit0063
  • Clear message formats explaining the issue and solution
  • Helpful descriptions in Resources.resx (TUnit.AspNetCore.Analyzers/Resources.resx:21-38)

4. Proper Integration

  • Multi-version Roslyn support (4.14, 4.4, 4.7) via separate projects
  • Pipeline integration for CI testing
  • NuGet tester project validates end-to-end functionality

🔍 Issues & Recommendations

CRITICAL: Potential Performance Issue

Location: TUnit.AspNetCore.Analyzers/WebApplicationFactoryAccessAnalyzer.cs:172-202

private static IMethodSymbol? GetContainingMethod(IOperation operation)
{
    var current = operation;
    while (current != null)
    {
        if (current is IMethodBodyOperation or IBlockOperation)
        {
            var syntax = current.Syntax;
            while (syntax != null)
            {
                if (syntax is MethodDeclarationSyntax or ConstructorDeclarationSyntax)
                {
                    var semanticModel = operation.SemanticModel;  // ⚠️ Issue here
                    if (semanticModel != null)
                    {
                        var symbol = semanticModel.GetDeclaredSymbol(syntax);
                        // ...

Problem: This method has two nested loops and repeatedly accesses operation.SemanticModel inside the inner loop. While this works correctly, it's inefficient.

Recommendation:

private static IMethodSymbol? GetContainingMethod(IOperation operation)
{
    var semanticModel = operation.SemanticModel;  // Cache outside loops
    if (semanticModel == null)
        return null;

    var syntax = operation.Syntax;
    while (syntax != null)
    {
        if (syntax is MethodDeclarationSyntax or ConstructorDeclarationSyntax)
        {
            if (semanticModel.GetDeclaredSymbol(syntax) is IMethodSymbol methodSymbol)
            {
                return methodSymbol;
            }
        }
        syntax = syntax.Parent;
    }
    
    return null;
}

Why:

  • Analyzers run on every keystroke in the IDE
  • From CLAUDE.md: "TUnit processes millions of tests daily. Performance is not optional."
  • Single syntax tree walk is sufficient - no need for nested operation tree traversal

MODERATE: Missing AOT/Trimming Annotations

Location: TUnit.AspNetCore.Analyzers/WebApplicationFactoryAccessAnalyzer.cs:146

Per CLAUDE.md Rule 5, reflection usage should be annotated. The IsWebApplicationTestType method uses reflection via type traversal but lacks trimming warnings.

Recommendation: While analyzers typically run at build-time (not in trimmed apps), consider adding a comment explaining why trimming annotations aren't needed, or add them for consistency:

// Note: Analyzers run at compile-time only, so trimming is not a concern
private static bool IsWebApplicationTestType(INamedTypeSymbol? type)

MINOR: Code Style Consistency

Location: TUnit.AspNetCore.Analyzers.Tests/Verifiers/LineEndingNormalizingVerifier.cs:1

Issue: Excellent implementation, but could use XML documentation for the class-level purpose.

Suggestion:

/// <summary>
/// A custom verifier that normalizes line endings to LF before comparison to support cross-platform testing.
/// This prevents tests from failing due to differences between Windows (CRLF) and Unix (LF) line endings.
/// By normalizing to LF (the universal standard), tests pass consistently on all platforms.
/// </summary>

This is already present! No action needed.


MINOR: Resource File Formatting

Location: TUnit.AspNetCore.Analyzers/Resources.resx:22

The description message is quite long (could be >200 chars). This is fine, but consider if users will see this in tooltips - some IDEs truncate long descriptions.

Current:

"The Factory, Services, and HttpCapture properties are not available in constructors or SetupAsync..."

This is actually good - descriptive is better than terse. ✅ No change needed.


📦 Package & Distribution

Question: Packaging Strategy

I notice TUnit.AspNetCore.Analyzers.csproj has <IsPackable>false</IsPackable> (line 17), but the Roslyn version projects will be packaged.

Verify: Will the analyzers be included in the TUnit.AspNetCore NuGet package? This should be automatic via the multi-targeting setup, but confirm the packaging includes all three Roslyn versions.

Expected: The analyzers should ship with TUnit.AspNetCore so users get compile-time errors automatically.


🧪 Testing

Excellent Coverage:

  • ✅ Constructor access violations
  • ✅ SetupAsync access violations
  • ✅ GlobalFactory member access violations
  • ✅ Proper access in test methods (no false positives)
  • ✅ Unrelated classes with same property names (no false positives)
  • ✅ Multiple diagnostic scenarios

Missing Test (Low Priority):

Consider adding a test for local variable assignments in constructors:

public MyTests()
{
    var factory = this.Factory;  // Should still error
    var f = Factory;              // Already tested
}

The current implementation should handle this correctly via IPropertyReferenceOperation, but an explicit test would confirm it.


🎯 CLAUDE.md Compliance Check

Rule Status Notes
Rule 1: Dual-Mode ✅ N/A Analyzers are compile-time only, not affected by dual-mode
Rule 2: Snapshots ✅ PASS No snapshot tests needed for analyzers
Rule 3: No VSTest ✅ PASS Uses Microsoft.Testing.Platform
Rule 4: Performance ⚠️ REVIEW See GetContainingMethod performance issue above
Rule 5: AOT ✅ PASS Analyzers don't run in AOT context
Code Style ✅ PASS Modern C# syntax, file-scoped namespaces, collection expressions

📋 Pre-Merge Checklist

Before merging, verify:

  • GetContainingMethod performance optimization applied
  • All tests pass: dotnet test TUnit.AspNetCore.Analyzers.Tests
  • Pipeline tests pass: RunAspNetCoreAnalyzersTestsModule
  • NuGet package includes analyzers in all three Roslyn versions
  • Verify analyzers work in both Visual Studio and Rider (if possible)
  • Confirm diagnostic IDs TUnit0062, TUnit0063 don't conflict with existing IDs

🎉 Overall Assessment

Rating: 9/10 - Excellent work! This is a well-designed analyzer with comprehensive tests.

Primary Action Item: Optimize GetContainingMethod to avoid nested loops (see performance section above).

Secondary: Verify NuGet packaging includes all analyzer DLLs.

Everything else looks great - the analyzer logic is sound, error messages are helpful, and test coverage is excellent. Once the performance optimization is applied, this is ready to merge!


Great contribution to TUnit! 🚀

@claude
Copy link
Contributor

claude bot commented Dec 22, 2025

Pull Request Review: TUnit.AspNetCore Analyzers

This PR adds Roslyn analyzers to prevent common mistakes when using the TUnit.AspNetCore testing framework. The implementation is well-structured and comprehensive.

✅ Strengths

1. Excellent Code Quality

  • Modern C# patterns (collection expressions, file-scoped namespaces, pattern matching) - follows CLAUDE.md style guide
  • Clean ConcurrentDiagnosticAnalyzer base class with proper concurrent execution setup
  • Proper use of immutable collections (ImmutableHashSet, ImmutableArray)
  • Well-organized analyzer logic with clear separation of concerns

2. Comprehensive Test Coverage

  • 547 lines of well-structured tests covering all analyzer scenarios
  • Both positive (no warning) and negative (warning expected) cases
  • Edge cases: unrelated factories, inheritance hierarchies, nested accesses
  • Custom LineEndingNormalizingVerifier for cross-platform compatibility - excellent!

3. Strong Documentation

  • Clear, actionable diagnostic messages in Resources.resx
  • Good descriptions explaining why the pattern is problematic
  • Proper diagnostic IDs (TUnit0062, TUnit0063) with release tracking

4. Proper Integration

  • Multi-targeting for Roslyn versions (4.4, 4.14, 4.7) follows existing patterns
  • CI/CD integration with RunAspNetCoreAnalyzersTestsModule
  • NuGet testing infrastructure in place

🔍 Issues and Recommendations

HIGH: Code Duplication in GetContainingMethod

The GetContainingMethod implementation (lines 172-202) walks the syntax tree manually. Consider simplifying with Roslyn built-in APIs. Current implementation has nested loops that could be flattened.

MEDIUM: Performance Considerations

Per CLAUDE.md Rule 4 (Performance First), the analyzer runs on every compilation:

  • ToDisplayString() in IsWebApplicationTestType allocates strings - consider comparing namespace symbols directly
  • Early exit optimization could be improved in type hierarchy walking

MEDIUM: Potential False Negative - Indirect Access

The analyzer detects direct property access but may miss indirect patterns (helper methods called from constructors). Add test case or document limitation.

LOW: Test Code Duplication

WebApplicationTestStub and WebApplicationFactoryStub are repeated throughout tests. Extract to helper methods.

LOW: XML Documentation Missing

Analyzer classes lack XML documentation comments explaining purpose and diagnostics.

🎯 Questions

  1. Lambda/Local Function Support: Does the analyzer handle property access in lambdas or local functions within constructors? Add test case to verify.

  2. Future C# features: How would async constructors be handled if they become available?

🚀 Additional Suggestions

  1. Code Fix Provider: Consider adding automated fixes that suggest moving property access to test methods or Before(HookType.Test) hooks. This would greatly improve developer experience.

  2. Integration Test: Per CLAUDE.md, add integration test in TUnit.TestProject verifying analyzer warnings work correctly in both source-gen and reflection modes.

  3. Documentation Update: Document these analyzer rules and the property initialization lifecycle in main docs.

📊 Overall Assessment

Aspect Rating Notes
Code Quality ⭐⭐⭐⭐⭐ Modern, clean, well-structured
Test Coverage ⭐⭐⭐⭐⭐ Comprehensive with good edge cases
Performance ⭐⭐⭐⭐☆ Minor optimization opportunities
Documentation ⭐⭐⭐⭐☆ Good messages, missing XML docs
Integration ⭐⭐⭐⭐⭐ Proper CI/CD and multi-targeting

Recommendation: Approve with minor suggestions

The core implementation is solid and ready to merge. Suggestions above are optimizations and nice-to-haves for follow-up PRs.

🎉 Nice Touches

  • LineEndingNormalizingVerifier is particularly well-designed
  • Separation of RestrictedInConstructorAndSetup vs RestrictedInConstructorOnly is clear
  • Test names are descriptive and follow good naming conventions
  • Diagnostic messages provide clear guidance

Great work on this addition to TUnit! 🚀

This was referenced Dec 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants