Skip to content

Commit a575a0a

Browse files
author
LinkDotNet Bot
committed
Updating to newest release
2 parents 5f7f509 + 210d1c5 commit a575a0a

22 files changed

+338
-264
lines changed

CHANGELOG.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ All notable changes to **ValueStringBuilder** will be documented in this file. T
66

77
## [Unreleased]
88

9+
## [2.2.0] - 2025-01-25
10+
11+
### Added
12+
13+
- Added `TrimPrefix(ReadOnlySpan<char>, StringComparison)` (by yours truly (@Joy-less) in #226)
14+
- Added `TrimSuffix(ReadOnlySpan<char>, StringComparison)` (also by yours truly (@Joy-less) in #226)
15+
- Added `Insert(int, char)` overload (by yours truly (@Joy-less) in #225)
16+
- Added `Insert(int, Rune)` overload (again by yours truly (@Joy-less) in #225)
17+
- Added `Replace(Rune, Rune)` overload (see yours truly (@Joy-less) in #225)
18+
- Improved `Replace(scoped ReadOnlySpan<char>, scoped ReadOnlySpan<char>, int, int)` fallback (achieved by yours truly (@Joy-less) in #225)
19+
920
## [2.1.0] - 2025-01-14
1021

1122
### Added
@@ -445,7 +456,8 @@ This release brings extensions to the `ValueStringBuilder` API. For `v1.0` the `
445456

446457
- Initial release
447458

448-
[unreleased]: https://github.com/linkdotnet/StringBuilder/compare/2.1.0...HEAD
459+
[unreleased]: https://github.com/linkdotnet/StringBuilder/compare/2.2.0...HEAD
460+
[2.2.0]: https://github.com/linkdotnet/StringBuilder/compare/2.1.0...2.2.0
449461
[2.1.0]: https://github.com/linkdotnet/StringBuilder/compare/2.0.0...2.1.0
450462
[2.0.0]: https://github.com/linkdotnet/StringBuilder/compare/1.22.0...2.0.0
451463
[1.22.0]: https://github.com/linkdotnet/StringBuilder/compare/1.21.1...1.22.0

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<ItemGroup>
4-
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.4.0.108396">
4+
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.5.0.109200">
55
<PrivateAssets>all</PrivateAssets>
66
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
77
</PackageReference>

src/LinkDotNet.StringBuilder/IntegerSpanList.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,15 @@ public void Add(int value)
3131
{
3232
if (count >= buffer.Length)
3333
{
34-
Grow();
34+
EnsureCapacity();
3535
}
3636

3737
buffer[count] = value;
3838
count++;
3939
}
4040

4141
[MethodImpl(MethodImplOptions.AggressiveInlining)]
42-
private void Grow(int capacity = 0)
42+
private void EnsureCapacity(int capacity = 0)
4343
{
4444
var currentSize = buffer.Length;
4545
var newSize = capacity > 0 ? capacity : currentSize * 2;

src/LinkDotNet.StringBuilder/LinkDotNet.StringBuilder.csproj

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
@@ -33,16 +33,16 @@
3333
</None>
3434
</ItemGroup>
3535

36-
<PropertyGroup Label="Analyzer settings">
37-
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
38-
<EnableNETAnalyzers>true</EnableNETAnalyzers>
39-
<AnalysisLevel>8</AnalysisLevel>
40-
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
41-
<PackageLicenseExpression>MIT</PackageLicenseExpression>
42-
</PropertyGroup>
36+
<PropertyGroup Label="Analyzer settings">
37+
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
38+
<EnableNETAnalyzers>true</EnableNETAnalyzers>
39+
<AnalysisLevel>8</AnalysisLevel>
40+
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
41+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
42+
</PropertyGroup>
4343

4444
<ItemGroup>
45-
<PackageReference Include="Meziantou.Analyzer" Version="2.0.184">
45+
<PackageReference Include="Meziantou.Analyzer" Version="2.0.186">
4646
<PrivateAssets>all</PrivateAssets>
4747
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
4848
</PackageReference>

src/LinkDotNet.StringBuilder/ValueStringBuilder.Append.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public unsafe void Append(bool value)
2020

2121
if (newSize > buffer.Length)
2222
{
23-
Grow(newSize);
23+
EnsureCapacity(newSize);
2424
}
2525

2626
fixed (char* dest = &buffer[bufferPosition])
@@ -67,7 +67,7 @@ public void Append(scoped ReadOnlySpan<char> str)
6767
var newSize = str.Length + bufferPosition;
6868
if (newSize > buffer.Length)
6969
{
70-
Grow(newSize);
70+
EnsureCapacity(newSize);
7171
}
7272

7373
ref var strRef = ref MemoryMarshal.GetReference(str);
@@ -111,7 +111,7 @@ public void Append(char value)
111111
var newSize = bufferPosition + 1;
112112
if (newSize > buffer.Length)
113113
{
114-
Grow(newSize);
114+
EnsureCapacity(newSize);
115115
}
116116

117117
buffer[bufferPosition] = value;
@@ -126,14 +126,14 @@ public void Append(char value)
126126
public void Append(Rune value)
127127
{
128128
Span<char> valueChars = stackalloc char[2];
129-
int valueCharsWritten = value.EncodeToUtf16(valueChars);
130-
ReadOnlySpan<char> valueCharsReadOnly = valueChars[..valueCharsWritten];
129+
var valueCharsWritten = value.EncodeToUtf16(valueChars);
130+
ReadOnlySpan<char> valueCharsSlice = valueChars[..valueCharsWritten];
131131

132-
Append(valueCharsReadOnly);
132+
Append(valueCharsSlice);
133133
}
134134

135135
/// <summary>
136-
/// Adds the default new line separator.
136+
/// Appends <see cref="Environment.NewLine"/>.
137137
/// </summary>
138138
[MethodImpl(MethodImplOptions.AggressiveInlining)]
139139
public void AppendLine()
@@ -142,7 +142,7 @@ public void AppendLine()
142142
}
143143

144144
/// <summary>
145-
/// Calls <see cref="Append(ReadOnlySpan{char})"/> and appends a newline.
145+
/// Calls <see cref="Append(ReadOnlySpan{char})"/> and appends <see cref="Environment.NewLine"/>.
146146
/// </summary>
147147
/// <param name="str">String to be added to this builder.</param>
148148
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -163,7 +163,7 @@ public Span<char> AppendSpan(int length)
163163
var origPos = bufferPosition;
164164
if (origPos > buffer.Length - length)
165165
{
166-
Grow(length);
166+
EnsureCapacity(length);
167167
}
168168

169169
bufferPosition = origPos + length;
@@ -177,7 +177,7 @@ private void AppendSpanFormattable<T>(T value, ReadOnlySpan<char> format = defau
177177
var newSize = bufferSize + bufferPosition;
178178
if (newSize >= Capacity)
179179
{
180-
Grow(newSize);
180+
EnsureCapacity(newSize);
181181
}
182182

183183
if (!value.TryFormat(buffer[bufferPosition..], out var written, format, null))
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Buffers;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
namespace LinkDotNet.StringBuilder;
6+
7+
public ref partial struct ValueStringBuilder
8+
{
9+
/// <summary>
10+
/// Ensures the builder's buffer size is at least <paramref name="newCapacity"/>, renting a larger buffer if not.
11+
/// </summary>
12+
/// <param name="newCapacity">New capacity for the builder.</param>
13+
/// <remarks>
14+
/// If <see cref="Length"/> is already &gt;= <paramref name="newCapacity"/>, nothing is done.
15+
/// </remarks>
16+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
17+
public void EnsureCapacity(int newCapacity)
18+
{
19+
if (Length >= newCapacity)
20+
{
21+
return;
22+
}
23+
24+
var newSize = FindSmallestPowerOf2Above(newCapacity);
25+
26+
var rented = ArrayPool<char>.Shared.Rent(newSize);
27+
28+
if (bufferPosition > 0)
29+
{
30+
ref var sourceRef = ref MemoryMarshal.GetReference(buffer);
31+
ref var destinationRef = ref MemoryMarshal.GetReference(rented.AsSpan());
32+
33+
Unsafe.CopyBlock(
34+
ref Unsafe.As<char, byte>(ref destinationRef),
35+
ref Unsafe.As<char, byte>(ref sourceRef),
36+
(uint)bufferPosition * sizeof(char));
37+
}
38+
39+
if (arrayFromPool is not null)
40+
{
41+
ArrayPool<char>.Shared.Return(arrayFromPool);
42+
}
43+
44+
buffer = rented;
45+
arrayFromPool = rented;
46+
}
47+
48+
/// <summary>
49+
/// Finds the smallest power of 2 which is greater than or equal to <paramref name="minimum"/>.
50+
/// </summary>
51+
/// <param name="minimum">The value the result should be greater than or equal to.</param>
52+
/// <returns>The smallest power of 2 >= <paramref name="minimum"/>.</returns>
53+
private static int FindSmallestPowerOf2Above(int minimum)
54+
{
55+
return 1 << (int)Math.Ceiling(Math.Log2(minimum));
56+
}
57+
}

src/LinkDotNet.StringBuilder/ValueStringBuilder.Insert.cs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Runtime.CompilerServices;
2+
using System.Text;
23

34
namespace LinkDotNet.StringBuilder;
45

@@ -12,6 +13,29 @@ public ref partial struct ValueStringBuilder
1213
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1314
public void Insert(int index, bool value) => Insert(index, value.ToString());
1415

16+
/// <summary>
17+
/// Insert the string representation of the character to the builder at the given index.
18+
/// </summary>
19+
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
20+
/// <param name="value">Character to insert into this builder.</param>
21+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
22+
public void Insert(int index, char value) => Insert(index, [value]);
23+
24+
/// <summary>
25+
/// Insert the string representation of the rune to the builder at the given index.
26+
/// </summary>
27+
/// <param name="index">Index where <paramref name="value"/> should be inserted.</param>
28+
/// <param name="value">Rune to insert into this builder.</param>
29+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
30+
public void Insert(int index, Rune value)
31+
{
32+
Span<char> valueChars = stackalloc char[2];
33+
var valueCharsWritten = value.EncodeToUtf16(valueChars);
34+
ReadOnlySpan<char> valueCharsSlice = valueChars[..valueCharsWritten];
35+
36+
Insert(index, valueCharsSlice);
37+
}
38+
1539
/// <summary>
1640
/// Insert the string representation of the char to the builder at the given index.
1741
/// </summary>
@@ -45,7 +69,7 @@ public void Insert(int index, scoped ReadOnlySpan<char> value)
4569
var newLength = bufferPosition + value.Length;
4670
if (newLength > buffer.Length)
4771
{
48-
Grow(newLength);
72+
EnsureCapacity(newLength);
4973
}
5074

5175
bufferPosition = newLength;
@@ -79,7 +103,7 @@ private void InsertSpanFormattable<T>(int index, T value, scoped ReadOnlySpan<ch
79103
var newLength = bufferPosition + written;
80104
if (newLength > buffer.Length)
81105
{
82-
Grow(newLength);
106+
EnsureCapacity(newLength);
83107
}
84108

85109
bufferPosition = newLength;

src/LinkDotNet.StringBuilder/ValueStringBuilder.Replace.cs

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,6 @@ public ref partial struct ValueStringBuilder
1313
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1414
public readonly void Replace(char oldValue, char newValue) => Replace(oldValue, newValue, 0, Length);
1515

16-
/// <summary>
17-
/// Replaces all instances of one rune with another in this builder.
18-
/// </summary>
19-
/// <param name="oldValue">The rune to replace.</param>
20-
/// <param name="newValue">The rune to replace <paramref name="oldValue"/> with.</param>
21-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
22-
public void Replace(Rune oldValue, Rune newValue)
23-
{
24-
Replace(oldValue, newValue, 0, Length);
25-
}
26-
2716
/// <summary>
2817
/// Replaces all instances of one character with another in this builder.
2918
/// </summary>
@@ -34,15 +23,8 @@ public void Replace(Rune oldValue, Rune newValue)
3423
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3524
public readonly void Replace(char oldValue, char newValue, int startIndex, int count)
3625
{
37-
if (startIndex < 0)
38-
{
39-
throw new ArgumentOutOfRangeException(nameof(startIndex), "Start index can't be smaller than 0.");
40-
}
41-
42-
if (count > bufferPosition)
43-
{
44-
throw new ArgumentOutOfRangeException(nameof(count), $"Count: {count} is bigger than the current size {bufferPosition}.");
45-
}
26+
ArgumentOutOfRangeException.ThrowIfLessThan(startIndex, 0);
27+
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex + count, Length);
4628

4729
for (var i = startIndex; i < startIndex + count; i++)
4830
{
@@ -53,6 +35,14 @@ public readonly void Replace(char oldValue, char newValue, int startIndex, int c
5335
}
5436
}
5537

38+
/// <summary>
39+
/// Replaces all instances of one rune with another in this builder.
40+
/// </summary>
41+
/// <param name="oldValue">The rune to replace.</param>
42+
/// <param name="newValue">The rune to replace <paramref name="oldValue"/> with.</param>
43+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
44+
public void Replace(Rune oldValue, Rune newValue) => Replace(oldValue, newValue, 0, Length);
45+
5646
/// <summary>
5747
/// Replaces all instances of one rune with another in this builder.
5848
/// </summary>
@@ -64,14 +54,14 @@ public readonly void Replace(char oldValue, char newValue, int startIndex, int c
6454
public void Replace(Rune oldValue, Rune newValue, int startIndex, int count)
6555
{
6656
Span<char> oldValueChars = stackalloc char[2];
67-
int oldValueCharsWritten = oldValue.EncodeToUtf16(oldValueChars);
68-
ReadOnlySpan<char> oldValueCharsReadOnly = oldValueChars[..oldValueCharsWritten];
57+
var oldValueCharsWritten = oldValue.EncodeToUtf16(oldValueChars);
58+
ReadOnlySpan<char> oldValueCharsSlice = oldValueChars[..oldValueCharsWritten];
6959

7060
Span<char> newValueChars = stackalloc char[2];
71-
int newValueCharsWritten = newValue.EncodeToUtf16(newValueChars);
72-
ReadOnlySpan<char> newValueCharsReadOnly = newValueChars[..newValueCharsWritten];
61+
var newValueCharsWritten = newValue.EncodeToUtf16(newValueChars);
62+
ReadOnlySpan<char> newValueCharsSlice = newValueChars[..newValueCharsWritten];
7363

74-
Replace(oldValueCharsReadOnly, newValueCharsReadOnly, startIndex, count);
64+
Replace(oldValueCharsSlice, newValueCharsSlice, startIndex, count);
7565
}
7666

7767
/// <summary>
@@ -99,6 +89,9 @@ public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char
9989
[MethodImpl(MethodImplOptions.AggressiveInlining)]
10090
public void Replace(scoped ReadOnlySpan<char> oldValue, scoped ReadOnlySpan<char> newValue, int startIndex, int count)
10191
{
92+
ArgumentOutOfRangeException.ThrowIfLessThan(startIndex, 0);
93+
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex + count, Length);
94+
10295
var length = startIndex + count;
10396
var slice = buffer[startIndex..length];
10497

@@ -188,7 +181,7 @@ public void ReplaceGeneric<T>(ReadOnlySpan<char> oldValue, T newValue, int start
188181
}
189182
else
190183
{
191-
Replace(oldValue, (ReadOnlySpan<char>)newValue?.ToString(), startIndex, count);
184+
Replace(oldValue, (newValue?.ToString() ?? string.Empty).AsSpan(), startIndex, count);
192185
}
193186
}
194187
}

0 commit comments

Comments
 (0)