Skip to content

fix: camelToKebab keeps underscores and non-ASCII letters readable - #42

Merged
jackielii merged 1 commit into
mainfrom
fix/kebab-separators
Oct 29, 2026
Merged

jackielii merged 1 commit into
mainfrom
fix/kebab-separators

Conversation

@jackielii

@jackielii jackielii commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

Problem

camelToKebab lowercased every rune with r | 0x20, which is only correct for ASCII capitals:

Name Id before Id now
Row_Edit row\x7f-edit (DEL control character) row-edit
row_edit row\x7fedit row-edit
Größe gröÿe größe
pageÜber pageüber page-über
ui-Kit (an import path element) ui--kit ui-kit

It also compared bytes rather than runes around multi-byte letters. These strings end up in every generated id (ID, IDTarget, and the htmx selectors' name matching), so a page field or component method named with an underscore got an id containing a control character.

Fix

  • Runes and Unicode case: walk runes, lowercase with unicode.ToLower, and find word starts with unicode.IsUpper/IsLower.
  • Separators: underscores and hyphens separate words. The result never starts or ends with a hyphen and never holds two in a row. That also covers a function's hyphenated package path element, so a generated id can't contain the -- that jackielii/structpages#41 uses as the keyed-id separator.

Compatibility

  • Unchanged: ids of names made of ASCII letters and digits. A test checks this against the old function over 100,000 random names.
  • Changed: names with underscores or non-ASCII letters. Latin-1 capitals happened to come out right before, so the difference shows outside that range and in where words split (pageÜber). A function's package path element also changes if it has leading, trailing or doubled hyphens, or a hyphen before a capital.
  • Collisions: two names that now kebab-case alike in one tree (RowEdit and Row_Edit) are reported by Mount's existing id uniqueness check. There's a test for it.

Testing

  • Root suite: passes. New tests in case_folding_test.go:
    • underscores, hyphens and non-ASCII cases;
    • the ASCII-unchanged comparison;
    • an end-to-end Row_Edit id selected by HTMXRenderTarget;
    • the Mount collision error.
  • Performance: about 15 ns slower per call for a short name, with the same single allocation.

Interaction with #41

Rebased onto main after jackielii/structpages#41 merged. #41's idSegment, which collapsed hyphen runs after camelToKebab, is now the same as camelToKebab, so this PR removes it. Its test cases moved to case_folding_test.go.

@codecov

codecov Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.09%. Comparing base (e26ca64) to head (8562fb1).

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #42      +/-   ##
==========================================
+ Coverage   95.72%   96.09%   +0.37%     
==========================================
  Files          20       20              
  Lines        1870     1869       -1     
==========================================
+ Hits         1790     1796       +6     
+ Misses         48       43       -5     
+ Partials       32       30       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

camelToKebab lowercased every rune with r | 0x20, which is only right for
ASCII capitals. An underscore (0x5F) became DEL (0x7F), so a page field
or component named Row_Edit got the id "row\x7f-edit", and letters
outside Latin-1 were garbled ("Größe" became "gröÿe"). It also compared
bytes, not runes, around multi-byte letters.

It now walks runes and lowercases with unicode.ToLower, finding word
starts with unicode.IsUpper/IsLower. Underscores and hyphens separate
words: "Row_Edit" and "row_edit" become "row-edit", and a hyphenated
import path element such as "ui-Kit" becomes "ui-kit" rather than
"ui--kit". Hyphens never repeat or start or end the result.

Ids of names made of ASCII letters and digits are unchanged, checked
against the old function over 100,000 random names. Ids change only for
names with underscores, non-ASCII letters, or a function's package path
element with leading, trailing or doubled hyphens or a hyphen before a
capital. Two names that now kebab-case alike on one tree (RowEdit and
Row_Edit) are reported by Mount's id uniqueness check.

idSegment, which #41 added to collapse hyphen runs after camelToKebab,
is now camelToKebab itself and is removed.
@jackielii
jackielii force-pushed the fix/kebab-separators branch from 4f01c3a to 8562fb1 Compare October 29, 2026 20:43
@jackielii
jackielii merged commit d57b741 into main Oct 29, 2026
16 checks passed
@jackielii
jackielii deleted the fix/kebab-separators branch October 29, 2026 20:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant