Last active
January 11, 2023 10:32
-
-
Save karol-majewski/1edbe3ae5f1a7bc4d47ba84cea50b8ba to your computer and use it in GitHub Desktop.
Filtering string literal types based on an affix in TypeScript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://www.typescriptlang.org/play?#code/PQKhFgCgAIWhhAFgQwDauQOwOYFMBc02AlgG66bTLSrEDOALtAPYBm0xAxs5ZsgLa46AGmitimACYdumOtAYomuAB70mE6HWIAvIdACMANhXGADithZJcAEwBmFbYAsFgHRRY0ACqJ6HeSxoAEEAOQARaAAnXAwGYh46PzNRTGYmIIB5ACVo2OR4xOSPGDhPOF9cLWZUAFdCyiTmWtRpACMCaAByYwBaFH422qi8KK7oAB9uvoB3ZCjMCWxxqa6XfoEhkdwxye71uYWlrpLYYCgGAE8zKoBJWVCBKoBeTz2eow3B4dHx4GBoIBeDcAEjuAHg3AN-7b1Ws3mixw42g0H+QLBkJg7z6rGYzC6UOmnza81x6NW6wGW1+SIBIIheLWzl6hzhy0RVJRtJJ+wZhJ0xMRq2cAA5elicXShb1CWNxcKeXz3nRmIJeulEDtenxlViovwCsSLtcquEhMQYpIAMq6F6GIx7FwAbigUFAEFK0HNDCixDM8jMMXEKn0rCiSugAEdamhiKxLksZDxoJqhKJULhkKQ4wArWqMBRqrQMXD8U4gc6QK43d2e70AHm8AD5oM8fNBVIWpPIAAYAEgA3phaoMdgBfXp9iSsHYFovDzvQAD80-40EImFw5CijsgzrA5WgAAUuABrPNVRhF+SKArQbiCDiUNCoaCSE1mrRWuhuaAACR2nQAROSPw7P+ez-kySz-iWZYVlUABqUYWoWy7NkeuCXGwB7Hm4ABClwIXUuA1r2byIgA2gA0veVZemYNb3DwjyCPWAC6hA9r2xraGalp6KOfYUbOrYqO2kjyAxmBMVUi6erUVSEKwaB0LgUDDqIsm4PWTqQC6e7BJIYmntAfq4AG+iEpwJ4MMwRnnvwn7lDBhrujU9QJJQzYcVxpq4BaVr8b2BHEEhM6dluO6ul4lTKVQMSJrgnBCHQ8yXAoNm6mhWGWbh+FoHJ0AzMwURHtBUAvpwGBxdgqDMIST4keiSZ0GYyCJVlJ4NayCjOXhBFyXWogAKqNs2h6WQNB4hjcURXJkrB9UR3hDfWWnosOqnabBk3MNNs3zXli2iHBI3QJ10CUdRaEYew3gsb086EN4lEscJonyHBpELtAFGfau647KpZFXZht3hZAqhmEVTC9sOQA