refactor: split oversized files under 500-LOC threshold#137
Conversation
DiagnosticTests.cs was 956 lines covering every EOE diagnostic. Split into five focused classes grouped by validation concern: - BindingTypeValidationTests (EOE010-014, 016-017) - RouteBodyValidationTests (EOE003, 005-006, 015, 018-021) - UnionTypeAndFactoryTests (EOE022-024) - JsonAotValidationTests (EOE007, 025, 039, 041) - NamingAndValidCaseTests (EOE033 + valid cases) No test methods were renamed or modified.
…ze/Extractor/Emitter partials The generator was 810 lines in a single sealed class. Mirror the partial pattern already used by ErrorOrEndpointGenerator (Core/*.cs): - OpenApiTransformerGenerator.cs - pipeline init + helpers - Core/OpenApiTransformerGenerator.Extractor.cs - metadata extraction + XML parsing - Core/OpenApiTransformerGenerator.Emitter.cs - transformer code emission No logic changes.
Splits the 580-line grab-bag at Models/EndpointModels.cs into cohesive per-domain files, each under 200 lines. Same namespace (ErrorOr.Generators), zero behavior changes — these are all record structs / enums used as incremental-generator cache keys. Before: Models/EndpointModels.cs 580 LOC After: Models/EndpointParameters.cs 175 LOC (enums + EndpointParameter + ParameterMeta + ParameterBindingResult) Models/EndpointDescriptor.cs 183 LOC (EndpointDescriptor + Metadata + Success + UnionTypeResult) Models/MethodAnalysis.cs 64 LOC (MethodAnalysis + ReturnTypeInfo + ErrorInferenceInfo + SseInfo) Models/MiddlewareInfo.cs 25 LOC Models/RouteModels.cs 39 LOC Models/VersioningModels.cs 31 LOC Models/OpenApiModels.cs 40 LOC Models/ValidationModels.cs 30 LOC Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…partials
Splits the 672-line ErrorOrEndpointGenerator.ParameterBinding partial
into three cohesive partials, each well under 300 lines. AttributeNameMatcher
was misplaced — it is only consumed by ExtractKeyFromKeyedServiceAttribute
in TypeAnalysis.cs and by the meta-builder, so it now lives next to its
caller in the new Meta partial.
Before:
Core/ErrorOrEndpointGenerator.ParameterBinding.cs 672 LOC
Core/ErrorOrEndpointGenerator.ParameterBinding.TypeAnalysis.cs 348 LOC
After:
Core/ErrorOrEndpointGenerator.ParameterBinding.cs 243 LOC
(entry + BuildEndpointParameters + ClassifyParameter dispatcher
+ InferParameterSource + ParameterSuccess + ParameterClassificationResult)
Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs 163 LOC
(CreateParameterMeta + BuildFlags + DetectSpecialKind
+ DetermineBoundName + AttributeNameMatcher)
Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs 295 LOC
(ClassifyFromRoute/ImplicitRoute/FromQuery/FromHeader/FromForm
+ ClassifyFormDtoParameter + ClassifyAsParameters)
Core/ErrorOrEndpointGenerator.ParameterBinding.TypeAnalysis.cs 348 LOC (unchanged)
Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ain classes
Splits the 660-line ParameterBindingTests.cs along its existing #region
boundaries into four cohesive test classes that mirror the production-side
split (ParameterBinding.{cs, Meta, Classifiers, TypeAnalysis}).
Before:
tests/ErrorOrX.Generators.Tests/ParameterBindingTests.cs 660 LOC (25 tests)
After:
tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs 249 LOC
(smart inference: HTTP+complex→body, service detection, EOE021)
tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs 170 LOC
(route name-match, query primitives, primitive arrays, TryParse, Guid)
tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs 151 LOC
([FromBody], [FromServices], [FromKeyedServices], [FromRoute],
[FromQuery], [FromHeader])
tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs 73 LOC
(HttpContext, CancellationToken, Stream)
Verified: dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ndling partials
Splits the 596-line ErrorOrEndpointGenerator.Emitter.cs along its three
existing concerns into sub-partials, matching the convention already
established by Emitter.JsonContext.cs / Emitter.Options.cs / Emitter.Support.cs.
Before:
Core/ErrorOrEndpointGenerator.Emitter.cs 596 LOC
After:
Core/ErrorOrEndpointGenerator.Emitter.cs 190 LOC
(EmitEndpoints orchestration + EmitMappings literal scaffold + EmitMapCall)
Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs 94 LOC
(ComputeGlobalVersionSet + EmitVersionSet + EmitVersioningCalls + VersionSetContext)
Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs 251 LOC
(EmitInvoker, body code, wrapper/core method emission, bind helpers,
form content-type guard, InvokerContext)
Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs 108 LOC
(Union-type dispatch, ProblemDetails building, validation handling,
Location-header for Created+Id, ErrorType switch)
The existing partials (JsonContext, Options, Support) are unchanged.
Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Detection partials
Splits the 580-line ErrorOrEndpointGenerator.Extractor partial along its
three distinct extraction phases. Sits alongside the existing
Extractor.Metadata.cs partial so the four files now mirror the four
extraction concerns of the generator pipeline.
Before:
Core/ErrorOrEndpointGenerator.Extractor.cs 580 LOC
Core/ErrorOrEndpointGenerator.Extractor.Metadata.cs 429 LOC (unchanged)
After:
Core/ErrorOrEndpointGenerator.Extractor.cs 203 LOC
(ExtractErrorOrReturnType + DetectIdProperty + TryUnwrap{AsyncEnumerable,SseItem}
+ IsErrorOrType + ExtractProducesErrorAttributes + HasAcceptedResponseAttribute)
Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs 274 LOC
(InferErrorTypesFromMethod + GetMethodBody + CollectErrorTypes(Recursive)
+ ProcessNode + TryHandleErrorFactoryInvocation + TryGetReferencedSymbol
+ GetBodyToScan + ExtractCustomErrorInfo + IsErrorFactoryInvocation
+ ToSortedErrorArray)
Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs 138 LOC
(TryDetectUndocumentedInterfaceCall + TryExtractReturnsErrorAttributes
+ MapEnumValueToName + ReturnsErrorOr)
Core/ErrorOrEndpointGenerator.Extractor.Metadata.cs 429 LOC (unchanged)
Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… partials
Splits the 543-line ErrorOrEndpointGenerator.Initialize partial along its
three responsibilities: pipeline wiring, attribute emission, and per-attribute
endpoint analysis.
Before:
Core/ErrorOrEndpointGenerator.Initialize.cs 543 LOC
After:
Core/ErrorOrEndpointGenerator.Initialize.cs 133 LOC
(Initialize method, EmitMappingsAndRunAnalysis, route/version reports,
option parsers, EmitContext, Helpers)
Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs 127 LOC
(EmitAttributes — marker-attribute literal-source emission via
PostInitializationOutput)
Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs 314 LOC
(CombineHttpMethodProviders, CreateEndpointProvider, AnalyzeEndpointFlow,
ProcessAttributeFlow, ExtractHttpMethodAndPattern, TrackingNames)
Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits the 535-line BindingCodeEmitter static class into four partials,
one per binding-source family. Class becomes 'internal static partial class'
to preserve the existing BindingCodeEmitter.EmitX(...) call sites.
Before:
Emitters/BindingCodeEmitter.cs 535 LOC
After:
Emitters/BindingCodeEmitter.cs 113 LOC
(dispatcher + Route + Service / KeyedService / HttpContext / CancellationToken
/ Stream / PipeReader / FormFile / FormFiles / FormCollection)
Emitters/BindingCodeEmitter.Query.cs 174 LOC
(Query + Header — share scalar/collection pattern, plus BindAsync)
Emitters/BindingCodeEmitter.Body.cs 142 LOC
(Body Allow/Disallow + Form + AsParameters expansion)
Emitters/BindingCodeEmitter.Parsing.cs 148 LOC
(BuildArgumentExpression + GetTryParseExpression + EmitValidationDictBuilder)
Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation/BodyAndValidation partials
Splits the 521-line ErrorOrEndpointAnalyzer sealed class into three partials.
The class becomes 'sealed partial class' (still DiagnosticAnalyzer) with the
same Initialize/SupportedDiagnostics surface.
Before:
Analyzers/ErrorOrEndpointAnalyzer.cs 521 LOC
After:
Analyzers/ErrorOrEndpointAnalyzer.cs 269 LOC
(DiagnosticAnalyzer surface, Initialize, AnalyzeMethod / AnalyzeEndpoint
top-level loop, GetEndpointAttributes, IsValidReturnType, IsErrorOr,
ExtractRouteParametersWithConstraints)
Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs 186 LOC
(EOE005 pattern syntax + EOE020 constraint vs CLR-type mismatch:
ValidateRoutePattern + ValidateConstraintTypes/SingleRouteConstraint
+ ValidateCatchAllConstraint/TypedConstraint + IsFormatOnlyConstraint
+ DoesTypeMatchConstraint + Type-name helpers)
Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs 104 LOC
(EOE006 multi-body counting via ErrorOrContext, EOE009 accepted-on-readonly,
EOE039 DataAnnotations reflection check + InheritsFrom)
Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…omain classes
Splits the 563-line MiddlewareEmissionTests.cs along its #region boundaries
into three cohesive test classes. Keeps the critical security-regression tests
(wrapper-does-not-drop-Authorize) co-located with metadata in the third class.
Before:
tests/ErrorOrX.Generators.Tests/MiddlewareEmissionTests.cs 563 LOC (22 tests)
After:
tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs 198 LOC
(Combined-middleware smoke test + 7 [Authorize]/[AllowAnonymous] tests)
tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs 176 LOC
([EnableRateLimiting]/[DisableRateLimiting] override + [OutputCache] variants)
tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs 180 LOC
([EnableCors]/[DisableCors] + .WithName/.WithTags + Security_*_NotLost_InWrapper)
Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ining classes
Splits the 565-line ErrorOrTests.cs (the misnamed grab-bag in the test project)
into three cohesive classes matching the existing ErrorOr.X.cs naming
convention used by every other file in the directory.
Before:
tests/ErrorOrX.Tests/ErrorOr/ErrorOrTests.cs 565 LOC (30 tests)
After:
tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs 283 LOC
(Error.Metadata + IsError/IsSuccess + Value access + Errors access
+ Implicit conversion + Error.Failure/Validation/NotFound/Conflict/
Unauthorized/Forbidden/Unexpected ErrorType assignment)
tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs 98 LOC
(Match transformation + Switch side-effect)
tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs 159 LOC
(Then / ThenDo + sync Else + FailIf — async variants stay in
ErrorOr.ElseAsyncTests.cs and ErrorOr.FailIfAsyncTests.cs)
Verified: dotnet build + dotnet test --solution ErrorOrX.slnx (443 passed).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughLarge-scale refactoring partitioning ChangesAnalyzer Infrastructure Refactoring
Generator Initialization and Extraction
Parameter Binding Classification and Metadata Extraction
Code Emission: Versioning, Invoker, and Error Handling
Binding Code Generation: Body, Query, Header, and Parsing
OpenAPI Transformer: Metadata Extraction and Emission
Internal Data Models
Test Reorganization
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Suggested labels
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
|
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 135 |
| Duplication | 2 |
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
This refactor successfully achieves the 500-LOC threshold across the codebase, but it introduces several high-severity functional bugs that must be resolved. Key concerns involve the analyzer logic for request body identification and a failure to correctly resolve binding attribute names when using positional arguments.
Furthermore, the review identified a critical gap in the acceptance criteria: the 'OpenAPI transformer generation' test scenarios are missing, leaving the refactored emitter and extractor logic unverified. Given the identified type mismatches and logic errors in these files, this PR is not yet ready for merging despite being up to Codacy standards.
About this PR
- The refactoring of the OpenApiTransformerGenerator lacks explicit test scenarios in the current diff. Given that logic errors were found in both the Extractor and Emitter components of this generator, existing test suites may not be granular enough to verify the new file structure.
Test suggestions
- Verify route pattern and route constraint validation diagnostics (EOE005, EOE003, EOE020)
- Verify parameter binding logic for implicit vs explicit sources (Route, Query, Header, Service, Body)
- Verify middleware emission for security-critical attributes (Authorize, AllowAnonymous, CORS)
- Verify JSON AOT safety diagnostics (EOE007, EOE025, EOE041)
- Verify error inference from method body walking (Error.X() vs Error.Custom())
- Verify OpenAPI transformer generation for tags, operations, and schemas
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify OpenAPI transformer generation for tags, operations, and schemas
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| code.AppendLine(" {"); | ||
| // OpenApiDocument.Tags setter auto-wraps with OpenApiTagComparer.Instance | ||
| // which handles deduplication by Name - no manual .Any() check needed | ||
| code.AppendLine(" document.Tags ??= new HashSet<OpenApiTag>();"); |
There was a problem hiding this comment.
🔴 HIGH RISK
The Tags property is an IList<OpenApiTag>. Initialize it as a List<OpenApiTag> and check if a tag with the same name already exists before adding it to avoid duplicates and type mismatch errors.
| return false; | ||
| } | ||
|
|
||
| private static (string? Location, string? Name) GetExplicitBinding(ISymbol param) |
There was a problem hiding this comment.
🔴 HIGH RISK
The bound name should be resolved from ConstructorArguments if it is not present in NamedArguments to support attributes like [FromRoute("id")].
| /// </summary> | ||
| public sealed partial class ErrorOrEndpointAnalyzer | ||
| { | ||
| private static int CountBodySources(IMethodSymbol method) |
There was a problem hiding this comment.
🔴 HIGH RISK
The body source counting logic should account for implicit DTO parameters (complex types not bound elsewhere) and must verify that parameters of type Stream or IFormFile are not explicitly bound to other sources like [FromServices] or [FromRoute] before counting them as body sources.
| { | ||
| // Error.Custom(int type, string code, string description, Dictionary<string, object>? metadata = null) | ||
| // The 'code' parameter (second arg) is what we want for deduplication | ||
| var args = invocation.ArgumentList.Arguments; |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: The extraction of the 'code' argument using index 1 is fragile. It assumes the error code is always the second argument. Consider using the semantic model to correctly identify the 'code' parameter regardless of its position at the call site.
There was a problem hiding this comment.
Actionable comments posted: 23
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs (2)
27-35:⚠️ Potential issue | 🟠 Major | ⚡ Quick winInferred body parameters bypass the multi-body guard.
BindParametersonly counts explicit body/form/stream flags, butInferParameterSourcecan still classify multiple complex parameters asParameterSource.Bodyon POST/PUT/PATCH. That allows an invalid multi-body endpoint through withoutEOE006/EOE008.Suggested fix (validate body-source categories after classification)
private static ParameterBindingResult BuildEndpointParameters( ParameterMeta[] metas, ImmutableArray<IParameterSymbol> parameters, ImmutableHashSet<string> routeParameters, ISymbol method, ImmutableArray<DiagnosticInfo>.Builder diagnostics, ErrorOrContext context, HttpVerb httpVerb) { var builder = ImmutableArray.CreateBuilder<EndpointParameter>(metas.Length); var isValid = true; + var bodyParameters = 0; + var hasForm = false; + var hasStream = false; for (var i = 0; i < metas.Length; i++) { var result = ClassifyParameter(in metas[i], parameters[i], routeParameters, method, diagnostics, context, httpVerb); if (result.IsError) { isValid = false; continue; } builder.Add(result.Parameter); + switch (result.Parameter.Source) + { + case ParameterSource.Body: + bodyParameters++; + break; + case ParameterSource.Form: + case ParameterSource.FormFile: + case ParameterSource.FormFiles: + case ParameterSource.FormCollection: + hasForm = true; + break; + case ParameterSource.Stream: + case ParameterSource.PipeReader: + hasStream = true; + break; + } } + if (bodyParameters > 1 || (bodyParameters > 0 ? 1 : 0) + (hasForm ? 1 : 0) + (hasStream ? 1 : 0) > 1) + { + diagnostics.Add(DiagnosticInfo.Create( + Descriptors.MultipleBodySources, method.Locations.FirstOrDefault() ?? Location.None, method.Name)); + return ParameterBindingResult.Invalid; + } + return isValid ? new ParameterBindingResult(IsValid: true, builder.ToImmutable().AsEquatableArray()) : ParameterBindingResult.Invalid; }As per coding guidelines, “Binding validation must reject multiple
[FromBody]parameters (EOE006, EOE008) and detect ambiguous bindings (EOE021).”Also applies to: 173-194
196-205:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEOE021 branch should fail classification instead of silently rebinding to service.
After emitting
Descriptors.AmbiguousParameterBinding, the method currently returns a successfulParameterSource.Serviceclassification. This hides an invalid binding behind generated service injection.Suggested fix
diagnostics.Add(DiagnosticInfo.Create( Descriptors.AmbiguousParameterBinding, method.Locations.FirstOrDefault() ?? Location.None, meta.Name, meta.TypeFqn, httpVerb.ToHttpString())); - return ParameterSuccess(in meta, ParameterSource.Service); + return ParameterClassificationResult.Error;As per coding guidelines, “Complex types in GET/DELETE endpoints without explicit
[FromQuery]binding must emit EOE021 diagnostic error.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs` around lines 196 - 205, After emitting Descriptors.AmbiguousParameterBinding you must not return a successful binding; replace the current return of ParameterSuccess(in meta, ParameterSource.Service) with a failure classification for this parameter so the caller sees an invalid binding (i.e., return the method's existing "parameter failure" result—use the same failure helper your generator provides such as ParameterFailure/ParameterError or the equivalent factory used elsewhere—to mark the parameter as failed due to EOE021). Ensure you reference the same meta value and emit the EOE021 diagnostic already added via diagnostics.Add(Descriptors.AmbiguousParameterBinding, ...).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs`:
- Around line 69-85: The current loop only checks param.GetAttributes() so
EOE039 misses ValidationAttributes on DTO properties; update the logic around
the parameter inspection (the foreach over method.Parameters and the
InheritsFrom(...) check against validationAttributeType used when reporting
Descriptors.ValidationUsesReflection) to also traverse the parameter type's
reachable properties and their attributes (starting from the parameter's
ITypeSymbol/INamedTypeSymbol), using a visited set to protect against cycles,
and treat any property attribute that InheritsFrom(validationAttributeType) as
equivalent to a parameter attribute so the analyzer reports the EOE039
diagnostic (use param.Locations.FirstOrDefault() or
method.Locations.FirstOrDefault() as before) once per parameter.
In
`@src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs`:
- Around line 107-131: The validator currently unwraps and compares types by FQN
strings in ValidateConstraintTypes/DoesTypeMatchConstraint (using
TypeNamesMatch), which can misrepresent semantics; update the call-site in
AnalyzeEndpoint to pass the method symbol (or extend RouteMethodParameterInfo to
include the resolved ITypeSymbol for each parameter) so validation can resolve
parameter types via ErrorOrContext.UnwrapNullable(ITypeSymbol) and perform
comparisons with ITypeSymbol.Equals (or SymbolEqualityComparer) instead of
string equality; adjust ValidateConstraintTypes and DoesTypeMatchConstraint to
accept ITypeSymbol(s) and use Roslyn type equality for constraint matching.
In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs`:
- Around line 54-67: The current gating uses hasValidation which only checks
ep.ErrorInference.InferredErrorTypeNames, so endpoints that declare a Validation
error but don't infer it are missing the 400 ValidationProblem path; update the
condition to consider declared/contract error types as well (e.g., check
ep.DeclaredErrorTypeNames or the collection that represents the endpoint's
declared errors in addition to ep.ErrorInference.InferredErrorTypeNames) so that
hasValidation becomes true if either inferred OR declared error type names
contain ErrorMapping.Validation; apply the same change to the other similar
guards around lines 89-106; keep the existing generation blocks
(BindingCodeEmitter.EmitValidationDictBuilder, the Type comparison to
WellKnownTypes.Fqn.ErrorType.Validation, and the ctx.WrapReturn call) unchanged
so only the presence check is broadened.
In `@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs`:
- Around line 151-167: The generated code currently dereferences {paramName}!
when calling {WellKnownTypes.Fqn.Validator}.TryValidateObject which will throw
on null; add a null-check for each validation target (from validationParams /
paramName) before calling TryValidateObject: if the parameter is null, build the
same validationDict indicating a required/null error for that parameter and
return the appropriate ValidationProblem (use the same isAsync-aware returnExpr
pattern used below), otherwise proceed to call TryValidateObject with the
non-null instance; ensure you reference the same
BindingCodeEmitter.EmitValidationDictBuilder/validationDict and the isAsync
return branches so behavior and return type remain consistent.
In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs`:
- Around line 195-204: The GetBodyToScan method currently falls back to
returning the original syntax node for unknown kinds, causing full-subtree
scans; change the default branch in GetBodyToScan to return null instead of
syntax so callers skip unsupported constructs (i.e., replace "_ => syntax" with
"_ => null"), and update any callers to handle a null return if they don't
already; alternatively, if keeping the catch-all is intentional, add a comment
above GetBodyToScan explaining that the default intentionally returns the full
node for unknown syntax kinds and why.
- Around line 35-36: The conditional checking refs for emptiness is redundant:
remove the "|| refs.Length is 0" portion and keep only the IsDefaultOrEmpty
check so the early-return uses "if (refs.IsDefaultOrEmpty) return null;"; update
the code where refs is assigned from method.DeclaringSyntaxReferences (in
ErrorOrEndpointGenerator.Extractor.ErrorInference) to rely solely on
refs.IsDefaultOrEmpty to determine emptiness.
- Around line 124-133: The recursion uses a single semanticModel bound to the
handler's SyntaxTree which fails for syntax nodes from other files; update the
logic around GetBodyToScan and CollectErrorTypesRecursive so you obtain the
correct semantic model per body by calling
Compilation.GetSemanticModel(bodyToScan.SyntaxTree) and use that semantic model
for all subsequent symbol/constant lookups; change the recursive signature of
CollectErrorTypesRecursive (and any callers) to accept the Compilation instead
of a fixed SemanticModel, and inside each recursion call fetch the per-tree
SemanticModel before calling GetSymbolInfo or GetConstantValue to ensure
cross-file helper methods, interface calls, and constant folding are detected
correctly.
In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs`:
- Around line 90-107: The ReturnsErrorAttribute first constructor
(ReturnsErrorAttribute(global::ErrorOr.ErrorType errorType, string errorCode))
never initializes StatusCode; explicitly set StatusCode = null in that
constructor to mirror the second constructor's behavior and avoid analyzer
warnings (touch the ReturnsErrorAttribute constructors and the
StatusCode/ErrorType properties to ensure symmetry).
In `@src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs`:
- Around line 221-240: The code appends parameter definitions from ParameterDefs
into operation.Parameters without checking for existing parameters, causing
duplicate (Name, In) entries; update the block that handles ParameterDefs for a
given operationId to first ensure operation.Parameters is initialized, then for
each param tuple from ParameterDefs check operation.Parameters for an existing
OpenApiParameter with the same Name and In (match on Name and In), if found
merge/update its Description/Schema/Required as appropriate, otherwise add a new
OpenApiParameter; refer to ParameterDefs, operation.Parameters, operationId and
OpenApiParameter when making this change.
In `@src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cs`:
- Around line 353-387: The ExtractTypeMetadata method currently accepts generic
type declarations and later emits invalid typeof(...) for open generics; update
ExtractTypeMetadata to skip types with generic arity by checking the
INamedTypeSymbol's Arity (symbol.Arity > 0) and returning null for those symbols
(add the check after obtaining the symbol and before producing the
TypeMetadataInfo), so only non-generic types proceed to
GetReflectionFullName/ConvertToTypeofExpression.
- Around line 150-153: The code currently makes decisions by passing
param.Type.ToDisplayString() into
IsSkippedParameterType/IsPrimitiveType/GetOpenApiSchema and manually stripping
"?" which is brittle; instead pass the ITypeSymbol (param.Type) directly, call
ErrorOrContext.UnwrapNullable() on that symbol before inspection, update
IsSkippedParameterType, IsPrimitiveType, and GetOpenApiSchema to accept and
operate on ITypeSymbol (not strings), and replace string checks with
SymbolEqualityComparer.Default.Equals(...) against known type symbols; remove
the manual EndsWithOrdinal("?") nullable stripping and apply these symbol-based
changes to all call sites of those helpers within the extractor.
In `@src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cs`:
- Around line 31-35: The code currently treats ctx.Request.ContentLength being
null as "no body", which breaks for chunked requests; replace that
ContentLength-based check with the ASP.NET Core request-body detection feature:
acquire IHttpRequestBodyDetectionFeature via
ctx.Request.HttpContext.Features.Get<IHttpRequestBodyDetectionFeature>() and use
its HasBody boolean to decide whether to parse or set {paramName} = default;
update both places that check ctx.Request.ContentLength (including the
occurrences around the if/else and the similar block at lines 55-56) and ensure
you handle a null feature reference by treating it conservatively (e.g., assume
HasBody == true unless explicitly false).
In `@src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs`:
- Around line 130-134: The header-collection emitter currently skips failed
parses (in the code path built by BindingCodeEmitter.Query.cs using
GetTryParseExpression and adding to {paramName}List), causing silent data loss;
change the emitted branch for non-string itemType so that when
GetTryParseExpression returns false you do not ignore the item but instead fail
the bind (e.g., set a parse failure flag / add a model error / return a failed
BindResult) so header binding mirrors query collection behavior; update the
branch around the generated parsedItem handling to produce a bind failure on
parse error rather than simply not adding to {paramName}List.
- Around line 14-15: EmitBindAsyncBinding currently always emits a
single-argument call but needs to emit a two-argument call when
param.CustomBinding == CustomBindingMethod.BindAsyncWithParam; update
EmitBindAsyncBinding to accept (or obtain from the existing param) the handler
method's ParameterInfo reflection metadata and, when
CustomBindingMethod.BindAsyncWithParam is detected, emit
"{baseType}.BindAsync(ctx, <ParameterInfoExpr>)" instead of
"{baseType}.BindAsync(ctx)"; make sure the emitter uses the same param/paramName
context passed from the dispatcher so the correct ParameterInfo for the handler
parameter is used and keep the existing bindFailFn handling.
In `@src/ErrorOrX.Generators/Models/VersioningModels.cs`:
- Line 23: The HasVersioning getter currently checks only SupportedVersions and
IsVersionNeutral and therefore ignores MappedVersions; update HasVersioning so
it also treats a non-empty MappedVersions as evidence of versioning by including
a check for MappedVersions.IsDefaultOrEmpty (i.e., return true if
SupportedVersions is non-empty OR MappedVersions is non-empty OR
IsVersionNeutral), touching the HasVersioning property and referencing
SupportedVersions, MappedVersions, and IsVersionNeutral.
In `@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cs`:
- Around line 105-125: The test Authorize_With_Roles_Emits_Roles is too
permissive because generated.Should().Match("*.RequireAuthorization*Admin*")
only asserts a loose substring; update the assertion to verify observable auth
configuration containing explicit role information (e.g. assert the generated
string contains either RequireAuthorization with an AuthorizeAttribute
specifying Roles = "Admin,Manager" or RequireAuthorization/RequireRole calls
that include both "Admin" and "Manager" exactly), using a precise
string.Contains or a tighter regex match that ensures both role names appear in
the same authorization call; apply the same tightening to the other related
test(s) around lines 177-197 so they assert role/scheme metadata is present
rather than just RequireAuthorization appearing.
- Around line 152-174: The test AllowAnonymous_Overrides_Authorize only asserts
the presence of ".AllowAnonymous()" but must also assert the absence of
authorization emission; update the test (in
MiddlewareEmissionAuthorizationTests.cs, inside the
AllowAnonymous_Overrides_Authorize method after obtaining generated from
ErrorOrEndpointMappings.cs via RunAsync) to assert that the generated content
does NOT contain any ".RequireAuthorization" call (e.g., use
Should().NotContain(".RequireAuthorization") or equivalent negative assertion)
so the test fails if RequireAuthorization is still emitted.
In `@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs`:
- Around line 56-76: The test DisableCors_Emits_DisableCors currently only
checks for a generic "Cors" token which matches both enable and disable paths;
update the assertion to validate the exact disable semantics by asserting the
generated API contains the explicit disable marker emitted by the generator
(e.g. the attribute or mapping representing disable CORS) rather than a
wildcard. Replace generated.Should().Match("*Cors*") in
DisableCors_Emits_DisableCors with a precise check on the generated string (for
example generated.Should().Contain("[DisableCors]") or the exact snippet the
generator emits into ErrorOrEndpointMappings.cs) and optionally assert that any
enable-CORS marker is not present to ensure correct disable behavior.
In
`@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs`:
- Around line 133-175: The two tests OutputCache_With_Duration_Emits_Duration
and OutputCache_With_VaryByQueryKeys_Emits_VaryBy currently only assert that
some CacheOutput call exists, so update each test to assert the emitted code
preserves the specific configuration: inspect the generated string (variable
generated from the file with HintName "ErrorOrEndpointMappings.cs") for the
concrete duration or vary-by-query representation (e.g., ensure the generated
text contains the duration value/setting such as "60" or a policy builder call
that sets Duration = 60 for OutputCache_With_Duration_Emits_Duration, and ensure
it contains the VaryByQueryKeys list or an equivalent builder call for
OutputCache_With_VaryByQueryKeys_Emits_VaryBy); modify the assertions on the
generated variable in those two test methods to look for those specific tokens
rather than just "*.CacheOutput*".
In `@tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs`:
- Around line 203-224: The test Complex_Type_With_Explicit_FromQuery_NoWarning
is asserting that diagnostic "EOE011" is produced, so rename the test method to
reflect that a warning is expected (for example
Complex_Type_With_Explicit_FromQuery_EmitsEOE011 or
Complex_Type_With_Explicit_FromQuery_ReportsEOE011) to match the assertion on
result.Diagnostics.Should().ContainSingle(d => d.Id == "EOE011"); update only
the method name of the test (the method signature on the Fact) so callers like
the test runner and references such as RunAsync and result.Diagnostics remain
unchanged.
In `@tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs`:
- Around line 27-30: The test currently asserts the emitter's temporary
parameter name by checking for the exact string "global::Api.GetInfo(p0)";
change this to assert observable behavior instead (e.g., that the generated
output contains a call to "global::Api.GetInfo(" or otherwise validates that
GetInfo is invoked) so the test no longer depends on emitter-local names; update
the assertion that references "global::Api.GetInfo(p0)" in
ParameterBindingSpecialTypesTests to use a name-agnostic check while keeping the
existing check for the HttpContext binding ("= ctx;") intact.
In `@tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs`:
- Around line 97-114: The test
EOE024_Interface_Call_With_ProducesError_No_Diagnostic is exercising the
generator fallback by placing [ProducesError(404, "NotFound")] on the handler
instead of the interface; move the attribute to the interface method so the
generator's interface metadata extraction is exercised: add [ProducesError(404,
"NotFound")] to ITodoService.GetById and remove it from TodoApi.GetById,
ensuring the generator path that invokes TryExtractReturnsErrorAttributes in
InterfaceDetection.cs is covered.
---
Outside diff comments:
In `@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs`:
- Around line 196-205: After emitting Descriptors.AmbiguousParameterBinding you
must not return a successful binding; replace the current return of
ParameterSuccess(in meta, ParameterSource.Service) with a failure classification
for this parameter so the caller sees an invalid binding (i.e., return the
method's existing "parameter failure" result—use the same failure helper your
generator provides such as ParameterFailure/ParameterError or the equivalent
factory used elsewhere—to mark the parameter as failed due to EOE021). Ensure
you reference the same meta value and emit the EOE021 diagnostic already added
via diagnostics.Add(Descriptors.AmbiguousParameterBinding, ...).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 42ff8503-1b0d-4a5b-8607-27b9f70a9941
📒 Files selected for processing (89)
src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cssrc/ErrorOrX.Generators/Models/EndpointDescriptor.cssrc/ErrorOrX.Generators/Models/EndpointModels.cssrc/ErrorOrX.Generators/Models/EndpointParameters.cssrc/ErrorOrX.Generators/Models/MethodAnalysis.cssrc/ErrorOrX.Generators/Models/MiddlewareInfo.cssrc/ErrorOrX.Generators/Models/OpenApiModels.cssrc/ErrorOrX.Generators/Models/RouteModels.cssrc/ErrorOrX.Generators/Models/ValidationModels.cssrc/ErrorOrX.Generators/Models/VersioningModels.cssrc/ErrorOrX.Generators/OpenApiTransformerGenerator.cstests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cstests/ErrorOrX.Generators.Tests/DiagnosticTests.cstests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionTests.cstests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingTests.cstests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cstests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE010_Invalid_FromRoute_Type_Complex.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE011_Invalid_FromQuery_Type_Complex.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE012_Invalid_AsParameters_Type_Primitive.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE013_AsParameters_No_Constructor.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE014_Invalid_FromHeader_Type_Complex.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE016_Nested_AsParameters.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/BindingTypeValidationTests.EOE017_Nullable_AsParameters.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE007_Type_Not_In_Json_Context.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE025_Missing_CamelCase_Policy.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE025_With_CamelCase_Policy_No_Diagnostic.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE039_Multiple_Validation_Attributes.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE039_Validation_Attribute_On_Parameter.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE041_Missing_ProblemDetails_In_JsonContext.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/JsonAotValidationTests.EOE041_No_Diagnostic_When_ProblemDetails_Present.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.EOE033_Method_Name_Lowercase_Start.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.EOE033_Method_Name_Snake_Case.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.EOE033_Method_Name_With_Underscore.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.Valid_Complex_Type_With_AsParameters.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.Valid_Complex_Type_With_FromBody_On_Post.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.Valid_Route_Parameter_Bound.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/NamingAndValidCaseTests.Valid_Service_Type_Inferred.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE003_Route_Parameter_Not_Bound.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE003_Route_Parameter_With_Constraint_Not_Bound.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE005_Empty_Parameter_Name.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE005_Unclosed_Brace_In_Route.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE005_Unmatched_Close_Brace.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE006_Multiple_Body_Sources_FromBody_And_FromForm.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE006_Multiple_Body_Sources_Stream_And_FromBody.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE015_Anonymous_Return_Type.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE018_Private_Return_Type.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE019_Generic_Type_Parameter.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE020_Guid_Constraint_With_Int_Parameter.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE020_Int_Constraint_With_String_Parameter.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE021_Complex_Type_On_Delete_Without_Binding.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/RouteBodyValidationTests.EOE021_Complex_Type_On_Get_Without_Binding.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/UnionTypeAndFactoryTests.EOE022_Too_Many_Result_Types.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/UnionTypeAndFactoryTests.EOE023_Unknown_Error_Factory.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/UnionTypeAndFactoryTests.EOE024_Interface_Call_With_ProducesError_No_Diagnostic.verified.txttests/ErrorOrX.Generators.Tests/Snapshots/UnionTypeAndFactoryTests.EOE024_Undocumented_Interface_Call.verified.txttests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs
💤 Files with no reviewable changes (4)
- src/ErrorOrX.Generators/Models/EndpointModels.cs
- tests/ErrorOrX.Generators.Tests/DiagnosticTests.cs
- tests/ErrorOrX.Generators.Tests/MiddlewareEmissionTests.cs
- tests/ErrorOrX.Generators.Tests/ParameterBindingTests.cs
📜 Review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Codacy Static Code Analysis
🧰 Additional context used
📓 Path-based instructions (11)
src/ErrorOrX.Generators/**/*.cs
📄 CodeRabbit inference engine (CLAUDE.md)
src/ErrorOrX.Generators/**/*.cs: Generated code must use ONLYIsError,Errors, andValuemembers fromErrorOr<T>to minimize interface coupling
Never emitErrorOr<T>.Match()in generated code; use minimal interface withIsErrorcheck andValue/Errorsaccess instead
Implement AOT Wrapper Pattern with two methods: a wrapper that returnsTask<Results<...>>for OpenAPI metadata and a core method with handler logic
Generated endpoint wrappers must use(Delegate)Invoke_Ep*cast to force the Delegate overload for OpenAPI visibility
Generator must emit.RequireAuthorization()for[Authorize],.RequireRateLimiting()for[EnableRateLimiting],.CacheOutput()for[OutputCache], and.RequireCors()for[EnableCors]attributes
Parameter binding must follow the Smart Parameter Binding priority table: explicit attributes > special types > route parameters > primitive query > interface/abstract service > service naming patterns > POST/PUT/PATCH body > GET/DELETE error > service fallback
Complex types in GET/DELETE endpoints without explicit[FromQuery]binding must emit EOE021 diagnostic error
Map ErrorType to HTTP status codes according to RFC 9110: Validation=400, Unauthorized=401, Forbidden=403, NotFound=404, Conflict=409, Failure/Unexpected=500
Handler methods must be static and returnErrorOr<T>; reject non-static handlers with EOE002 and invalid return types with EOE001
Route validation must detect and error on unbound parameters (EOE003), duplicate routes (EOE004), and invalid route patterns (EOE005)
Binding validation must reject multiple[FromBody]parameters (EOE006, EOE008) and detect ambiguous bindings (EOE021)
Response types must be JSON serializable; check against the provided JsonSerializerContext with EOE007 and EOE025-026 for CamelCase/context issues
Union return types must not exceed the maximum allowed count (EOE022), must use known factories (EOE023), and all interfaces must be documented (EOE024)
AOT safety validation m...
Files:
src/ErrorOrX.Generators/Models/VersioningModels.cssrc/ErrorOrX.Generators/Models/OpenApiModels.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cssrc/ErrorOrX.Generators/Models/MiddlewareInfo.cssrc/ErrorOrX.Generators/Models/RouteModels.cssrc/ErrorOrX.Generators/Models/ValidationModels.cssrc/ErrorOrX.Generators/Models/EndpointDescriptor.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cssrc/ErrorOrX.Generators/Models/MethodAnalysis.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cssrc/ErrorOrX.Generators/Models/EndpointParameters.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cssrc/ErrorOrX.Generators/OpenApiTransformerGenerator.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
**/*.cs
📄 CodeRabbit inference engine (.editorconfig)
**/*.cs: Suppress CA1062 (Validate arguments of public methods) in C# — Roslyn callbacks guarantee non-null parameters
Suppress CA1308 (Normalize strings to uppercase) in C# — lowercase is standard for URL routes
Suppress Meziantou analyzer rules (MA0002, MA0006, MA0008, MA0011, MA0048, MA0049, MA0004, MA0016, MA0051) in C# for opinionated style rules
Suppress RCS1084 (Use coalesce instead of conditional) in C# — the ternary form is idiomatic for lifting T? to ErrorOr via implicit conversion
Set pattern matching style rules (IDE0019, IDE0020, IDE0078, IDE0083, IDE0170) to suggestion severity in C# due to netstandard2.0 source generator compatibility constraints
Enable C# pattern matching style preferences (csharp_style_prefer_pattern_matching, csharp_style_prefer_not_pattern, csharp_style_prefer_extended_property_pattern) at suggestion level
**/*.cs: Use[Get],[Post],[Put],[Delete],[Patch]attributes on static handler methods to define ASP.NET Core Minimal API endpoints
Handler methods must be static and returnErrorOr<T>orErrorOr<Result.{Marker}>types
Use error factory methods likeError.Validation(),Error.Unauthorized(),Error.Forbidden(),Error.NotFound(),Error.Conflict(),Error.Failure(), andError.Unexpected()to create properly typed errors mapped to HTTP status codes
Use nullable-to-ErrorOr extensions like.OrNotFound(),.OrValidation(),.OrUnauthorized(),.OrForbidden(),.OrConflict(),.OrFailure(), and.OrUnexpected()to convert nullable values to ErrorOr results
Use fluent API methods (Then,Else,Match,Switch,FailIf) to chain operations in railway-oriented programming style
Handler method parameters must be inferred correctly: route parameters match{param}in the route, primitive types not in route become query parameters, complex types in POST/PUT/PATCH become body parameters, interfaces and DI types become service injections, andHttpContext,CancellationToken,IFormFileare...
Files:
src/ErrorOrX.Generators/Models/VersioningModels.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cssrc/ErrorOrX.Generators/Models/OpenApiModels.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cssrc/ErrorOrX.Generators/Models/MiddlewareInfo.cssrc/ErrorOrX.Generators/Models/RouteModels.cssrc/ErrorOrX.Generators/Models/ValidationModels.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cstests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cssrc/ErrorOrX.Generators/Models/EndpointDescriptor.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cstests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cstests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cstests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cssrc/ErrorOrX.Generators/Models/MethodAnalysis.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cstests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cstests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cstests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cssrc/ErrorOrX.Generators/Models/EndpointParameters.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cssrc/ErrorOrX.Generators/OpenApiTransformerGenerator.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
⚙️ CodeRabbit configuration file
C#/.NET review. Treat warnings, nullable annotations, async flow, package boundaries, and generated code as correctness concerns. Flag sync-over-async (
.Result,.Wait(),.GetAwaiter().GetResult()), fire-and-forget work without lifecycle/error handling, missing CancellationToken on public/internal async methods,DateTime.Now/UtcNowin domain logic instead of TimeProvider, null-forgiving!without a documented proof, catch-and-fallback paths that hide failures, and service registration drift. Prefer data-driven, composable code over hardcoded switch/case tables. Public package/API surface changes need XML docs and explicit compatibility notes.
Files:
src/ErrorOrX.Generators/Models/VersioningModels.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cssrc/ErrorOrX.Generators/Models/OpenApiModels.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cssrc/ErrorOrX.Generators/Models/MiddlewareInfo.cssrc/ErrorOrX.Generators/Models/RouteModels.cssrc/ErrorOrX.Generators/Models/ValidationModels.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cstests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cssrc/ErrorOrX.Generators/Models/EndpointDescriptor.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cstests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cstests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cstests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cssrc/ErrorOrX.Generators/Models/MethodAnalysis.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cstests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cstests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cstests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cssrc/ErrorOrX.Generators/Models/EndpointParameters.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cssrc/ErrorOrX.Generators/OpenApiTransformerGenerator.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
**/src/**/*.cs
📄 CodeRabbit inference engine (.editorconfig)
Suppress ReSharper highlighting for enumerable parameter type suggestions and base type suggestions in src C# files
Files:
src/ErrorOrX.Generators/Models/VersioningModels.cssrc/ErrorOrX.Generators/Models/OpenApiModels.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cssrc/ErrorOrX.Generators/Models/MiddlewareInfo.cssrc/ErrorOrX.Generators/Models/RouteModels.cssrc/ErrorOrX.Generators/Models/ValidationModels.cssrc/ErrorOrX.Generators/Models/EndpointDescriptor.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cssrc/ErrorOrX.Generators/Models/MethodAnalysis.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cssrc/ErrorOrX.Generators/Models/EndpointParameters.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cssrc/ErrorOrX.Generators/OpenApiTransformerGenerator.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
**/*.{cs,ts,tsx,py}
📄 CodeRabbit inference engine (Custom checks)
Fail C#/TypeScript/Python changes that introduce sync-over-async, unobserved fire-and-forget work, missing CancellationToken/AbortSignal propagation on public/internal async boundaries, sleeps for synchronization, or resource disposal paths that can drop in-flight work
Files:
src/ErrorOrX.Generators/Models/VersioningModels.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cssrc/ErrorOrX.Generators/Models/OpenApiModels.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cssrc/ErrorOrX.Generators/Models/MiddlewareInfo.cssrc/ErrorOrX.Generators/Models/RouteModels.cssrc/ErrorOrX.Generators/Models/ValidationModels.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cstests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cssrc/ErrorOrX.Generators/Models/EndpointDescriptor.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cstests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Body.cstests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cstests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cssrc/ErrorOrX.Generators/Models/MethodAnalysis.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cstests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cstests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Extractor.cssrc/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cstests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cssrc/ErrorOrX.Generators/Models/EndpointParameters.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cssrc/ErrorOrX.Generators/OpenApiTransformerGenerator.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cssrc/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
tests/ErrorOrX.Generators.Tests/**/*.cs
📄 CodeRabbit inference engine (CLAUDE.md)
Generator snapshot tests must be located in
ErrorOrX.Generators.Testsusing the ANcpLua testing framework
Files:
tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cstests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cstests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cstests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cstests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cstests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs
tests/**/*.cs
📄 CodeRabbit inference engine (CLAUDE.md)
Use AwesomeAssertions for fluent assertion syntax in test files
Suppress ReSharper highlighting for nullable warnings, unused positional properties, multiple enumeration, pure method return values, and redundant nullable suppressions in test C# files
Files:
tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cstests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cstests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cstests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cstests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cstests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
**/*Tests/**/*.cs
⚙️ CodeRabbit configuration file
.NET tests. Review that tests assert observable behavior, not implementation details; async tests return Task; timing-sensitive tests use deterministic signals/fakes instead of sleeps; Testcontainers resources are disposed; cancellation tokens are threaded; mocks are strict where the repo convention expects strictness; and new behavior has focused regression coverage. Do not accept weakening or deleting truthful tests to make a PR pass.
Files:
tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cstests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cstests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cstests/ErrorOrX.Generators.Tests/MiddlewareEmissionAuthorizationTests.cstests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cstests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cstests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cstests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
tests/ErrorOrX.Tests/**/*.cs
📄 CodeRabbit inference engine (CLAUDE.md)
Runtime unit tests must be located in
ErrorOrX.Testsusing xunit.v3
Files:
tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cstests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs
src/ErrorOrX.Generators/**/*Emitter.cs
📄 CodeRabbit inference engine (src/ErrorOrX.Generators/CLAUDE.md)
src/ErrorOrX.Generators/**/*Emitter.cs: Generated code must use ONLYIsError,Errors, andValueproperties when working withErrorOr<T>- never emitMatch()method calls
Use AOT wrapper pattern: wrapper method returnsTask<Results<...>>for OpenAPI visibility, core method returnsTask<IResult>, and MapGet uses(Delegate)Invoke_*cast to force Delegate overload
Files:
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cssrc/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cssrc/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs
**/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
📄 CodeRabbit inference engine (.editorconfig)
Suppress AL0029 (Use HasAttribute instead of foreach-over-GetAttributes) in ErrorOrEndpointGenerator.Extractor.cs — legitimate need to extract constructor and named arguments from AttributeData
Files:
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs
src/ErrorOrX.Generators/**/*ParameterBinding.cs
📄 CodeRabbit inference engine (src/ErrorOrX.Generators/CLAUDE.md)
src/ErrorOrX.Generators/**/*ParameterBinding.cs: Parameter binding classification must follow priority order: (1) Explicit attributes ([FromBody], [FromServices], etc.), (2) Special types (HttpContext, CancellationToken, IFormFile), (3) Route match, (4) Primitives (query binding), (5) Custom binding (TryParse/BindAsync), (6) Smart inference
Smart inference for parameter binding must detect service types including interfaces with Service suffix (ITodoService) and common DI patterns (*Repository, *Handler, *Manager, *Provider, *Factory, *Client, *DbContext)
Files:
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs
🔇 Additional comments (37)
tests/ErrorOrX.Tests/ErrorOr/ErrorOr.AccessTests.cs (1)
4-7: LGTM!tests/ErrorOrX.Tests/ErrorOr/ErrorOr.ChainingTests.cs (1)
1-160: LGTM!tests/ErrorOrX.Tests/ErrorOr/ErrorOr.MatchAndSwitchTests.cs (1)
1-99: LGTM!src/ErrorOrX.Generators/Models/EndpointDescriptor.cs (1)
1-183: LGTM!src/ErrorOrX.Generators/Models/EndpointParameters.cs (1)
1-176: LGTM!src/ErrorOrX.Generators/Models/MethodAnalysis.cs (1)
1-65: LGTM!src/ErrorOrX.Generators/Models/MiddlewareInfo.cs (1)
1-26: LGTM!src/ErrorOrX.Generators/Models/OpenApiModels.cs (1)
1-41: LGTM!src/ErrorOrX.Generators/Models/RouteModels.cs (1)
1-40: LGTM!src/ErrorOrX.Generators/Models/ValidationModels.cs (1)
1-31: LGTM!tests/ErrorOrX.Generators.Tests/BindingTypeValidationTests.cs (1)
1-200: LGTM!tests/ErrorOrX.Generators.Tests/JsonAotValidationTests.cs (1)
1-204: LGTM!tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs (1)
1-202: LGTM!Also applies to: 225-250
tests/ErrorOrX.Generators.Tests/ParameterBindingExplicitAttributeTests.cs (1)
1-152: LGTM!tests/ErrorOrX.Generators.Tests/ParameterBindingRouteQueryTests.cs (1)
1-171: LGTM!tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs (1)
1-26: LGTM!Also applies to: 31-74
tests/ErrorOrX.Generators.Tests/RouteBodyValidationTests.cs (1)
1-321: LGTM!src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.EndpointFlow.cs (4)
1-55: LGTM!
57-157: LGTM!
159-262: LGTM!
292-313: LGTM!src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.Attributes.cs (2)
109-126: LGTM!
1-88: LGTM!src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Initialize.cs (3)
22-55: LGTM!
57-99: LGTM!
101-133: LGTM!src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs (1)
20-295: LGTM!src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs (1)
12-163: LGTM!tests/ErrorOrX.Generators.Tests/NamingAndValidCaseTests.cs (1)
1-155: LGTM!tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs (1)
13-94: LGTM!Also applies to: 121-122
src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Versioning.cs (1)
15-93: LGTM!src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs (1)
8-189: LGTM!src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.cs (1)
1-138: LGTM!src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.cs (2)
22-90: LGTM!Also applies to: 128-174, 176-203
9-14: ⚡ Quick winNo issues identified. The sibling partial
Extractor.Metadata.csexists and the documentation reference is accurate.src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs (1)
13-147: LGTM!src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.cs (1)
3-38: LGTM!
| foreach (var param in method.Parameters) | ||
| { | ||
| foreach (var attr in param.GetAttributes()) | ||
| { | ||
| if (attr.AttributeClass is null) continue; | ||
|
|
||
| // Check if the attribute inherits from ValidationAttribute | ||
| if (InheritsFrom(attr.AttributeClass, validationAttributeType)) | ||
| { | ||
| context.ReportDiagnostic(Diagnostic.Create( | ||
| Descriptors.ValidationUsesReflection, | ||
| param.Locations.FirstOrDefault() ?? method.Locations.FirstOrDefault(), | ||
| param.Name, | ||
| method.Name)); | ||
| break; // Only report once per parameter | ||
| } | ||
| } |
There was a problem hiding this comment.
EOE039 misses validation attributes on request DTO properties.
This only inspects param.GetAttributes(). The generator stack already collects validation properties for emitted DataAnnotations validation, so [Required]/[Range] on a body or query DTO will bypass the analyzer while still taking the reflection path at generation/runtime. Traverse the parameter type's reachable properties as well, with cycle protection, before deciding EOE039 is absent.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.BodyAndValidation.cs`
around lines 69 - 85, The current loop only checks param.GetAttributes() so
EOE039 misses ValidationAttributes on DTO properties; update the logic around
the parameter inspection (the foreach over method.Parameters and the
InheritsFrom(...) check against validationAttributeType used when reporting
Descriptors.ValidationUsesReflection) to also traverse the parameter type's
reachable properties and their attributes (starting from the parameter's
ITypeSymbol/INamedTypeSymbol), using a visited set to protect against cycles,
and treat any property attribute that InheritsFrom(validationAttributeType) as
equivalent to a parameter attribute so the analyzer reports the EOE039
diagnostic (use param.Locations.FirstOrDefault() or
method.Locations.FirstOrDefault() as before) once per parameter.
| // Get the actual type, unwrapping Nullable<T> for optional parameters | ||
| var actualTypeFqn = typeFqn.UnwrapNullable(rp.IsOptional || mp.IsNullable); | ||
|
|
||
| // Check if actual type matches any expected type | ||
| if (!DoesTypeMatchConstraint(actualTypeFqn, expectedTypes)) | ||
| { | ||
| context.ReportDiagnostic(Diagnostic.Create( | ||
| Descriptors.RouteConstraintTypeMismatch, | ||
| attributeLocation, | ||
| rp.Name, | ||
| constraint, | ||
| expectedTypes[0], | ||
| mp.Name, | ||
| NormalizeTypeName(typeFqn))); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if an actual type matches any of the expected types for a constraint. | ||
| /// </summary> | ||
| private static bool DoesTypeMatchConstraint(string actualTypeFqn, IEnumerable<string> expectedTypes) | ||
| { | ||
| foreach (var expected in expectedTypes) | ||
| { | ||
| if (TypeNamesMatch(actualTypeFqn, expected)) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== RouteMethodParameterInfo definition =="
rg -n -C3 'RouteMethodParameterInfo' src/ErrorOrX.Generators
echo
echo "== String-based type comparison / nullable unwrapping in generator sources =="
rg -n -C2 'TypeNamesEqual\(|UnwrapNullable\(|NormalizeTypeName\(' src/ErrorOrX.Generators
echo
echo "== ErrorOrContext nullable helpers =="
rg -n -C2 'class ErrorOrContext|UnwrapNullable' src/ErrorOrX.GeneratorsRepository: ANcpLua/ErrorOrX
Length of output: 32891
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Call sites of ValidateConstraintTypes in analyzer =="
rg -n -B2 -A2 'ValidateConstraintTypes\(' src/ErrorOrX.Generators/Analyzers/
echo
echo "== ErrorOrEndpointAnalyzer main entry point =="
rg -n 'AnalyzeSymbol|SymbolAnalysisContext' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | head -20
echo
echo "== How method symbol is accessed in analyzer =="
rg -n 'IMethodSymbol\|context\.' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs | head -15Repository: ANcpLua/ErrorOrX
Length of output: 1504
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== AnalyzeMethod signature and how it uses context =="
rg -n -A30 'private static void AnalyzeMethod' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
echo
echo "== What does SymbolAnalysisContext expose =="
rg -n 'context\.Symbol\|context\.Compilation' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | head -10Repository: ANcpLua/ErrorOrX
Length of output: 1492
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Full AnalyzeEndpoint method =="
rg -n -A50 'private static void AnalyzeEndpoint' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | head -80
echo
echo "== RouteMethodParameterInfo creation =="
rg -n -B5 -A10 'BuildRouteParameterLookup.*methodParams' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.csRepository: ANcpLua/ErrorOrX
Length of output: 2518
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== RouteBindingHelper.BindRouteParameters implementation =="
rg -n -A50 'public static.*BindRouteParameters' src/ErrorOrX.Generators/Validation/RouteBindingHelper.cs | head -70
echo
echo "== Where RouteMethodParameterInfo is constructed =="
rg -n -B3 -A3 'new RouteMethodParameterInfo' src/ErrorOrX.Generators/Repository: ANcpLua/ErrorOrX
Length of output: 2973
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== EndpointParameter definition =="
rg -n -A15 'record.*EndpointParameter|struct.*EndpointParameter' src/ErrorOrX.Generators/Models/
echo
echo "== ErrorOrEndpointGenerator.BindParameters implementation (first 100 lines) =="
rg -n -A100 'public static.*BindParameters' src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs | head -120Repository: ANcpLua/ErrorOrX
Length of output: 2235
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for BindParameters method =="
rg -n 'BindParameters' src/ErrorOrX.Generators/Core/ --type cs
echo
echo "== Search for where EndpointParameter is created from IParameterSymbol =="
rg -n -B3 -A8 'IParameterSymbol' src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.cs | head -100Repository: ANcpLua/ErrorOrX
Length of output: 1656
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== RouteValidator.ValidateConstraintTypes (generator phase) =="
rg -n -A30 'public static.*ValidateConstraintTypes' src/ErrorOrX.Generators/Validation/RouteValidator.cs | head -50
echo
echo "== Check if RouteValidator uses symbols or strings =="
rg -n 'MatchesExpectedType\|TypeNamesEqual\|NormalizeTypeName' src/ErrorOrX.Generators/Validation/RouteValidator.csRepository: ANcpLua/ErrorOrX
Length of output: 1780
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== How is IMethodSymbol.Parameters used in the generator =="
rg -n 'method\.Parameters' src/ErrorOrX.Generators/Core/ | head -20
echo
echo "== Does RouteValidator have access to method symbol in generator phase? =="
rg -n -B5 'ValidateConstraintTypes.*method' src/ErrorOrX.Generators/Validation/RouteValidator.cs | head -30
echo
echo "== Can we retrieve parameter symbol from method given parameter name? =="
rg -n 'method\.Parameters.*FirstOrDefault\|method\.Parameters.*Where' src/ErrorOrX.Generators/Repository: ANcpLua/ErrorOrX
Length of output: 2148
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Analyzer call: does it have access to method.Parameters? =="
rg -n -B10 -A10 'ValidateConstraintTypes.*in context' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | grep -A20 'AnalyzeEndpoint'
echo
echo "== Check if method symbol is available when ValidateConstraintTypes is called =="
rg -n -B15 'ValidateConstraintTypes\(in context' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs
echo
echo "== Full context of where ValidateConstraintTypes is invoked in analyzer =="
sed -n '110,150p' src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.csRepository: ANcpLua/ErrorOrX
Length of output: 123
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Get lines around the ValidateConstraintTypes invocation in analyzer =="
cat -n src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | sed -n '130,160p'
echo
echo "== Get the full AnalyzeEndpoint method =="
cat -n src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.cs | sed -n '87,175p'Repository: ANcpLua/ErrorOrX
Length of output: 5796
Pass the method symbol to route-constraint validation and use Roslyn type comparison.
The analyzer calls ValidateConstraintTypes without passing method, forcing constraint validation to work entirely on string-based type FQNs. Line 108 unwraps via typeFqn.UnwrapNullable(...) and lines 127–135 compare via TypeNamesEqual(...), both violating the repo guidelines requiring symbol-based semantics.
The method symbol is available in AnalyzeEndpoint (line 89) but not forwarded to ValidateConstraintTypes (line 143). Either extend RouteMethodParameterInfo to carry the resolved ITypeSymbol for each parameter, or pass method explicitly and resolve parameter types within validation. Then use ErrorOrContext.UnwrapNullable(ITypeSymbol) and Roslyn ITypeSymbol.Equals() for constraint matching.
String-based type comparison can drift on generic aliases, nullable lifting semantics, and nested type normalization, leading to inconsistent EOE020 diagnostics.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ErrorOrX.Generators/Analyzers/ErrorOrEndpointAnalyzer.RouteValidation.cs`
around lines 107 - 131, The validator currently unwraps and compares types by
FQN strings in ValidateConstraintTypes/DoesTypeMatchConstraint (using
TypeNamesMatch), which can misrepresent semantics; update the call-site in
AnalyzeEndpoint to pass the method symbol (or extend RouteMethodParameterInfo to
include the resolved ITypeSymbol for each parameter) so validation can resolve
parameter types via ErrorOrContext.UnwrapNullable(ITypeSymbol) and perform
comparisons with ITypeSymbol.Equals (or SymbolEqualityComparer) instead of
string equality; adjust ValidateConstraintTypes and DoesTypeMatchConstraint to
accept ITypeSymbol(s) and use Roslyn type equality for constraint matching.
| var hasValidation = !ep.ErrorInference.InferredErrorTypeNames.IsDefaultOrEmpty && | ||
| ep.ErrorInference.InferredErrorTypeNames.AsImmutableArray().Contains(ErrorMapping.Validation); | ||
|
|
||
| if (!hasValidation) return; | ||
|
|
||
| code.AppendLine($" if (first.Type == {WellKnownTypes.Fqn.ErrorType}.Validation)"); | ||
| code.AppendLine(" {"); | ||
| BindingCodeEmitter.EmitValidationDictBuilder( | ||
| code, 20, "validationDict", "result.Errors", "e", | ||
| "e.Code", "e.Description", | ||
| $"e.Type != {WellKnownTypes.Fqn.ErrorType}.Validation"); | ||
| code.AppendLine( | ||
| $" return {ctx.WrapReturn($"{WellKnownTypes.Fqn.TypedResults.ValidationProblem}(validationDict)")};"); | ||
| code.AppendLine(" }"); |
There was a problem hiding this comment.
Validation dispatch is gated only by inferred errors, not full declared error contract.
hasValidation (and downstream switch coverage) only inspects InferredErrorTypeNames. Endpoints that declare validation errors but don’t infer them from body analysis can miss the ValidationProblem 400 path and fall through to failure handling.
As per coding guidelines, "Map ErrorType to HTTP status codes according to RFC 9110: Validation=400, Unauthorized=401, Forbidden=403, NotFound=404, Conflict=409, Failure/Unexpected=500".
Also applies to: 89-106
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs`
around lines 54 - 67, The current gating uses hasValidation which only checks
ep.ErrorInference.InferredErrorTypeNames, so endpoints that declare a Validation
error but don't infer it are missing the 400 ValidationProblem path; update the
condition to consider declared/contract error types as well (e.g., check
ep.DeclaredErrorTypeNames or the collection that represents the endpoint's
declared errors in addition to ep.ErrorInference.InferredErrorTypeNames) so that
hasValidation becomes true if either inferred OR declared error type names
contain ErrorMapping.Validation; apply the same change to the other similar
guards around lines 89-106; keep the existing generation blocks
(BindingCodeEmitter.EmitValidationDictBuilder, the Type comparison to
WellKnownTypes.Fqn.ErrorType.Validation, and the ctx.WrapReturn call) unchanged
so only the presence check is broadened.
| foreach (var (_, paramName) in validationParams) | ||
| { | ||
| code.AppendLine( | ||
| $" var {paramName}ValidationResults = new {WellKnownTypes.Fqn.List}<{WellKnownTypes.Fqn.ValidationResult}>();"); | ||
| code.AppendLine( | ||
| $" if (!{WellKnownTypes.Fqn.Validator}.TryValidateObject({paramName}!, new {WellKnownTypes.Fqn.ValidationContext}({paramName}!), {paramName}ValidationResults, validateAllProperties: true))"); | ||
| code.AppendLine(" {"); | ||
| BindingCodeEmitter.EmitValidationDictBuilder( | ||
| code, 16, "validationDict", $"{paramName}ValidationResults", "vr", | ||
| "key", "vr.ErrorMessage ?? \"\"", | ||
| keyVarDecl: "var key = vr.MemberNames.FirstOrDefault() ?? \"\";"); | ||
|
|
||
| var returnExpr = isAsync | ||
| ? $"{WellKnownTypes.Fqn.TypedResults.ValidationProblem}(validationDict)" | ||
| : $"Task.FromResult<{returnTypeFqn}>({WellKnownTypes.Fqn.TypedResults.ValidationProblem}(validationDict))"; | ||
| code.AppendLine($" return {returnExpr};"); | ||
| code.AppendLine(" }"); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the EmitBclValidation method and surrounding context
fd -t f "*.cs" src/ErrorOrX.Generators | while read f; do
if grep -q "EmitBclValidation" "$f"; then
echo "=== $f ==="
wc -l "$f"
fi
doneRepository: ANcpLua/ErrorOrX
Length of output: 344
🏁 Script executed:
#!/bin/bash
# Search for the file mentioned in the review
cat -n "src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs" | sed -n '130,180p'Repository: ANcpLua/ErrorOrX
Length of output: 2830
🏁 Script executed:
#!/bin/bash
# Find how validation params are collected and filtered
rg "RequiresValidation" -A 3 -B 3 src/ErrorOrX.GeneratorsRepository: ANcpLua/ErrorOrX
Length of output: 9645
🏁 Script executed:
#!/bin/bash
# Check parameter binding and nullability handling
rg "validationParams" -B 5 -A 10 src/ErrorOrX.Generators/CoreRepository: ANcpLua/ErrorOrX
Length of output: 6230
🏁 Script executed:
#!/bin/bash
# Find the RequiresValidation method implementation
rg "public static bool RequiresValidation" -A 20 src/ErrorOrX.Generators/TypeResolutionRepository: ANcpLua/ErrorOrX
Length of output: 1934
🏁 Script executed:
#!/bin/bash
# Check if nullable parameters are filtered out
rg "IsNullable" src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs -B 3 -A 3Repository: ANcpLua/ErrorOrX
Length of output: 42
🏁 Script executed:
#!/bin/bash
# Search for how validation is gated on parameter nullability
rg "RequiresValidation.*IsNullable\|IsNullable.*RequiresValidation" src/ErrorOrX.GeneratorsRepository: ANcpLua/ErrorOrX
Length of output: 42
🌐 Web query:
System.ComponentModel.DataAnnotations.Validator.TryValidateObject null reference behavior
💡 Result:
The System.ComponentModel.DataAnnotations.Validator.TryValidateObject method exhibits specific behaviors regarding null references, primarily centered on its input parameters and internal validation logic. 1. Instance Parameter: If the instance parameter passed to TryValidateObject is null, the method will throw an ArgumentNullException [1]. 2. ValidationContext Parameter: The ValidationContext constructor itself requires a non-null instance [2]. If you attempt to create a ValidationContext with a null instance, the constructor will throw an ArgumentNullException [2]. 3. Internal Null Reference Issues: Historically, there have been reports of NullReferenceException occurring within the Validator logic itself, particularly when dealing with IValidatableObject implementations that return null instead of an empty collection of validation results [3]. In such cases, the internal implementation of Validator may fail to check for null before iterating over the results, leading to an exception [3]. 4. ValidationResults Collection: If the ICollection parameter is provided but the validation fails, it is populated with errors [1]. If this collection is null, the method may throw a NullReferenceException when it attempts to add results to it. Ensure you initialize this collection (e.g., new List) before passing it to the method [4][5]. To avoid these issues, ensure that the object being validated is not null, the ValidationContext is correctly initialized with a non-null instance, and the validation results collection is instantiated [1][2][5].
Citations:
- 1: https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.validator.tryvalidateobject?view=net-9.0
- 2: Should ValidationContext constructor check that instance is null? microsoft/referencesource#6
- 3: Issue when calling Validator.TryValidateObject dotnet/runtime#24237
- 4: https://stackoverflow.com/questions/30766293/validationresults-on-tryvalidateobject-is-null
- 5: https://stackoverflow.com/questions/51773391/tryvalidatemodel-in-asp-net-core-throws-null-reference-exception-while-performin
🏁 Script executed:
#!/bin/bash
# Check if parameters that require validation can also be nullable
# Look at parameter binding flag assignment logic
cat -n src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Meta.cs | sed -n '1,150p'Repository: ANcpLua/ErrorOrX
Length of output: 7008
🏁 Script executed:
#!/bin/bash
# Check parameter binding to see what determines nullability
rg "ParameterFlags.Nullable" src/ErrorOrX.Generators/Core -B 3 -A 3Repository: ANcpLua/ErrorOrX
Length of output: 918
🏁 Script executed:
#!/bin/bash
# Look for any guards on nullable parameters in validation flow
rg "IsNullable.*validat\|validat.*IsNullable" -i src/ErrorOrX.GeneratorsRepository: ANcpLua/ErrorOrX
Length of output: 42
🏁 Script executed:
#!/bin/bash
# Verify that nullable validatable parameters can occur in practice
# Check if there's any constraint preventing nullable + requires validation combo
rg "RequiresValidation" src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.cs -B 5 -A 5Repository: ANcpLua/ErrorOrX
Length of output: 377
Guard nullable validation targets before emitting TryValidateObject.
Parameters marked RequiresValidation can be nullable. The emitted validation code unconditionally dereferences paramName! and passes it to Validator.TryValidateObject, which throws ArgumentNullException on null instance. This bypasses the intended validation error response path.
Proposed fix
- if (!{WellKnownTypes.Fqn.Validator}.TryValidateObject({paramName}!, new {WellKnownTypes.Fqn.ValidationContext}({paramName}!), {paramName}ValidationResults, validateAllProperties: true))
+ if ({paramName} is not null
+ && !{WellKnownTypes.Fqn.Validator}.TryValidateObject({paramName}, new {WellKnownTypes.Fqn.ValidationContext}({paramName}), {paramName}ValidationResults, validateAllProperties: true))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs`
around lines 151 - 167, The generated code currently dereferences {paramName}!
when calling {WellKnownTypes.Fqn.Validator}.TryValidateObject which will throw
on null; add a null-check for each validation target (from validationParams /
paramName) before calling TryValidateObject: if the parameter is null, build the
same validationDict indicating a required/null error for that parameter and
return the appropriate ValidationProblem (use the same isAsync-aware returnExpr
pattern used below), otherwise proceed to call TryValidateObject with the
non-null instance; ensure you reference the same
BindingCodeEmitter.EmitValidationDictBuilder/validationDict and the isAsync
return branches so behavior and return type remain consistent.
| var refs = method.DeclaringSyntaxReferences; | ||
| if (refs.IsDefaultOrEmpty || refs.Length is 0) return null; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Redundant length check.
IsDefaultOrEmpty already returns true for zero-length arrays. The || refs.Length is 0 clause is unreachable.
Proposed fix
var refs = method.DeclaringSyntaxReferences;
- if (refs.IsDefaultOrEmpty || refs.Length is 0) return null;
+ if (refs.IsDefaultOrEmpty) return null;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.ErrorInference.cs`
around lines 35 - 36, The conditional checking refs for emptiness is redundant:
remove the "|| refs.Length is 0" portion and keep only the IsDefaultOrEmpty
check so the early-return uses "if (refs.IsDefaultOrEmpty) return null;"; update
the code where refs is assigned from method.DeclaringSyntaxReferences (in
ErrorOrEndpointGenerator.Extractor.ErrorInference) to rely solely on
refs.IsDefaultOrEmpty to determine emptiness.
| public async Task DisableCors_Emits_DisableCors() | ||
| { | ||
| const string Source = """ | ||
| using ErrorOr; | ||
| using Microsoft.AspNetCore.Cors; | ||
|
|
||
| public static class Api | ||
| { | ||
| [Get("/internal")] | ||
| [DisableCors] | ||
| public static ErrorOr<string> Internal() => "internal"; | ||
| } | ||
| """; | ||
|
|
||
| using var result = await RunAsync(Source); | ||
|
|
||
| result.Diagnostics.Should().BeEmpty(); | ||
| var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content; | ||
| // DisableCors should be emitted | ||
| generated.Should().Match("*Cors*"); | ||
| } |
There was a problem hiding this comment.
DisableCors test does not validate disable behavior.
Match("*Cors*") passes for both enable and disable paths. This test should assert the exact emitted API for disable semantics.
Suggested assertion hardening
- // DisableCors should be emitted
- generated.Should().Match("*Cors*");
+ generated.Should().Contain(".DisableCors()");
+ generated.Should().NotContain(".RequireCors(");As per coding guidelines **/*Tests/**/*.cs: “Review that tests assert observable behavior, not implementation details”.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public async Task DisableCors_Emits_DisableCors() | |
| { | |
| const string Source = """ | |
| using ErrorOr; | |
| using Microsoft.AspNetCore.Cors; | |
| public static class Api | |
| { | |
| [Get("/internal")] | |
| [DisableCors] | |
| public static ErrorOr<string> Internal() => "internal"; | |
| } | |
| """; | |
| using var result = await RunAsync(Source); | |
| result.Diagnostics.Should().BeEmpty(); | |
| var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content; | |
| // DisableCors should be emitted | |
| generated.Should().Match("*Cors*"); | |
| } | |
| public async Task DisableCors_Emits_DisableCors() | |
| { | |
| const string Source = """ | |
| using ErrorOr; | |
| using Microsoft.AspNetCore.Cors; | |
| public static class Api | |
| { | |
| [Get("/internal")] | |
| [DisableCors] | |
| public static ErrorOr<string> Internal() => "internal"; | |
| } | |
| """; | |
| using var result = await RunAsync(Source); | |
| result.Diagnostics.Should().BeEmpty(); | |
| var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content; | |
| generated.Should().Contain(".DisableCors()"); | |
| generated.Should().NotContain(".RequireCors("); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionCorsAndMetadataTests.cs`
around lines 56 - 76, The test DisableCors_Emits_DisableCors currently only
checks for a generic "Cors" token which matches both enable and disable paths;
update the assertion to validate the exact disable semantics by asserting the
generated API contains the explicit disable marker emitted by the generator
(e.g. the attribute or mapping representing disable CORS) rather than a
wildcard. Replace generated.Should().Match("*Cors*") in
DisableCors_Emits_DisableCors with a precise check on the generated string (for
example generated.Should().Contain("[DisableCors]") or the exact snippet the
generator emits into ErrorOrEndpointMappings.cs) and optionally assert that any
enable-CORS marker is not present to ensure correct disable behavior.
| public async Task OutputCache_With_Duration_Emits_Duration() | ||
| { | ||
| const string Source = """ | ||
| using ErrorOr; | ||
| using Microsoft.AspNetCore.OutputCaching; | ||
|
|
||
| public static class Api | ||
| { | ||
| [Get("/cached")] | ||
| [OutputCache(Duration = 60)] | ||
| public static ErrorOr<string> Cached() => "cached"; | ||
| } | ||
| """; | ||
|
|
||
| using var result = await RunAsync(Source); | ||
|
|
||
| result.Diagnostics.Should().BeEmpty(); | ||
| var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content; | ||
| // Duration should be handled - either via policy builder or inline | ||
| generated.Should().Match("*.CacheOutput*"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task OutputCache_With_VaryByQueryKeys_Emits_VaryBy() | ||
| { | ||
| const string Source = """ | ||
| using ErrorOr; | ||
| using Microsoft.AspNetCore.OutputCaching; | ||
|
|
||
| public static class Api | ||
| { | ||
| [Get("/cached")] | ||
| [OutputCache(VaryByQueryKeys = new[] { "page", "sort" })] | ||
| public static ErrorOr<string> Cached() => "cached"; | ||
| } | ||
| """; | ||
|
|
||
| using var result = await RunAsync(Source); | ||
|
|
||
| result.Diagnostics.Should().BeEmpty(); | ||
| var generated = result.Files.First(static f => f.HintName == "ErrorOrEndpointMappings.cs").Content; | ||
| generated.Should().Match("*.CacheOutput*"); | ||
| } |
There was a problem hiding this comment.
Duration/VaryByQueryKeys tests can pass when those settings are silently dropped.
Both tests only check that some .CacheOutput* call exists. They do not verify that duration/vary configuration is preserved in emitted code.
Suggested assertion hardening
- // Duration should be handled - either via policy builder or inline
- generated.Should().Match("*.CacheOutput*");
+ generated.Should().Contain(".CacheOutput(");
+ generated.Should().Contain("60");
...
- generated.Should().Match("*.CacheOutput*");
+ generated.Should().Contain(".CacheOutput(");
+ generated.Should().Contain("page");
+ generated.Should().Contain("sort");As per coding guidelines **/*Tests/**/*.cs: “Review that tests assert observable behavior, not implementation details”.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@tests/ErrorOrX.Generators.Tests/MiddlewareEmissionRateLimitingAndCachingTests.cs`
around lines 133 - 175, The two tests OutputCache_With_Duration_Emits_Duration
and OutputCache_With_VaryByQueryKeys_Emits_VaryBy currently only assert that
some CacheOutput call exists, so update each test to assert the emitted code
preserves the specific configuration: inspect the generated string (variable
generated from the file with HintName "ErrorOrEndpointMappings.cs") for the
concrete duration or vary-by-query representation (e.g., ensure the generated
text contains the duration value/setting such as "60" or a policy builder call
that sets Duration = 60 for OutputCache_With_Duration_Emits_Duration, and ensure
it contains the VaryByQueryKeys list or an equivalent builder call for
OutputCache_With_VaryByQueryKeys_Emits_VaryBy); modify the assertions on the
generated variable in those two test methods to look for those specific tokens
rather than just "*.CacheOutput*".
| [Fact] | ||
| public async Task Complex_Type_With_Explicit_FromQuery_NoWarning() | ||
| { | ||
| const string Source = """ | ||
| using ErrorOr; | ||
| using Microsoft.AspNetCore.Mvc; | ||
|
|
||
| public record SearchFilter(string Query, int Page); | ||
|
|
||
| public static class Api | ||
| { | ||
| [Get("/test")] | ||
| public static ErrorOr<string> Handler([FromQuery] SearchFilter filter) => "result"; | ||
| } | ||
| """; | ||
|
|
||
| using var result = await RunAsync(Source); | ||
|
|
||
| // EOE011: [FromQuery] only supports primitives or collections of primitives | ||
| // This is expected behavior - complex types can't be query bound without [AsParameters] | ||
| result.Diagnostics.Should().ContainSingle(static d => d.Id == "EOE011"); | ||
| } |
There was a problem hiding this comment.
Rename test to match asserted diagnostic behavior.
Complex_Type_With_Explicit_FromQuery_NoWarning currently asserts EOE011 is emitted. The method name should reflect expected warning/diagnostic to avoid future false assumptions during refactors.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/ErrorOrX.Generators.Tests/ParameterBindingInferenceTests.cs` around
lines 203 - 224, The test Complex_Type_With_Explicit_FromQuery_NoWarning is
asserting that diagnostic "EOE011" is produced, so rename the test method to
reflect that a warning is expected (for example
Complex_Type_With_Explicit_FromQuery_EmitsEOE011 or
Complex_Type_With_Explicit_FromQuery_ReportsEOE011) to match the assertion on
result.Diagnostics.Should().ContainSingle(d => d.Id == "EOE011"); update only
the method name of the test (the method signature on the Fact) so callers like
the test runner and references such as RunAsync and result.Diagnostics remain
unchanged.
| // HttpContext binds directly from ctx parameter (uses p0, p1, etc. naming) | ||
| generated.Should().Contain("= ctx;"); | ||
| generated.Should().Contain("global::Api.GetInfo(p0)"); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Avoid asserting generated temporary parameter names.
Asserting global::Api.GetInfo(p0) couples this test to emitter-local symbol naming instead of binding behavior and will create churn on harmless refactors.
Proposed change
- generated.Should().Contain("global::Api.GetInfo(p0)");
+ generated.Should().Contain("global::Api.GetInfo(");As per coding guidelines, ".NET tests. Review that tests assert observable behavior, not implementation details."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/ErrorOrX.Generators.Tests/ParameterBindingSpecialTypesTests.cs` around
lines 27 - 30, The test currently asserts the emitter's temporary parameter name
by checking for the exact string "global::Api.GetInfo(p0)"; change this to
assert observable behavior instead (e.g., that the generated output contains a
call to "global::Api.GetInfo(" or otherwise validates that GetInfo is invoked)
so the test no longer depends on emitter-local names; update the assertion that
references "global::Api.GetInfo(p0)" in ParameterBindingSpecialTypesTests to use
a name-agnostic check while keeping the existing check for the HttpContext
binding ("= ctx;") intact.
| public Task EOE024_Interface_Call_With_ProducesError_No_Diagnostic() | ||
| { | ||
| const string Source = """ | ||
| using ErrorOr; | ||
|
|
||
| namespace DiagnosticTest; | ||
|
|
||
| public interface ITodoService | ||
| { | ||
| ErrorOr<string> GetById(int id); | ||
| } | ||
|
|
||
| public static class TodoApi | ||
| { | ||
| [Get("/todos/{id}")] | ||
| [ProducesError(404, "NotFound")] | ||
| public static ErrorOr<string> GetById(int id, ITodoService svc) | ||
| => svc.GetById(id); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify attribute contract usage for EOE024 and related extraction path.
rg -n -C3 '\b(ProducesError|ReturnsError)\s*\(' tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs
rg -n -C3 'UndocumentedInterfaceCall|ReturnsError|ProducesError' src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Extractor.InterfaceDetection.csRepository: ANcpLua/ErrorOrX
Length of output: 4488
🏁 Script executed:
rg -n 'ReturnsError|ProducesError' tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs | head -30Repository: ANcpLua/ErrorOrX
Length of output: 260
🏁 Script executed:
grep -n "public Task.*EOE024" tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.csRepository: ANcpLua/ErrorOrX
Length of output: 192
🏁 Script executed:
sed -n '73,96p' tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.csRepository: ANcpLua/ErrorOrX
Length of output: 871
EOE024 no-diagnostic case exercises the fallback path, not the documented interface metadata contract.
The test lacks coverage of the primary code path: extracting [ReturnsError] from interface method declarations. Line 112 places [ProducesError(...)] on the handler, which satisfies the generator's escape-hatch logic (line 57 of InterfaceDetection.cs: "endpoint already has [ProducesError], assume developer knows what they're doing"), but this bypasses the intended interface metadata extraction altogether. The test can pass without the generator ever executing the TryExtractReturnsErrorAttributes path.
Per coding guidelines and the generator's own design, [ReturnsError] belongs on the interface method declaration to document the error contract at the source. The test should validate this primary path.
Proposed fix
public interface ITodoService
{
+ [ReturnsError(ErrorType.NotFound, "NotFound")]
ErrorOr<string> GetById(int id);
}
public static class TodoApi
{
[Get("/todos/{id}")]
- [ProducesError(404, "NotFound")]
public static ErrorOr<string> GetById(int id, ITodoService svc)
=> svc.GetById(id);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public Task EOE024_Interface_Call_With_ProducesError_No_Diagnostic() | |
| { | |
| const string Source = """ | |
| using ErrorOr; | |
| namespace DiagnosticTest; | |
| public interface ITodoService | |
| { | |
| ErrorOr<string> GetById(int id); | |
| } | |
| public static class TodoApi | |
| { | |
| [Get("/todos/{id}")] | |
| [ProducesError(404, "NotFound")] | |
| public static ErrorOr<string> GetById(int id, ITodoService svc) | |
| => svc.GetById(id); | |
| public Task EOE024_Interface_Call_With_ProducesError_No_Diagnostic() | |
| { | |
| const string Source = """ | |
| using ErrorOr; | |
| namespace DiagnosticTest; | |
| public interface ITodoService | |
| { | |
| [ReturnsError(ErrorType.NotFound, "NotFound")] | |
| ErrorOr<string> GetById(int id); | |
| } | |
| public static class TodoApi | |
| { | |
| [Get("/todos/{id}")] | |
| public static ErrorOr<string> GetById(int id, ITodoService svc) | |
| => svc.GetById(id); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/ErrorOrX.Generators.Tests/UnionTypeAndFactoryTests.cs` around lines 97
- 114, The test EOE024_Interface_Call_With_ProducesError_No_Diagnostic is
exercising the generator fallback by placing [ProducesError(404, "NotFound")] on
the handler instead of the interface; move the attribute to the interface method
so the generator's interface metadata extraction is exercised: add
[ProducesError(404, "NotFound")] to ITodoService.GetById and remove it from
TodoApi.GetById, ensuring the generator path that invokes
TryExtractReturnsErrorAttributes in InterfaceDetection.cs is covered.
Summary
Brings every
.cssource/test file in the repo under a 500-LOC threshold by splitting along natural responsibility seams. Verifieddotnet build+dotnet test --solution ErrorOrX.slnx(443 passed, 0 failed) at every step.Threshold
No explicit file-length setting exists in
.editorconfig,Directory.Build.props,.globalconfig, ruleset, or per-repo CLAUDE.md. SonarAnalyzer S104 defaults to 1000 lines and is not configured. Per the task spec, this PR uses 500 LOC for.cs— aggressive but matches the repo's existing partial-class / one-type-per-file conventions.Before / After (baseline → final)
Core/ErrorOrEndpointGenerator.ParameterBinding.cs.Meta+.Classifierspartialstests/.../ParameterBindingTests.csCore/ErrorOrEndpointGenerator.Emitter.cs.Versioning+.Invoker+.ErrorHandlingpartialsModels/EndpointModels.csCore/ErrorOrEndpointGenerator.Extractor.cs.ErrorInference+.InterfaceDetectionpartialstests/.../ErrorOrTests.cstests/.../MiddlewareEmissionTests.csCore/ErrorOrEndpointGenerator.Initialize.cs.Attributes+.EndpointFlowpartialsEmitters/BindingCodeEmitter.cspartial, split into 4 per-source filesAnalyzers/ErrorOrEndpointAnalyzer.cspartial, split into 3 filesPlus three commits inherited from the earlier branch state:
OpenApiTransformerGenerator.cs(810→1),DiagnosticTests.cs(split into 5), and an earlierendpoint generator internalssplit (the original 4.Emitter.*/.Extractor.Metadatapartials).Result: zero
.csfiles over 500 LOC remain (find . -name '*.cs' -not -path '*/bin/*' -not -path '*/obj/*' -not -path '*/Generated/*' -exec wc -l {} + | awk '\$1 > 500'returns empty).Public-API changes
None. Every split preserved the public surface:
ErrorOrEndpointGenerator(theIIncrementalGenerator) — stillpublic sealed partial class, sameInitialize(...)entry point.ErrorOrEndpointAnalyzer(theDiagnosticAnalyzer) — becamepublic sealed partial class, sameSupportedDiagnosticsarray +Initializeoverride.BindingCodeEmitter(consumed viaBindingCodeEmitter.EmitX(...)fromEmitter.Invoker.csand elsewhere) — becameinternal static partial class. No call sites changed.Models/EndpointModels.cstypes —internal readonly record struct/internal enum. Namespace unchanged (ErrorOr.Generators, note: deliberately notErrorOrX.Generators).AttributeNameMatcher— relocated fromParameterBinding.cstoParameterBinding.Meta.cswhere it's actually used byExtractKeyFromKeyedServiceAttribute(inTypeAnalysis.cs). It'sprivate, no external surface.Logic bugs fixed
None. This PR is mechanical refactoring; no behavior changes. Two minor opportunities were noted but not taken because they exceed scope:
BindingCodeEmitter.GetTryParseExpressionis a hand-rolled 40-case switch on type FQN. Table-driven rewrite would be cleaner but is a separate refactor.Emitter.cs'sEmitMappingsbuilds a ~110-line literal C# scaffold viaStringBuilder.AppendLine. A raw string literal or template would be more readable but is also a separate refactor.Duplication killed
AttributeNameMatcheris now co-located with its sole consumer (ExtractKeyFromKeyedServiceAttributeinParameterBinding.TypeAnalysis.cscalls into the same partial class). Previously it sat at the bottom of the unrelated dispatcher file.Files left untouched + why
Generated/files and*.g.cs— explicitly excluded per task rules.Polyfills.cs(under 500 LOC but the.editorconfigcarries existing suppressions that document its intentional namespace-folder mismatch).RouteValidator.csat 467,Descriptors.csexactly at 500).ParameterBinding.TypeAnalysis.cs(348 LOC),Extractor.Metadata.cs(429), andOpenApiTransformerGenerator.Extractor.cs(388) sub-partials — already under threshold from the prior agent's work.Test plan
dotnet build ErrorOrX.slnx— clean (0 warnings, 0 errors)dotnet test --solution ErrorOrX.slnx— 443 passed, 0 failed🤖 Generated with Claude Code