Skip to content

Commit 20a3b3d

Browse files
Copilotsamtrion
andauthored
chore: Add Scope method for automatic indentation management (#15)
* chore: Initial plan * chore: Initial plan for Scope method implementation Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com> * chore: Implement Scope method for automatic indentation management with comprehensive tests Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com> * chore: Add example usage test and restore original build configuration Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com> * chore: Update the implementation * chore: Exteded `AppendXmlDocSee` and `AppendXmlDocSeeAlso` with `href` * test: Updated tests --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: samtrion <3283596+samtrion@users.noreply.github.com> Co-authored-by: Martin Stühmer <me@samtrion.net>
1 parent 45354b4 commit 20a3b3d

File tree

7 files changed

+574
-245
lines changed

7 files changed

+574
-245
lines changed

src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Append.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ public CSharpCodeBuilder Append(string? value)
197197
return this;
198198
}
199199

200-
if (value is "\n" or "\r")
200+
if (value is "\n" or "\r" or "\r\n")
201201
{
202202
return AppendLine(); // Handle new line characters
203203
}

src/NetEvolve.CodeBuilder/CSharpCodeBuilder.Documentation.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ public CSharpCodeBuilder AppendXmlDocRemarks(string? remarks)
129129

130130
return EnsureNewLineForXmlDoc()
131131
.AppendLine("/// <remarks>")
132-
.AppendLine($"/// {remarks}")
132+
.Append("/// ")
133+
.AppendLine(remarks)
133134
.AppendLine("/// </remarks>");
134135
}
135136

@@ -176,6 +177,17 @@ public CSharpCodeBuilder AppendXmlDocException(string? exceptionType, string? de
176177
.AppendLine($"/// <exception cref=\"{exceptionType}\">{description}</exception>");
177178
}
178179

180+
/// <summary>
181+
/// Appends an XML documentation exception element.
182+
/// </summary>
183+
/// <param name="description">The description of when the exception is thrown.</param>
184+
/// <typeparam name="TException">The type of exception that can be thrown. Must be a subclass of <see cref="Exception"/>.</typeparam>
185+
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
186+
/// <remarks>If the exception type or description is null or empty, the method returns without appending anything.</remarks>
187+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
188+
public CSharpCodeBuilder AppendXmlDocException<TException>(string? description)
189+
where TException : Exception => AppendXmlDocException(typeof(TException).Name, description);
190+
179191
/// <summary>
180192
/// Appends multiple XML documentation exception elements.
181193
/// </summary>
@@ -246,32 +258,34 @@ public CSharpCodeBuilder AppendXmlDocExample(IEnumerable<string>? exampleLines)
246258
/// Appends an XML documentation see element for cross-references.
247259
/// </summary>
248260
/// <param name="cref">The cross-reference to another member or type.</param>
261+
/// <param name="isHref">If set to <c>true</c>, uses 'href' instead of 'cref' for external links.</param>
249262
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
250263
/// <remarks>If the cref is null or empty, the method returns without appending anything.</remarks>
251-
public CSharpCodeBuilder AppendXmlDocSee(string? cref)
264+
public CSharpCodeBuilder AppendXmlDocSee(string? cref, bool isHref = false)
252265
{
253266
if (string.IsNullOrEmpty(cref))
254267
{
255268
return this;
256269
}
257270

258-
return EnsureNewLineForXmlDoc().AppendLine($"/// <see cref=\"{cref}\"/>");
271+
return EnsureNewLineForXmlDoc().AppendLine($"/// <see {(isHref ? "href" : "cref")}=\"{cref}\"/>");
259272
}
260273

261274
/// <summary>
262275
/// Appends an XML documentation seealso element for see-also references.
263276
/// </summary>
264277
/// <param name="cref">The cross-reference to another member or type.</param>
278+
/// <param name="isHref">If set to <c>true</c>, uses 'href' instead of 'cref' for external links.</param>
265279
/// <returns>The current <see cref="CSharpCodeBuilder"/> instance to allow for method chaining.</returns>
266280
/// <remarks>If the cref is null or empty, the method returns without appending anything.</remarks>
267-
public CSharpCodeBuilder AppendXmlDocSeeAlso(string? cref)
281+
public CSharpCodeBuilder AppendXmlDocSeeAlso(string? cref, bool isHref = false)
268282
{
269283
if (string.IsNullOrEmpty(cref))
270284
{
271285
return this;
272286
}
273287

274-
return EnsureNewLineForXmlDoc().AppendLine($"/// <seealso cref=\"{cref}\"/>");
288+
return EnsureNewLineForXmlDoc().AppendLine($"/// <seealso {(isHref ? "href" : "cref")}=\"{cref}\"/>");
275289
}
276290

277291
/// <summary>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
namespace NetEvolve.CodeBuilder;
2+
3+
using System;
4+
5+
public partial record CSharpCodeBuilder
6+
{
7+
/// <summary>
8+
/// Creates a scope that automatically manages indentation levels.
9+
/// </summary>
10+
/// <returns>A <see cref="ScopeHandler"/> that increments indentation on creation and decrements on disposal.</returns>
11+
/// <remarks>
12+
/// The returned scope handler implements <see cref="IDisposable"/> and is designed for use with
13+
/// the 'using' statement. When the scope is created, indentation is incremented by one level.
14+
/// When the scope is disposed (at the end of the using block), indentation is decremented.
15+
/// </remarks>
16+
/// <example>
17+
/// <code>
18+
/// using (builder.Scope())
19+
/// {
20+
/// builder.AppendLine("return true;");
21+
/// }
22+
/// </code>
23+
/// </example>
24+
public IDisposable Scope() => new ScopeHandler(this);
25+
26+
/// <summary>
27+
/// A disposable struct that manages indentation scope for a <see cref="CSharpCodeBuilder"/>.
28+
/// </summary>
29+
/// <remarks>
30+
/// This struct increments the indentation level when created and decrements it when disposed.
31+
/// It is designed to work with the 'using' statement to provide automatic indentation management.
32+
/// </remarks>
33+
private readonly struct ScopeHandler : IDisposable, IEquatable<ScopeHandler>
34+
{
35+
private readonly CSharpCodeBuilder _builder;
36+
37+
/// <summary>
38+
/// Initializes a new instance of the <see cref="ScopeHandler"/> struct and increments the indentation level.
39+
/// </summary>
40+
/// <param name="builder">The <see cref="CSharpCodeBuilder"/> instance to manage indentation for.</param>
41+
internal ScopeHandler(CSharpCodeBuilder builder)
42+
{
43+
_builder = builder;
44+
_ = _builder.Append('{');
45+
}
46+
47+
/// <summary>
48+
/// Decrements the indentation level when the scope is disposed.
49+
/// </summary>
50+
/// <remarks>
51+
/// This method is called automatically when the 'using' statement block ends.
52+
/// </remarks>
53+
public void Dispose() => _ = _builder.Append('}');
54+
55+
/// <summary>
56+
/// Determines whether the specified object is equal to the current instance.
57+
/// </summary>
58+
/// <param name="obj">The object to compare with the current instance.</param>
59+
/// <returns>Always returns <c>false</c> since ScopeHandler instances should not be compared.</returns>
60+
public override readonly bool Equals(object? obj) => false;
61+
62+
/// <summary>
63+
/// Determines whether the specified ScopeHandler is equal to the current instance.
64+
/// </summary>
65+
/// <param name="other">The ScopeHandler to compare with the current instance.</param>
66+
/// <returns>Always returns <c>false</c> since ScopeHandler instances should not be compared.</returns>
67+
public readonly bool Equals(ScopeHandler other) => false;
68+
69+
/// <summary>
70+
/// Returns the hash code for this instance.
71+
/// </summary>
72+
/// <returns>A hash code based on the internal builder reference.</returns>
73+
public override readonly int GetHashCode() => _builder?.GetHashCode() ?? 0;
74+
75+
/// <summary>
76+
/// Determines whether two ScopeHandler instances are equal.
77+
/// </summary>
78+
/// <param name="_">The first instance to compare.</param>
79+
/// <param name="__">The second instance to compare.</param>
80+
/// <returns>Always returns <c>false</c> since ScopeHandler instances should not be compared.</returns>
81+
public static bool operator ==(ScopeHandler _, ScopeHandler __) => false;
82+
83+
/// <summary>
84+
/// Determines whether two ScopeHandler instances are not equal.
85+
/// </summary>
86+
/// <param name="_">The first instance to compare.</param>
87+
/// <param name="__">The second instance to compare.</param>
88+
/// <returns>Always returns <c>true</c> since ScopeHandler instances should not be compared.</returns>
89+
public static bool operator !=(ScopeHandler _, ScopeHandler __) => true;
90+
}
91+
}

tests/NetEvolve.CodeBuilder.Tests.Integration/CSharpCodeBuilderTests.Formatting.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace NetEvolve.CodeBuilder.Tests.Integration;
1+
namespace NetEvolve.CodeBuilder.Tests.Integration;
22

33
public partial class CSharpCodeBuilderTests
44
{
@@ -114,4 +114,30 @@ public async Task GenerateEnum_WithDocumentation_Should_ProduceCorrectOutput()
114114

115115
_ = await Verify(result);
116116
}
117+
118+
[Test]
119+
public async Task Scopes_in_wild_nature()
120+
{
121+
var builder = new CSharpCodeBuilder()
122+
.AppendLine("namespace Hello.World;")
123+
.AppendLine()
124+
.AppendXmlDocSummary("Represents the status of an order.")
125+
.AppendLine("public enum Weekday");
126+
127+
using (builder.Scope())
128+
{
129+
_ = builder
130+
.AppendLine("Monday,")
131+
.AppendLine("Tuesday,")
132+
.AppendLine("Wednesday,")
133+
.AppendLine("Thursday,")
134+
.AppendLine("Friday,")
135+
.AppendLine("Saturday,")
136+
.AppendLine("Sunday,");
137+
}
138+
139+
var result = builder.ToString();
140+
141+
_ = await Verify(result);
142+
}
117143
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace Hello.World;
2+
3+
/// <summary>
4+
/// Represents the status of an order.
5+
/// </summary>
6+
public enum Weekday
7+
{
8+
Monday,
9+
Tuesday,
10+
Wednesday,
11+
Thursday,
12+
Friday,
13+
Saturday,
14+
Sunday,
15+
}

0 commit comments

Comments
 (0)