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 +1 -0
- package/package.json +14 -2
- package/readme.md +1 -0
- package/source/delimiter-case.d.ts +31 -57
- package/source/kebab-case.d.ts +6 -1
- package/source/paths.d.ts +60 -3
- package/source/replace.d.ts +10 -2
- package/source/screaming-snake-case.d.ts +8 -17
- package/source/snake-case.d.ts +6 -1
- package/source/tsconfig-json.d.ts +18 -2
- package/source/tuple-to-object.d.ts +42 -0
- package/source/words.d.ts +64 -13
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.
|
|
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 {
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
@see StringArrayToDelimiterCase
|
|
5
|
+
Convert an array of words to delimiter case starting with a delimiter with input capitalization.
|
|
32
6
|
*/
|
|
33
|
-
type
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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;
|
package/source/kebab-case.d.ts
CHANGED
|
@@ -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<
|
|
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],
|
|
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
|
package/source/replace.d.ts
CHANGED
|
@@ -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
|
-
?
|
|
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 {
|
|
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<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
: Value;
|
|
20
|
+
*/
|
|
21
|
+
export type ScreamingSnakeCase<
|
|
22
|
+
Value,
|
|
23
|
+
Options extends WordsOptions = {},
|
|
24
|
+
> = Value extends string ? Uppercase<SnakeCase<Value, Options>> : Value;
|
package/source/snake-case.d.ts
CHANGED
|
@@ -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<
|
|
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 {
|
|
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<
|
|
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>, ...
|
|
89
|
+
? [...SkipEmptyWord<CurrentWord>, ...WordsImplementation<RemainingCharacters, Options>]
|
|
47
90
|
: LastCharacter extends ''
|
|
48
91
|
// Fist char of word
|
|
49
|
-
?
|
|
50
|
-
// Case change: non-numeric to numeric
|
|
92
|
+
? WordsImplementation<RemainingCharacters, Options, FirstCharacter, FirstCharacter>
|
|
93
|
+
// Case change: non-numeric to numeric
|
|
51
94
|
: [false, true] extends [IsNumeric<LastCharacter>, IsNumeric<FirstCharacter>]
|
|
52
|
-
? [
|
|
53
|
-
|
|
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
|
-
? [
|
|
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
|
-
?
|
|
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>, ...
|
|
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>, ...
|
|
115
|
+
? [...RemoveLastCharacter<CurrentWord, LastCharacter>, ...WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${LastCharacter}${FirstCharacter}`>]
|
|
65
116
|
// No case change: concat word
|
|
66
|
-
:
|
|
117
|
+
: WordsImplementation<RemainingCharacters, Options, FirstCharacter, `${CurrentWord}${FirstCharacter}`>
|
|
67
118
|
: [...SkipEmptyWord<CurrentWord>];
|