Skip to content

fix(utils): keep capitalize from lowercasing the rest of the string - #876

Open
pucedoteth wants to merge 1 commit into
coinbase:masterfrom
pucedoteth:fix-capitalize-preserves-remaining-characters
Open

fix(utils): keep capitalize from lowercasing the rest of the string#876
pucedoteth wants to merge 1 commit into
coinbase:masterfrom
pucedoteth:fix-capitalize-preserves-remaining-characters

Conversation

@pucedoteth

Copy link
Copy Markdown

What

capitalize is declared as returning Capitalize<T>, which uppercases only the first character and leaves the remainder untouched. The implementation lowercases everything after it:

export const capitalize = <T extends string>(str: T): Capitalize<T> => {
  return (str.charAt(0).toUpperCase() + str.toLowerCase().slice(1)) as Capitalize<T>;
};

So the value contradicts its own type:

call declared type actual value
capitalize('USD') 'USD' 'Usd'
capitalize('ETH') 'ETH' 'Eth'
capitalize('helloWorld') 'HelloWorld' 'Helloworld'
capitalize('a11yLabel') 'A11yLabel' 'A11ylabel'

Because the declared type is a literal, TypeScript accepts the result wherever the original literal is required, while the runtime value differs. The as Capitalize<T> assertion is what lets that through.

Why this direction

capitalize could equally have been meant as lodash-style ("first upper, rest lower"), in which case the type would be the thing to change. Two reasons for fixing the value instead:

  1. The return type is explicit and matches a TypeScript built-in with defined semantics, and it is the part consumers can actually rely on at compile time.
  2. It is behaviour-preserving for the pairing that exists in this module. wordCase is decamelize(str, { separator: ' ' }), whose output is already lowercase, so capitalize(wordCase('helloWorld')) is 'Hello world' before and after.

The output only changes for inputs carrying uppercase characters past the first position, which are exactly the inputs whose current result violates Capitalize<T>.

If you would rather keep the lowercasing and relax the type instead, that is a one-line change in the other direction and I am happy to switch.

Tests

packages/utils had no test files. Added packages/utils/src/string.test.ts covering capitalize (including the empty string, an already-capitalized input, and the wordCase pairing), the case helpers, and the css var helpers.

Verification, and what I could not run

I could not run yarn nx run utils:test here. The repo is ~1 GB and I worked from a partial clone, so the working tree is not complete enough to install and run the monorepo toolchain. Flagging that rather than implying the suite passed.

What I did do: executed every assertion in the new test file against the real humps build, since camelCase/kebabCase/snakeCase/wordCase delegate to it. All 13 hold, including the capitalize(wordCase(...)) case. The capitalize change itself is one expression and is covered by the table above.

capitalize is declared as returning Capitalize<T>, which uppercases only the
first character and leaves the remainder untouched. The implementation
lowercased everything after it:

    str.charAt(0).toUpperCase() + str.toLowerCase().slice(1)

so the value contradicted its own type:

    capitalize('USD')         // typed 'USD',        returns 'Usd'
    capitalize('helloWorld')  // typed 'HelloWorld', returns 'Helloworld'
    capitalize('a11yLabel')   // typed 'A11yLabel',  returns 'A11ylabel'

Because the declared type is a literal, TypeScript will accept the result
wherever the original literal is required, while the runtime value differs.

Drop the toLowerCase() call. Pairing with wordCase is unaffected: decamelize
already lowercases its output, so capitalize(wordCase('helloWorld')) is
'Hello world' either way. The output only changes for inputs that carry
uppercase characters past the first position, which are exactly the inputs
whose current result violates Capitalize<T>.

packages/utils had no tests. Added string.test.ts covering capitalize, the
case helpers and the css var helpers.
@cb-heimdall

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 1
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1
CODEOWNERS 🟡 See below

🟡 CODEOWNERS

Code Owner Status Calculation
ui-systems-eng-team 🟡 0/1
Denominator calculation
Additional CODEOWNERS Requirement
Show calculation
Sum 0
0
From CODEOWNERS 1
Sum 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants