diff --git a/src/array/arrayContains/algo.ts b/src/array/arrayContains/algo.ts new file mode 100644 index 0000000..ef37fdf --- /dev/null +++ b/src/array/arrayContains/algo.ts @@ -0,0 +1,13 @@ +export type _ArrayContains< + Array extends (string | number | boolean)[], + Value extends string | number | boolean, + IndexArr extends number[] = [], +> = IndexArr["length"] extends Array["length"] + ? false + : Array[IndexArr["length"]] extends Value + ? true + : _ArrayContains< + Array, + Value, + [...IndexArr, 7] + > diff --git a/src/array/arrayContains/index.test.ts b/src/array/arrayContains/index.test.ts new file mode 100644 index 0000000..a10f9dc --- /dev/null +++ b/src/array/arrayContains/index.test.ts @@ -0,0 +1,104 @@ +import { + describe, + it, + expectTypeOf, +} from "vitest" +import type { _ArrayContains } from "./algo" + +describe("ArrayContains type tests", () => { + it("should return true if the array contains the element", () => { + type T1 = _ArrayContains<[1, 2, 3], 2> // 2 is in the array + type T2 = _ArrayContains<["a", "b", "c"], "b"> // "b" is in the array + type T3 = _ArrayContains< + [true, false, true], + true + > // true is in the array + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) + + it("should return false if the array does not contain the element", () => { + type T1 = _ArrayContains<[1, 2, 3], 4> // 4 is not in the array + type T2 = _ArrayContains<["a", "b", "c"], "d"> // "d" is not in the array + type T3 = _ArrayContains< + [true, false, true], + false + > // false is in the array + type T4 = _ArrayContains<[], "anything"> // empty array, always false + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) + + it("should handle arrays of mixed types correctly", () => { + type T1 = _ArrayContains<[1, "a", true], 1> // 1 is in the array + type T2 = _ArrayContains<[1, "a", true], "a"> // "a" is in the array + type T3 = _ArrayContains< + [1, "a", true], + false + > // false is not in the array + type T4 = _ArrayContains<[1, "a", true], "b"> // "b" is not in the array + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) + + it("should return false for incompatible types", () => { + type T1 = _ArrayContains<[1, 2, 3], "1"> // string "1" is not the same as number 1 + type T2 = _ArrayContains<["a", "b", "c"], 1> // numbers are not in a string array + type T3 = _ArrayContains< + [true, false], + "true" + > // string "true" is not the same as boolean true + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) + + it("should handle arrays of a single type", () => { + type T1 = _ArrayContains< + ["apple", "banana", "cherry"], + "banana" + > // "banana" is in the array + type T2 = _ArrayContains<[1, 2, 3], 3> // 3 is in the array + type T3 = _ArrayContains<[true, false], true> // true is in the array + type T4 = _ArrayContains<[true, false], false> // false is in the array + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) + + it("should handle arrays with duplicate values", () => { + type T1 = _ArrayContains<[1, 2, 2, 3], 2> // 2 appears multiple times, still true + type T2 = _ArrayContains< + ["a", "b", "a", "c"], + "a" + > // "a" appears multiple times, still true + type T3 = _ArrayContains< + [true, false, true], + false + > // false is present, still true + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) + + it("should return false for arrays that do not match the specified type", () => { + type T1 = _ArrayContains< + ["a", "b", "c"], + number + > // numbers are not in a string array + type T2 = _ArrayContains<[1, 2, 3], boolean> // booleans are not in a number array + type T3 = _ArrayContains< + [true, false], + string + > // strings are not in a boolean array + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() + }) +}) diff --git a/src/array/arrayContains/index.ts b/src/array/arrayContains/index.ts new file mode 100644 index 0000000..0adbe05 --- /dev/null +++ b/src/array/arrayContains/index.ts @@ -0,0 +1,20 @@ +/** + * A type that searches for a match in the array, if there is a match between array element and given the thing you look for it returns true. + * Only primitive values are supported. + * + * @template Array - Array of primitive values. + * @template Value - Value you look for. + * @returns {boolean} - Returns `true` if array contains or 'false' otherwise. + * + * + * @example + * type ArrayContainsBanana = ArrayContains<["apple", "banana", "cherry"], "banana">; // Result: true + * type ArrayContainsFalse = StartsWith<[true, true, false], false>; // Result: true + */ + +import type { _ArrayContains } from "./algo" + +export type ArrayContains< + A extends (string | number | boolean)[], + V extends string | number | boolean, +> = _ArrayContains diff --git a/src/string/strToArr/algo.ts b/src/string/strToArr/algo.ts index 8fca604..6f748a3 100644 --- a/src/string/strToArr/algo.ts +++ b/src/string/strToArr/algo.ts @@ -1,9 +1,9 @@ -export type _StrToArr = [ - Str, -] extends [`${infer First}${infer Rest}`] +export type _StrToArr< + Str extends string, + Acc extends string[], +> = [Str] extends [`${infer First}${infer Rest}`] ? _StrToArr : Acc -export type _StrToArr_Back$ = Str extends string - ? _StrToArr - : never +export type _StrToArr_Back$ = + Str extends string ? _StrToArr : never diff --git a/src/string/toKebabCase/algo.ts b/src/string/toKebabCase/algo.ts new file mode 100644 index 0000000..f7d3e2c --- /dev/null +++ b/src/string/toKebabCase/algo.ts @@ -0,0 +1,25 @@ +import type { ArrayContains } from "array/arrayContains" +import type { _StrToArr } from "string/strToArr/algo" + +type Separators = [" ", "_", "-"] + +type Saintize = + Result extends `-${infer Rest}` + ? Saintize + : Result extends `${infer P}--${infer Q}` + ? Saintize<`${P}-${Q}`> + : Uncapitalize + +export type _ToKebabCase< + Str extends string, + Builder extends string = "", +> = Str extends `${infer First}${infer Rest}` + ? ArrayContains extends true + ? _ToKebabCase + : First extends Uppercase + ? _ToKebabCase< + Rest, + `${Builder}-${Lowercase}` + > + : _ToKebabCase + : Saintize diff --git a/src/string/toKebabCase/index.test.ts b/src/string/toKebabCase/index.test.ts new file mode 100644 index 0000000..ee69f89 --- /dev/null +++ b/src/string/toKebabCase/index.test.ts @@ -0,0 +1,91 @@ +import { + describe, + it, + expectTypeOf, +} from "vitest" +import type { _ToKebabCase } from "./algo" + +describe("_ToKebabCase type tests", () => { + it("should convert camelCase to kebab-case", () => { + type T1 = _ToKebabCase<"camelCase"> + type T2 = _ToKebabCase<"thisIsATest"> + type T3 = _ToKebabCase<"anotherExampleHere"> + expectTypeOf().toEqualTypeOf<"camel-case">() + expectTypeOf().toEqualTypeOf<"this-is-a-test">() + expectTypeOf().toEqualTypeOf<"another-example-here">() + }) + + it("should handle strings with separators (space, underscore, hyphen)", () => { + type T1 = _ToKebabCase<"hello world"> + type T2 = _ToKebabCase<"hello_world"> + type T3 = _ToKebabCase<"hello-world"> + expectTypeOf().toEqualTypeOf<"hello-world">() + expectTypeOf().toEqualTypeOf<"hello-world">() + expectTypeOf().toEqualTypeOf<"hello-world">() + }) + + it("should handle uppercase letters properly", () => { + type T1 = _ToKebabCase<"HelloWorld"> + type T2 = _ToKebabCase<"UpperCase"> + expectTypeOf().toEqualTypeOf<"hello-world">() + expectTypeOf().toEqualTypeOf<"upper-case">() + }) + + it("should handle strings with mixed separators and cases", () => { + type T1 = + _ToKebabCase<"MixOf_Separators And-Cases"> + type T2 = + _ToKebabCase<"another_Example-Here Today"> + expectTypeOf().toEqualTypeOf<"mix-of-separators-and-cases">() + expectTypeOf().toEqualTypeOf<"another-example-here-today">() + }) + + it("should handle strings without any uppercase letters or separators", () => { + type T1 = _ToKebabCase<"lowercase"> + type T2 = _ToKebabCase<"already-kebab"> + type T3 = _ToKebabCase<"plain"> + expectTypeOf().toEqualTypeOf<"lowercase">() + expectTypeOf().toEqualTypeOf<"already-kebab">() + expectTypeOf().toEqualTypeOf<"plain">() + }) + + it("should handle empty strings and strings without transformations", () => { + type T1 = _ToKebabCase<""> + type T2 = _ToKebabCase<"simple"> + expectTypeOf().toEqualTypeOf<"">() + expectTypeOf().toEqualTypeOf<"simple">() + }) + + it("should handle strings with leading or trailing separators", () => { + type T1 = _ToKebabCase<"trailing-space "> + type T2 = _ToKebabCase<"trailing-hyphen-"> + expectTypeOf().toEqualTypeOf<"trailing-space-">() + expectTypeOf().toEqualTypeOf<"trailing-hyphen-">() + }) + + it("should handle uppercase strings", () => { + type T1 = _ToKebabCase<"UPPERCASE"> + type T2 = _ToKebabCase<"MIXEDUppercase"> + expectTypeOf().toEqualTypeOf<"u-p-p-e-r-c-a-s-e">() + expectTypeOf().toEqualTypeOf<"m-i-x-e-d-uppercase">() + }) + + it("should handle strings with multiple separators in sequence", () => { + type T1 = + _ToKebabCase<"string__with__multiple__underscores"> + type T2 = + _ToKebabCase<"string--with--multiple--hyphens"> + type T3 = + _ToKebabCase<"string with multiple spaces"> + expectTypeOf().toEqualTypeOf<"string-with-multiple-underscores">() + expectTypeOf().toEqualTypeOf<"string-with-multiple-hyphens">() + expectTypeOf().toEqualTypeOf<"string-with-multiple-spaces">() + }) + + it("should preserve existing kebab-case", () => { + type T1 = _ToKebabCase<"already-kebab-case"> + type T2 = _ToKebabCase<"more-kebab-case-here"> + expectTypeOf().toEqualTypeOf<"already-kebab-case">() + expectTypeOf().toEqualTypeOf<"more-kebab-case-here">() + }) +}) diff --git a/src/string/toKebabCase/index.ts b/src/string/toKebabCase/index.ts new file mode 100644 index 0000000..60c4496 --- /dev/null +++ b/src/string/toKebabCase/index.ts @@ -0,0 +1,15 @@ +/** + * A ToKebabCase implementation on a type level. + * + * @template String - A string to turn into kebab case. + * @returns {string} - a-string-to-turn-into-kebab-case. + * + * + * @example + * type KebabCased = ToKebabCase<"MixOf_Separators And-Cases">; // Result: mix-of-separators-and-cases + */ + +import type { _ToKebabCase } from "./algo" + +export type ToKebabCase = + _ToKebabCase diff --git a/src/string/toSnakeCase/algo.ts b/src/string/toSnakeCase/algo.ts new file mode 100644 index 0000000..af297f0 --- /dev/null +++ b/src/string/toSnakeCase/algo.ts @@ -0,0 +1,25 @@ +import type { ArrayContains } from "array/arrayContains" +import type { _StrToArr } from "string/strToArr/algo" + +type Separators = [" ", "_", "-"] + +type Saintize = + Result extends `_${infer Rest}` + ? Saintize + : Result extends `${infer P}__${infer Q}` + ? Saintize<`${P}_${Q}`> + : Uncapitalize + +export type _ToSnakeCase< + Str extends string, + Builder extends string = "", +> = Str extends `${infer First}${infer Rest}` + ? ArrayContains extends true + ? _ToSnakeCase + : First extends Uppercase + ? _ToSnakeCase< + Rest, + `${Builder}_${Lowercase}` + > + : _ToSnakeCase + : Saintize diff --git a/src/string/toSnakeCase/index.test.ts b/src/string/toSnakeCase/index.test.ts new file mode 100644 index 0000000..ff78c05 --- /dev/null +++ b/src/string/toSnakeCase/index.test.ts @@ -0,0 +1,91 @@ +import { + describe, + it, + expectTypeOf, +} from "vitest" +import type { _ToSnakeCase } from "./algo" + +describe("_ToSnakeCase type tests", () => { + it("should convert camelCase to snake_case", () => { + type T1 = _ToSnakeCase<"camelCase"> + type T2 = _ToSnakeCase<"thisIsATest"> + type T3 = _ToSnakeCase<"anotherExampleHere"> + expectTypeOf().toEqualTypeOf<"camel_case">() + expectTypeOf().toEqualTypeOf<"this_is_a_test">() + expectTypeOf().toEqualTypeOf<"another_example_here">() + }) + + it("should handle strings with separators (space, underscore, hyphen)", () => { + type T1 = _ToSnakeCase<"hello world"> + type T2 = _ToSnakeCase<"hello_world"> + type T3 = _ToSnakeCase<"hello-world"> + expectTypeOf().toEqualTypeOf<"hello_world">() + expectTypeOf().toEqualTypeOf<"hello_world">() + expectTypeOf().toEqualTypeOf<"hello_world">() + }) + + it("should handle uppercase letters properly", () => { + type T1 = _ToSnakeCase<"HelloWorld"> + type T2 = _ToSnakeCase<"UpperCase"> + expectTypeOf().toEqualTypeOf<"hello_world">() + expectTypeOf().toEqualTypeOf<"upper_case">() + }) + + it("should handle strings with mixed separators and cases", () => { + type T1 = + _ToSnakeCase<"MixOf_Separators And-Cases"> + type T2 = + _ToSnakeCase<"another_Example-Here Today"> + expectTypeOf().toEqualTypeOf<"mix_of_separators_and_cases">() + expectTypeOf().toEqualTypeOf<"another_example_here_today">() + }) + + it("should handle strings without any uppercase letters or separators", () => { + type T1 = _ToSnakeCase<"lowercase"> + type T2 = _ToSnakeCase<"already_snake"> + type T3 = _ToSnakeCase<"plain"> + expectTypeOf().toEqualTypeOf<"lowercase">() + expectTypeOf().toEqualTypeOf<"already_snake">() + expectTypeOf().toEqualTypeOf<"plain">() + }) + + it("should handle empty strings and strings without transformations", () => { + type T1 = _ToSnakeCase<""> + type T2 = _ToSnakeCase<"simple"> + expectTypeOf().toEqualTypeOf<"">() + expectTypeOf().toEqualTypeOf<"simple">() + }) + + it("should handle strings with leading or trailing separators", () => { + type T1 = _ToSnakeCase<"trailing space "> + type T2 = _ToSnakeCase<"trailing-hyphen-"> + expectTypeOf().toEqualTypeOf<"trailing_space_">() + expectTypeOf().toEqualTypeOf<"trailing_hyphen_">() + }) + + it("should handle uppercase strings", () => { + type T1 = _ToSnakeCase<"UPPERCASE"> + type T2 = _ToSnakeCase<"MIXEDUppercase"> + expectTypeOf().toEqualTypeOf<"u_p_p_e_r_c_a_s_e">() + expectTypeOf().toEqualTypeOf<"m_i_x_e_d_uppercase">() + }) + + it("should handle strings with multiple separators in sequence", () => { + type T1 = + _ToSnakeCase<"string__with__multiple__underscores"> + type T2 = + _ToSnakeCase<"string--with--multiple--hyphens"> + type T3 = + _ToSnakeCase<"string with multiple spaces"> + expectTypeOf().toEqualTypeOf<"string_with_multiple_underscores">() + expectTypeOf().toEqualTypeOf<"string_with_multiple_hyphens">() + expectTypeOf().toEqualTypeOf<"string_with_multiple_spaces">() + }) + + it("should preserve existing snake_case", () => { + type T1 = _ToSnakeCase<"already_snake_case"> + type T2 = _ToSnakeCase<"more_snake_case_here"> + expectTypeOf().toEqualTypeOf<"already_snake_case">() + expectTypeOf().toEqualTypeOf<"more_snake_case_here">() + }) +}) diff --git a/src/string/toSnakeCase/index.ts b/src/string/toSnakeCase/index.ts new file mode 100644 index 0000000..268ba4a --- /dev/null +++ b/src/string/toSnakeCase/index.ts @@ -0,0 +1,15 @@ +/** + * A ToSnakeCase implementation on a type level. + * + * @template String - A string to turn into snake case. + * @returns {string} - a_string_to_turn_into_snake_case. + * + * + * @example + * type SnakeCased = ToSnakeCase<"MixOf_Separators And-Cases">; // Result: mix_of_separators_and_cases + */ + +import type { _ToSnakeCase } from "./algo" + +export type ToSnakeCase = + _ToSnakeCase