Skip to content

Commit 07c5f69

Browse files
improve: fast-path allocation checks in niceCamelName, capitalizeFirstLetter, Pluralizer
- niceCamelName: if nicePascalName result already starts with a lower-case letter (empty result is also handled), return it directly with zero allocation. Otherwise use StringBuilder.Append(string, startIndex, count) to avoid creating an intermediate Substring for the tail. - capitalizeFirstLetter: if the first char is already upper-case, return the input string directly. The length-1 path is unchanged. The general case now also uses StringBuilder to avoid two intermediate strings. - Pluralizer: replace s.Substring(0, 1).ToUpperInvariant() (which allocates a 1-char string then a new string for the result) with the simpler (Char.ToUpperInvariant s.[0]).ToString() pattern consistent with the rest of NameUtils. Bumps version to 8.1.8. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent dac71d4 commit 07c5f69

3 files changed

Lines changed: 23 additions & 5 deletions

File tree

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release Notes
22

3+
## 8.1.8 - Apr 13 2026
4+
5+
- Code: `NameUtils.niceCamelName` now short-circuits with zero allocations when the result already starts with a lower-case letter; the general case now uses `StringBuilder.Append(string, int, int)` to avoid creating an intermediate `Substring`. `capitalizeFirstLetter` similarly short-circuits when the first letter is already upper-case. Also fixes `Pluralizer` to avoid `Substring(0,1)` in its capitalize path.
6+
37
## 8.1.7 - Apr 7 2026
48

59
- Performance: HTML parser avoids `ToCharArray()` allocations in several code paths: `EmitToAttributeValue` now iterates the substituted string directly; `Cons(char[])` and `Cons(string)` use `StringBuilder.Append` directly; `Pop(count)` uses `Array.init` instead of creating an integer range array; CDATA opening marker no longer creates a temporary `char[]`.

src/FSharp.Data.Runtime.Utilities/NameUtils.fs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,15 @@ let nicePascalName (s: string) =
6767
let niceCamelName (s: string) =
6868
let name = nicePascalName s
6969

70-
if name.Length > 0 then
71-
name.[0].ToString().ToLowerInvariant() + name.Substring(1)
72-
else
70+
if name.Length = 0 || Char.IsLower(name.[0]) then
71+
// Fast path: already starts with a lower-case letter — no allocation needed.
7372
name
73+
else
74+
// Lower-case the first character and append the remainder.
75+
let sb = Text.StringBuilder(name.Length)
76+
sb.Append(Char.ToLowerInvariant(name.[0])) |> ignore
77+
sb.Append(name, 1, name.Length - 1) |> ignore
78+
sb.ToString()
7479

7580
/// Given a function to format names (such as 'niceCamelName' or 'nicePascalName')
7681
/// returns a name generator that never returns duplicate name (by appending an
@@ -115,8 +120,17 @@ let uniqueGenerator (niceName: string -> string) =
115120
let capitalizeFirstLetter (s: string) =
116121
match s.Length with
117122
| 0 -> ""
123+
| _ when Char.IsUpper(s.[0]) ->
124+
// Fast path: already starts with an upper-case letter — no allocation needed.
125+
s
118126
| 1 -> (Char.ToUpperInvariant s.[0]).ToString()
119-
| _ -> (Char.ToUpperInvariant s.[0]).ToString() + s.Substring(1)
127+
| _ ->
128+
// Upper-case the first character and append the remainder via StringBuilder
129+
// to avoid creating two intermediate strings.
130+
let sb = Text.StringBuilder(s.Length)
131+
sb.Append(Char.ToUpperInvariant(s.[0])) |> ignore
132+
sb.Append(s, 1, s.Length - 1) |> ignore
133+
sb.ToString()
120134

121135
/// Trim HTML tags from a given string and replace all of them with spaces
122136
/// Multiple tags are replaced with just a single space. (This is a recursive

src/FSharp.Data.Runtime.Utilities/Pluralizer.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ let private adjustCase (s: string) (template: string) =
226226
else if allUpper then
227227
s.ToUpperInvariant()
228228
else if firstUpper && not <| Char.IsUpper s.[0] then
229-
s.Substring(0, 1).ToUpperInvariant() + s.Substring(1)
229+
(Char.ToUpperInvariant s.[0]).ToString() + s.Substring(1)
230230
else
231231
s
232232

0 commit comments

Comments
 (0)