Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions source/internal/type.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type {If} from '../if.d.ts';
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.d.ts';
import type {If} from '../if.d.ts';

/**
Matches any primitive, `void`, `Date`, or `RegExp` value.
Expand All @@ -13,6 +14,13 @@ 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<string | number, number>` => `false | true` => `boolean`.
*/
export type Extends<Left, Right> = IsNever<Left> extends true ? IsNever<Right> : [Left] extends [Right] ? true : false;

/**
Returns a boolean for whether the two given types extends the base type.
*/
Expand Down Expand Up @@ -40,9 +48,19 @@ export type HasMultipleCallSignatures<T extends (...arguments_: any[]) => unknow
: false;

/**
Returns a boolean for whether the given `boolean` is not `false`.
Returns a boolean for whether the given `boolean` Union containe's `false`.
*/
export type IsNotFalse<T extends boolean> = Not<IsFalse<T>>;

/**
Returns a boolean for whether the given `boolean` Union members are all `true`.
*/
export type IsTrue<T extends boolean> = Extends<T, true>;

/**
Returns a boolean for whether the given `boolean` Union members are all `false`.
*/
export type IsNotFalse<T extends boolean> = [T] extends [false] ? false : true;
export type IsFalse<T extends boolean> = Extends<T, false>;

/**
Returns a boolean for whether the given type is primitive value or primitive type.
Expand All @@ -59,7 +77,7 @@ IsPrimitive<Object>
//=> false
```
*/
export type IsPrimitive<T> = [T] extends [Primitive] ? true : false;
export type IsPrimitive<T> = Extends<T, Primitive>;

/**
Returns a boolean for whether A is false.
Expand Down
108 changes: 73 additions & 35 deletions source/is-literal.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,22 @@
import type {ApplyDefaultOptions, CollapseLiterals} from './internal/object.d.ts';
import type {Extends, IsNotFalse, IsTrue, Not} from './internal/type.d.ts';
import type {TagContainer, UnwrapTagged} from './tagged.d.ts';
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.d.ts';
import type {Numeric} from './numeric.d.ts';
import type {IsAny} from './is-any.d.ts';
import type {And} from './and.d.ts';

/**
@see {@link IsLiteral}
*/
type IsLiteralOptions = {
strict?: boolean;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strict name is ambiguous here, since IsLiteral doesn't involve just strings.


I was thinking of referencing the IsStringLiteralOptions in IsLiteralOptions, so we'd not have to repeat ourself.

type IsStringLiteralOptions = {
	strict?: boolean;
};

/**
Supports all {@link IsStringLiteralOptions} options.
*/
type IsLiteralOptions = IsStringLiteralOptions;

In future if we add options to any other Is*Literal type, we can simply & it here and add a link, like:

/**
Supports all {@link IsStringLiteralOptions} and {@link IsNumberLiteralOptions} options.
*/
type IsLiteralOptions = IsStringLiteralOptions & IsNumberLiteralOptions;

Only downside is that we would have ensure there's no conflicting names. @sindresorhus WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sound amazing actualy. great thinking !

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I Did it, and test it with and other options and worked flawlessly

Copy link
Contributor Author

@benzaria benzaria Jun 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sindresorhus @som-sm Hey guys what should we keep the current strictStringCheck options name as is or change it?
here are some suggestion I'm thinking of:

  • strictStringLiteral
  • strictLiteralCheck

};

type DefaultIsLiteralOptions = {
strict: true;
};

/**
Returns a boolean for whether the given type `T` is the specified `LiteralType`.
Expand All @@ -24,11 +38,10 @@ LiteralCheck<1, string>
type LiteralCheck<T, LiteralType extends Primitive> = (
IsNever<T> 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<U, LiteralType>, // Must be narrower than `LiteralType`
Not<Extends<LiteralType, U>> // Cannot be wider than `LiteralType`
>
: false
: false
);
Expand All @@ -47,12 +60,16 @@ LiteralChecks<1n, Numeric>
LiteralChecks<bigint, Numeric>
//=> false
```

@deprecated

*/
type LiteralChecks<T, LiteralUnionType> = (
// Conditional type to force union distribution.
// If `T` is none of the literal types in the union `LiteralUnionType`, then `LiteralCheck<T, LiteralType>` 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<LiteralUnionType extends Primitive
IsNotFalse<
LiteralUnionType extends Primitive
? LiteralCheck<T, LiteralUnionType>
: never
>
Expand Down Expand Up @@ -111,21 +128,31 @@ type L2 = Length<`${number}`>;
//=> number
```

@see IsStringPrimitive
@category Type Guard
@category Utilities
*/
export type IsStringLiteral<S> = IfNotAnyOrNever<S,
_IsStringLiteral<CollapseLiterals<S extends TagContainer<any> ? UnwrapTagged<S> : S>>,
false, false>;

export type _IsStringLiteral<S> =
// If `T` is an infinite string type (e.g., `on${string}`), `Record<T, never>` produces an index signature,
// and since `{}` extends index signatures, the result becomes `false`.
S extends string
? {} extends Record<S, never>
? false
: true
: false;
export type IsStringLiteral<T, Options extends IsLiteralOptions = {}> = (
ApplyDefaultOptions<IsLiteralOptions, DefaultIsLiteralOptions, Options> extends infer ResolvedOptions extends Required<IsLiteralOptions>
? IsNever<T> extends false
? CollapseLiterals<T extends TagContainer<any> ? UnwrapTagged<T> : T> extends infer Type
? ResolvedOptions['strict'] extends true
? IsTrue<_IsStringLiteral<Type>>
: LiteralCheck<Type, string>
: never
: false
: never
);

type _IsStringLiteral<S> = (
// If `T` is an infinite string type (e.g., `on${string}`), `Record<T, never>` produces an index signature,
// and since `{}` extends index signatures, the result becomes `false`.
S extends string
? {} extends Record<S, never>
? 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).
Expand Down Expand Up @@ -170,10 +197,17 @@ endsWith('abc123', end);
//=> boolean
```

@see IsNumericPrimitive
@category Type Guard
@category Utilities
*/
export type IsNumericLiteral<T> = LiteralChecks<T, Numeric>;
export type IsNumericLiteral<T> = IsTrue<
T extends number
? T extends bigint
? LiteralCheck<T, Numeric>
: LiteralCheck<T, number>
: LiteralCheck<T, bigint>
>;

/**
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).
Expand Down Expand Up @@ -210,6 +244,7 @@ const eitherId = getId({asString: runtimeBoolean});
//=> number | string
```

@see IsBooleanPrimitive
@category Type Guard
@category Utilities
*/
Expand Down Expand Up @@ -245,14 +280,15 @@ get({[symbolValue]: 1} as const, symbolValue);
//=> number
```

@see IsSymbolPrimitive
@category Type Guard
@category Utilities
*/
export type IsSymbolLiteral<T> = LiteralCheck<T, symbol>;

/** Helper type for `IsLiteral`. */
type IsLiteralUnion<T> =
| IsStringLiteral<T>
type IsLiteralUnion<T, O extends IsLiteralOptions> =
| IsStringLiteral<T, O>
| IsNumericLiteral<T>
| IsBooleanLiteral<T>
| IsSymbolLiteral<T>;
Expand All @@ -270,13 +306,13 @@ 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, B> =
A extends string
? B extends string
? IsLiteral<A> extends true
? string extends B ? never : A extends `${B & string}${infer After}` ? After : A
: string
: A
: A;
A extends string
? B extends string
? IsLiteral<A> extends true
? string extends B ? never : A extends `${B & string}${infer After}` ? After : A
: string
: A
: A;

function stripLeading<Input extends string, Strip extends string>(input: Input, strip: Strip) {
return input.replace(`^${strip}`, '') as StripLeading<Input, Strip>;
Expand All @@ -291,10 +327,12 @@ stripLeading(str, 'abc');
//=> string
```

@see IsPrimitive
@category Type Guard
@category Utilities
*/
export type IsLiteral<T> =
IsPrimitive<T> extends true
? IsNotFalse<IsLiteralUnion<T>>
: false;
export type IsLiteral<T, Options extends IsLiteralOptions = {}> = (
Extends<T, Primitive> extends true
? IsNotFalse<IsLiteralUnion<T, Options>>
: false
);
Loading