string - TypeScript-sjabloonlettertype om Title Case te matchen
Ik werk aan een template literal type dat alleen strings in titelhoofdletters zou matchen. Ik dacht dat ik eerst een matcher voor een enkel woord zou maken met Uppercase<>:
const uppercaseWord: Uppercase<string> = 'U';
maar het lijkt ook kleine letters overeen te komen:
const uppercaseWord: Uppercase<string> = 'u';
deze zal niet gooien.
Ik heb geprobeerd het te parametriseren:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase = 'U';
Maar het typeargument kon niet worden afgeleid. Het werkt door een stringliteral expliciet door te geven:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase<'u'> = 'U';
het werkt, maar ik zou iets algemeners nodig hebben. Iets dat overeenkomt met elke hoofdletterreeks. Als ik het opnieuw probeerde door string als typeargument mee te geven, wordt de fout opgelost, maar de kleine letter kan zonder fout toegewezen worden:
type SingleUpperCase<Str extends string> = `${Uppercase<Str>}`;
const upperCaseWord: SingleUpperCase<string> = 'u'; // no error
Ik vraag me af of dit überhaupt mogelijk is, aangezien het een regex-achtige tekenreeksvergelijking door de compiler zou vereisen.
Antwoord #1
Tussen haakjes: ik weet niet precies welke regels je hanteert voor "titel-hoofdletters"; ik weet niet of "HTML String" in titel-hoofdletters staat of niet. Voor het volgende ga ik ervan uit dat je alleen hoeft te zorgen dat het eerste teken van de string en het eerste teken na elke spatie (" ") geen kleine letter is. Dat betekent dat "HTML String" prima is. Als je een andere regel hebt, kun je de code in het onderstaande antwoord aanpassen.
Er is geen specifiek type in TypeScript dat titel-hoofdletter strings vertegenwoordigt. Sjabloon-letterlijke typen geven dit niet aan; terwijl Uppercase<string> en Capitalize<string> sinds TypeScript 4.8 hun eigen typen zijn, geïmplementeerd in microsoft/TypeScript#47050, zullen ze de spatievereisten niet afdwingen.
Voor een echt titel-hoofdletter string-type zou je, zoals je zei, iets nodig hebben zoals regulier expressie gevalideerde string-types. Er is een openstaande kwestie bij microsoft/TypeScript#41160 waarin wordt gevraagd naar gebruikssituaties voor dergelijke regex-gevalideerde types; als de onderstaande oplossing niet aan je behoeften voldoet, wil je misschien op die kwestie reageren met je gebruikssituatie, waarom het overtuigend is, en waarom de alternatieve oplossingen niet volstaan.
Hoewel er hier geen specifiek type is dat werkt, kun je een recursief template literal type TitleCase<T> schrijven dat kan worden gebruikt als een beperking op T. Dit betekent dat T extends TitleCase<T> als en slechts als T een titel-hoofdletter string is.
Dan, om mensen te besparen van het moeten annoteren van hun strings met een generiek type, zou je een hulpfunctie schrijven zoals asTitleCase() die gewoon zijn invoer retourneert, maar een compilerfout geeft als je een slechte string doorgeeft.
Dus, terwijl je ideale oplossing hier er als volgt uit zou zien:
/* 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
*/
de uitvoerbare oplossing ziet er zo uit:
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"'
Nogmaals, dit is wat implementeerbaar is, niet wat ideaal is. Alle toepassingen van stringtypes met hoofdletters zullen een extra generiek typeparameter moeten krijgen.
Merk op dat je waarschijnlijk niet echt asTitleCase(...) hoeft te schrijven tenzij je de fout direct bij de declaratie wilt zien. Vermoedelijk heb je een functie (bijvoorbeeld lookupBookTitle()) die geeft om hoofdlettergebruik in titels. In dat geval zou je gewoon die functie generiek maken en de beperking daar afdwingen. Dus in plaats van const str=asTitleCase("XXX"); lookupBookTitle(str);, zou je gewoon schrijven const str="XXX"; lookupBookTitle(str); Het enige verschil is waar de fout verschijnt.
Ook, binnen de implementatie van iets zoals lookupBookTitle(), zou je waarschijnlijk gewoon de invoer moeten verbreden naar string en het behandelen alsof het al gevalideerd is. Zelfs al heeft T extends TitleCase<T> het effect van het afdwingen van de beperking bij aanroepen, de compiler zal de logica niet kunnen volgen wanneer T een ongespecificeerde generieke typeparameter is:
// 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;
}
Hoe dan ook, hier is de implementatie:
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>;
Het type TitleCase<T, D> splitst de string T bij de scheidingsteken D en maakt elk stuk met een hoofdletter (het eerste teken wordt in hoofdletters gezet). Zo verandert het een string in een titel-hoofdletterversie van zichzelf:
type X = TitleCase<"the quick brown fox jumps over the lazy dog.">
// type X = "The Quick Brown Fox Jumps Over The Lazy Dog."
Dan kunnen we een VerifyTitleCase<T>-type schrijven dat controleert of T extends TitleCase<T>. Als dat zo is, wordt het opgelost naar T. Zo niet, dan wordt het opgelost naar TitleCase<T> of een enkele hard-coded foutstring die hopelijk de gebruikers een idee geeft van wat er mis is gegaan. (Er zijn geen "throw types" of "Invalid types" in TypeScript, zoals gevraagd in microsoft/TypeScript#23689; dus het gebruik van een hard-coded foutstringliteral is een oplossing).
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>
En tot slot, de hulpfunctie:
const asTitleCase = <T extends string>(s: VerifyTitleCase<T>) => s;
Speeltuinlink naar code
