string - TypeScript-malteksttype for å matche tittelstil
Jeg jobber med en mal-literaltype som kun skal matche strenger med stor forbokstav på hvert ord. Jeg tenkte jeg først skulle lage en matcher for ett enkelt ord ved å bruke Uppercase<>:
const uppercaseWord: Uppercase<string> = 'U';
men det ser ut til å passe til små bokstaver også:
const uppercaseWord: Uppercase<string> = 'u';
denne vil ikke kaste.
Jeg har prøvd å parametrisere det:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase = 'U';
Men typeargumentet kunne ikke utledes. Det fungerer ved å sende en strengliteral eksplisitt:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase<'u'> = 'U';
det fungerer, men jeg ville trengt noe mer generelt. Noe som passer til hvilken som helst stor bokstavstreng. Hvis jeg prøver igjen å sende string som typeargument, blir feilen løst, men den små bokstaven kan tilordnes uten en feil:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase<string> = 'u'; // no error
Jeg lurer på om dette i det hele tatt er mulig, siden det vil kreve en regex-lignende strengmatching fra kompilatoren.
Svar #1
Forresten: Jeg er ikke helt sikker på hvilke regler du har for «title case»; jeg vet ikke om «HTML String» er i title case eller ikke. For det som følger, vil jeg anta at du bare trenger å sørge for at det første tegnet i strengen og det første tegnet etter hvert mellomrom (« ») ikke er små bokstaver. Det betyr at «HTML String» er greit. Hvis du har en annen regel, kan du justere koden i svaret nedenfor.
Det finnes ingen spesifikk type i TypeScript som representerer tittel-case strings. Malbokstavgjenningstyper gir deg ikke dette; mens Uppercase<string> og Capitalize<string> nå er sine egne typer fra og med TypeScript 4.8, implementert i microsoft/TypeScript#47050, vil de ikke håndheve mellomromskravene.
For en ekte tittel-case string-type ville du trengt, som du sa, noe som regulære uttrykk-validerte string-typer. Det finnes en åpen sak på microsoft/TypeScript#41160 som etterspør brukstilfeller for slike regex-validerte typer; hvis løsningen nedenfor ikke møter dine behov, kan det være lurt å kommentere på den saken med ditt brukstilfelle, hvorfor det er overbevisende, og hvorfor de alternative løsningene ikke er tilstrekkelige.
Selv om det ikke finnes en spesifikk type som fungerer her, kan du skrive en rekursiv template literal type TitleCase<T> som kan brukes som en begrensning på T. Dette betyr at T extends TitleCase<T> hvis og bare hvis T er en tittel-skrevet streng.
Deretter, for å spare folk for å måtte annotere strengene sine med en eller annen generisk type, ville du skrevet en hjelpefunksjon som asTitleCase() som bare returnerer sin input, men produserer en kompilatorfeil hvis du sender inn en dårlig streng.
Så, mens din ideelle løsning her ville se slik ut:
/* THIS IS NOT POSSIBLE
const okay: TitleCase = "This Is Fine"; // okay
const error: TitleCase = "This is not fine"; // error
const alsoError: TitleCase = String(Math.random()); // error
*/
den implementerbare løsningen ser slik ut:
const okay = asTitleCase("This Is Fine"); // no error
const error = asTitleCase("This is not fine"); // error!
// ---------------------> ~~~~~~~~~~~~~~~~~~
// Argument of type '"This is not fine"' is not assignable to
// parameter of type '"This Is Not Fine"'.
const alsoError = asTitleCase(String(Math.random())); // error!
// Argument of type 'string' is not assignable to parameter of type
// '"Please Use a Title Cased String Literal Here, Thx"'
Dette er igjen det som kan implementeres, ikke det som er ideelt. Alle bruk av strenger med store forbokstaver i typene vil måtte få en ekstra generisk typeparameter.
Merk at du sannsynligvis ikke trenger å faktisk skrive asTitleCase(...) med mindre du vil se feilen rett ved deklarasjonen. Antagelig har du en funksjon (si, lookupBookTitle()) som bryr seg om tittelkaps. Hvis så, ville du bare gjøre den funksjonen generisk og håndheve begrensningen der. Så i stedet for const str=asTitleCase("XXX"); lookupBookTitle(str);, ville du bare skrevet const str="XXX";; lookupBookTitle(str); Den eneste forskjellen er hvor feilen vises.
Også, inne i implementeringen av noe som lookupBookTitle(), bør du sannsynligvis bare utvide inputen til string og bare behandle den som om den allerede er validert. Selv om T extends TitleCase<T> har effekten av å håndheve begrensningen på kallere, vil ikke kompilatoren kunne følge logikken når T er en uspesifisert generisk typeparameter:
// callers see a function that constrains title to TitleCase
function lookupBookTitle<T extends string>(title: VerifyTitleCase<T>): Book;
// implementer just uses string
function lookupBookTitle(title: string) {
const book = db.lookupByTitle(title);
if (!book) throw new Error("NO BOOK");
return book;
}
Uansett, her er implementeringen:
type TitleCase<T extends string, D extends string = " "> =
string extends T ? never :
T extends `${infer F}${D}${infer R}` ?
`${Capitalize<F>}${D}${TitleCase<R, D>}` : Capitalize<T>;
Typen TitleCase<T, D> deler strengen T ved skilletegnet D, og gjør hver del med stor forbokstav (første tegn blir stor). Så den gjør om en streng til en tittel-versjon av seg selv:
type X = TitleCase<"the quick brown fox jumps over the lazy dog.">
// type X = "The Quick Brown Fox Jumps Over The Lazy Dog."
Da kan vi skrive en VerifyTitleCase<T>-type som sjekker om T extends TitleCase<T>. Hvis ja, løses den til T. Hvis ikke, løses den enten til TitleCase<T>, eller en forhåndsdefinert feilmelding som forhåpentligvis gir brukere en idé om hva som gikk galt. (Det finnes ingen "throw-typer" eller "Invalid-typer" i TypeScript, som forespurt i microsoft/TypeScript#23689; så bruk av en forhåndsdefinert feilmelding som streng er en løsning).
type VerifyTitleCase<T extends string> = T extends TitleCase<T> ? T :
TitleCase<T> extends never ? "Please Use a Title Cased String Literal Here, Thx" :
TitleCase<T>
Og til slutt, hjelpefunksjonen:
const asTitleCase = <T extends string>(s: VerifyTitleCase<T>) => s;
Lenke til kode på lekeplassen
