UNPKG

2.8 kBTypeScriptView Raw
1import type {IsLowerCase, IsNumeric, IsUpperCase, WordSeparators} from './internal';
2
3type SkipEmptyWord<Word extends string> = Word extends '' ? [] : [Word];
4
5type RemoveLastCharacter<Sentence extends string, Character extends string> = Sentence extends `${infer LeftSide}${Character}`
6 ? SkipEmptyWord<LeftSide>
7 : never;
8
9/**
10Split a string (almost) like Lodash's `_.words()` function.
11
12- Split on each word that begins with a capital letter.
13- Split on each {@link WordSeparators}.
14- Split on numeric sequence.
15
16@example
17```
18type Words0 = SplitWords<'helloWorld'>; // ['hello', 'World']
19type Words1 = SplitWords<'helloWORLD'>; // ['hello', 'WORLD']
20type Words2 = SplitWords<'hello-world'>; // ['hello', 'world']
21type Words3 = SplitWords<'--hello the_world'>; // ['hello', 'the', 'world']
22type Words4 = SplitWords<'lifeIs42'>; // ['life', 'Is', '42']
23```
24
25@internal
26@category Change case
27@category Template literal
28*/
29export type SplitWords<
30 Sentence extends string,
31 LastCharacter extends string = '',
32 CurrentWord extends string = '',
33> = Sentence extends `${infer FirstCharacter}${infer RemainingCharacters}`
34 ? FirstCharacter extends WordSeparators
35 // Skip word separator
36 ? [...SkipEmptyWord<CurrentWord>, ...SplitWords<RemainingCharacters>]
37 : LastCharacter extends ''
38 // Fist char of word
39 ? SplitWords<RemainingCharacters, FirstCharacter, FirstCharacter>
40 // Case change: non-numeric to numeric, push word
41 : [false, true] extends [IsNumeric<LastCharacter>, IsNumeric<FirstCharacter>]
42 ? [...SkipEmptyWord<CurrentWord>, ...SplitWords<RemainingCharacters, FirstCharacter, FirstCharacter>]
43 // Case change: numeric to non-numeric, push word
44 : [true, false] extends [IsNumeric<LastCharacter>, IsNumeric<FirstCharacter>]
45 ? [...SkipEmptyWord<CurrentWord>, ...SplitWords<RemainingCharacters, FirstCharacter, FirstCharacter>]
46 // No case change: concat word
47 : [true, true] extends [IsNumeric<LastCharacter>, IsNumeric<FirstCharacter>]
48 ? SplitWords<RemainingCharacters, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
49 // Case change: lower to upper, push word
50 : [true, true] extends [IsLowerCase<LastCharacter>, IsUpperCase<FirstCharacter>]
51 ? [...SkipEmptyWord<CurrentWord>, ...SplitWords<RemainingCharacters, FirstCharacter, FirstCharacter>]
52 // Case change: upper to lower, brings back the last character, push word
53 : [true, true] extends [IsUpperCase<LastCharacter>, IsLowerCase<FirstCharacter>]
54 ? [...RemoveLastCharacter<CurrentWord, LastCharacter>, ...SplitWords<RemainingCharacters, FirstCharacter, `${LastCharacter}${FirstCharacter}`>]
55 // No case change: concat word
56 : SplitWords<RemainingCharacters, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
57 : [...SkipEmptyWord<CurrentWord>];