From 84dd90fcaa1ae92c83226f0dea96b97526466c0a Mon Sep 17 00:00:00 2001 From: Tommy Date: Sun, 5 Mar 2023 18:01:38 -0600 Subject: [PATCH 01/24] feat: add `IsXLiteral` types --- index.d.ts | 9 +++++ readme.md | 7 ++++ source/is-literal.d.ts | 59 ++++++++++++++++++++++++++++++++ test-d/is-literal.ts | 76 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 source/is-literal.d.ts create mode 100644 test-d/is-literal.ts diff --git a/index.d.ts b/index.d.ts index 5770e8731..9d849829d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -76,6 +76,15 @@ export type {HasRequiredKeys} from './source/has-required-keys'; export type {Spread} from './source/spread'; export type {TupleToUnion} from './source/tuple-to-union'; export type {IsEqual} from './source/is-equal'; +export type { + IsLiteral, + IsStringLiteral, + IsNumericLiteral, + IsBooleanLiteral, + IsSymbolLiteral, + IsUndefinedLiteral, + IsNullLiteral, +} from './source/is-literal'; // Template literal types export type {CamelCase} from './source/camel-case'; diff --git a/readme.md b/readme.md index 245862986..628052263 100644 --- a/readme.md +++ b/readme.md @@ -171,6 +171,13 @@ Click the type names for complete docs. - [`HasRequiredKeys`](source/has-required-keys.d.ts) - Create a `true`/`false` type depending on whether the given type has any required fields. - [`Spread`](source/spread.d.ts) - Mimic the type inferred by TypeScript when merging two objects or two arrays/tuples using the spread syntax. - [`IsEqual`](source/is-equal.d.ts) - Returns a boolean for whether the two given types are equal. +- [`IsLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +- [`IsStringLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +- [`IsNumericLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +- [`IsBooleanLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +- [`IsSymbolLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +- [`IsNullLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `null` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +- [`IsUndefinedLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is an `undefined` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). ### JSON diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts new file mode 100644 index 000000000..3a43d39da --- /dev/null +++ b/source/is-literal.d.ts @@ -0,0 +1,59 @@ +import type {Numeric} from './numeric'; +import type {Primitive} from './primitive'; +import type {Includes} from './includes'; + +/** @link https://stackoverflow.com/a/52806744/10292952 */ +type LiteralCheck = ( + [T] extends [never] // Must be wider than `never` + ? false + : T extends LiteralType // Must be narrower than `LiteralType` + ? LiteralType extends T // Cannot be wider than `LiteralType` + ? false + : true + : false +); + +type StringifiedLiteralCheck = ( + [T] extends [never] // Must be wider than `never` + ? false + : T extends LiteralType // Safe stringify + ? `${T}` extends `${LiteralType}` // Must be narrower than `${LiteralType}` + ? true + : false + : false +); + +export type IsStringLiteral = LiteralCheck; + +export type IsNumericLiteral = Includes<[LiteralCheck, LiteralCheck], true>; + +export type IsBooleanLiteral = ( + [T] extends [never] // Must be wider than `never` + ? false + : [T] extends [true] + ? boolean extends T // Must be narrower than `boolean` + ? false + : true + : [T] extends [false] + ? boolean extends T // Must be narrower than `boolean` + ? false + : true + : false +); + +export type IsSymbolLiteral = LiteralCheck; + +export type IsNullLiteral = StringifiedLiteralCheck; + +export type IsUndefinedLiteral = StringifiedLiteralCheck; + +type IsLiteralTuple = [ + IsStringLiteral, + IsNumericLiteral, + IsBooleanLiteral, + IsSymbolLiteral, + IsNullLiteral, + IsUndefinedLiteral, +]; + +export type IsLiteral = Includes, true>; diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts new file mode 100644 index 000000000..49e9cbedc --- /dev/null +++ b/test-d/is-literal.ts @@ -0,0 +1,76 @@ +import {expectError, expectType} from 'tsd'; +import type { + IsLiteral, + IsStringLiteral, + IsNumericLiteral, + IsBooleanLiteral, + IsSymbolLiteral, + IsUndefinedLiteral, + IsNullLiteral, +} from '../index'; + +const stringLiteral = ''; +const numberLiteral = 1; +// @ts-expect-error (suppress BigInt literal tsd warning) +const bigintLiteral = 1n; +const booleanLiteral = true; +const symbolLiteral = Symbol(''); + +declare const _string: string; +declare const _number: number; +declare const _bigint: bigint; +declare const _boolean: boolean; +declare const _symbol: symbol; +declare const _undefined: undefined; +declare const _null: null; + +// Literals should be true +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); + +// Primitives and others should be false +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(false); +// Fix: expectType>(false); +// Fix: expectType>(false); +expectType>(false); +expectType>(false); + +expectType>(true); +expectType>(false); + +expectType>(true); +expectType>(true); +expectType>(false); +expectType>(false); + +expectType>(true); +expectType>(false); + +expectType>(true); +expectType>(false); + +expectType>(true); +// Fix: expectType>(false); + +expectType>(true); +// Fix: expectType>(false); + +declare const anything: any; + +// Missing generic parameter +expectError(anything); +expectError(anything); +expectError(anything); +expectError(anything); +expectError(anything); +expectError(anything); +expectError(anything); From 6769a2f9fc1c107ca75b2e7df70bf749c64d1aee Mon Sep 17 00:00:00 2001 From: Tommy Date: Sun, 5 Mar 2023 18:12:28 -0600 Subject: [PATCH 02/24] fix: ordering --- test-d/is-literal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts index 49e9cbedc..90e7d4a7b 100644 --- a/test-d/is-literal.ts +++ b/test-d/is-literal.ts @@ -21,8 +21,8 @@ declare const _number: number; declare const _bigint: bigint; declare const _boolean: boolean; declare const _symbol: symbol; -declare const _undefined: undefined; declare const _null: null; +declare const _undefined: undefined; // Literals should be true expectType>(true); From e4ecfed27d6aaa0149e687f54285948785c05f19 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 6 Mar 2023 00:11:36 -0600 Subject: [PATCH 03/24] chore: remove `IsNull` and `IsUndefined` --- index.d.ts | 2 -- readme.md | 2 -- source/is-literal.d.ts | 6 ------ test-d/is-literal.ts | 14 -------------- 4 files changed, 24 deletions(-) diff --git a/index.d.ts b/index.d.ts index 9d849829d..9bd37ed98 100644 --- a/index.d.ts +++ b/index.d.ts @@ -82,8 +82,6 @@ export type { IsNumericLiteral, IsBooleanLiteral, IsSymbolLiteral, - IsUndefinedLiteral, - IsNullLiteral, } from './source/is-literal'; // Template literal types diff --git a/readme.md b/readme.md index 628052263..9fcc5a548 100644 --- a/readme.md +++ b/readme.md @@ -176,8 +176,6 @@ Click the type names for complete docs. - [`IsNumericLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsBooleanLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). - [`IsSymbolLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -- [`IsNullLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is a `null` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -- [`IsUndefinedLiteral`](source/is-literal.d.ts) - Returns a boolean for whether the given type is an `undefined` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). ### JSON diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 3a43d39da..5b1a39e10 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -43,17 +43,11 @@ export type IsBooleanLiteral = ( export type IsSymbolLiteral = LiteralCheck; -export type IsNullLiteral = StringifiedLiteralCheck; - -export type IsUndefinedLiteral = StringifiedLiteralCheck; - type IsLiteralTuple = [ IsStringLiteral, IsNumericLiteral, IsBooleanLiteral, IsSymbolLiteral, - IsNullLiteral, - IsUndefinedLiteral, ]; export type IsLiteral = Includes, true>; diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts index 90e7d4a7b..9086931ef 100644 --- a/test-d/is-literal.ts +++ b/test-d/is-literal.ts @@ -5,8 +5,6 @@ import type { IsNumericLiteral, IsBooleanLiteral, IsSymbolLiteral, - IsUndefinedLiteral, - IsNullLiteral, } from '../index'; const stringLiteral = ''; @@ -30,8 +28,6 @@ expectType>(true); expectType>(true); expectType>(true); expectType>(true); -expectType>(true); -expectType>(true); // Primitives and others should be false expectType>(false); @@ -39,8 +35,6 @@ expectType>(false); expectType>(false); expectType>(false); expectType>(false); -// Fix: expectType>(false); -// Fix: expectType>(false); expectType>(false); expectType>(false); @@ -58,12 +52,6 @@ expectType>(false); expectType>(true); expectType>(false); -expectType>(true); -// Fix: expectType>(false); - -expectType>(true); -// Fix: expectType>(false); - declare const anything: any; // Missing generic parameter @@ -72,5 +60,3 @@ expectError(anything); expectError(anything); expectError(anything); expectError(anything); -expectError(anything); -expectError(anything); From 79c94a92f118d2321b43b200ec673ace069d268e Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 6 Mar 2023 00:13:19 -0600 Subject: [PATCH 04/24] fix: missing cases --- test-d/is-literal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts index 9086931ef..606220857 100644 --- a/test-d/is-literal.ts +++ b/test-d/is-literal.ts @@ -19,8 +19,6 @@ declare const _number: number; declare const _bigint: bigint; declare const _boolean: boolean; declare const _symbol: symbol; -declare const _null: null; -declare const _undefined: undefined; // Literals should be true expectType>(true); @@ -37,6 +35,8 @@ expectType>(false); expectType>(false); expectType>(false); expectType>(false); +expectType>(false); +expectType>(false); expectType>(true); expectType>(false); From 9a325cb8d8359d429836f88c4ae15f2664c207c3 Mon Sep 17 00:00:00 2001 From: Tommy Date: Mon, 6 Mar 2023 00:52:34 -0600 Subject: [PATCH 05/24] fix: remove unused type helper --- source/is-literal.d.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 5b1a39e10..a17ebb9e7 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -13,16 +13,6 @@ type LiteralCheck = ( : false ); -type StringifiedLiteralCheck = ( - [T] extends [never] // Must be wider than `never` - ? false - : T extends LiteralType // Safe stringify - ? `${T}` extends `${LiteralType}` // Must be narrower than `${LiteralType}` - ? true - : false - : false -); - export type IsStringLiteral = LiteralCheck; export type IsNumericLiteral = Includes<[LiteralCheck, LiteralCheck], true>; From 921b11dc7eac8c386611f5797594f30eadb67073 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:16:28 -0600 Subject: [PATCH 06/24] feat(`internal`): add `IsNotFalse` type --- source/internal.d.ts | 5 +++++ test-d/internal.ts | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/source/internal.d.ts b/source/internal.d.ts index 46c2d0920..6870d2b1b 100644 --- a/source/internal.d.ts +++ b/source/internal.d.ts @@ -257,3 +257,8 @@ export type HasMultipleCallSignatures unknown ? false : true : false; + +/** +Returns a boolean for whether the given `boolean` is not `false`. +*/ +export type IsNotFalse = [T] extends [false] ? false : true; diff --git a/test-d/internal.ts b/test-d/internal.ts index ad7a44705..631966a54 100644 --- a/test-d/internal.ts +++ b/test-d/internal.ts @@ -1,5 +1,5 @@ import {expectType} from 'tsd'; -import type {IsWhitespace, IsNumeric} from '../source/internal'; +import type {IsWhitespace, IsNumeric, IsNotFalse} from '../source/internal'; expectType>(false); expectType>(true); @@ -27,3 +27,11 @@ expectType>(false); expectType>(false); expectType>(false); expectType>(false); + +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(false); +expectType>(false); +expectType>(false); From 3537f3b875c5a8cda118736d6cc7dda8bc449505 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:16:28 -0600 Subject: [PATCH 07/24] feat: simplify literal checks --- source/is-literal.d.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index a17ebb9e7..d363758e0 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -1,6 +1,6 @@ -import type {Numeric} from './numeric'; import type {Primitive} from './primitive'; -import type {Includes} from './includes'; +import type {Numeric} from './numeric'; +import type {IsNotFalse} from './internal'; /** @link https://stackoverflow.com/a/52806744/10292952 */ type LiteralCheck = ( @@ -13,9 +13,11 @@ type LiteralCheck = ( : false ); +type LiteralChecks = Union extends Primitive ? IsNotFalse> : false; + export type IsStringLiteral = LiteralCheck; -export type IsNumericLiteral = Includes<[LiteralCheck, LiteralCheck], true>; +export type IsNumericLiteral = LiteralChecks; export type IsBooleanLiteral = ( [T] extends [never] // Must be wider than `never` @@ -33,11 +35,10 @@ export type IsBooleanLiteral = ( export type IsSymbolLiteral = LiteralCheck; -type IsLiteralTuple = [ - IsStringLiteral, - IsNumericLiteral, - IsBooleanLiteral, - IsSymbolLiteral, -]; +type IsLiteralUnion = + | IsStringLiteral + | IsNumericLiteral + | IsBooleanLiteral + | IsSymbolLiteral; -export type IsLiteral = Includes, true>; +export type IsLiteral = IsNotFalse>; From e593486f88643bf78a9499de00bc53edb07cb931 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:16:28 -0600 Subject: [PATCH 08/24] fix: formatting --- test-d/is-literal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts index 606220857..ed9c07914 100644 --- a/test-d/is-literal.ts +++ b/test-d/is-literal.ts @@ -9,7 +9,7 @@ import type { const stringLiteral = ''; const numberLiteral = 1; -// @ts-expect-error (suppress BigInt literal tsd warning) +// @ts-expect-error: suppress BigInt literal tsd warning const bigintLiteral = 1n; const booleanLiteral = true; const symbolLiteral = Symbol(''); From 4c92b2f144b78e5874948db5dd0e982733388f4f Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:16:28 -0600 Subject: [PATCH 09/24] feat: simplify `LiteralCheck` --- source/is-literal.d.ts | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index d363758e0..c5dfafaf2 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -1,37 +1,29 @@ import type {Primitive} from './primitive'; -import type {Numeric} from './numeric'; -import type {IsNotFalse} from './internal'; +import type {IsNever, IsNotFalse} from './internal'; /** @link https://stackoverflow.com/a/52806744/10292952 */ type LiteralCheck = ( - [T] extends [never] // Must be wider than `never` - ? false - : T extends LiteralType // Must be narrower than `LiteralType` - ? LiteralType extends T // Cannot be wider than `LiteralType` + IsNever extends false // Must be wider than `never` + ? [T] extends [LiteralType] // Must be narrower than `LiteralType` + ? [LiteralType] extends [T] // Cannot be wider than `LiteralType` ? false : true : false + : false ); -type LiteralChecks = Union extends Primitive ? IsNotFalse> : false; +type LiteralChecks = ( + // Conditional type to force union distribution + LiteralUnionType extends Primitive + ? IsNotFalse> + : false +); export type IsStringLiteral = LiteralCheck; -export type IsNumericLiteral = LiteralChecks; +export type IsNumericLiteral = IsNotFalse | LiteralCheck>; -export type IsBooleanLiteral = ( - [T] extends [never] // Must be wider than `never` - ? false - : [T] extends [true] - ? boolean extends T // Must be narrower than `boolean` - ? false - : true - : [T] extends [false] - ? boolean extends T // Must be narrower than `boolean` - ? false - : true - : false -); +export type IsBooleanLiteral = LiteralCheck; export type IsSymbolLiteral = LiteralCheck; From ecb3d13ca45384d48152c97dd1dd0f9191d9d3d7 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:16:28 -0600 Subject: [PATCH 10/24] feat: simplify `IsNumericLiteral` --- source/is-literal.d.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index c5dfafaf2..fc07e1bd9 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -1,4 +1,5 @@ import type {Primitive} from './primitive'; +import type {Numeric} from './numeric'; import type {IsNever, IsNotFalse} from './internal'; /** @link https://stackoverflow.com/a/52806744/10292952 */ @@ -14,14 +15,15 @@ type LiteralCheck = ( type LiteralChecks = ( // Conditional type to force union distribution - LiteralUnionType extends Primitive - ? IsNotFalse> - : false + IsNotFalse + : never + > ); export type IsStringLiteral = LiteralCheck; -export type IsNumericLiteral = IsNotFalse | LiteralCheck>; +export type IsNumericLiteral = LiteralChecks; export type IsBooleanLiteral = LiteralCheck; From da3c48e5c236bb35eabb66a1a963aeb14662fb45 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:16:28 -0600 Subject: [PATCH 11/24] feat: scaffold doc comments --- source/is-literal.d.ts | 56 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index fc07e1bd9..4cba221c6 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -21,18 +21,74 @@ type LiteralChecks = ( > ); +/** +Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). + +@example +``` +import type {IsStringLiteral} from 'type-fest'; + +``` + +@category Utilities +*/ export type IsStringLiteral = LiteralCheck; +/** +Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). + +@example +``` +import type {IsNumericLiteral} from 'type-fest'; + +``` + +@category Utilities +*/ export type IsNumericLiteral = LiteralChecks; +/** +Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). + +@example +``` +import type {IsBooleanLiteral} from 'type-fest'; + +``` + +@category Utilities +*/ export type IsBooleanLiteral = LiteralCheck; +/** +Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). + +@example +``` +import type {IsLiteral} from 'type-fest'; + +``` + +@category Utilities +*/ export type IsSymbolLiteral = LiteralCheck; +/** Helper type for `IsLiteral`. */ type IsLiteralUnion = | IsStringLiteral | IsNumericLiteral | IsBooleanLiteral | IsSymbolLiteral; +/** +Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). + +@example +``` +import type {IsLiteral} from 'type-fest'; + +``` + +@category Utilities +*/ export type IsLiteral = IsNotFalse>; From 13c308f2dfeb0968f749d26aeec8125d8852c30e Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:16:28 -0600 Subject: [PATCH 12/24] feat: `IsStringLiteral` documentation --- source/is-literal.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 4cba221c6..c93c20832 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -24,10 +24,20 @@ type LiteralChecks = ( /** Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +Useful for constraining strings to be a string literal, or for providing strongly-typed string manipulation functions. + @example ``` import type {IsStringLiteral} from 'type-fest'; +type CapitalizedString = IsStringLiteral extends true ? Capitalize : string; + +function capitalize>(input: T): CapitalizedString { + return (input.slice(0, 1).toUpperCase() + input.slice(1)) as CapitalizedString; +} + +const output = capitalize('hello, world!'); +//=> 'Hello, world!' ``` @category Utilities From 04e690aec4017002b6e22463d517d5bb17b6ba18 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:16:29 -0600 Subject: [PATCH 13/24] fix: add source of example --- source/is-literal.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index c93c20832..fcad21354 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -32,6 +32,7 @@ import type {IsStringLiteral} from 'type-fest'; type CapitalizedString = IsStringLiteral extends true ? Capitalize : string; +// https://github.com/yankeeinlondon/native-dash/blob/master/src/capitalize.ts function capitalize>(input: T): CapitalizedString { return (input.slice(0, 1).toUpperCase() + input.slice(1)) as CapitalizedString; } From ef12d12514a547798f77384959c51dac2276f3a5 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:37:45 -0600 Subject: [PATCH 14/24] chore: scaffold more doc comments --- source/is-literal.d.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index fcad21354..b37f7f8c1 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -24,7 +24,7 @@ type LiteralChecks = ( /** Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -Useful for constraining strings to be a string literal, or for providing strongly-typed string manipulation functions. +Useful for constraining strings to be a string literal, or for providing strongly-typed string manipulation functions, as well as constructing parsers and ASTs. @example ``` @@ -48,6 +48,8 @@ export type IsStringLiteral = LiteralCheck; /** Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +Useful for constructing parsers and ASTs. + @example ``` import type {IsNumericLiteral} from 'type-fest'; @@ -61,6 +63,8 @@ export type IsNumericLiteral = LiteralChecks; /** Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +Useful for constructing parsers and ASTs. + @example ``` import type {IsBooleanLiteral} from 'type-fest'; @@ -74,9 +78,11 @@ export type IsBooleanLiteral = LiteralCheck; /** Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +Useful for constructing parsers and ASTs. + @example ``` -import type {IsLiteral} from 'type-fest'; +import type {IsSymbolLiteral} from 'type-fest'; ``` From e302d94c48fc3b2bf0c819de36678527fb1792cc Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Mon, 6 Mar 2023 18:45:57 -0600 Subject: [PATCH 15/24] fix(`test-d`): reorder test cases --- test-d/is-literal.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts index ed9c07914..0ee48a819 100644 --- a/test-d/is-literal.ts +++ b/test-d/is-literal.ts @@ -27,16 +27,18 @@ expectType>(true); expectType>(true); expectType>(true); -// Primitives and others should be false +// Primitives should be false expectType>(false); expectType>(false); expectType>(false); expectType>(false); expectType>(false); -expectType>(false); -expectType>(false); + +// Null, undefined, and non-primitives should fail all literal checks expectType>(false); expectType>(false); +expectType>(false); +expectType>(false); expectType>(true); expectType>(false); From d9f3208dbc3d0e87964dd118c5a3bfb34269ff86 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Wed, 8 Mar 2023 00:30:36 -0600 Subject: [PATCH 16/24] feat(`IsLiteral`): add example --- source/is-literal.d.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index b37f7f8c1..11a942f06 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -104,6 +104,27 @@ Returns a boolean for whether the given type is a [literal type](https://www.typ ``` import type {IsLiteral} from 'type-fest'; +// https://github.com/inocan-group/inferred-types/blob/master/src/types/string-literals/StripLeading.ts +export type StripLeading = + A extends string + ? B extends string + ? IsLiteral extends true + ? string extends B ? never : A extends `${B & string}${infer After}` ? After : A + : string + : A + : A; + +function stripLeading(input: Input, strip: Strip) { + return input.replace(`^${strip}`, '') as StripLeading; +} + +stripLeading('abc123', 'abc'); +//=> '123' + +const str = 'abc123' as string; + +stripLeading(str, 'abc'); +//=> string ``` @category Utilities From bbdcbb43ad8b1d4785a62a4b038c573cdb1474db Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Wed, 8 Mar 2023 00:46:44 -0600 Subject: [PATCH 17/24] docs(`IsStringLiteral`): use cases --- source/is-literal.d.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 11a942f06..29a1b119d 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -24,7 +24,10 @@ type LiteralChecks = ( /** Returns a boolean for whether the given type is a `string` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -Useful for constraining strings to be a string literal, or for providing strongly-typed string manipulation functions, as well as constructing parsers and ASTs. +Useful for: + - providing strongly-typed string manipulation functions + - constraining strings to be a string literal + - type utilities, such as when constructing parsers and ASTs @example ``` From a6cc7010fdca7b77e6cb43115d4441eda3b05136 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Wed, 8 Mar 2023 00:49:29 -0600 Subject: [PATCH 18/24] docs: unify use cases --- source/is-literal.d.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 29a1b119d..db7df9af6 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -51,7 +51,7 @@ export type IsStringLiteral = LiteralCheck; /** Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -Useful for constructing parsers and ASTs. +Useful for type utilities, such as when constructing parsers and ASTs. @example ``` @@ -66,7 +66,7 @@ export type IsNumericLiteral = LiteralChecks; /** Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -Useful for constructing parsers and ASTs. +Useful for type utilities, such as when constructing parsers and ASTs. @example ``` @@ -81,7 +81,7 @@ export type IsBooleanLiteral = LiteralCheck; /** Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -Useful for constructing parsers and ASTs. +Useful for type utilities, such as when constructing parsers and ASTs. @example ``` @@ -103,6 +103,10 @@ type IsLiteralUnion = /** Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). +Useful for: + - providing strongly-typed functions when given literal arguments + - type utilities, such as when constructing parsers and ASTs + @example ``` import type {IsLiteral} from 'type-fest'; From 1af5f313169cc924806d7f802999d5e69faa2361 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Wed, 8 Mar 2023 01:00:45 -0600 Subject: [PATCH 19/24] docs: document type helpers --- source/is-literal.d.ts | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index db7df9af6..bc66010d9 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -2,7 +2,23 @@ import type {Primitive} from './primitive'; import type {Numeric} from './numeric'; import type {IsNever, IsNotFalse} from './internal'; -/** @link https://stackoverflow.com/a/52806744/10292952 */ +/** +Returns a boolean for whether the given type `T` is the specified `LiteralType`. + +@link https://stackoverflow.com/a/52806744/10292952 + +@example +``` +LiteralCheck<1, number> +//=> true + +LiteralCheck +//=> false + +LiteralCheck<1, string> +//=> false +``` +*/ type LiteralCheck = ( IsNever extends false // Must be wider than `never` ? [T] extends [LiteralType] // Must be narrower than `LiteralType` @@ -13,8 +29,25 @@ type LiteralCheck = ( : false ); +/** +Returns a boolean for whether the given type `T` is one of the specified literal types in `LiteralUnionType`. + +@example +``` +LiteralChecks<1, Numeric> +//=> true + +LiteralChecks<1n, Numeric> +//=> true + +LiteralChecks +//=> false +``` +*/ type LiteralChecks = ( - // Conditional type to force union distribution + // Conditional type to force union distribution. + // If `T` is none of the literal types in the union `LiteralUnionType`, then `LiteralCheck` will evaluate to `false` for the whole union. + // If `T` is one of the literal types in the union, it will evaluate to `boolean` (i.e. `true | false`) IsNotFalse : never From a5b806a44a4dc00ea85f2848b5b99df2314f49ac Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Wed, 8 Mar 2023 01:17:00 -0600 Subject: [PATCH 20/24] docs(`IsNumericLiteral`): add example --- source/is-literal.d.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index bc66010d9..48afe23ec 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -90,6 +90,36 @@ Useful for type utilities, such as when constructing parsers and ASTs. ``` import type {IsNumericLiteral} from 'type-fest'; +// https://github.com/inocan-group/inferred-types/blob/master/src/types/boolean-logic/EndsWith.ts +type EndsWith = + TValue extends string + ? IsStringLiteral extends true + ? IsStringLiteral extends true + ? TValue extends `${string}${TEndsWith}` + ? true + : false + : boolean + : boolean + : TValue extends number + ? IsNumericLiteral extends true + ? EndsWith<`${TValue}`, TEndsWith> + : false + : false; + +function endsWith(input: Input, end: End) { + return `${input}`.endsWith(end) as EndsWith; +} + +endsWith('abc', 'c'); +//=> true + +endsWith(123456, '456'); +//=> true + +const end = '123' as string; + +endsWith('abc123', end); +//=> boolean ``` @category Utilities From 0b75c1684d3c8adedba9c1cf27a4a0da601090a6 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Wed, 8 Mar 2023 01:21:42 -0600 Subject: [PATCH 21/24] fix(`IsNumericLiteral`): use cases --- source/is-literal.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 48afe23ec..a913b44de 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -84,7 +84,9 @@ export type IsStringLiteral = LiteralCheck; /** Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -Useful for type utilities, such as when constructing parsers and ASTs. +Useful for: + - providing strongly-typed functions when given literal arguments + - type utilities, such as when constructing parsers and ASTs @example ``` From 5f7ca088994443b63d8cfaa05675a8f00ce88ac8 Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Wed, 8 Mar 2023 12:52:01 -0600 Subject: [PATCH 22/24] feat(`IsBooleanLiteral`): add example --- source/is-literal.d.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index a913b44de..26b385cd0 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -131,12 +131,36 @@ export type IsNumericLiteral = LiteralChecks; /** Returns a boolean for whether the given type is a `true` or `false` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -Useful for type utilities, such as when constructing parsers and ASTs. +Useful for: + - providing strongly-typed functions when given literal arguments + - type utilities, such as when constructing parsers and ASTs @example ``` import type {IsBooleanLiteral} from 'type-fest'; +const id = 123; + +type GetId = + IsBooleanLiteral extends true + ? AsString extends true + ? `${typeof id}` + : typeof id + : number | string; + +function getId(options?: {asString: AsString}) { + return (options?.asString ? `${id}` : id) as GetId; +} + +const numberId = getId(); +//=> 123 + +const stringId = getId({asString: true}); +//=> '123' + +declare const runtimeBoolean: boolean; +const eitherId = getId({asString: runtimeBoolean}); +//=> number | string ``` @category Utilities From df6f026b2d3a85059ed358bcebbe7f2bbe69347f Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Wed, 8 Mar 2023 20:24:10 -0600 Subject: [PATCH 23/24] docs(`IsSymbolLiteral`): add example --- source/is-literal.d.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 26b385cd0..559baa0bc 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -170,12 +170,31 @@ export type IsBooleanLiteral = LiteralCheck; /** Returns a boolean for whether the given type is a `symbol` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types). -Useful for type utilities, such as when constructing parsers and ASTs. +Useful for: + - providing strongly-typed functions when given literal arguments + - type utilities, such as when constructing parsers and ASTs @example ``` import type {IsSymbolLiteral} from 'type-fest'; +type Get, Key extends keyof Obj> = + IsSymbolLiteral extends true + ? Obj[Key] + : number; + +function get, Key extends keyof Obj>(o: Obj, key: Key) { + return o[key] as Get; +} + +const symbolLiteral = Symbol('literal'); +const symbolValue: symbol = Symbol('value'); + +get({[symbolLiteral]: 1} as const, symbolLiteral); +//=> 1 + +get({[symbolValue]: 1} as const, symbolValue); +//=> number ``` @category Utilities From bded982eaece12acb01d8e9b668693e6de7151cf Mon Sep 17 00:00:00 2001 From: tommy-mitchell Date: Fri, 10 Mar 2023 14:45:43 -0600 Subject: [PATCH 24/24] chore: add `Type Guard` category --- source/is-literal.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 559baa0bc..ef458cab1 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -78,6 +78,7 @@ const output = capitalize('hello, world!'); ``` @category Utilities +@category Type Guard */ export type IsStringLiteral = LiteralCheck; @@ -125,6 +126,7 @@ endsWith('abc123', end); ``` @category Utilities +@category Type Guard */ export type IsNumericLiteral = LiteralChecks; @@ -164,6 +166,7 @@ const eitherId = getId({asString: runtimeBoolean}); ``` @category Utilities +@category Type Guard */ export type IsBooleanLiteral = LiteralCheck; @@ -198,6 +201,7 @@ get({[symbolValue]: 1} as const, symbolValue); ``` @category Utilities +@category Type Guard */ export type IsSymbolLiteral = LiteralCheck; @@ -243,5 +247,6 @@ stripLeading(str, 'abc'); ``` @category Utilities +@category Type Guard */ export type IsLiteral = IsNotFalse>;