Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Release Notes

## 8.1.8 - Apr 13 2026

- 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.

## 8.1.7 - Apr 7 2026

- 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[]`.
Expand Down
22 changes: 18 additions & 4 deletions src/FSharp.Data.Runtime.Utilities/NameUtils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,15 @@ let nicePascalName (s: string) =
let niceCamelName (s: string) =
let name = nicePascalName s

if name.Length > 0 then
name.[0].ToString().ToLowerInvariant() + name.Substring(1)
else
if name.Length = 0 || Char.IsLower(name.[0]) then
// Fast path: already starts with a lower-case letter β€” no allocation needed.
name
else
// Lower-case the first character and append the remainder.
let sb = Text.StringBuilder(name.Length)
sb.Append(Char.ToLowerInvariant(name.[0])) |> ignore
sb.Append(name, 1, name.Length - 1) |> ignore
sb.ToString()

/// Given a function to format names (such as 'niceCamelName' or 'nicePascalName')
/// returns a name generator that never returns duplicate name (by appending an
Expand Down Expand Up @@ -115,8 +120,17 @@ let uniqueGenerator (niceName: string -> string) =
let capitalizeFirstLetter (s: string) =
match s.Length with
| 0 -> ""
| _ when Char.IsUpper(s.[0]) ->
// Fast path: already starts with an upper-case letter β€” no allocation needed.
s
| 1 -> (Char.ToUpperInvariant s.[0]).ToString()
| _ -> (Char.ToUpperInvariant s.[0]).ToString() + s.Substring(1)
| _ ->
// Upper-case the first character and append the remainder via StringBuilder
// to avoid creating two intermediate strings.
let sb = Text.StringBuilder(s.Length)
sb.Append(Char.ToUpperInvariant(s.[0])) |> ignore
sb.Append(s, 1, s.Length - 1) |> ignore
sb.ToString()

/// Trim HTML tags from a given string and replace all of them with spaces
/// Multiple tags are replaced with just a single space. (This is a recursive
Expand Down
2 changes: 1 addition & 1 deletion src/FSharp.Data.Runtime.Utilities/Pluralizer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ let private adjustCase (s: string) (template: string) =
else if allUpper then
s.ToUpperInvariant()
else if firstUpper && not <| Char.IsUpper s.[0] then
s.Substring(0, 1).ToUpperInvariant() + s.Substring(1)
(Char.ToUpperInvariant s.[0]).ToString() + s.Substring(1)
else
s

Expand Down
Loading