|
| 1 | +import type {ApplyDefaultOptions} from './internal/object.d.ts'; |
| 2 | +import type {IfNotAnyOrNever, Not} from './internal/type.d.ts'; |
| 3 | +import type {IsStringLiteral} from './is-literal.d.ts'; |
| 4 | +import type {Or} from './or.d.ts'; |
| 5 | + |
| 6 | +/** |
| 7 | +@see {@link RemovePrefix} |
| 8 | +*/ |
| 9 | +type RemovePrefixOptions = { |
| 10 | + /** |
| 11 | + When enabled, instantiations with non-literal prefixes (e.g., `string`, `Uppercase<string>`, `` `on${string}` ``) simply return `string`, since their precise structure cannot be statically determined. |
| 12 | +
|
| 13 | + Note: Disabling this option can produce misleading results that might not reflect the actual runtime behavior. |
| 14 | + For example, ``RemovePrefix<'on-change', `${string}-`, {strict: false}>`` returns `'change'`, but at runtime, prefix could be `'handle-'` (which satisfies `` `${string}-` ``) and removing `'handle-'` from `'on-change'` would not result in `'change'`. |
| 15 | +
|
| 16 | + So, it is recommended to not disable this option unless you are aware of the implications. |
| 17 | +
|
| 18 | + @default true |
| 19 | +
|
| 20 | + @example |
| 21 | + ``` |
| 22 | + type A = RemovePrefix<'on-change', `${string}-`, {strict: true}>; |
| 23 | + //=> string |
| 24 | +
|
| 25 | + type B = RemovePrefix<'on-change', `${string}-`, {strict: false}>; |
| 26 | + //=> 'change' |
| 27 | +
|
| 28 | + type C = RemovePrefix<'on-change', string, {strict: true}>; |
| 29 | + //=> string |
| 30 | +
|
| 31 | + type D = RemovePrefix<'on-change', string, {strict: false}>; |
| 32 | + //=> 'n-change' |
| 33 | +
|
| 34 | + type E = RemovePrefix<`${string}/${number}`, `${string}/`, {strict: true}>; |
| 35 | + //=> string |
| 36 | +
|
| 37 | + type F = RemovePrefix<`${string}/${number}`, `${string}/`, {strict: false}>; |
| 38 | + //=> `${number}` |
| 39 | + ``` |
| 40 | +
|
| 41 | + Note: This option has no effect when only the input string type is non-literal. For example, ``RemovePrefix<`on-${string}`, 'on-'>`` will always return `string`. |
| 42 | +
|
| 43 | + @example |
| 44 | + ``` |
| 45 | + import type {RemovePrefix} from 'type-fest'; |
| 46 | +
|
| 47 | + type A = RemovePrefix<`on-${string}`, 'on-', {strict: true}>; |
| 48 | + //=> string |
| 49 | +
|
| 50 | + type B = RemovePrefix<`on-${string}`, 'on-', {strict: false}>; |
| 51 | + //=> string |
| 52 | +
|
| 53 | + type C = RemovePrefix<`id-${number}`, 'id-', {strict: true}>; |
| 54 | + //=> `${number}` |
| 55 | +
|
| 56 | + type D = RemovePrefix<`id-${number}`, 'id-', {strict: false}>; |
| 57 | + //=> `${number}` |
| 58 | + ``` |
| 59 | +
|
| 60 | + Note: If it can be statically determined that the input string can never start with the specified non-literal prefix, then the input string is returned as-is, regardless of the value of this option. |
| 61 | + For example, ``RemovePrefix<`${string}/${number}`, `${string}:`>`` returns `` `${string}/${number}` ``, since a string of type `` `${string}/${number}` `` can never start with a prefix of type `` `${string}:` ``. |
| 62 | + ``` |
| 63 | + import type {RemovePrefix} from 'type-fest'; |
| 64 | +
|
| 65 | + type A = RemovePrefix<`${string}/${number}`, `${string}:`, {strict: true}>; |
| 66 | + //=> `${string}/${number}` |
| 67 | +
|
| 68 | + type B = RemovePrefix<`${string}/${number}`, `${string}:`, {strict: false}>; |
| 69 | + //=> `${string}/${number}` |
| 70 | +
|
| 71 | + type C = RemovePrefix<'on-change', `${number}-`, {strict: true}>; |
| 72 | + //=> 'on-change' |
| 73 | +
|
| 74 | + type D = RemovePrefix<'on-change', `${number}-`, {strict: false}>; |
| 75 | + //=> 'on-change' |
| 76 | + ``` |
| 77 | + */ |
| 78 | + strict?: boolean; |
| 79 | +}; |
| 80 | + |
| 81 | +type DefaultRemovePrefixOptions = { |
| 82 | + strict: true; |
| 83 | +}; |
| 84 | + |
| 85 | +/** |
| 86 | +Removes the specified prefix from the start of a string. |
| 87 | +
|
| 88 | +@example |
| 89 | +``` |
| 90 | +import type {RemovePrefix} from 'type-fest'; |
| 91 | +
|
| 92 | +type A = RemovePrefix<'on-change', 'on-'>; |
| 93 | +//=> 'change' |
| 94 | +
|
| 95 | +type B = RemovePrefix<'sm:flex' | 'sm:p-4' | 'sm:gap-2', 'sm:'>; |
| 96 | +//=> 'flex' | 'p-4' | 'gap-2' |
| 97 | +
|
| 98 | +type C = RemovePrefix<'on-change', 'off-'>; |
| 99 | +//=> 'on-change' |
| 100 | +
|
| 101 | +type D = RemovePrefix<`handle${Capitalize<string>}`, 'handle'>; |
| 102 | +//=> Capitalize<string> |
| 103 | +``` |
| 104 | +
|
| 105 | +@see {@link RemovePrefixOptions} |
| 106 | +
|
| 107 | +@category String |
| 108 | +@category Template literal |
| 109 | +*/ |
| 110 | +export type RemovePrefix<S extends string, Prefix extends string, Options extends RemovePrefixOptions = {}> = |
| 111 | +IfNotAnyOrNever< |
| 112 | + S, |
| 113 | + IfNotAnyOrNever< |
| 114 | + Prefix, |
| 115 | + _RemovePrefix<S, Prefix, ApplyDefaultOptions<RemovePrefixOptions, DefaultRemovePrefixOptions, Options>>, |
| 116 | + string, |
| 117 | + S |
| 118 | + > |
| 119 | +>; |
| 120 | + |
| 121 | +type _RemovePrefix<S extends string, Prefix extends string, Options extends Required<RemovePrefixOptions>> = |
| 122 | +Prefix extends string // For distributing `Prefix` |
| 123 | + ? S extends `${Prefix}${infer Rest}` |
| 124 | + ? Or<IsStringLiteral<Prefix>, Not<Options['strict']>> extends true |
| 125 | + ? Rest |
| 126 | + : string // Fallback to `string` when `Prefix` is non-literal and `strict` is disabled |
| 127 | + : S // Return back `S` when `Prefix` is not present at the start of `S` |
| 128 | + : never; |
0 commit comments