type-fest 4.34.1 → 4.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -104,6 +104,7 @@ export type {HasWritableKeys} from './source/has-writable-keys';
104
104
  export type {Spread} from './source/spread';
105
105
  export type {IsInteger} from './source/is-integer';
106
106
  export type {IsFloat} from './source/is-float';
107
+ export type {TupleToObject} from './source/tuple-to-object';
107
108
  export type {TupleToUnion} from './source/tuple-to-union';
108
109
  export type {UnionToTuple} from './source/union-to-tuple';
109
110
  export type {IntRange} from './source/int-range';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "type-fest",
3
- "version": "4.34.1",
3
+ "version": "4.36.0",
4
4
  "description": "A collection of essential TypeScript types",
5
5
  "license": "(MIT OR CC0-1.0)",
6
6
  "repository": "sindresorhus/type-fest",
@@ -69,7 +69,19 @@
69
69
  "error",
70
70
  "prefer-top-level"
71
71
  ]
72
- }
72
+ },
73
+ "overrides": [
74
+ {
75
+ "files": "**/*.d.ts",
76
+ "rules": {
77
+ "no-restricted-imports": [
78
+ "error",
79
+ "tsd",
80
+ "expect-type"
81
+ ]
82
+ }
83
+ }
84
+ ]
73
85
  },
74
86
  "tsd": {
75
87
  "compilerOptions": {
package/readme.md CHANGED
@@ -292,6 +292,7 @@ type ShouldBeNever = IfAny<'not any', 'not never', 'never'>;
292
292
  - [`ReadonlyTuple`](source/readonly-tuple.d.ts) - Create a type that represents a read-only tuple of the given type and length.
293
293
  - [`TupleToUnion`](source/tuple-to-union.d.ts) - Convert a tuple/array into a union type of its elements.
294
294
  - [`UnionToTuple`](source/union-to-tuple.d.ts) - Convert a union type into an unordered tuple type of its elements.
295
+ - [`TupleToObject`](source/tuple-to-object.d.ts) - Transforms a tuple into an object, mapping each tuple index to its corresponding type as a key-value pair.
295
296
 
296
297
  ### Numeric
297
298
 
@@ -1,54 +1,23 @@
1
- import type {UpperCaseCharacters, WordSeparators} from './internal';
2
-
3
- // Transforms a string that is fully uppercase into a fully lowercase version. Needed to add support for SCREAMING_SNAKE_CASE, see https://github.com/sindresorhus/type-fest/issues/385
4
- type UpperCaseToLowerCase<T extends string> = T extends Uppercase<T> ? Lowercase<T> : T;
5
-
6
- // This implementation does not support SCREAMING_SNAKE_CASE, it is used internally by `SplitIncludingDelimiters`.
7
- type SplitIncludingDelimiters_<Source extends string, Delimiter extends string> =
8
- Source extends '' ? [] :
9
- Source extends `${infer FirstPart}${Delimiter}${infer SecondPart}` ?
10
- (
11
- Source extends `${FirstPart}${infer UsedDelimiter}${SecondPart}`
12
- ? UsedDelimiter extends Delimiter
13
- ? Source extends `${infer FirstPart}${UsedDelimiter}${infer SecondPart}`
14
- ? [...SplitIncludingDelimiters<FirstPart, Delimiter>, UsedDelimiter, ...SplitIncludingDelimiters<SecondPart, Delimiter>]
15
- : never
16
- : never
17
- : never
18
- ) :
19
- [Source];
20
-
21
- /**
22
- Unlike a simpler split, this one includes the delimiter splitted on in the resulting array literal. This is to enable splitting on, for example, upper-case characters.
23
-
24
- @category Template literal
25
- */
26
- export type SplitIncludingDelimiters<Source extends string, Delimiter extends string> = SplitIncludingDelimiters_<UpperCaseToLowerCase<Source>, Delimiter>;
1
+ import type {IsStringLiteral} from './is-literal';
2
+ import type {Words, WordsOptions} from './words';
27
3
 
28
4
  /**
29
- Format a specific part of the splitted string literal that `StringArrayToDelimiterCase<>` fuses together, ensuring desired casing.
30
-
31
- @see StringArrayToDelimiterCase
5
+ Convert an array of words to delimiter case starting with a delimiter with input capitalization.
32
6
  */
33
- type StringPartToDelimiterCase<StringPart extends string, Start extends boolean, UsedWordSeparators extends string, UsedUpperCaseCharacters extends string, Delimiter extends string> =
34
- StringPart extends UsedWordSeparators ? Delimiter :
35
- Start extends true ? Lowercase<StringPart> :
36
- StringPart extends UsedUpperCaseCharacters ? `${Delimiter}${Lowercase<StringPart>}` :
37
- StringPart;
38
-
39
- /**
40
- Takes the result of a splitted string literal and recursively concatenates it together into the desired casing.
41
-
42
- It receives `UsedWordSeparators` and `UsedUpperCaseCharacters` as input to ensure it's fully encapsulated.
43
-
44
- @see SplitIncludingDelimiters
45
- */
46
- type StringArrayToDelimiterCase<Parts extends readonly any[], Start extends boolean, UsedWordSeparators extends string, UsedUpperCaseCharacters extends string, Delimiter extends string> =
47
- Parts extends [`${infer FirstPart}`, ...infer RemainingParts]
48
- ? `${StringPartToDelimiterCase<FirstPart, Start, UsedWordSeparators, UsedUpperCaseCharacters, Delimiter>}${StringArrayToDelimiterCase<RemainingParts, false, UsedWordSeparators, UsedUpperCaseCharacters, Delimiter>}`
49
- : Parts extends [string]
50
- ? string
51
- : '';
7
+ type DelimiterCaseFromArray<
8
+ Words extends string[],
9
+ Delimiter extends string,
10
+ OutputString extends string = '',
11
+ > = Words extends [
12
+ infer FirstWord extends string,
13
+ ...infer RemainingWords extends string[],
14
+ ]
15
+ ? DelimiterCaseFromArray<RemainingWords, Delimiter, `${OutputString}${Delimiter}${FirstWord}`>
16
+ : OutputString;
17
+
18
+ type RemoveFirstLetter<S extends string> = S extends `${infer _}${infer Rest}`
19
+ ? Rest
20
+ : '';
52
21
 
53
22
  /**
54
23
  Convert a string literal to a custom string delimiter casing.
@@ -65,6 +34,7 @@ import type {DelimiterCase} from 'type-fest';
65
34
  // Simple
66
35
 
67
36
  const someVariable: DelimiterCase<'fooBar', '#'> = 'foo#bar';
37
+ const someVariableNoSplitOnNumbers: DelimiterCase<'p2pNetwork', '#', {splitOnNumbers: false}> = 'p2p#network';
68
38
 
69
39
  // Advanced
70
40
 
@@ -87,13 +57,17 @@ const rawCliOptions: OddlyCasedProperties<SomeOptions> = {
87
57
 
88
58
  @category Change case
89
59
  @category Template literal
90
- */
91
- export type DelimiterCase<Value, Delimiter extends string> = string extends Value ? Value : Value extends string
92
- ? StringArrayToDelimiterCase<
93
- SplitIncludingDelimiters<Value, WordSeparators | UpperCaseCharacters>,
94
- true,
95
- WordSeparators,
96
- UpperCaseCharacters,
97
- Delimiter
98
- >
60
+ */
61
+ export type DelimiterCase<
62
+ Value,
63
+ Delimiter extends string,
64
+ Options extends WordsOptions = {},
65
+ > = Value extends string
66
+ ? IsStringLiteral<Value> extends false
67
+ ? Value
68
+ : Lowercase<
69
+ RemoveFirstLetter<
70
+ DelimiterCaseFromArray<Words<Value, Options>, Delimiter>
71
+ >
72
+ >
99
73
  : Value;
@@ -1,4 +1,5 @@
1
1
  import type {DelimiterCase} from './delimiter-case';
2
+ import type {WordsOptions} from './words';
2
3
 
3
4
  /**
4
5
  Convert a string literal to kebab-case.
@@ -12,6 +13,7 @@ import type {KebabCase} from 'type-fest';
12
13
  // Simple
13
14
 
14
15
  const someVariable: KebabCase<'fooBar'> = 'foo-bar';
16
+ const someVariableNoSplitOnNumbers: KebabCase<'p2pNetwork', {splitOnNumbers: false}> = 'p2p-network';
15
17
 
16
18
  // Advanced
17
19
 
@@ -35,4 +37,7 @@ const rawCliOptions: KebabCasedProperties<CliOptions> = {
35
37
  @category Change case
36
38
  @category Template literal
37
39
  */
38
- export type KebabCase<Value> = DelimiterCase<Value, '-'>;
40
+ export type KebabCase<
41
+ Value,
42
+ Options extends WordsOptions = {},
43
+ > = DelimiterCase<Value, '-', Options>;
package/source/paths.d.ts CHANGED
@@ -91,12 +91,48 @@ export type PathsOptions = {
91
91
  ```
92
92
  */
93
93
  leavesOnly?: boolean;
94
+
95
+ /**
96
+ Only include paths at the specified depth. By default all paths up to {@link PathsOptions.maxRecursionDepth | `maxRecursionDepth`} are included.
97
+
98
+ Note: Depth starts at `0` for root properties.
99
+
100
+ @default number
101
+
102
+ @example
103
+ ```
104
+ type Post = {
105
+ id: number;
106
+ author: {
107
+ id: number;
108
+ name: {
109
+ first: string;
110
+ last: string;
111
+ };
112
+ };
113
+ };
114
+
115
+ type DepthZero = Paths<Post, {depth: 0}>;
116
+ //=> 'id' | 'author'
117
+
118
+ type DepthOne = Paths<Post, {depth: 1}>;
119
+ //=> 'author.id' | 'author.name'
120
+
121
+ type DepthTwo = Paths<Post, {depth: 2}>;
122
+ //=> 'author.name.first' | 'author.name.last'
123
+
124
+ type LeavesAtDepthOne = Paths<Post, {leavesOnly: true; depth: 1}>;
125
+ //=> 'author.id'
126
+ ```
127
+ */
128
+ depth?: number;
94
129
  };
95
130
 
96
131
  type DefaultPathsOptions = {
97
132
  maxRecursionDepth: 10;
98
133
  bracketNotation: false;
99
134
  leavesOnly: false;
135
+ depth: number;
100
136
  };
101
137
 
102
138
  /**
@@ -147,6 +183,8 @@ export type Paths<T, Options extends PathsOptions = {}> = _Paths<T, {
147
183
  bracketNotation: Options['bracketNotation'] extends boolean ? Options['bracketNotation'] : DefaultPathsOptions['bracketNotation'];
148
184
  // Set default leavesOnly to false
149
185
  leavesOnly: Options['leavesOnly'] extends boolean ? Options['leavesOnly'] : DefaultPathsOptions['leavesOnly'];
186
+ // Set default depth to number
187
+ depth: Options['depth'] extends number ? Options['depth'] : DefaultPathsOptions['depth'];
150
188
  }>;
151
189
 
152
190
  type _Paths<T, Options extends Required<PathsOptions>> =
@@ -186,17 +224,36 @@ type InternalPaths<T, Options extends Required<PathsOptions>> =
186
224
  ) extends infer TranformedKey extends string | number ?
187
225
  // 1. If style is 'a[0].b' and 'Key' is a numberlike value like 3 or '3', transform 'Key' to `[${Key}]`, else to `${Key}` | Key
188
226
  // 2. If style is 'a.0.b', transform 'Key' to `${Key}` | Key
189
- | (Options['leavesOnly'] extends true
227
+ | ((Options['leavesOnly'] extends true
190
228
  ? MaxDepth extends 0
191
229
  ? TranformedKey
192
230
  : T[Key] extends EmptyObject | readonly [] | NonRecursiveType | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown>
193
231
  ? TranformedKey
194
232
  : never
195
- : TranformedKey)
233
+ : TranformedKey
234
+ ) extends infer _TransformedKey
235
+ // If `depth` is provided, the condition becomes truthy only when it reaches `0`.
236
+ // Otherwise, since `depth` defaults to `number`, the condition is always truthy, returning paths at all depths.
237
+ ? 0 extends Options['depth']
238
+ ? _TransformedKey
239
+ : never
240
+ : never)
196
241
  | (
197
242
  // Recursively generate paths for the current key
198
243
  GreaterThan<MaxDepth, 0> extends true // Limit the depth to prevent infinite recursion
199
- ? _Paths<T[Key], {bracketNotation: Options['bracketNotation']; maxRecursionDepth: Subtract<MaxDepth, 1>; leavesOnly: Options['leavesOnly']}> extends infer SubPath
244
+ ? _Paths<T[Key],
245
+ {
246
+ bracketNotation: Options['bracketNotation'];
247
+ maxRecursionDepth: Subtract<MaxDepth, 1>;
248
+ leavesOnly: Options['leavesOnly'];
249
+ depth: Options['depth'] extends infer Depth extends number // For distributing `Options['depth']`
250
+ ? Depth extends 0 // Don't subtract further if `Depth` has reached `0`
251
+ ? never
252
+ : ToString<Depth> extends `-${number}` // Don't subtract if `Depth` is -ve
253
+ ? never
254
+ : Subtract<Options['depth'], 1> // If `Subtract` supported -ve numbers, then `depth` could have simply been `Subtract<Options['depth'], 1>`
255
+ : never; // Should never happen
256
+ }> extends infer SubPath
200
257
  ? SubPath extends string | number
201
258
  ? (
202
259
  Options['bracketNotation'] extends true
@@ -60,8 +60,16 @@ export type Replace<
60
60
  Search extends string,
61
61
  Replacement extends string,
62
62
  Options extends ReplaceOptions = {},
63
+ > = _Replace<Input, Search, Replacement, Options>;
64
+
65
+ type _Replace<
66
+ Input extends string,
67
+ Search extends string,
68
+ Replacement extends string,
69
+ Options extends ReplaceOptions,
70
+ Accumulator extends string = '',
63
71
  > = Input extends `${infer Head}${Search}${infer Tail}`
64
72
  ? Options['all'] extends true
65
- ? `${Head}${Replacement}${Replace<Tail, Search, Replacement, Options>}`
73
+ ? _Replace<Tail, Search, Replacement, Options, `${Accumulator}${Head}${Replacement}`>
66
74
  : `${Head}${Replacement}${Tail}`
67
- : Input;
75
+ : `${Accumulator}${Input}`;
@@ -1,15 +1,5 @@
1
- import type {SplitIncludingDelimiters} from './delimiter-case';
2
1
  import type {SnakeCase} from './snake-case';
3
- import type {Includes} from './includes';
4
-
5
- /**
6
- Returns a boolean for whether the string is screaming snake case.
7
- */
8
- type IsScreamingSnakeCase<Value extends string> = Value extends Uppercase<Value>
9
- ? Includes<SplitIncludingDelimiters<Lowercase<Value>, '_'>, '_'> extends true
10
- ? true
11
- : false
12
- : false;
2
+ import type {WordsOptions} from './words';
13
3
 
14
4
  /**
15
5
  Convert a string literal to screaming-snake-case.
@@ -21,13 +11,14 @@ This can be useful when, for example, converting a camel-cased object property t
21
11
  import type {ScreamingSnakeCase} from 'type-fest';
22
12
 
23
13
  const someVariable: ScreamingSnakeCase<'fooBar'> = 'FOO_BAR';
14
+ const someVariableNoSplitOnNumbers: ScreamingSnakeCase<'p2pNetwork', {splitOnNumbers: false}> = 'P2P_NETWORK';
15
+
24
16
  ```
25
17
 
26
18
  @category Change case
27
19
  @category Template literal
28
- */
29
- export type ScreamingSnakeCase<Value> = Value extends string
30
- ? IsScreamingSnakeCase<Value> extends true
31
- ? Value
32
- : Uppercase<SnakeCase<Value>>
33
- : Value;
20
+ */
21
+ export type ScreamingSnakeCase<
22
+ Value,
23
+ Options extends WordsOptions = {},
24
+ > = Value extends string ? Uppercase<SnakeCase<Value, Options>> : Value;
@@ -1,4 +1,5 @@
1
1
  import type {DelimiterCase} from './delimiter-case';
2
+ import type {WordsOptions} from './words';
2
3
 
3
4
  /**
4
5
  Convert a string literal to snake-case.
@@ -12,6 +13,7 @@ import type {SnakeCase} from 'type-fest';
12
13
  // Simple
13
14
 
14
15
  const someVariable: SnakeCase<'fooBar'> = 'foo_bar';
16
+ const someVariableNoSplitOnNumbers: SnakeCase<'p2pNetwork', {splitOnNumbers: false}> = 'p2p_network';
15
17
 
16
18
  // Advanced
17
19
 
@@ -35,4 +37,7 @@ const dbResult: SnakeCasedProperties<ModelProps> = {
35
37
  @category Change case
36
38
  @category Template literal
37
39
  */
38
- export type SnakeCase<Value> = DelimiterCase<Value, '_'>;
40
+ export type SnakeCase<
41
+ Value,
42
+ Options extends WordsOptions = {},
43
+ > = DelimiterCase<Value, '_', Options>;
@@ -18,6 +18,7 @@ declare namespace TsConfigJson {
18
18
  | 'ES2022'
19
19
  | 'ESNext'
20
20
  | 'Node16'
21
+ | 'Node18'
21
22
  | 'NodeNext'
22
23
  | 'Preserve'
23
24
  | 'None'
@@ -32,6 +33,7 @@ declare namespace TsConfigJson {
32
33
  | 'es2022'
33
34
  | 'esnext'
34
35
  | 'node16'
36
+ | 'node18'
35
37
  | 'nodenext'
36
38
  | 'preserve'
37
39
  | 'none';
@@ -1109,6 +1111,20 @@ declare namespace TsConfigJson {
1109
1111
  Suppress deprecation warnings
1110
1112
  */
1111
1113
  ignoreDeprecations?: CompilerOptions.IgnoreDeprecations;
1114
+
1115
+ /**
1116
+ Do not allow runtime constructs that are not part of ECMAScript.
1117
+
1118
+ @default false
1119
+ */
1120
+ erasableSyntaxOnly?: boolean;
1121
+
1122
+ /**
1123
+ Enable lib replacement.
1124
+
1125
+ @default true
1126
+ */
1127
+ libReplacement?: boolean;
1112
1128
  };
1113
1129
 
1114
1130
  namespace WatchOptions {
@@ -1160,12 +1176,12 @@ declare namespace TsConfigJson {
1160
1176
  synchronousWatchDirectory?: boolean;
1161
1177
 
1162
1178
  /**
1163
- Specifies a list of directories to exclude from watch
1179
+ Specifies a list of directories to exclude from watch.
1164
1180
  */
1165
1181
  excludeDirectories?: string[];
1166
1182
 
1167
1183
  /**
1168
- Specifies a list of files to exclude from watch
1184
+ Specifies a list of files to exclude from watch.
1169
1185
  */
1170
1186
  excludeFiles?: string[];
1171
1187
  };
@@ -0,0 +1,42 @@
1
+ import type {IsTuple} from './is-tuple';
2
+ import type {UnknownArray} from './unknown-array';
3
+ import type {IfAny} from './if-any';
4
+
5
+ /**
6
+ Transforms a tuple into an object, mapping each tuple index to its corresponding type as a key-value pair.
7
+
8
+ Note: Tuple labels are [lost in the transformation process](https://stackoverflow.com/a/70398429/11719314). For example, `TupleToObject<[x: number, y: number]>` produces `{0: number; 1: number}`, and not `{x: number; y: number}`.
9
+
10
+ @example
11
+ ```
12
+ type Example1 = TupleToObject<[number, string, boolean]>;
13
+ //=> { 0: number; 1: string; 2: boolean }
14
+
15
+ // Tuples with optional indices
16
+ type Example2 = TupleToObject<[number, string?, boolean?]>;
17
+ //=> { 0: number; 1?: string; 2?: boolean }
18
+
19
+ // Readonly tuples
20
+ type Example3 = TupleToObject<readonly [number, string?]>;
21
+ //=> { readonly 0: number; readonly 1?: string }
22
+
23
+ // Non-tuple arrays get transformed into index signatures
24
+ type Example4 = TupleToObject<string[]>;
25
+ //=> { [x: number]: string }
26
+
27
+ // Tuples with rest elements
28
+ type Example5 = TupleToObject<[number, string, ...boolean[]]>;
29
+ //=> { [x: number]: number | string | boolean; 0: number; 1: string }
30
+
31
+ // Tuple labels are not preserved
32
+ type Example6 = TupleToObject<[x: number, y: number]>;
33
+ //=> { 0: number; 1: number }
34
+ ```
35
+
36
+ @category Array
37
+ */
38
+ export type TupleToObject<TArray extends UnknownArray> = IfAny<TArray, any, {
39
+ [
40
+ Key in keyof TArray as Key & (`${number}` | (IsTuple<TArray> extends true ? never : number))
41
+ ]: TArray[Key];
42
+ }>;
package/source/words.d.ts CHANGED
@@ -1,11 +1,46 @@
1
- import type {IsLowerCase, IsNumeric, IsUpperCase, WordSeparators} from './internal';
1
+ import type {
2
+ IsLowerCase,
3
+ IsNumeric,
4
+ IsUpperCase,
5
+ WordSeparators,
6
+ } from './internal';
2
7
 
3
8
  type SkipEmptyWord<Word extends string> = Word extends '' ? [] : [Word];
4
9
 
5
- type RemoveLastCharacter<Sentence extends string, Character extends string> = Sentence extends `${infer LeftSide}${Character}`
10
+ type RemoveLastCharacter<
11
+ Sentence extends string,
12
+ Character extends string,
13
+ > = Sentence extends `${infer LeftSide}${Character}`
6
14
  ? SkipEmptyWord<LeftSide>
7
15
  : never;
8
16
 
17
+ /**
18
+ Words options.
19
+
20
+ @see {@link Words}
21
+ */
22
+ export type WordsOptions = {
23
+ /**
24
+ Split on numeric sequence.
25
+
26
+ @default true
27
+
28
+ @example
29
+ ```
30
+ type Example1 = Words<'p2pNetwork', {splitOnNumbers: true}>;
31
+ //=> ["p", "2", "p", "Network"]
32
+
33
+ type Example2 = Words<'p2pNetwork', {splitOnNumbers: false}>;
34
+ //=> ["p2p", "Network"]
35
+ ```
36
+ */
37
+ splitOnNumbers?: boolean;
38
+ };
39
+
40
+ type DefaultOptions = {
41
+ splitOnNumbers: true;
42
+ };
43
+
9
44
  /**
10
45
  Split a string (almost) like Lodash's `_.words()` function.
11
46
 
@@ -31,37 +66,53 @@ type Words3 = Words<'--hello the_world'>;
31
66
 
32
67
  type Words4 = Words<'lifeIs42'>;
33
68
  //=> ['life', 'Is', '42']
69
+
70
+ type Words5 = Words<'p2pNetwork', {splitOnNumbers: false}>;
71
+ //=> ['p2p', 'Network']
34
72
  ```
35
73
 
36
74
  @category Change case
37
75
  @category Template literal
38
76
  */
39
- export type Words<
77
+ export type Words<Sentence extends string, Options extends WordsOptions = {}> = WordsImplementation<Sentence, {
78
+ splitOnNumbers: Options['splitOnNumbers'] extends boolean ? Options['splitOnNumbers'] : DefaultOptions['splitOnNumbers'];
79
+ }>;
80
+
81
+ type WordsImplementation<
40
82
  Sentence extends string,
83
+ Options extends Required<WordsOptions>,
41
84
  LastCharacter extends string = '',
42
85
  CurrentWord extends string = '',
43
86
  > = Sentence extends `${infer FirstCharacter}${infer RemainingCharacters}`
44
87
  ? FirstCharacter extends WordSeparators
45
88
  // Skip word separator
46
- ? [...SkipEmptyWord<CurrentWord>, ...Words<RemainingCharacters>]
89
+ ? [...SkipEmptyWord<CurrentWord>, ...WordsImplementation<RemainingCharacters, Options>]
47
90
  : LastCharacter extends ''
48
91
  // Fist char of word
49
- ? Words<RemainingCharacters, FirstCharacter, FirstCharacter>
50
- // Case change: non-numeric to numeric, push word
92
+ ? WordsImplementation<RemainingCharacters, Options, FirstCharacter, FirstCharacter>
93
+ // Case change: non-numeric to numeric
51
94
  : [false, true] extends [IsNumeric<LastCharacter>, IsNumeric<FirstCharacter>]
52
- ? [...SkipEmptyWord<CurrentWord>, ...Words<RemainingCharacters, FirstCharacter, FirstCharacter>]
53
- // Case change: numeric to non-numeric, push word
95
+ ? Options['splitOnNumbers'] extends true
96
+ // Split on number: push word
97
+ ? [...SkipEmptyWord<CurrentWord>, ...WordsImplementation<RemainingCharacters, Options, FirstCharacter, FirstCharacter>]
98
+ // No split on number: concat word
99
+ : WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
100
+ // Case change: numeric to non-numeric
54
101
  : [true, false] extends [IsNumeric<LastCharacter>, IsNumeric<FirstCharacter>]
55
- ? [...SkipEmptyWord<CurrentWord>, ...Words<RemainingCharacters, FirstCharacter, FirstCharacter>]
102
+ ? Options['splitOnNumbers'] extends true
103
+ // Split on number: push word
104
+ ? [...SkipEmptyWord<CurrentWord>, ...WordsImplementation<RemainingCharacters, Options, FirstCharacter, FirstCharacter>]
105
+ // No split on number: concat word
106
+ : WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
56
107
  // No case change: concat word
57
108
  : [true, true] extends [IsNumeric<LastCharacter>, IsNumeric<FirstCharacter>]
58
- ? Words<RemainingCharacters, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
109
+ ? WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
59
110
  // Case change: lower to upper, push word
60
111
  : [true, true] extends [IsLowerCase<LastCharacter>, IsUpperCase<FirstCharacter>]
61
- ? [...SkipEmptyWord<CurrentWord>, ...Words<RemainingCharacters, FirstCharacter, FirstCharacter>]
112
+ ? [...SkipEmptyWord<CurrentWord>, ...WordsImplementation<RemainingCharacters, Options, FirstCharacter, FirstCharacter>]
62
113
  // Case change: upper to lower, brings back the last character, push word
63
114
  : [true, true] extends [IsUpperCase<LastCharacter>, IsLowerCase<FirstCharacter>]
64
- ? [...RemoveLastCharacter<CurrentWord, LastCharacter>, ...Words<RemainingCharacters, FirstCharacter, `${LastCharacter}${FirstCharacter}`>]
115
+ ? [...RemoveLastCharacter<CurrentWord, LastCharacter>, ...WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${LastCharacter}${FirstCharacter}`>]
65
116
  // No case change: concat word
66
- : Words<RemainingCharacters, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
117
+ : WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
67
118
  : [...SkipEmptyWord<CurrentWord>];