-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DashCase and CamelCase intrinsic string types, or similar #40710
Comments
Something that’s important to clarify in this suggestion is whether it’s important that these types be intrinsic or if you just want some solution built into lib.d.ts. Various comments in #40336 demonstrate that these transformations are already possible, but are complex to write. If they were written into |
Type If there's no consensus on a standard I don't know how typescript plans to regulate custom intrinsic implementations... would they be fishable like Also I am on team |
We don’t plan to allow custom intrinsic implementations. |
@andrewbranch why not? Wouldn't e.g. a graphql schema parser be unoptimized written as a type and have concerns about recursion depth? Right now function mockingly<S extends string>(s: S): intrinsic {
return s.split('').map((c, i) => c[i % 2 ? 'toUpperCase' : 'toLowerCase']().join(' ')
}
Expect<typeof mockingly("hello world"), "H e L l O W o R l D"> |
Yes, absolutely. We would not recommend you do that either! 😄
Great, problem solved! Preprocessing is our recommendation for this kind of problem in general. We’re not interested in performing arbitrary code execution during compile time. (At any rate, this discussion is off-topic for this issue, which is asking us to add new instrinsics ourselves.) |
Personally standard lib or intrinsic is fine for me as someone who will simply use it. I'll leave it to the TS experts what's better. From my point of view as an end user, lib.d.ts and actual intrinsics are all "intrinsic" in the end-user sense: built-in types that I get to use. |
I instead toyed with the idea of a general Enabling something like: type CamelCase<K extends string> = `${Split<K, "-">[0]}${Capitalize<Split<K, "-"|"_"|" ">[1] | "">}
type CamelCasedProps<T> = {
[K in keyof T as CamelCase<K>]: () => T[K]
};
interface KebabCased {
"foo-bar": string;
foo: number;
}
type CamelCased = CamelCasedProps<KebabCased>; I think |
Actually, scratch that, as shown in the top description of #40336, a Trick is to use S extends `${infer T}-${infer U}` ? [T, D] : S I made a test on the playground which I'm pasting here: type Split<S extends string, D extends string> =
string extends S ? string[] :
S extends '' ? [] :
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] :
[S];
type SplitOnWordSeparator<T extends string> = Split<T, "-"|"_"|" ">;
type UndefinedToEmptyString<T extends string> = T extends undefined ? "" : T;
type CamelCaseStringArray<K extends string[]> = `${K[0]}${Capitalize<UndefinedToEmptyString<K[1]>>}`;
type CamelCase<K> = K extends string ? CamelCaseStringArray<SplitOnWordSeparator<K>> : K;
type foo3 = CamelCase<"foo-bar">; // Becomes "fooBar"
type foo5 = CamelCase<"foo bar">; // Becomes "fooBar"
type foo6 = CamelCase<"foo_bar">; // Becomes "fooBar"
type foo4 = CamelCase<"foobar">; // Becomes "foobar"
type CamelCasedProps<T> = {
[K in keyof T as CamelCase<K>]: T[K]
};
interface KebabCased {
"foo-bar": string;
foo: number;
}
// Becomes
// {
// fooBar: string;
// foo: number;
// }
type CamelCased = CamelCasedProps<KebabCased>; |
Sorry for spamming, but just wanted to add an implementation of It's a bit more awkward as it eg. as to split on all A-Z uppercase chars as well: type UpperCaseChars = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'X' | 'Y' | 'Z'; |
@voxpelli great work! The only thing I would point out is that the implementation in your latest linked playground cannot properly handle strings which start with a separator or have multiple separators side by side. Example: interface KebabCased {
"-webkit-animation": string;
"--main-bg-color": string;
"something--else": string;
}
// Becomes
// {
// Webkit: string;
// '': number;
// something: string;
// }
type CamelCased = CamelCasedProps<KebabCased>; I was working on my own type Separator = ' ' | '-' | '_';
type CamelCase<T extends string> =
T extends `${Separator}${infer Suffix}`
? CamelCase<Suffix>
: T extends `${infer Prefix}${Separator}`
? CamelCase<Prefix>
: T extends `${infer Prefix}${Separator}${infer Suffix}`
? CamelCase<`${Prefix}${Capitalize<Suffix>}`>
: T;
type CamelCasedProps<T> = { [K in keyof T as `${CamelCase<string & K>}`]: T[K] }
type SnakeObject = {
'-webkit-animation': string;
'--main-bg-color': string;
'something--else': string;
}
// Becomes
// {
// webkitAnimation: string;
// mainBgColor: number;
// somethingElse: string;
// }
type CamelObject = CamelCasedProps<SnakeObject>; See this playground. |
@knpwrs your solution kills the playground tab in my browser (and the TS language service in my VSC 😅 ) when I use the following keys:
I came up with my own implementation and it seems to be working better for strings up to 15 characters long. Unfortunately anything longer than that triggers the
error 😞 (but doesn't kill the IDE 😅 ) |
@voxpelli Your solution has been working super great, but today I ran into a weird one. Based on your code, I tried this: type DashCasedProps<T> = {
[K in keyof T as KebabCase<K>]: T[K]
};
type test = DashCasedProps<{dracoDecorderPath: number}> And the resulting type test = {
dracoDecorderPath: number;
dracoDecorderDecorderPath: number;
"draco-dath": number;
"draco-path": number;
"dracoDecorder-dath": number;
"dracoDecorder-path": number;
dracoDecorderPecorderath: number;
"draco-decorderath": number;
"draco-pecorderath": number;
dracoDecorderDecorderPecorderath: number;
"dracoDecorder-decorderath": number;
"dracoDecorder-pecorderath": number;
} playground link (see at the bottom) Anyone know why that happens with that particular example? |
@knpwrs Expanding on your example, what would the opposite |
@trusktr Is the same issue happening with the version of it that we're maintaining in https://github.com/sindresorhus/type-fest ? |
Is there any interest in making it possible to write custom intrinsic string types? For popular libraries like Material UI, it would be really helpful (i.e. below): const UnCamelCase = (str: string) => str.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase());
interface CustomObject {
id: number;
name: string;
someValue: string;
}
interface CustomGridColDef extends GridColDef {
field: keyof CustomObject;
headerName: UnCamelCase<keyof CustomObject>;
}
const columns: CustomGridColDef[] = [
{ field: "id", headerName: "Id", flex: 1, minWidth: 150, hide: true },
{ field: "name", headerName: "Name", flex: 1, minWidth: 150 },
{ field: "someValue", headerName: "Some Value", flex: 1, minWidth: 150 },
];
const SomeComponent = () => <DataGrid rows={data} columns={columns}))} /> Or perhaps a Typescript guru would be able to come up with a hack for this that is eluding me? |
@voxpelli thx for the good work. Your implementation works only If word has only two segments. I upgraded it to work with an endless number of segments type Split<S extends string, D extends string> =
string extends S ? string[] :
S extends '' ? [] :
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] :
[S];
type SplitOnWordSeparator<T extends string> = Split<T, "-"|"_"|" ">;
type UndefinedToEmptyString<T> = T extends undefined ? "" : T;
type CapitalizeStringsInTuple<T extends any[]> =
T extends [infer First, ...infer Rest] ?
[First, ...(Rest extends string[] ? CapitalizeStringsInTuple<Rest> : [])] :
[];
// Converts an array of strings to a camelCase string
type JoinStringsAsCamelCase<T extends any[], Prev extends string = ''> =
T extends [infer First, ...infer Rest] ?
Rest extends string[] ?
JoinStringsAsCamelCase<Rest, `${Prev}${First extends string ? Capitalize<First> : ''}`> :
`${Prev}${First}` :
Prev;
type CamelCase<K> = K extends string ? JoinStringsAsCamelCase<SplitOnWordSeparator<K>> : K; |
@michalica 👍 You can find the latest iteration of mine in the |
Search Terms
dashcase snakecase
Suggestion
It would be great to also have
DashCase
andCamelCase
and similar!Use Cases
It is common in DOM libs to map from JS camelCase properties to DOM dash-case attributes (f.e.
el.fooBar = 123
and<el foo-bar="123">
), or CapitalizedClass names to dash-case custom element names (f.e.class FooBar extends HTMLElement
and<foo-bar>
).Examples
Or similar.
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: