Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
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
119 changes: 82 additions & 37 deletions source/is-literal.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
import type {ApplyDefaultOptions, CollapseLiterals} from './internal/object.d.ts';
import type {TagContainer, UnwrapTagged} from './tagged.d.ts';
import type {IsNotFalse, Not} from './internal/type.d.ts';
import type {ExtendsStrict} from './extends-strict.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 = {
/**
Whether to match only finite literal types.

@default true
*/
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 +44,9 @@ 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<
ExtendsStrict<U, LiteralType>, // Must be narrower than `LiteralType`
Not<ExtendsStrict<LiteralType, U>>> // Cannot be wider than `LiteralType`
: false
: false
);
Expand All @@ -47,20 +65,26 @@ 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
? LiteralCheck<T, LiteralUnionType>
: never
IsNotFalse<
LiteralUnionType extends Primitive
? LiteralCheck<T, LiteralUnionType>
: never
>
);

/**
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).

By default, it's Strict only matching finite literals, See {@link IsLiteralOptions.strict strict} option to change this behaviour.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Run it through AI please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Check the new doc ! and tell me if it any good

Useful for:
- providing strongly-typed string manipulation functions
- constraining strings to be a string literal
Expand Down Expand Up @@ -111,21 +135,30 @@ 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 = {}> = (
IsNever<T> extends false
? _IsStringLiteral<
CollapseLiterals<T extends TagContainer<any> ? UnwrapTagged<T> : T>,
ApplyDefaultOptions<IsLiteralOptions, DefaultIsLiteralOptions, Options>
>
: false
);

type _IsStringLiteral<S, Options extends Required<IsLiteralOptions>> = (
// 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
? Options['strict'] extends false
? LiteralChecks<S, 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 +203,16 @@ endsWith('abc123', end);
//=> boolean
```

@see IsNumericPrimitive
@category Type Guard
@category Utilities
*/
export type IsNumericLiteral<T> = LiteralChecks<T, Numeric>;
export type IsNumericLiteral<T> =
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 +249,7 @@ const eitherId = getId({asString: runtimeBoolean});
//=> number | string
```

@see IsBooleanPrimitive
@category Type Guard
@category Utilities
*/
Expand Down Expand Up @@ -245,21 +285,24 @@ 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>;

/**
Returns a boolean for whether the given type is a [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).

By default, it's Strict only matching finite literals, See {@link IsLiteralOptions.strict strict} option to change this behaviour.

Useful for:
- providing strongly-typed functions when given literal arguments
- type utilities, such as when constructing parsers and ASTs
Expand All @@ -270,13 +313,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 +334,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 = {}> = (
ExtendsStrict<T, Primitive> extends true
? IsNotFalse<IsLiteralUnion<T, Options>>
: false
);
Loading