From 3170ee33f705655d20dcb4a5c15235a005e1c485 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 30 Nov 2023 18:21:55 +0100 Subject: [PATCH 1/3] feat(pascalCase, camelCase): move normalize behind a flag --- README.md | 6 +++--- src/index.ts | 17 +++++++++++++---- src/types.ts | 4 ++++ test/scule.test.ts | 4 ++-- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6c7991a..bc87c0c 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ import { pascalCase } from "scule"; ## Utils -### `pascalCase(str)` +### `pascalCase(str, opts?: { normalize })` Splits string and joins by PascalCase convention: @@ -38,9 +38,9 @@ pascalCase("foo-bar_baz"); // FooBarBaz ``` -**Notice:** If an uppercase letter is followed by other uppercase letters (like `FooBAR`), they are preserved. +**Notice:** If an uppercase letter is followed by other uppercase letters (like `FooBAR`), they are preserved. You can use `{ normalize: true }` for strictly following pascalCase convention. -### `camelCase` +### `camelCase(str, opts?: { normalize })` Splits string and joins by camelCase convention: diff --git a/src/index.ts b/src/index.ts index cd5d9f1..59dde0a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import type { PascalCase, SnakeCase, SplitByCase, + CaseOptions, } from "./types"; const NUMBER_CHAR_RE = /\d/; @@ -88,11 +89,15 @@ export function lowerFirst(str: S): Uncapitalize { export function pascalCase(): ""; export function pascalCase( str: T, + opts?: CaseOptions, ): PascalCase; -export function pascalCase(str?: T) { +export function pascalCase( + str?: T, + opts?: CaseOptions, +) { return str ? ((Array.isArray(str) ? str : splitByCase(str as string)) - .map((p) => upperFirst(p.toLowerCase())) + .map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)) .join("") as PascalCase) : ""; } @@ -100,9 +105,13 @@ export function pascalCase(str?: T) { export function camelCase(): ""; export function camelCase( str: T, + opts?: CaseOptions, ): CamelCase; -export function camelCase(str?: T) { - return lowerFirst(pascalCase(str || "")) as CamelCase; +export function camelCase( + str?: T, + opts?: CaseOptions, +) { + return lowerFirst(pascalCase(str || "", opts)) as CamelCase; } export function kebabCase(): ""; diff --git a/src/types.ts b/src/types.ts index 573e874..c6dbf14 100644 --- a/src/types.ts +++ b/src/types.ts @@ -36,6 +36,10 @@ type RemoveLastOfArray = T extends [...infer F, any] ? F : never; +export type CaseOptions = { + normalize?: boolean; +}; + export type SplitByCase< T, Separator extends string = Splitter, diff --git a/test/scule.test.ts b/test/scule.test.ts index cb17472..08fa309 100644 --- a/test/scule.test.ts +++ b/test/scule.test.ts @@ -47,7 +47,7 @@ describe("pascalCase", () => { ["FOO_BAR", "FooBar"], ["foo--bar-Baz", "FooBarBaz"], ])("%s => %s", (input, expected) => { - expect(pascalCase(input)).toMatchObject(expected); + expect(pascalCase(input, { normalize: true })).toMatchObject(expected); }); }); @@ -56,7 +56,7 @@ describe("camelCase", () => { ["FooBarBaz", "fooBarBaz"], ["FOO_BAR", "fooBar"], ])("%s => %s", (input, expected) => { - expect(camelCase(input)).toMatchObject(expected); + expect(camelCase(input, { normalize: true })).toMatchObject(expected); }); }); From 3302435f9d90a6273abb94a4aebb800dca535dc6 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 30 Nov 2023 18:35:21 +0100 Subject: [PATCH 2/3] update type tests --- src/index.ts | 39 +++++++++++++++++++++------------------ src/types.ts | 23 +++++++++++++++++------ test/types.test-d.ts | 38 +++++++++++++++++++------------------- 3 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/index.ts b/src/index.ts index 59dde0a..afd7485 100644 --- a/src/index.ts +++ b/src/index.ts @@ -87,31 +87,34 @@ export function lowerFirst(str: S): Uncapitalize { } export function pascalCase(): ""; -export function pascalCase( - str: T, - opts?: CaseOptions, -): PascalCase; -export function pascalCase( - str?: T, - opts?: CaseOptions, -) { +export function pascalCase< + T extends string | readonly string[], + UserCaseOptions extends CaseOptions = CaseOptions, +>(str: T, opts?: CaseOptions): PascalCase; +export function pascalCase< + T extends string | readonly string[], + UserCaseOptions extends CaseOptions = CaseOptions, +>(str?: T, opts?: UserCaseOptions) { return str ? ((Array.isArray(str) ? str : splitByCase(str as string)) .map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)) - .join("") as PascalCase) + .join("") as PascalCase) : ""; } export function camelCase(): ""; -export function camelCase( - str: T, - opts?: CaseOptions, -): CamelCase; -export function camelCase( - str?: T, - opts?: CaseOptions, -) { - return lowerFirst(pascalCase(str || "", opts)) as CamelCase; +export function camelCase< + T extends string | readonly string[], + UserCaseOptions extends CaseOptions = CaseOptions, +>(str: T, opts?: UserCaseOptions): CamelCase; +export function camelCase< + T extends string | readonly string[], + UserCaseOptions extends CaseOptions = CaseOptions, +>(str?: T, opts?: UserCaseOptions) { + return lowerFirst(pascalCase(str || "", opts)) as CamelCase< + T, + UserCaseOptions["normalize"] + >; } export function kebabCase(): ""; diff --git a/src/types.ts b/src/types.ts index c6dbf14..462fb62 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,8 +18,13 @@ type SameLetterCase< type CapitalizedWords< T extends readonly string[], Accumulator extends string = "", + Normalize extends boolean | undefined = false, > = T extends readonly [infer F extends string, ...infer R extends string[]] - ? CapitalizedWords>}`> + ? CapitalizedWords< + R, + `${Accumulator}${Capitalize : F>}`, + Normalize + > : Accumulator; type JoinLowercaseWords< T extends readonly string[], @@ -108,23 +113,29 @@ export type JoinByCase = string extends T ? JoinLowercaseWords : never; -export type PascalCase = string extends T +export type PascalCase< + T, + Normalize extends boolean | undefined, +> = string extends T ? string : string[] extends T ? string : T extends string ? SplitByCase extends readonly string[] - ? CapitalizedWords> + ? CapitalizedWords, "", Normalize> : never : T extends readonly string[] - ? CapitalizedWords + ? CapitalizedWords : never; -export type CamelCase = string extends T +export type CamelCase< + T, + Normalize extends boolean | undefined, +> = string extends T ? string : string[] extends T ? string - : Uncapitalize>; + : Uncapitalize>; export type KebabCase< T extends string | readonly string[], diff --git a/test/types.test-d.ts b/test/types.test-d.ts index 17267e7..4a9a4a8 100644 --- a/test/types.test-d.ts +++ b/test/types.test-d.ts @@ -37,23 +37,23 @@ describe("SplitByCase", () => { describe("PascalCase", () => { test("types", () => { - expectTypeOf>().toEqualTypeOf(); - expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); }); test("string", () => { - assertType>(""); - assertType>("Foo"); - assertType>("FooBAr"); - assertType>("FooBaRb"); - assertType>("FooBarBazQux"); - assertType>("FooBarBaz"); - assertType>("FooBar"); + assertType>(""); + assertType>("Foo"); + assertType>("FooBAr"); + assertType>("FooBaRb"); + assertType>("FooBarBazQux"); + assertType>("FooBarBaz"); + assertType>("FooBar"); }); test("array", () => { - assertType>("FooBar"); - assertType>( + assertType>("FooBar"); + assertType>( "FooBarFuzzFiZz", ); }); @@ -61,20 +61,20 @@ describe("PascalCase", () => { describe("CamelCase", () => { test("types", () => { - expectTypeOf>().toEqualTypeOf(); - expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); + expectTypeOf>().toEqualTypeOf(); }); test("string", () => { - assertType>(""); - assertType>("foo"); - assertType>("fooBaRb"); - assertType>("fooBarBazQux"); - assertType>("fooBar"); + assertType>(""); + assertType>("foo"); + assertType>("fooBaRb"); + assertType>("fooBarBazQux"); + assertType>("fooBar"); }); test("array", () => { - assertType>("fooBar"); + assertType>("fooBar"); }); }); From 8f0d2e9973f89328cb56fe016d5c1aaf07dd47f4 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 30 Nov 2023 19:05:01 +0100 Subject: [PATCH 3/3] add default for generic --- src/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types.ts b/src/types.ts index 462fb62..d1e72e8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -115,7 +115,7 @@ export type JoinByCase = string extends T export type PascalCase< T, - Normalize extends boolean | undefined, + Normalize extends boolean | undefined = false, > = string extends T ? string : string[] extends T @@ -130,7 +130,7 @@ export type PascalCase< export type CamelCase< T, - Normalize extends boolean | undefined, + Normalize extends boolean | undefined = false, > = string extends T ? string : string[] extends T