diff --git a/index.d.ts b/index.d.ts index ee07d85a8..9af047a2f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -39,6 +39,7 @@ export type {PartialOnUndefinedDeep, PartialOnUndefinedDeepOptions} from './sour export type {UndefinedOnPartialDeep} from './source/undefined-on-partial-deep.d.ts'; export type {ReadonlyDeep} from './source/readonly-deep.d.ts'; export type {LiteralUnion} from './source/literal-union.d.ts'; +export type {ExtractLiterals} from './source/extract-literals.js'; export type {Promisable} from './source/promisable.d.ts'; export type {Arrayable} from './source/arrayable.d.ts'; export type {Opaque, UnwrapOpaque, Tagged, GetTagMetadata, UnwrapTagged} from './source/tagged.d.ts'; @@ -122,6 +123,13 @@ export type { IsBooleanLiteral, IsSymbolLiteral, } from './source/is-literal.d.ts'; +export type { + IsPrimitive, + IsStringPrimitive, + IsNumericPrimitive, + IsBooleanPrimitive, + IsSymbolPrimitive, +} from './source/is-primitive.d.ts'; export type {IsAny} from './source/is-any.d.ts'; export type {IfAny} from './source/if-any.d.ts'; export type {IsNever} from './source/is-never.d.ts'; diff --git a/readme.md b/readme.md index 3f04fef88..11b21d2fd 100644 --- a/readme.md +++ b/readme.md @@ -122,6 +122,7 @@ Click the type names for complete docs. - [`UndefinedOnPartialDeep`](source/undefined-on-partial-deep.d.ts) - Create a deep version of another type where all optional keys are set to also accept `undefined`. - [`ReadonlyDeep`](source/readonly-deep.d.ts) - Create a deeply immutable version of an `object`/`Map`/`Set`/`Array` type. Use [`Readonly`](https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype) if you only need one level deep. - [`LiteralUnion`](source/literal-union.d.ts) - Create a union type by combining primitive types and literal types without sacrificing auto-completion in IDEs for the literal type part of the union. Workaround for [Microsoft/TypeScript#29729](https://github.com/Microsoft/TypeScript/issues/29729). +- [`ExtractLiterals`](source/literal-of.d.ts) - Creates a union of the literal members from a given union type, removing wide primitive or infinite types, and optionally specify the Strictness. - [`Tagged`](source/tagged.d.ts) - Create a [tagged type](https://medium.com/@KevinBGreene/surviving-the-typescript-ecosystem-branding-and-type-tagging-6cf6e516523d) that can support [multiple tags](https://github.com/sindresorhus/type-fest/issues/665) and [per-tag metadata](https://medium.com/@ethanresnick/advanced-typescript-tagged-types-improved-with-type-level-metadata-5072fc125fcf). (This replaces the previous [`Opaque`](source/tagged.d.ts) type, which is now deprecated.) - [`UnwrapTagged`](source/tagged.d.ts) - Get the untagged portion of a tagged type created with `Tagged`. (This replaces the previous [`UnwrapOpaque`](source/tagged.d.ts) type, which is now deprecated.) - [`InvariantOf`](source/invariant-of.d.ts) - Create an [invariant type](https://basarat.gitbook.io/typescript/type-system/type-compatibility#footnote-invariance), which is a type that does not accept supertypes and subtypes. @@ -189,10 +190,15 @@ Click the type names for complete docs. - [`If`](source/if.d.ts) - An if-else-like type that resolves depending on whether the given `boolean` type is `true` or `false`. - [`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). +- [`IsPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). - [`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). +- [`IsStringPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `string` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). +- [`IsNumericPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `number` or `bigint` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). +- [`IsBooleanPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `true` or `false` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). +- [`IsSymbolPrimitive`](source/is-primitive.d.ts) - Returns a boolean for whether the given type is strictly a `symbol` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). - [`IsAny`](source/is-any.d.ts) - Returns a boolean for whether the given type is `any`. - [`IsNever`](source/is-never.d.ts) - Returns a boolean for whether the given type is `never`. - [`IsUnknown`](source/is-unknown.d.ts) - Returns a boolean for whether the given type is `unknown`. diff --git a/source/extract-literals.d.ts b/source/extract-literals.d.ts new file mode 100644 index 000000000..8fb2d5253 --- /dev/null +++ b/source/extract-literals.d.ts @@ -0,0 +1,97 @@ +import type {ApplyDefaultOptions} from './internal/object.js'; +import type {UnwrapTagged, TagContainer} from './tagged.js'; +import type {IsPrimitive} from './is-primitive.js'; +import type {IsLiteral} from './is-literal.js'; +import type {Primitive} from './primitive.js'; +import type {Not} from './internal/type.js'; + +/** +ExtactLiterals options. + +@see {@link ExtactLiterals} +*/ +export type ExtractLiteralsOptions = { + /** + Whether to remove infinite signature types (e.g., `abc${string}`). + + @default true + */ + strict?: boolean; +}; + +export type DefaultExtractLiteralsOptions = { + strict: true; +}; + +/** +Creates a union of the literal members from a given union type, removing wide primitive or infinite types. + +This utility helps you extract only the literal members from a "literal union" type +(e.g., `'foo' | 'bar' | string` becomes `'foo' | 'bar'`), saving you from defining separate types for literals and unions. + +It works with all primitive and tagged types, and supports two strictness modes: + +- **Strict mode (`Strict = true`)**: Removes any infinite signature type (e.g., `abc${string}`, `123${number}`). +- **Non-strict mode (`Strict = false`)**: Removes only wide primitive types (e.g., `string`, `number`). + +@default true + +@example +```ts +import type { ExtractLiterals } from 'type-fest'; + +// String example: +type Pet = LiteralUnion<'dog' | 'cat' | `${string}Dog`, string>; +// ^? type Pet = 'dog' | 'cat' | `${string}Dog` | (string & {}) +type PetLiteralStrict = ExtractLiterals; +// ^? type PetLiteralNonStrict = 'dog' | 'cat' +type PetLiteralNonStrict = ExtractLiterals; +// ^? type PetLiteralStrict = 'dog' | 'cat' | `${string}Dog` + +// Number example: +type Nums = LiteralUnion<0 | 1 | 2, number>; +// ^? type Nums = 0 | 1 | 2 | (number & {}) +type NumsLiteral = ExtractLiterals; +// ^? type NumsLiteral = 0 | 1 | 2 + +// Symbol example: +declare const sym1: unique symbol; +declare const sym2: unique symbol; +type Symbols = LiteralUnion; +// ^? type Symbols = typeof sym1 | typeof sym2 | (symbol & {}) +type SymbolsLiteral = ExtractLiterals; +// ^? type SymbolsLiteral = typeof sym1 | typeof sym2 + +// BigInt example: +type Big = LiteralUnion<1n | 2n, bigint>; +// ^? type Big = 1n | 2n | (bigint & {}) +type BigLiteral = ExtractLiterals; +// ^? type BigLiteral = 1n | 2n + +``` + +@author benzaria +@see LiteralUnion +@category Type +*/ +export type ExtractLiterals< + LiteralUnion extends Primitive, + Options extends ExtractLiteralsOptions = {}, +> = LiteralUnion extends infer Member + ? (( + Member extends TagContainer + ? UnwrapTagged + : Member + ) extends infer Type + ? ApplyDefaultOptions< + ExtractLiteralsOptions, + DefaultExtractLiteralsOptions, + Options + >['strict'] extends true + ? Not> + : IsPrimitive + : never + ) extends true + ? never + : Member + : never; diff --git a/source/internal/type.d.ts b/source/internal/type.d.ts index 4325679f7..889da1596 100644 --- a/source/internal/type.d.ts +++ b/source/internal/type.d.ts @@ -1,7 +1,8 @@ -import type {If} from '../if.js'; -import type {IsAny} from '../is-any.d.ts'; -import type {IsNever} from '../is-never.d.ts'; import type {Primitive} from '../primitive.d.ts'; +import type {IsNever} from '../is-never.d.ts'; +import type {IsAny} from '../is-any.d.ts'; +import type {And} from '../and.js'; +import type {If} from '../if.js'; /** Matches any primitive, `void`, `Date`, or `RegExp` value. @@ -13,14 +14,24 @@ Matches non-recursive types. */ export type NonRecursiveType = BuiltIns | Function | (new (...arguments_: any[]) => unknown); +/** +Checks if one type extends another. Note: this is not quite the same as `Left extends Right` because: +1. If either type is `never`, the result is `true` iff the other type is also `never`. +2. Types are wrapped in a 1-tuple so that union types are not distributed - instead we consider `string | number` to _not_ extend `number`. If we used `Left extends Right` directly you would get `Extends` => `false | true` => `boolean`. +*/ +export type Extends = IsNever extends true ? IsNever : [Left] extends [Right] ? true : false; + /** Returns a boolean for whether the two given types extends the base type. */ -export type IsBothExtends = FirstType extends BaseType - ? SecondType extends BaseType - ? true - : false - : false; +export type IsBothExtends = ( + IsNotFalse< + And< + Extends, + Extends + > + > +); /** Test if the given function has multiple call signatures. @@ -44,23 +55,6 @@ Returns a boolean for whether the given `boolean` is not `false`. */ export type IsNotFalse = [T] extends [false] ? false : true; -/** -Returns a boolean for whether the given type is primitive value or primitive type. - -@example -``` -IsPrimitive<'string'> -//=> true - -IsPrimitive -//=> true - -IsPrimitive -//=> false -``` -*/ -export type IsPrimitive = [T] extends [Primitive] ? true : false; - /** Returns a boolean for whether A is false. diff --git a/source/is-literal.d.ts b/source/is-literal.d.ts index 1e97bf41b..e0c3c7511 100644 --- a/source/is-literal.d.ts +++ b/source/is-literal.d.ts @@ -1,8 +1,10 @@ +import type {Extends, IsNotFalse, Not} from './internal/type.d.ts'; +import type {CollapseLiterals} from './internal/object.d.ts'; +import type {TagContainer, UnwrapTagged} from './tagged.js'; import type {Primitive} from './primitive.d.ts'; -import type {Numeric} from './numeric.d.ts'; -import type {CollapseLiterals, IfNotAnyOrNever, IsNotFalse, IsPrimitive} from './internal/index.d.ts'; import type {IsNever} from './is-never.d.ts'; -import type {TagContainer, UnwrapTagged} from './tagged.js'; +import type {Numeric} from './numeric.d.ts'; +import type {And} from './and.js'; /** Returns a boolean for whether the given type `T` is the specified `LiteralType`. @@ -24,11 +26,10 @@ LiteralCheck<1, string> type LiteralCheck = ( IsNever extends false // Must be wider than `never` ? [T] extends [LiteralType & infer U] // Remove any branding - ? [U] extends [LiteralType] // Must be narrower than `LiteralType` - ? [LiteralType] extends [U] // Cannot be wider than `LiteralType` - ? false - : true - : false + ? And< + Extends, // Must be narrower than `LiteralType` + Not> // Cannot be wider than `LiteralType` + > : false : false ); @@ -52,7 +53,8 @@ type LiteralChecks = ( // 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 > @@ -111,22 +113,24 @@ type L2 = Length<`${number}`>; //=> number ``` +@see IsStringPrimitive @category Type Guard @category Utilities */ -export type IsStringLiteral = IfNotAnyOrNever ? UnwrapTagged : S>>, -false, false>; - -export type _IsStringLiteral = -// If `T` is an infinite string type (e.g., `on${string}`), `Record` produces an index signature, -// and since `{}` extends index signatures, the result becomes `false`. -S extends string - ? {} extends Record - ? false - : true +export type IsStringLiteral = IsNever extends false + ? _IsStringLiteral ? UnwrapTagged : T>> : false; +export type _IsStringLiteral = ( + // If `T` is an infinite string type (e.g., `on${string}`), `Record` produces an index signature, + // and since `{}` extends index signatures, the result becomes `false`. + S extends string + ? {} extends Record + ? false + : true + : false +); + /** 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). @@ -170,10 +174,13 @@ endsWith('abc123', end); //=> boolean ``` +@see IsNumericPrimitive @category Type Guard @category Utilities */ -export type IsNumericLiteral = LiteralChecks; +export type IsNumericLiteral = T extends Numeric + ? LiteralChecks + : false; /** 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). @@ -210,6 +217,7 @@ const eitherId = getId({asString: runtimeBoolean}); //=> number | string ``` +@see IsBooleanPrimitive @category Type Guard @category Utilities */ @@ -245,6 +253,7 @@ get({[symbolValue]: 1} as const, symbolValue); //=> number ``` +@see IsSymbolPrimitive @category Type Guard @category Utilities */ @@ -291,10 +300,12 @@ stripLeading(str, 'abc'); //=> string ``` +@see IsPrimitive @category Type Guard @category Utilities */ -export type IsLiteral = - IsPrimitive extends true +export type IsLiteral = ( + Extends extends true ? IsNotFalse> - : false; + : false +); diff --git a/source/is-primitive.d.ts b/source/is-primitive.d.ts new file mode 100644 index 000000000..db99a3215 --- /dev/null +++ b/source/is-primitive.d.ts @@ -0,0 +1,192 @@ +import type {IsNotFalse, Extends} from './internal/type.js'; +import type {Primitive} from './primitive.js'; +import type {Numeric} from './numeric.js'; +import type {IsAny} from './is-any.js'; +import type {And} from './and.js'; + +/** +Returns a boolean for whether the given type `T` is the specified `PrimitiveType`. + +@example +``` +type ex = PrimitiveCheck +// ^? type ex = true + +type ex = PrimitiveCheck<1, number> +// ^? type ex = false + +type ex = PrimitiveCheck<(string & {}), string> // Because (string & {}) does not change the type string it just hide's it. See PrimitiveUnion +// ^? type ex = true +``` +*/ +type PrimitiveCheck = ( + IsAny extends false // Cannot be wider than `any` + ? And< + Extends, // Must be narrower than `PrimitiveType` + Extends // Must be wider than `PrimitiveType` + > + : false +); + +/** +Returns a boolean for whether the given type `T` is one of the specified primitive types in `PrimitiveUnionType`. + +@example +``` +type ex = PrimitiveChecks +// ^? type ex = true + +type ex = PrimitiveChecks +// ^? type ex = true + +type ex = PrimitiveChecks +// ^? type ex = false +``` +*/ +type PrimitiveChecks = ( + // Conditional type to force union distribution. + // If `T` is none of the primitive types in the union `PrimitiveUnionType`, then `PrimitiveCheck` will evaluate to `false` for the whole union. + // If `T` is one of the primitive types in the union, it will evaluate to `boolean` (i.e. `true | false`) + IsNotFalse< + PrimitiveUnionType extends Primitive + ? PrimitiveCheck + : never + > +); + +/** +Returns a boolean for whether the given type is strictly a `string` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +import type {IsStringPrimitive} from 'type-fest'; + +type ex = IsStringPrimitive +// ^? type ex = true + +type ex = IsStringPrimitive<(string & {})> +// ^? type ex = true + +type ex = IsStringPrimitive<`on${string}`> +// ^? type ex = false + +type ex = IsStringPrimitive> +// ^? type ex = false + +``` + +@see IsStringLiteral +@category Type Guard +@category Utilities +*/ +export type IsStringPrimitive = PrimitiveCheck; + +/** +Returns a boolean for whether the given type is strictly a `number` or `bigint` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +type ex = IsNumericPrimitive +// ^? type ex = true + +type ex = IsNumericPrimitive +// ^? type ex = true + +type ex = IsNumericPrimitive +// ^? type ex = true + +type ex = IsNumericPrimitive<1> +// ^? type ex = false + +``` + +@see IsNumericLiteral +@category Type Guard +@category Utilities +*/ +export type IsNumericPrimitive = T extends Numeric + ? PrimitiveChecks + : false; + +/** +Returns a boolean for whether the given type is strictly a `boolean` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +import type {IsBooleanPrimitive} from 'type-fest'; + +type ex = IsBooleanPrimitive +// ^? type ex = true + +type ex = IsBooleanPrimitive +// ^? type ex = true + +type ex = IsBooleanPrimitive +// ^? type ex = false + +type ex = IsBooleanPrimitive +// ^? type ex = false + +``` + +@see IsBooleanLiteral +@category Type Guard +@category Utilities +*/ +export type IsBooleanPrimitive = PrimitiveCheck; + +/** +Returns a boolean for whether the given type is strictly a `symbol` [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +type ex = IsSymbolPrimitive +// ^? type ex = true + +type ex = IsSymbolPrimitive +// ^? type ex = false + +``` + +@see IsSymbolLiteral +@category Type Guard +@category Utilities +*/ +export type IsSymbolPrimitive = PrimitiveCheck; + +/** Helper type for `IsPrimitive`. */ +type IsPrimitiveUnion = + | IsStringPrimitive + | IsNumericPrimitive + | IsBooleanPrimitive + | IsSymbolPrimitive; + +/** +Returns a boolean for whether the given type is strictly a [primitive type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#the-primitives-string-number-and-boolean). + +@example +``` +type ex = IsPrimitive +// ^? type ex = true + +type ex = IsPrimitive +// ^? type ex = true + +type ex = IsPrimitive<'string'> +// ^? type ex = false + +type ex = IsPrimitive +// ^? type ex = false + +``` + +@author benzaria +@see IsLiteral +@category Type Guard +@category Utilities +*/ +export type IsPrimitive = ( + Extends extends true + ? IsNotFalse> + : false +); diff --git a/test-d/extract-literals.ts b/test-d/extract-literals.ts new file mode 100644 index 000000000..b67a7d1f3 --- /dev/null +++ b/test-d/extract-literals.ts @@ -0,0 +1,88 @@ +import {expectType} from 'tsd'; +import type {ExtractLiterals, LiteralUnion, IsEqual, Tagged} from '../index.js'; + +// For Strings: + +type Events = 'hover' | 'click' | `click-${number}` | (string & {}); +// ^? +type EventsLiteralStrict = ExtractLiterals; +// ^? +type EventsLiteralNonStrict = ExtractLiterals; +// ^? + +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); + +// For Numbers: + +type Nums = LiteralUnion<0 | 1 | 2, number>; +// ^? +type NumsLiteral = ExtractLiterals; +// ^? + +expectType>(true); +expectType>(false); + +// For Symbols: + +declare const symbol1: unique symbol; +declare const symbol2: unique symbol; + +type Symbol1 = typeof symbol1; +type Symbol2 = typeof symbol2; + +type Symbols = LiteralUnion; +// ^? +type SymbolsLiteral = ExtractLiterals; +// ^? + +expectType>(true); +expectType>(false); + +// For Bigints: + +type Big = LiteralUnion<1n | 2n, bigint>; +// ^? +type BigLiteral = ExtractLiterals; +// ^? + +expectType>(true); +expectType>(false); + +// For Tagged types: + +type Animals = Tagged<'dog' | 'cat' | `${string}Dog`, 'alimals'>; +type AnimalsStrict = Exclude; + +type TaggedUnion = LiteralUnion; +// ^? +type TaggedStrict = ExtractLiterals; +// ^? +type TaggedNonStrict = ExtractLiterals; +// ^? + +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); + +type _LiteralUnion = 'dog' | 'cat' | `${string}Dog`; +type _Alimals = Tagged<_LiteralUnion, 'alimals'>; +type _AlimalsStrict = Exclude<_Alimals, `${string}Dog`>; + +type _TaggedUnion = LiteralUnion<_Alimals, string>; +// ^? +type _TaggedStrict = ExtractLiterals<_TaggedUnion>; +// ^? +type _TaggedNonStrict = ExtractLiterals<_TaggedUnion, {strict: false}>; +// ^? + +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(false); diff --git a/test-d/is-literal.ts b/test-d/is-literal.ts index 9fb176033..b7d527a28 100644 --- a/test-d/is-literal.ts +++ b/test-d/is-literal.ts @@ -1,4 +1,5 @@ import {expectType} from 'tsd'; +import type tag from 'tagged-tag'; import type { IsLiteral, IsStringLiteral, @@ -8,33 +9,30 @@ import type { Tagged, LiteralUnion, } from '../index.d.ts'; +import type {Numeric} from '../source/numeric.js'; -const stringLiteral = ''; -const numberLiteral = 1; -// Note: tsd warns on direct literal usage so we cast to the literal type -const bigintLiteral = BigInt(1) as 1n; -const booleanLiteral = true; -const symbolLiteral = Symbol(''); +type stringLiteral = 'aA'; +type numberLiteral = 1; +type bigintLiteral = 1n; +type booleanLiteral = true; +type symbolLiteral = typeof tag; +type numericLiteral = numberLiteral | bigintLiteral; -declare const _string: string; -declare const _number: number; -declare const _bigint: bigint; -declare const _boolean: boolean; -declare const _symbol: symbol; +declare const boolean: boolean; // Literals should be true -expectType>(true); -expectType>(true); -expectType>(true); -expectType>(true); -expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); // Primitives should be false -expectType>(false); -expectType>(false); -expectType>(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); @@ -42,8 +40,8 @@ expectType>(false); expectType>(false); expectType>(false); -expectType>(true); -expectType>(false); +expectType>(true); +expectType>(false); // Strings with infinite set of possible values return `false` expectType>>(false); @@ -75,12 +73,20 @@ expectType>(true); expectType | 'C' | 'D'>>(true); expectType | Capitalize<'abc'>>>(true); +// Union of literals and non-literals return `boolean` +expectType | stringLiteral>>(boolean); +expectType | stringLiteral>>(boolean); + +// Union of diffrent literal types return `boolean` +expectType>(boolean); +expectType>(boolean); + // Strings with union of literals and non-literals return `boolean` -expectType | 'abc'>>({} as boolean); -expectType | 'Abc'>>({} as boolean); -expectType>({} as boolean); -expectType>({} as boolean); -expectType>({} as boolean); +expectType | 'abc'>>(boolean); +expectType | 'Abc'>>(boolean); +expectType>(boolean); +expectType>(boolean); +expectType>(boolean); // Types other than string return `false` expectType>(false); @@ -93,16 +99,19 @@ expectType>(false); expectType>(false); expectType>(false); -expectType>(true); -expectType>(true); -expectType>(false); -expectType>(false); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(false); +expectType>(false); +expectType>(false); -expectType>(true); -expectType>(false); +expectType>(true); +expectType>(false); +expectType>(false); -expectType>(true); -expectType>(false); +expectType>(true); +expectType>(false); // Missing generic parameter // @ts-expect-error @@ -118,25 +127,27 @@ type A4 = IsSymbolLiteral; // Tagged types expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); + expectType, 'Tag'>>>(false); expectType>>(false); expectType>>(true); -expectType>>({} as boolean); -expectType>>({} as boolean); +expectType>>(boolean); +expectType>>(boolean); expectType | Tagged>>(false); expectType | Tagged<'bar', 'Tag'>>>(true); -expectType | Tagged>>({} as boolean); -expectType | number>>({} as boolean); +expectType | Tagged>>(boolean); +expectType | number>>(boolean); // Uncollapsed unions (e.g., `'foo' | 'bar' | (string & {})`) expectType>(false); expectType>>(false); expectType>>(false); -expectType>>({} as boolean); -expectType>>({} as boolean); +expectType>>(boolean); +expectType>>(boolean); expectType, 'Tag'>>>(false); -expectType, 'Tag'>>>({} as boolean); - -expectType>>(false); -expectType>>(false); +expectType, 'Tag'>>>(boolean); diff --git a/test-d/is-primitive.ts b/test-d/is-primitive.ts new file mode 100644 index 000000000..99dc2aaaf --- /dev/null +++ b/test-d/is-primitive.ts @@ -0,0 +1,115 @@ +import {expectType} from 'tsd'; +import type tag from 'tagged-tag'; +import type { + IsPrimitive, + IsBooleanPrimitive, + IsNumericPrimitive, + IsStringPrimitive, + IsSymbolPrimitive, + Tagged, +} from '../index.d.ts'; +import type {Numeric} from '../source/numeric.js'; + +type stringLiteral = 'aA'; +type numberLiteral = 1; +type bigintLiteral = 1n; +type booleanLiteral = true; +type symbolLiteral = typeof tag; +type numericLiteral = numberLiteral | bigintLiteral; + +declare const boolean: boolean; + +// Primitives should be true +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); +expectType>(true); + +// Literals should be 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>(false); +expectType>(true); + +// Strings with infinite set of possible values return `false` +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>>(false); +expectType>>>(false); +expectType>(false); +expectType>(false); +expectType>(false); +expectType}`>>(false); +expectType}abc`>>(false); +expectType>(false); +expectType>(false); +expectType>>(false); +expectType | Uppercase>>(false); +expectType>(false); + +// Strings with finite set of possible values return `false` +expectType>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>(false); +expectType | 'C' | 'D'>>(false); +expectType | Capitalize<'abc'>>>(false); + +// Union of literals and non-literals return `true` +expectType | (string & {})>>(true); +expectType | (string & {})>>(true); + +// Boundary types +expectType>(false); +expectType>(false); +expectType>(false); + +expectType>(false); +expectType>(false); +expectType>(false); +expectType>(true); +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(true); +expectType>(true); + +expectType>(false); +expectType>(true); + +// Missing generic parameter +// @ts-expect-error +type A0 = IsPrimitive; +// @ts-expect-error +type A1 = IsStringPrimitive; +// @ts-expect-error +type A2 = IsNumericPrimitive; +// @ts-expect-error +type A3 = IsBooleanPrimitive; +// @ts-expect-error +type A4 = IsSymbolPrimitive; + +// Tagged types should be false +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false); +expectType>>(false);