Skip to content

Commit c6e5af0

Browse files
feat: Addedd ReadOnlySpan, Refactor to class, improve XML docs (#187)
* feat: Addedd ReadOnlySpan, Refactor to class, improve XML docs - Refactored CSharpCodeBuilder and CodeBuilderBase from record to class, clarifying lack of thread-safety. - Added Append, AppendIf, AppendLine, and AppendLineIf overloads for ReadOnlySpan<char> (and slices), with .NET Standard 2.0 compatibility. - Expanded and clarified XML documentation, especially for special character handling and collection-based doc methods. - Refined XML doc comment generation to use Append/AppendLine for consistency. - Renamed Intend() to Indent(); marked Intend() as obsolete. - Improved Clear() and indentation level management (no longer uses Interlocked). - Enhanced handling of closing braces/brackets in Append(string). - Updated .editorconfig with static readonly naming rules and other fixes. - Various minor code and documentation cleanups for clarity and consistency. * fix: Update src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Martin Stühmer <me@samtrion.net> * feat: Add interpolated string support and FormattableString overloads Added AppendInterpolated/AppendLineInterpolated methods with a custom interpolated string handler for efficient, culture-invariant code generation (C# 10+). Introduced FormattableString overloads for AppendFormat and AppendLineFormat. Added comprehensive unit tests for new features. Improved code formatting and performed minor test cleanup. * fix: Remove AppendFormatted(string?) from interpolated handler Removed the AppendFormatted(string?) method from CSharpInterpolatedStringHandler, as its functionality is now redundant. Also updated the #if NET6_0_OR_GREATER directive to wrap the namespace declaration for improved clarity and consistency. --------- Signed-off-by: Martin Stühmer <me@samtrion.net> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 9e76634 commit c6e5af0

37 files changed

Lines changed: 1921 additions & 457 deletions

File tree

.editorconfig

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,6 @@ insert_final_newline = false
8787
[*.sln]
8888
indent_style = tab
8989

90-
[*.{received,verified}.txt]
91-
insert_final_newline = false
92-
trim_trailing_whitespace = false
93-
9490
[*.{cs,csx,vb,vbx}]
9591
# .NET Code Style Settings
9692
# See https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference
@@ -132,9 +128,16 @@ dotnet_naming_style.all_const.capitalization = pascal
132128
dotnet_naming_symbols.all_const.applicable_kinds = field
133129
dotnet_naming_symbols.all_const.required_modifiers = const
134130
dotnet_naming_rule.all_const.severity = error
135-
dotnet_naming_rule.all_const.style = all_elements
131+
dotnet_naming_rule.all_const.style = all_const
136132
dotnet_naming_rule.all_const.symbols = all_const
137133

134+
dotnet_naming_style.all_static_readonly.capitalization = pascal_case
135+
dotnet_naming_symbols.all_static_readonly.applicable_kinds = field
136+
dotnet_naming_symbols.all_static_readonly.required_modifiers = static, readonly
137+
dotnet_naming_rule.all_static_readonly.severity = error
138+
dotnet_naming_rule.all_static_readonly.style = all_static_readonly
139+
dotnet_naming_rule.all_static_readonly.symbols = all_static_readonly
140+
138141
dotnet_naming_style.all_fields.required_prefix = _
139142
dotnet_naming_style.all_fields.capitalization = camel_case
140143
dotnet_naming_symbols.all_fields.applicable_kinds = field
@@ -267,18 +270,6 @@ dotnet_diagnostic.IDE0290.severity = sugges
267270
# [CSharpier] Incompatible rules deactivated
268271
# https://csharpier.com/docs/IntegratingWithLinters#code-analysis-rules
269272
dotnet_diagnostic.IDE0055.severity = none
270-
dotnet_diagnostic.SA1000.severity = none
271-
dotnet_diagnostic.SA1009.severity = none
272-
dotnet_diagnostic.SA1111.severity = none
273-
dotnet_diagnostic.SA1118.severity = none
274-
dotnet_diagnostic.SA1137.severity = none
275-
dotnet_diagnostic.SA1413.severity = none
276-
dotnet_diagnostic.SA1500.severity = none
277-
dotnet_diagnostic.SA1501.severity = none
278-
dotnet_diagnostic.SA1502.severity = none
279-
dotnet_diagnostic.SA1504.severity = none
280-
dotnet_diagnostic.SA1515.severity = none
281-
dotnet_diagnostic.SA1516.severity = none
282273

283274
# Support for NetEvolve.Arguments Methods
284275
# https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1062#null-check-validation-methods

src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
namespace NetEvolve.CodeBuilder;
1+
namespace NetEvolve.CodeBuilder;
22

33
using System;
44

5-
public partial record CSharpCodeBuilder
5+
public partial class CSharpCodeBuilder
66
{
77
/// <summary>
88
/// Appends a boolean value to the current builder.
99
/// </summary>
1010
/// <param name="value">The boolean value to append.</param>
1111
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
12-
/// <remarks>Appends either "True" or "False" based on the value.</remarks>
12+
/// <remarks>Appends either <see langword="true"/> or <see langword="false"/> based on the value.</remarks>
1313
public CSharpCodeBuilder Append(bool value)
1414
{
1515
EnsureIndented();
@@ -36,6 +36,16 @@ public CSharpCodeBuilder Append(char value, int repeatCount)
3636
/// </summary>
3737
/// <param name="value">The character to append.</param>
3838
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
39+
/// <remarks>
40+
/// The following characters receive special treatment:
41+
/// <list type="bullet">
42+
/// <item><description><c>'\0'</c> — ignored; the method returns without appending.</description></item>
43+
/// <item><description><c>'\n'</c> or <c>'\r'</c> — treated as a line terminator; equivalent to calling <see cref="AppendLine()"/>.</description></item>
44+
/// <item><description><c>'{'</c> or <c>'['</c> — appended with the current indentation, then the indentation level is incremented and a line terminator is added.</description></item>
45+
/// <item><description><c>'}'</c> or <c>']'</c> — the indentation level is decremented first, then the character is appended with the new indentation level, followed by a line terminator.</description></item>
46+
/// </list>
47+
/// All other characters are appended after applying the current indentation at the start of a new line.
48+
/// </remarks>
3949
public CSharpCodeBuilder Append(char value)
4050
{
4151
if (value is '\0')
@@ -164,6 +174,53 @@ public CSharpCodeBuilder Append(ReadOnlyMemory<char> value, int startIndex, int
164174
return this;
165175
}
166176

177+
/// <summary>
178+
/// Appends a read-only span of characters to the current builder.
179+
/// </summary>
180+
/// <param name="value">The read-only span containing the characters to append.</param>
181+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
182+
/// <remarks>If the span is empty, the method returns without appending anything.</remarks>
183+
public CSharpCodeBuilder Append(ReadOnlySpan<char> value)
184+
{
185+
if (value.IsEmpty)
186+
{
187+
return this;
188+
}
189+
190+
EnsureIndented();
191+
#if NETSTANDARD2_0
192+
_ = _builder.Append(value.ToString());
193+
#else
194+
_ = _builder.Append(value);
195+
#endif
196+
return this;
197+
}
198+
199+
/// <summary>
200+
/// Appends a subset of a read-only span of characters to the current builder.
201+
/// </summary>
202+
/// <param name="value">The read-only span containing the characters to append.</param>
203+
/// <param name="startIndex">The starting position in the span.</param>
204+
/// <param name="count">The number of characters to append.</param>
205+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
206+
/// <remarks>If the span is empty, the method returns without appending anything.</remarks>
207+
public CSharpCodeBuilder Append(ReadOnlySpan<char> value, int startIndex, int count)
208+
{
209+
var slice = value.Slice(startIndex, count);
210+
if (slice.IsEmpty)
211+
{
212+
return this;
213+
}
214+
215+
EnsureIndented();
216+
#if NETSTANDARD2_0
217+
_ = _builder.Append(slice.ToString());
218+
#else
219+
_ = _builder.Append(slice);
220+
#endif
221+
return this;
222+
}
223+
167224
/// <summary>
168225
/// Appends a subset of a string to the current builder.
169226
/// </summary>
@@ -189,7 +246,16 @@ public CSharpCodeBuilder Append(string? value, int startIndex, int count)
189246
/// </summary>
190247
/// <param name="value">The string to append.</param>
191248
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
192-
/// <remarks>If the string is null or empty, the method returns without appending anything.</remarks>
249+
/// <remarks>
250+
/// <c>null</c>, an empty string, or <c>"\0"</c> are ignored; the method returns without appending.
251+
/// The following single-character strings receive special treatment:
252+
/// <list type="bullet">
253+
/// <item><description><c>"\n"</c>, <c>"\r"</c>, or <c>"\r\n"</c> — treated as a line terminator; equivalent to calling <see cref="AppendLine()"/>.</description></item>
254+
/// <item><description><c>"{"</c> or <c>"["</c> — appended with the current indentation, then the indentation level is incremented and a line terminator is added.</description></item>
255+
/// <item><description><c>"}"</c> or <c>"]"</c> — the indentation level is decremented first; if the current position is mid-line, a line terminator is inserted before the character. The character is then appended with the new indentation level, followed by a line terminator. This behavior is consistent with <see cref="Append(char)"/>.</description></item>
256+
/// </list>
257+
/// All other strings are appended after applying the current indentation at the start of a new line.
258+
/// </remarks>
193259
public CSharpCodeBuilder Append(string? value)
194260
{
195261
if (string.IsNullOrEmpty(value) || value is "\0")
@@ -205,7 +271,10 @@ public CSharpCodeBuilder Append(string? value)
205271
if (value is "}" or "]")
206272
{
207273
DecrementIndent();
208-
_ = AppendLine();
274+
if (!_isNewline)
275+
{
276+
_ = AppendLine(); // Ensure we start a new line before the closing brace
277+
}
209278
}
210279

211280
EnsureIndented();
@@ -216,6 +285,10 @@ public CSharpCodeBuilder Append(string? value)
216285
IncrementIndent();
217286
_ = AppendLine();
218287
}
288+
else if (value is "}" or "]")
289+
{
290+
_ = AppendLine(); // Newline after closing brace, consistent with char overload
291+
}
219292

220293
return this;
221294
}

src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendFormat.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Globalization;
55
using System.Runtime.CompilerServices;
66

7-
public partial record CSharpCodeBuilder
7+
public partial class CSharpCodeBuilder
88
{
99
/// <summary>
1010
/// Appends a formatted string to the current builder using invariant culture.
@@ -45,4 +45,68 @@ public CSharpCodeBuilder AppendFormat(IFormatProvider? provider, string format,
4545
_ = _builder.AppendFormat(provider, format, args);
4646
return this;
4747
}
48+
49+
/// <summary>
50+
/// Appends a formattable string to the current builder using invariant culture.
51+
/// </summary>
52+
/// <param name="formattable">The formattable string to append.</param>
53+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
54+
/// <remarks>If <paramref name="formattable"/> is <see langword="null"/>, the method returns without appending anything.</remarks>
55+
public CSharpCodeBuilder AppendFormat(FormattableString? formattable)
56+
{
57+
if (formattable is null)
58+
{
59+
return this;
60+
}
61+
62+
EnsureIndented();
63+
_ = _builder.Append(formattable.ToString(CultureInfo.InvariantCulture));
64+
return this;
65+
}
66+
67+
/// <summary>
68+
/// Appends a formatted string followed by a line terminator to the current builder using invariant culture.
69+
/// </summary>
70+
/// <param name="format">A composite format string.</param>
71+
/// <param name="arg0">The object to format.</param>
72+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
73+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="format"/> is <see langword="null"/>.</exception>
74+
/// <exception cref="FormatException">Thrown when <paramref name="format"/> is invalid or the index of a format item is greater than zero.</exception>
75+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
76+
public CSharpCodeBuilder AppendLineFormat(string format, object? arg0) =>
77+
AppendFormat(CultureInfo.InvariantCulture, format, arg0).AppendLine();
78+
79+
/// <summary>
80+
/// Appends a formatted string followed by a line terminator to the current builder using invariant culture.
81+
/// </summary>
82+
/// <param name="format">A composite format string.</param>
83+
/// <param name="args">An array of objects to format.</param>
84+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
85+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="format"/> is <see langword="null"/>.</exception>
86+
/// <exception cref="FormatException">Thrown when <paramref name="format"/> is invalid or the index of a format item is greater than the number of elements in <paramref name="args"/> minus 1.</exception>
87+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
88+
public CSharpCodeBuilder AppendLineFormat(string format, params object?[] args) =>
89+
AppendFormat(CultureInfo.InvariantCulture, format, args).AppendLine();
90+
91+
/// <summary>
92+
/// Appends a formatted string followed by a line terminator to the current builder using the specified format provider.
93+
/// </summary>
94+
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
95+
/// <param name="format">A composite format string.</param>
96+
/// <param name="args">An array of objects to format.</param>
97+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
98+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="format"/> is <see langword="null"/>.</exception>
99+
/// <exception cref="FormatException">Thrown when <paramref name="format"/> is invalid or the index of a format item is greater than the number of elements in <paramref name="args"/> minus 1.</exception>
100+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
101+
public CSharpCodeBuilder AppendLineFormat(IFormatProvider? provider, string format, params object?[] args) =>
102+
AppendFormat(provider, format, args).AppendLine();
103+
104+
/// <summary>
105+
/// Appends a formattable string followed by a line terminator to the current builder using invariant culture.
106+
/// </summary>
107+
/// <param name="formattable">The formattable string to append.</param>
108+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
109+
/// <remarks>If <paramref name="formattable"/> is <see langword="null"/>, only the line terminator is appended.</remarks>
110+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
111+
public CSharpCodeBuilder AppendLineFormat(FormattableString? formattable) => AppendFormat(formattable).AppendLine();
48112
}

src/NetEvolve.CodeBuilder/CSharpCodeBuilder.AppendIf.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
namespace NetEvolve.CodeBuilder;
1+
namespace NetEvolve.CodeBuilder;
22

33
using System;
44
using System.Runtime.CompilerServices;
55

6-
public partial record CSharpCodeBuilder
6+
public partial class CSharpCodeBuilder
77
{
88
/// <summary>
99
/// Appends a boolean value to the current builder if the specified condition is true.
1010
/// </summary>
1111
/// <param name="condition">The condition that determines whether to append the value.</param>
1212
/// <param name="value">The boolean value to append.</param>
1313
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
14-
/// <remarks>Appends either "true" or "false" based on the value if the condition is true.</remarks>
14+
/// <remarks>Appends either <see langword="true"/> or <see langword="false"/> based on the value if the condition is <see langword="true"/>.</remarks>
1515
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1616
public CSharpCodeBuilder AppendIf(bool condition, bool value) => condition ? Append(value) : this;
1717

@@ -94,6 +94,29 @@ public unsafe CSharpCodeBuilder AppendIf(bool condition, char* value, int length
9494
public CSharpCodeBuilder AppendIf(bool condition, ReadOnlyMemory<char> value, int startIndex, int count) =>
9595
condition ? Append(value, startIndex, count) : this;
9696

97+
/// <summary>
98+
/// Appends a read-only span of characters to the current builder if the specified condition is <see langword="true"/>.
99+
/// </summary>
100+
/// <param name="condition">The condition that determines whether to append the value.</param>
101+
/// <param name="value">The read-only span containing the characters to append.</param>
102+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
103+
/// <remarks>If the span is empty, the method returns without appending anything.</remarks>
104+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
105+
public CSharpCodeBuilder AppendIf(bool condition, ReadOnlySpan<char> value) => condition ? Append(value) : this;
106+
107+
/// <summary>
108+
/// Appends a subset of a read-only span of characters to the current builder if the specified condition is <see langword="true"/>.
109+
/// </summary>
110+
/// <param name="condition">The condition that determines whether to append the value.</param>
111+
/// <param name="value">The read-only span containing the characters to append.</param>
112+
/// <param name="startIndex">The starting position in the span.</param>
113+
/// <param name="count">The number of characters to append.</param>
114+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
115+
/// <remarks>If the span is empty, the method returns without appending anything.</remarks>
116+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
117+
public CSharpCodeBuilder AppendIf(bool condition, ReadOnlySpan<char> value, int startIndex, int count) =>
118+
condition ? Append(value, startIndex, count) : this;
119+
97120
/// <summary>
98121
/// Appends a subset of a string to the current builder if the specified condition is true.
99122
/// </summary>

0 commit comments

Comments
 (0)