Skip to content

Instantly share code, notes, and snippets.

@karol-majewski
Last active January 11, 2023 10:32
Show Gist options
  • Save karol-majewski/1edbe3ae5f1a7bc4d47ba84cea50b8ba to your computer and use it in GitHub Desktop.
Save karol-majewski/1edbe3ae5f1a7bc4d47ba84cea50b8ba to your computer and use it in GitHub Desktop.
Filtering string literal types based on an affix in TypeScript
/**
* Challenge: given a list of icon names, find icons that exist in sizes 16x16px *and* 24x24px.
* This is an AND relationship, not an OR relationship.
*
* The solution should be: '16-hamburger' | '16-warning' | '24-hamburger' | '24-warning'.
*/
type IconName =
| '16-hamburger' // πŸ‘ˆπŸ»
| '16-warning' // πŸ‘ˆπŸ»
| '16-foo'
| '16-bar'
| '24-hamburger' // πŸ‘ˆπŸ»
| '24-warning' // πŸ‘ˆπŸ»
| '24-baz'
| '48-foo'
| '48-bar'
| '48-baz'
| 'some-other-name-format'
type DesiredSize = 16 | 24;
/**
* Strips prefixes from qualifying icon names, leaving just the stem.
*/
type Strip<T> = T extends `${number}-${infer stem}` ? stem : never;
/**
* Pick the stems that come in all desired sizes. Here: "hamburger" | "warning".
*/
type ValidStem = keyof Pick.ByValue<{
[K in Strip<IconName>]: `${DesiredSize}-${K}` extends IconName ? true : false
}, true>
/**
* Adds the prefixes back to the stems.
*/
type Solution = `${DesiredSize}-${ValidStem}`;
/**
* These are necessary to make Pick.ByValue work.
*/
declare global {
namespace Pick {
type ByValue<T, U> = Pick<T, PropertyOfValue<T, U>>
}
}
type PropertyOfValue<T, V> = {
[K in keyof T]-?: T[K] extends V
? K
: never
}[keyof T];
export {}
type IconName =
| '8-hamburger'
| '16-hamburger'
| '24-hamburger'
| '48-hamburger'
| '64-hamburger'
| 'some-other-name-format';
/**
* Let's define how icons that come in sizes 16x16px and 24x24px are called.
*/
type MediumSizedIconNameFormat = `${16 | 24}-${string}`;
/**
* Target a subset of all string literals.
*/
type SmallOrMediumSizedIconName = Extract<IconName, MediumSizedIconNameFormat> // '16-hamburger' | '24-hamburger', same as IconName & MediumSizedIconNameFormat