Skip to content

Commit 14db148

Browse files
ANcpLuaclaude
andcommitted
refactor(generators): apply Append form only where it drops {{ }} escaping
Follow-up to the MA0028 decision: instead of the all-or-nothing bulk, the emitter now uses the StringBuilder.Append chain at exactly the sites where it removes literal-brace `{{ }}` escaping from an interpolated emit template, and keeps interpolation everywhere else. Converted (literal braces, low fragmentation): - BindingCodeEmitter.Parsing.cs: `$"{pad}{{"` / `$"{pad}}}"` block braces -> Append("{")/("}") - BindingCodeEmitter.Query.cs: `is {{ Length: > 0 }}` property pattern (x2) - OpenApiTransformerGenerator.Emitter.cs: `new OpenApiTag { Name = ... }` - Emitter.ErrorHandling.cs / Emitter.Support.cs: `first.Type switch { ... }` + the `{ hasValidation... }` loop body Reverted back to interpolation (no braces — pure fragmentation, false positives): - Emitter.Support.cs: `$"{caseExpr},"`, `_ => {...}` - Emitter.Invoker.cs: BindFail local-function lines (kept the blank-line dedup) Left interpolated on purpose (braces present but >2 other substitutions, or a string expression not a builder call): Parsing `new[] { v };`, ValidationResolver ctor `{ }`, ErrorHandling Created/nested-message, Invoker/ErrorOrContext exprs. Also keeps two unrelated cleanups: Analyzer.cs (indentation + lambda->method group) and ParameterBinding.Classifiers.cs (indentation of flush-left ctor args). Byte-identical output: build 0/0, 156/156 generator tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ef5bb07 commit 14db148

8 files changed

Lines changed: 17 additions & 22 deletions

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Analyzer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ private static void AnalyzeJsonContextCoverage(
8888
foreach (var ctx in userContexts)
8989
{
9090
foreach (var typeFqn in ctx.SerializableTypes)
91-
registeredTypes.Add(typeFqn);
91+
registeredTypes.Add(typeFqn);
9292
}
9393

9494
// Combine all needed types
@@ -109,7 +109,7 @@ private static void AnalyzeJsonContextCoverage(
109109
{
110110
if (kvp.Key.IsPrimitiveJsonType()) continue;
111111

112-
var isRegistered = registeredTypes.Any(rt => kvp.Key.TypeNamesEqual(rt));
112+
var isRegistered = registeredTypes.Any(kvp.Key.TypeNamesEqual);
113113
if (!isRegistered)
114114
{
115115
var displayType = kvp.Key.StripGlobalPrefix();

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.ErrorHandling.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,7 @@ private static void EmitProblemDetailsBuilding(StringBuilder code)
7878
code.AppendLine(" {");
7979
code.AppendLine(" Title = first.Code,");
8080
code.AppendLine(" Detail = first.Description,");
81-
code.AppendLine(
82-
$" Status = first.Type switch {{ {ErrorMapping.GenerateStatusSwitch(WellKnownTypes.Fqn.ErrorType)} }}");
81+
code.Append(" Status = first.Type switch { ").Append(ErrorMapping.GenerateStatusSwitch(WellKnownTypes.Fqn.ErrorType)).AppendLine(" }");
8382
code.AppendLine(" };");
8483
code.AppendLine(" problem.Type = $\"https://httpstatuses.io/{problem.Status}\";");
8584
code.AppendLine(" ApplyProblemMetadata(problem, first);");

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Invoker.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ private static void EmitBindFailHelper(StringBuilder code, string returnTypeFqn,
185185

186186
code.AppendLine($" static {returnType} BindFail(string param, string reason)");
187187
code.AppendLine($" => {returnExpr};");
188-
code.AppendLine();
189188
}
190189
else
191190
{
@@ -206,8 +205,9 @@ private static void EmitBindFailHelper(StringBuilder code, string returnTypeFqn,
206205

207206
code.AppendLine($" static {returnType} BindFail(string param, string reason)");
208207
code.AppendLine($" => {returnExpr};");
209-
code.AppendLine();
210208
}
209+
210+
code.AppendLine();
211211
}
212212

213213
private static void EmitBindFail415Helper(StringBuilder code, string returnTypeFqn, bool isAsync)

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.Emitter.Support.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ private static void EmitSupportMethods(StringBuilder code)
2727
code.AppendLine(" {");
2828
code.AppendLine($" if (errors.Count is 0) return {WellKnownTypes.Fqn.TypedResults.Problem}();");
2929
code.AppendLine(" var hasValidation = false;");
30-
code.AppendLine(
31-
$" for (var i = 0; i < errors.Count; i++) if (errors[i].Type == {WellKnownTypes.Fqn.ErrorType}.Validation) {{ hasValidation = true; break; }}");
30+
code.Append(" for (var i = 0; i < errors.Count; i++) if (errors[i].Type == ").Append(WellKnownTypes.Fqn.ErrorType).AppendLine(".Validation) { hasValidation = true; break; }");
3231
code.AppendLine(" if (hasValidation)");
3332
code.AppendLine(" {");
3433
BindingCodeEmitter.EmitValidationDictBuilder(
@@ -42,8 +41,7 @@ private static void EmitSupportMethods(StringBuilder code)
4241
code.AppendLine(" {");
4342
code.AppendLine(" Title = first.Code,");
4443
code.AppendLine(" Detail = first.Description,");
45-
code.AppendLine(
46-
$" Status = first.Type switch {{ {ErrorMapping.GenerateStatusSwitch(WellKnownTypes.Fqn.ErrorType)} }}");
44+
code.Append(" Status = first.Type switch { ").Append(ErrorMapping.GenerateStatusSwitch(WellKnownTypes.Fqn.ErrorType)).AppendLine(" }");
4745
code.AppendLine(" };");
4846
code.AppendLine(" problem.Type = $\"https://httpstatuses.io/{problem.Status}\";");
4947
code.AppendLine(" ApplyProblemMetadata(problem, first);");

src/ErrorOrX.Generators/Core/ErrorOrEndpointGenerator.ParameterBinding.Classifiers.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,11 +333,11 @@ private static ParameterClassificationResult ClassifyAsParameters(
333333
meta.Name,
334334
meta.TypeFqn,
335335
ParameterSource.AsParameters,
336-
KeyName: null,
336+
KeyName: null,
337337
meta.IsNullable,
338338
meta.IsNonNullableValueType,
339-
IsCollection: false,
340-
CollectionItemTypeFqn: null,
339+
IsCollection: false,
340+
CollectionItemTypeFqn: null,
341341
new EquatableArray<EndpointParameter>(children.ToImmutable()),
342342
CustomBindingMethod.None,
343343
meta.RequiresValidation,

src/ErrorOrX.Generators/Core/OpenApiTransformerGenerator.Emitter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ private static void EmitTagTransformer(StringBuilder code, string tagName)
7070
// OpenApiDocument.Tags setter auto-wraps with OpenApiTagComparer.Instance
7171
// which handles deduplication by Name - no manual .Any() check needed
7272
code.AppendLine(" document.Tags ??= new HashSet<OpenApiTag>();");
73-
code.AppendLine($" document.Tags.Add(new OpenApiTag {{ Name = \"{tagName}\" }});");
73+
code.Append(" document.Tags.Add(new OpenApiTag { Name = \"").Append(tagName).AppendLine("\" });");
7474
code.AppendLine(" return Task.CompletedTask;");
7575
code.AppendLine(" }");
7676
code.AppendLine("}");

src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Parsing.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ internal static void EmitValidationDictBuilder(
134134

135135
code.AppendLine($"{pad}var {dictName} = new {WellKnownTypes.Fqn.Dictionary}<string, string[]>();");
136136
code.AppendLine($"{pad}foreach (var {iteratorVar} in {iteratorSource})");
137-
code.AppendLine($"{pad}{{");
137+
code.Append(pad).AppendLine("{");
138138

139139
if (filterExpr is not null) code.AppendLine($"{pad4}if ({filterExpr}) continue;");
140140

@@ -143,12 +143,12 @@ internal static void EmitValidationDictBuilder(
143143
code.AppendLine($"{pad4}if (!{dictName}.TryGetValue({keyExpr}, out var existing))");
144144
code.AppendLine($"{pad8}{dictName}[{keyExpr}] = new[] {{ {valueExpr} }};");
145145
code.AppendLine($"{pad4}else");
146-
code.AppendLine($"{pad4}{{");
146+
code.Append(pad4).AppendLine("{");
147147
code.AppendLine($"{pad8}var arr = new string[existing.Length + 1];");
148148
code.AppendLine($"{pad8}existing.CopyTo(arr, 0);");
149149
code.AppendLine($"{pad8}arr[existing.Length] = {valueExpr};");
150150
code.AppendLine($"{pad8}{dictName}[{keyExpr}] = arr;");
151-
code.AppendLine($"{pad4}}}");
152-
code.AppendLine($"{pad}}}");
151+
code.Append(pad4).AppendLine("}");
152+
code.Append(pad).AppendLine("}");
153153
}
154154
}

src/ErrorOrX.Generators/Emitters/BindingCodeEmitter.Query.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ internal static bool EmitCollectionQueryBinding(StringBuilder code, in EndpointP
4343
var usesBindFail = false;
4444
if (itemType.IsStringType())
4545
{
46-
code.AppendLine(
47-
$" if (item is {{ Length: > 0 }} validItem) {paramName}List.Add(validItem);");
46+
code.Append(" if (item is { Length: > 0 } validItem) ").Append(paramName).AppendLine("List.Add(validItem);");
4847
}
4948
else
5049
{
@@ -131,8 +130,7 @@ internal static bool EmitHeaderBinding(StringBuilder code, in EndpointParameter
131130
{
132131
// Empty header values are dropped (BCL convention for HTTP collections); non-empty
133132
// strings always succeed because the type IS string. No failure mode for strings.
134-
code.AppendLine(
135-
$" if (item is {{ Length: > 0 }} validItem) {paramName}List.Add(validItem);");
133+
code.Append(" if (item is { Length: > 0 } validItem) ").Append(paramName).AppendLine("List.Add(validItem);");
136134
}
137135
else
138136
{

0 commit comments

Comments
 (0)