Typescript: Suggestion : Type de chaîne validé par Regex

Créé le 22 janv. 2016  ·  146Commentaires  ·  Source: microsoft/TypeScript

Il existe des cas où une propriété ne peut pas être n'importe quelle chaîne (ou un ensemble de chaînes), mais doit correspondre à un modèle.

let fontStyle: 'normal' | 'italic' = 'normal'; // already available in master
let fontColor: /^#([0-9a-f]{3}|[0-9a-f]{6})$/i = '#000'; // my suggestion

Il est courant en JavaScript de stocker les valeurs de couleur en notation CSS, comme dans le reflet de style CSS des nœuds DOM ou de diverses bibliothèques tierces.

Qu'est-ce que tu penses?

Literal Types Needs Proposal Suggestion

Commentaire le plus utile

Proposition de conception

Il y a beaucoup de cas où les développeurs ont besoin plus de valeur spécifiée puis juste une chaîne, mais ne peut pas les énumérer comme l' union des couleurs css de littéraux de chaîne simples, des e - mails, numéros de téléphone, ZipCode, fanfaronnades extensions etc. Même spécification schéma JSON qui communément utilisé pour décrire le schéma de l'objet JSON a pattern et patternProperties qui, en termes de système de type TS, pourraient être appelés regex-validated string type et regex-validated string type of index .

Buts

Fournissez aux développeurs un système de types qui se rapproche un peu plus du schéma JSON, celui qu'ils utilisent couramment et les empêche également d'oublier les vérifications de validation de chaîne en cas de besoin.

Aperçu syntaxique

La mise en œuvre de cette fonctionnalité se compose de 4 parties :

Type d'expression régulière validé

type CssColor = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
type Email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
type Gmail = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;

Type de variable validé par Regex

let fontColor: /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;

et pareil, mais plus lisible

let fontColor: CssColor;

Type d'index de variable validé par Regex

interface UsersCollection {
    [email: /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i]: User;
}

et pareil, mais plus lisible

interface UsersCollection {
    [email: Email]: User;
}

Type de garde pour le type variable

setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color)) {
        fontColor = color;// correct
    }
}

et pareil

setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (!(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color))) return;
    fontColor = color;// correct
}

et en utilisant un type défini pour une meilleure lisibilité

setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (CssColor.test(color)) {
        fontColor = color;// correct
    }
}

pareil que

setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (!(CssColor.test(color))) return;
    fontColor = color;// correct
}

Type gurard pour le type d'index

let collection: UsersCollection;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i.test(email)) {
        collection[email];// type is User
    }
}

pareil que

let collection: UsersCollection;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (!(/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i.test(email))) return;
    collection[email];// type is User
}

et en utilisant un type défini pour une meilleure lisibilité

let collection: UsersCollection;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (Email.test(email)) {
        collection[email];// type is User
    }
}

pareil que

let collection: UsersCollection;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (!(Email.test(email))) return;
    collection[email];// type is User
}

Aperçu sémantique

Devoirs

let email: Email;
let gmail: Gmail;
email = '[email protected]';// correct
email = '[email protected]';// correct
gmail = '[email protected]';// compile time error
gmail = '[email protected]';// correct
gmail = email;// obviously compile time error
email = gmail;// unfortunately compile time error too

Malheureusement, nous ne pouvons pas vérifier si une regex est le sous-type d'une autre sans impact significatif sur les performances en raison de cet article . Il doit donc être restreint. Mais il existe des solutions de contournement suivantes :

// explicit cast
gmail = <Gmail>email;// correct
// type guard
if (Gmail.test(email)) {
    gmail = email;// correct
}
// another regex subtype declaration
type Gmail = Email & /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
gmail = email;// correct

Malheureusement, l'affectation de la variable string variable regex-validated devrait également être restreinte, car il n'y a aucune garantie au moment de la compilation qu'elle correspondra à l'expression régulière.

let someEmail = '[email protected]';
let someGmail = '[email protected]';
email = someEmail;// compile time error
gmail = someGmail;// compile time error

Mais nous pouvons utiliser des gardes de cast ou de type explicites comme indiqué ici . La deuxième est recommandée.
Heureusement, ce n'est pas un cas pour les littéraux de chaîne, car en les utilisant, nous SOMMES en mesure de vérifier que sa valeur correspond à regex.

let someEmail: '[email protected]' = '[email protected]';
let someGmail: '[email protected]' = '[email protected]';
email = someEmail;// correct
gmail = someGmail;// correct

Type de rétrécissement pour les index

Pour les cas simples de regex-validated type d'index, voir Type gurard pour le type d'index .
Mais il peut y avoir des cas plus compliqués :

type Email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
type Gmail = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
interface UsersCollection {
    [email: Email]: User;
    [gmail: Gmail]: GmailUser;
}
let collection: UsersCollection;
let someEmail = '[email protected]';
let someGmail = '[email protected]';
collection['[email protected]'];// type is User
collection['[email protected]'];// type is User & GmailUser
collection[someEmail];// unfortunately type is any
collection[someGmail];// unfortunately type is any
// explicit cast is still an unsafe workaround
collection[<Email> someEmail];// type is User
collection[<Gmail> someGmail];// type is GmailUser
collection[<Email & Gmail> someGmail];// type is User & GmailUser

Les littéraux n'ont pas ce problème :

let collection: UsersCollection;
let someEmail: '[email protected]' = '[email protected]';
let someGmail: '[email protected]' = '[email protected]';
collection[someEmail];// type is User
collection[someGmail];// type is User & GmailUser

Mais pour les variables, la meilleure option consiste à utiliser des gardes de type comme dans les prochains exemples plus réalistes :

getUserByEmail(email: string) {
    collection[email];// type is any
    if (Email.test(email)) {
        collection[email];// type is User
        if (Gmail.test(email)) {
            collection[email];// type is User & GmailUser
        }
    }
    if (Gmail.test(email)) {
        collection[email];// type is GmailUser
    }
}

Mais si nous utilisons une meilleure définition pour le type Gmail cela aurait un autre type de rétrécissement :

type Gmail = Email & /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (Email.test(email)) {
        collection[email];// type is User
        if (Gmail.test(email)) {
            collection[email];// type is User & GmailUser
        }
    }
    if (Gmail.test(email)) {
        collection[email];// type is User & GmailUser
    }
}

Syndicats et carrefours

En fait, les types communs et les types regex-validated sont vraiment différents, nous avons donc besoin de règles pour gérer correctement leurs unions et intersections.

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_1 = Regex_1 | Regex_2;// correct
type test_2 = Regex_1 & Regex_2;// correct
type test_3 = Regex_1 | NonRegex;// correct
type test_4 = Regex_1 & NonRegex;// compile time error
if (test_1.test(something)) {
    something;// type is test_1
    // something matches Regex_1 OR Regex_2
}
if (test_2.test(something)) {
    something;// type is test_2
    // something matches Regex_1 AND Regex_2
}
if (test_3.test(something)) {
    something;// type is Regex_1
} else {
    something;// type is NonRegex
}

Génériques

Il n'y a pas de cas particuliers pour les génériques, donc le type regex-validated peut être utilisé avec les génériques de la même manière que les types habituels.
Pour les génériques avec des contraintes comme ci-dessous, le type regex-validated se comporte comme une chaîne :

class Something<T extends String> { ... }
let something = new Something<Email>();// correct

Aperçu des émissions

Contrairement aux types habituels, les regex-validated ont un certain impact sur l'émission :

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_1 = Regex_1 | Regex_2;
type test_2 = Regex_1 & Regex_2;
type test_3 = Regex_1 | NonRegex;
type test_4 = Regex_1 & NonRegex;
if (test_1.test(something)) {
    /* ... */
}
if (test_2.test(something)) {
    /* ... */
}
if (test_3.test(something)) {
    /* ... */
} else {
    /* ... */
}

compilera pour :

var Regex_1 = / ... /;
var Regex_2 = / ... /;
if (Regex_1.test(something) || Regex_2.test(something)) {
    /* ... */
}
if (Regex_1.test(something) && Regex_2.test(something)) {
    /* ... */
}
if (Regex_1.test(something)) {
    /* ... */
} else {
    /* ... */
}

Présentation de la compatibilité

Cette fonctionnalité n'a aucun problème de compatibilité, car il n'y a qu'un seul cas qui pourrait la casser et qui est lié à ce type regex-validated a un impact différent du type habituel, c'est donc un code TS valide :

type someType = { ... };
var someType = { ... };

lorsque le code ci-dessous n'est pas :

type someRegex = / ... /;
var someRegex = { ... };

Mais le deuxième ÉTAIT déjà invalide, mais pour une autre raison (la déclaration de type était erronée).
Nous devons donc maintenant restreindre la déclaration de la variable avec le même nom au type, au cas où ce type est regex-validated .

PS

N'hésitez pas à signaler des choses que j'ai probablement manquées. Si vous aimez cette proposition, je pourrais essayer de créer des tests qui la couvrent et les ajouter en tant que PR.

Tous les 146 commentaires

Ouais, j'ai vu ça passer au peigne fin DefinitelyTyped, . Même nous pourrions utiliser quelque chose comme ça avec ScriptElementKind dans la couche de services , où nous serions idéalement en mesure de les décrire comme une liste de chaînes spécifiques séparées par des virgules.

Les principaux problèmes sont :

  • Il n'est pas clair comment bien les composer. Si je veux une liste séparée par des virgules de "cat" , "dog" et "fish" , alors je dois écrire quelque chose comme /dog|cat|fish(,(dog|cat|fish))*/ .

    • Si j'ai déjà des types décrivant des types littéraux de chaîne pour "cat" , "dog " et "fish" , comment puis-je les intégrer dans cette expression régulière ?

    • Il est clair qu'il y a une répétition ici, ce qui n'est pas souhaitable. Peut-être que la résolution du problème précédent faciliterait les choses.

  • Les extensions non standard rendent ce genre de doute.

Énorme +1 à ce sujet, ZipCode, SSN, ONet, de nombreux autres cas d'utilisation pour cela.

J'ai rencontré le même problème et je vois qu'il n'est pas encore implémenté, peut-être que cette solution de contournement sera utile :
http://stackoverflow.com/questions/37144672/guid-uuid-type-in-typescript

Comme @mhegazy l'a suggéré, je vais mettre ma suggestion (#8665) ici. Qu'en est-il des fonctions de validation simples dans les déclarations de type ? Quelque chose comme ca:

type Integer(n:number) => String(n).macth(/^[0-9]+$/)
let x:Integer = 3 //OK
let y:Integer = 3.6 //wrong

type ColorLevel(n:number) => n>0 && n<= 255
type RGB = {red:ColorLevel, green:ColorLevel, blue:ColorLevel};
let redColor:RGB = {red:255, green:0, blue:0}   //OK
let wrongColor:RGB = {red:255, green:900, blue:0} //wrong

type Hex(n:string) => n.match(/^([0-9]|[A-F])+$/)
let hexValue:Hex = "F6A5" //OK
let wrongHexValue:Hex = "F6AZ5" //wrong

La valeur que le type peut accepter serait déterminée par le type de paramètre de fonction et par l'évaluation de la fonction elle-même. Cela résoudrait également #7982.

@rylphs +1 cela rendrait TypeScript extrêmement puissant

Comment le sous-typage fonctionne-t-il avec les _types de chaîne validés par regex_ ?

let a: RegExType_1
let b: RegExType_2

a = b // Is this allowed? Is RegExType_2 subtype of RegExType_1?
b = a // Is this allowed? Is RegExType_1 subtype of RegExType_2?

RegExType_1 et RegExType_2 sont des _types de chaîne validés par regex_.

Edit : il semble que ce problème soit résolu en temps polynomial (voir Le problème d'inclusion pour les expressions régulières ).

Aiderait également avec TypeStyle : https://github.com/typestyle/typestyle/issues/5 :rose:

Dans JSX, @RyanCavanaugh et moi avons vu des gens ajouter des attributs aria- (et potentiellement data- ). Quelqu'un a en fait ajouté une signature d'index de chaîne dans DefinitelyTyped en tant que fourre-tout. Une nouvelle signature d'index pour cela aurait été utile.

interface IntrinsicElements {
    // ....
    [attributeName: /aria-\w+/]: number | string | boolean;
}

Proposition de conception

Il y a beaucoup de cas où les développeurs ont besoin plus de valeur spécifiée puis juste une chaîne, mais ne peut pas les énumérer comme l' union des couleurs css de littéraux de chaîne simples, des e - mails, numéros de téléphone, ZipCode, fanfaronnades extensions etc. Même spécification schéma JSON qui communément utilisé pour décrire le schéma de l'objet JSON a pattern et patternProperties qui, en termes de système de type TS, pourraient être appelés regex-validated string type et regex-validated string type of index .

Buts

Fournissez aux développeurs un système de types qui se rapproche un peu plus du schéma JSON, celui qu'ils utilisent couramment et les empêche également d'oublier les vérifications de validation de chaîne en cas de besoin.

Aperçu syntaxique

La mise en œuvre de cette fonctionnalité se compose de 4 parties :

Type d'expression régulière validé

type CssColor = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
type Email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
type Gmail = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;

Type de variable validé par Regex

let fontColor: /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;

et pareil, mais plus lisible

let fontColor: CssColor;

Type d'index de variable validé par Regex

interface UsersCollection {
    [email: /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i]: User;
}

et pareil, mais plus lisible

interface UsersCollection {
    [email: Email]: User;
}

Type de garde pour le type variable

setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color)) {
        fontColor = color;// correct
    }
}

et pareil

setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (!(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(color))) return;
    fontColor = color;// correct
}

et en utilisant un type défini pour une meilleure lisibilité

setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (CssColor.test(color)) {
        fontColor = color;// correct
    }
}

pareil que

setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (!(CssColor.test(color))) return;
    fontColor = color;// correct
}

Type gurard pour le type d'index

let collection: UsersCollection;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i.test(email)) {
        collection[email];// type is User
    }
}

pareil que

let collection: UsersCollection;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (!(/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i.test(email))) return;
    collection[email];// type is User
}

et en utilisant un type défini pour une meilleure lisibilité

let collection: UsersCollection;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (Email.test(email)) {
        collection[email];// type is User
    }
}

pareil que

let collection: UsersCollection;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (!(Email.test(email))) return;
    collection[email];// type is User
}

Aperçu sémantique

Devoirs

let email: Email;
let gmail: Gmail;
email = '[email protected]';// correct
email = '[email protected]';// correct
gmail = '[email protected]';// compile time error
gmail = '[email protected]';// correct
gmail = email;// obviously compile time error
email = gmail;// unfortunately compile time error too

Malheureusement, nous ne pouvons pas vérifier si une regex est le sous-type d'une autre sans impact significatif sur les performances en raison de cet article . Il doit donc être restreint. Mais il existe des solutions de contournement suivantes :

// explicit cast
gmail = <Gmail>email;// correct
// type guard
if (Gmail.test(email)) {
    gmail = email;// correct
}
// another regex subtype declaration
type Gmail = Email & /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
gmail = email;// correct

Malheureusement, l'affectation de la variable string variable regex-validated devrait également être restreinte, car il n'y a aucune garantie au moment de la compilation qu'elle correspondra à l'expression régulière.

let someEmail = '[email protected]';
let someGmail = '[email protected]';
email = someEmail;// compile time error
gmail = someGmail;// compile time error

Mais nous pouvons utiliser des gardes de cast ou de type explicites comme indiqué ici . La deuxième est recommandée.
Heureusement, ce n'est pas un cas pour les littéraux de chaîne, car en les utilisant, nous SOMMES en mesure de vérifier que sa valeur correspond à regex.

let someEmail: '[email protected]' = '[email protected]';
let someGmail: '[email protected]' = '[email protected]';
email = someEmail;// correct
gmail = someGmail;// correct

Type de rétrécissement pour les index

Pour les cas simples de regex-validated type d'index, voir Type gurard pour le type d'index .
Mais il peut y avoir des cas plus compliqués :

type Email = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i;
type Gmail = /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
interface UsersCollection {
    [email: Email]: User;
    [gmail: Gmail]: GmailUser;
}
let collection: UsersCollection;
let someEmail = '[email protected]';
let someGmail = '[email protected]';
collection['[email protected]'];// type is User
collection['[email protected]'];// type is User & GmailUser
collection[someEmail];// unfortunately type is any
collection[someGmail];// unfortunately type is any
// explicit cast is still an unsafe workaround
collection[<Email> someEmail];// type is User
collection[<Gmail> someGmail];// type is GmailUser
collection[<Email & Gmail> someGmail];// type is User & GmailUser

Les littéraux n'ont pas ce problème :

let collection: UsersCollection;
let someEmail: '[email protected]' = '[email protected]';
let someGmail: '[email protected]' = '[email protected]';
collection[someEmail];// type is User
collection[someGmail];// type is User & GmailUser

Mais pour les variables, la meilleure option consiste à utiliser des gardes de type comme dans les prochains exemples plus réalistes :

getUserByEmail(email: string) {
    collection[email];// type is any
    if (Email.test(email)) {
        collection[email];// type is User
        if (Gmail.test(email)) {
            collection[email];// type is User & GmailUser
        }
    }
    if (Gmail.test(email)) {
        collection[email];// type is GmailUser
    }
}

Mais si nous utilisons une meilleure définition pour le type Gmail cela aurait un autre type de rétrécissement :

type Gmail = Email & /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@gmail\.com$/i;
getUserByEmail(email: string) {
    collection[email];// type is any
    if (Email.test(email)) {
        collection[email];// type is User
        if (Gmail.test(email)) {
            collection[email];// type is User & GmailUser
        }
    }
    if (Gmail.test(email)) {
        collection[email];// type is User & GmailUser
    }
}

Syndicats et carrefours

En fait, les types communs et les types regex-validated sont vraiment différents, nous avons donc besoin de règles pour gérer correctement leurs unions et intersections.

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_1 = Regex_1 | Regex_2;// correct
type test_2 = Regex_1 & Regex_2;// correct
type test_3 = Regex_1 | NonRegex;// correct
type test_4 = Regex_1 & NonRegex;// compile time error
if (test_1.test(something)) {
    something;// type is test_1
    // something matches Regex_1 OR Regex_2
}
if (test_2.test(something)) {
    something;// type is test_2
    // something matches Regex_1 AND Regex_2
}
if (test_3.test(something)) {
    something;// type is Regex_1
} else {
    something;// type is NonRegex
}

Génériques

Il n'y a pas de cas particuliers pour les génériques, donc le type regex-validated peut être utilisé avec les génériques de la même manière que les types habituels.
Pour les génériques avec des contraintes comme ci-dessous, le type regex-validated se comporte comme une chaîne :

class Something<T extends String> { ... }
let something = new Something<Email>();// correct

Aperçu des émissions

Contrairement aux types habituels, les regex-validated ont un certain impact sur l'émission :

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_1 = Regex_1 | Regex_2;
type test_2 = Regex_1 & Regex_2;
type test_3 = Regex_1 | NonRegex;
type test_4 = Regex_1 & NonRegex;
if (test_1.test(something)) {
    /* ... */
}
if (test_2.test(something)) {
    /* ... */
}
if (test_3.test(something)) {
    /* ... */
} else {
    /* ... */
}

compilera pour :

var Regex_1 = / ... /;
var Regex_2 = / ... /;
if (Regex_1.test(something) || Regex_2.test(something)) {
    /* ... */
}
if (Regex_1.test(something) && Regex_2.test(something)) {
    /* ... */
}
if (Regex_1.test(something)) {
    /* ... */
} else {
    /* ... */
}

Présentation de la compatibilité

Cette fonctionnalité n'a aucun problème de compatibilité, car il n'y a qu'un seul cas qui pourrait la casser et qui est lié à ce type regex-validated a un impact différent du type habituel, c'est donc un code TS valide :

type someType = { ... };
var someType = { ... };

lorsque le code ci-dessous n'est pas :

type someRegex = / ... /;
var someRegex = { ... };

Mais le deuxième ÉTAIT déjà invalide, mais pour une autre raison (la déclaration de type était erronée).
Nous devons donc maintenant restreindre la déclaration de la variable avec le même nom au type, au cas où ce type est regex-validated .

PS

N'hésitez pas à signaler des choses que j'ai probablement manquées. Si vous aimez cette proposition, je pourrais essayer de créer des tests qui la couvrent et les ajouter en tant que PR.

J'ai oublié de signaler certains cas d'intersections et d'unions de types regex-validated , mais je les ai décrits dans le dernier cas de test. Dois-je mettre Design proposal jour

@Igmat , question sur votre proposition de conception : pourriez-vous élaborer sur la vue d'ensemble de l'émission ? Pourquoi des types validés par regex devraient-ils être émis ? Autant que je sache, d'autres types ne prennent pas en charge les contrôles d'exécution... est-ce que j'ai raté quelque chose ?

@alexanderbird , oui, tout autre type n'a aucun impact sur l'émission. Au début, je pensais que regex-validated le ferait aussi, alors j'ai commencé à créer la proposition et à jouer avec la syntaxe proposée.
La première approche était comme ceci:

let fontColor: /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
fontColor = "#000";

et ça:

type CssColor: /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
let fontColor: CssColor;
fontColor = "#000";

C'est ok et n'a pas besoin d'émettre des modifications, car "#000" peut être vérifié au moment de la compilation.
Mais nous devons également gérer le rétrécissement du type string à regex-validated afin de le rendre utile. J'ai donc pensé à cela pour les deux configurations précédentes:

let someString: string;
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(someString)) {
    fontColor = someString; // Ok
}
fontColor = someString; // compile time error

Cela n'a donc pas non plus d'impact sur l'émission et semble correct, sauf que l'expression régulière n'est pas très lisible et doit être copiée partout, de sorte que l'utilisateur peut facilement se tromper. Mais dans ce cas particulier, cela semble toujours mieux que de changer le fonctionnement de type .
Mais ensuite j'ai réalisé que ce truc:

let someString: string;
let email: /^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/I;
if (/^[-a-z0-9~!$%^&*_=+}{\'?]+(\.[-a-z0-9~!$%^&*_=+}{\'?]+)*@([a-z0-9_][-a-z0-9_]*(\.[-a-z0-9_]+[a-z][a-z])|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,5})?$/i.test(someString)) {
    email = someString; // Ok
}
email = someString; // compile time error

est un cauchemar. Et c'est même sans intersections et sans syndicats. Donc, pour éviter que ce genre de choses ne se produise, nous devons modifier légèrement type émis comme indiqué dans la proposition.

@DanielRosenwasser , pourriez-vous, s'il vous plaît, donner votre avis sur cette proposition ? Et aussi pour les tests référencés ici, si possible ?
Je veux vraiment aider à la mise en œuvre de cette fonctionnalité, mais cela prend beaucoup de temps ( tsc est un projet vraiment compliqué et je dois encore travailler pour comprendre comment cela fonctionne à l'intérieur) et je ne sais pas si c'est cette proposition est prête à être implémentée ou vous rejetterez cette fonctionnalité implémentée de cette manière en raison d'une autre vision de conception de langage ou pour toute autre raison.

Hey @Igmat , je pense qu'il y a quelques choses que j'aurais dû demander au départ

Pour commencer, je ne comprends toujours pas pourquoi vous avez besoin de toute sorte de changement pour émettre, et je ne pense pas qu'une sorte d'émission basée sur les types serait acceptable. Découvrez nos non-buts ici .

Un autre problème que j'aurais dû soulever est le problème des expressions régulières qui utilisent des références arrière. Ma compréhension (et mon expérience) est que les références arrière dans une expression régulière peuvent forcer un test à s'exécuter dans un temps exponentiel à son entrée. Est-ce un cas de coin? Peut-être, mais c'est quelque chose que je préfère éviter en général. Ceci est particulièrement important étant donné que dans les scénarios d'éditeur, une vérification de type à un emplacement devrait prendre un minimum de temps.

Un autre problème est que nous devions soit nous appuyer sur le moteur sur lequel le compilateur TypeScript s'exécute, soit créer un moteur d'expressions régulières personnalisé pour exécuter ces choses. Par exemple, TC39 va inclure un nouveau drapeau s afin que . puisse correspondre aux nouvelles lignes. Il y aurait un écart entre ESXXXX et les anciens environnements d'exécution qui le prennent en charge.

@igmat - il ne fait aucun doute dans mon esprit qu'il serait utile d'avoir des expressions régulières émises au moment de l'exécution. Cependant, je ne pense pas qu'ils soient nécessaires pour que cette fonctionnalité soit utile (et d'après les sons de ce que @DanielRosenwasser a dit, elle ne serait probablement pas approuvée de toute façon). Tu as dit

Mais nous devons également gérer le rétrécissement de la chaîne au type validé par regex afin de le rendre utile

Je pense que ce n'est le cas que si nous devons passer d'une chaîne dynamique à un type validé par regex. Cela devient très compliqué. Même dans ce cas simple :

function foo(bar: number) {
    let baz: /prefix:\d+/ = 'prefix:' + number;
}

Nous ne pouvons pas être sûrs que les types correspondront - et si le nombre est négatif ? Et à mesure que les expressions régulières deviennent plus compliquées, cela devient de plus en plus compliqué. Si nous voulions vraiment cela, nous autorisons peut-être "l'interpolation de type : type Baz = /prefix:{number}/ ... mais je ne sais pas si cela vaut la peine d'y aller.

Au lieu de cela, nous pourrions atteindre la moitié de l'objectif si nous autorisions uniquement l'affectation de littéraux de chaîne à des types validés par regex.

Considérer ce qui suit:

type Color = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
let foo: Color = '#000000';
let bar: Color = '#0000'; // Error - string literal '#0000' is not assignable to type 'Color'; '#0000' does not match /^#([0-9a-f]{3}|[0-9a-f]{6})$/i
let baz: Color = '#' + config.userColorChoice; // Error - type 'string' is not assignable to type 'regex-validated-string'

Pensez-vous que c'est une alternative viable?

@DanielRosenwasser , j'ai lu attentivement les objectifs de conception et, si je vous comprends bien, le problème est la violation des non-objectifs n°5.
Mais cela ne me semble pas comme une violation, mais comme une amélioration de la syntaxe. Par exemple, auparavant nous avions :

const emailRegex = /.../;
/**
 * assign it only with values tested to emailRegex 
 */
let email: string;
let userInput: string;
// somehow get user input
if (emailRegex.test(userInput)) {
    email = userInput;
} else {
    console.log('User provided invalid email. Showing validation error');
    // Some code for validation error
}

Avec cette proposition mise en œuvre, cela ressemblerait à :

type Email = /.../;
let email: Email;
let userInput: string;
// somehow get user input
if (Email.test(userInput)) {
    email = userInput;
} else {
    console.log('User provided invalid email. Showing validation error');
    // Some code for validation error
}

Comme vous le voyez, le code est presque le même - c'est une utilisation simple et courante de regex. Mais le deuxième cas est beaucoup plus expressif et empêchera l'utilisateur d'erreur accidentelle, comme oublier de vérifier la chaîne avant de l'affecter à une variable qui devait être validée par regex.
La deuxième chose est que sans un tel rétrécissement de type, nous ne pourrons normalement pas utiliser le type validé par regex dans les index, car dans la plupart des cas, ces champs d'index fonctionnent avec une variable qui ne peut pas être vérifiée à l'exécution comme cela pourrait être fait avec des littéraux .

@alexanderbird , je ne suggère pas de rendre ce code valide ou d'ajouter des vérifications cachées à la fois à l'exécution et à la compilation.

function foo(bar: number) {
    let baz: /prefix:\d+/ = 'prefix:' + number;
}

Ce code doit générer une erreur en raison de ma proposition. Mais ça:

function foo(bar: number) {
    let baz: /prefix:\d+/ = ('prefix:' + number) as /prefix:\d+/;
}

ou ca:

function foo(bar: number) {
    let baz: /prefix:\d+/;
    let possibleBaz: string = 'prefix:' + number;
    if (/prefix:\d+/.test(possibleBaz)) {
        baz = possibleBaz;
    }
}

serait correct, et n'aurait même aucun impact sur le code émis.

Et comme je l'ai montré dans le commentaire précédent, les littéraux ne seraient certainement pas suffisants même pour les cas d'utilisation courants, car nous devons souvent travailler avec des piqûres provenant d'entrées d'utilisateurs ou d'autres sources. Sans la mise en œuvre de cet impact d'émission, les utilisateurs devraient travailler avec ce type de la manière suivante :

export type Email = /.../;
export const Email = /.../;
let email: Email;
let userInput: string;
// somehow get user input
if (Email.test(userInput)) {
    email = <Email>userInput;
} else {
    console.log('User provided invalid email. Showing validation error');
    // Some code for validation error
}

ou pour les carrefours :

export type Email = /email-regex/;
export const Email = /email-regex/;
export type Gmail = Email & /gmail-regex/;
export const Gmail = {
    test: (input: string) => Email.test(input) && /gmail-regex/.test(input)
};
let gmail: Gmail;
let userInput: string;
// somehow get user input
if (Gmail.test(userInput)) {
    gmail = <Gmail>userInput;
} else {
    console.log('User provided invalid gmail. Showing validation error');
    // Some code for validation error
}

Je ne pense pas que forcer les utilisateurs à dupliquer du code et à utiliser un transtypage explicite, alors qu'il pourrait être facilement géré par le compilateur, n'est pas une bonne voie à suivre. L'impact des émissions est vraiment très faible et prévisible, je suis sûr que cela ne surprendra pas les utilisateurs ou ne conduira pas à une fonctionnalité mal comprise ou à des bogues difficiles à localiser, tout en implémentant cette fonctionnalité sans émettre de changements.

En conclusion, je tiens à dire qu'en termes simples, le type regex-validated est à la fois une variable étendue et un type de compilateur.

@DanielRosenwasser et @alexanderbird ok, j'ai encore une idée pour ça. Qu'en est-il de la syntaxe comme celle-ci :

const type Email = /email-regex/;

Dans ce cas, l'utilisateur doit définir explicitement qu'il/elle veut cela à la fois comme type et const , donc le système de type réel n'a pas de changements d'émission à moins qu'il ne soit utilisé avec un tel modificateur. Mais s'il est utilisé avec, nous pouvons toujours éviter beaucoup d'erreurs, de transtypages et de duplication de code en ajoutant les mêmes émissions que pour :

const Email = /email-regex/;

Cela semble être encore plus important qu'une simple amélioration pour cette proposition, car cela pourrait probablement permettre quelque chose comme ça (l'exemple provient du projet avec Redux ):

export type SOME_ACTION = 'SOME_ACTION';
export const SOME_ACTION = 'SOME_ACTION' as SOME_ACTION;

étant converti en

export const type SOME_ACTION = 'SOME_ACTION';

J'ai essayé de trouver une suggestion similaire mais sans succès. Si cela peut être une solution de contournement et si vous aimez une telle idée, je peux préparer une proposition de conception et des tests pour cela.

@DanielRosenwasser , à propos de votre deuxième problème - je ne pense pas que cela arriverait jamais, car dans ma suggestion, le compilateur exécute regex uniquement pour les littéraux et il ne semble pas que quelqu'un fasse quelque chose comme ça:

let something: /some-regex-with-backreferences/ = `
long enough string to make regex.test significantly affect performance
`

Quoi qu'il en soit, nous pourrions tester combien de temps le littéral devrait durer pour affecter les performances en temps réel et créer une heuristique qui avertira l'utilisateur si nous ne sommes pas en mesure de le vérifier alors qu'il fait face à ces circonstances dans certains scénarios d'éditeur, mais nous le vérifierions quand il compilera le projet. Ou il pourrait y avoir d'autres solutions de contournement.

À propos de la troisième question, je ne suis pas sûr de tout comprendre correctement, mais il semble que le moteur regex devrait être sélectionné en fonction de target de tsconfig s'ils ont des implémentations différentes. A besoin de plus d'enquête.

@DanielRosenwasser y a-t-il des idées ? A propos de la proposition initiale et de la dernière. Peut-être que je dois faire un aperçu plus détaillé du deuxième, n'est-ce pas ?

@Igmat Votre proposition limite la validation à n'être utile qu'avec les types de chaîne. Que pensez-vous de la proposition de @rylphs ? Cela permettrait une validation plus générique pour tous les types primitifs :

type ColorLevel = (n:number) => n>0 && n<= 255
type RGB = {red:ColorLevel, green:ColorLevel, blue:ColorLevel};
let redColor:RGB = {red:255, green:0, blue:0}   //OK
let wrongColor:RGB = {red:255, green:900, blue:0} //wrong

Je soupçonne cependant qu'étendre ce mécanisme au-delà des primitifs aux types non primitifs serait trop.
Un point, le problème soulevé par @DanielRosenwasser - concernant les différentes implémentations du moteur regex - serait amplifié : selon le moteur Javascript sous lequel le compilateur Typescript s'exécute, la fonction de validation peut fonctionner différemment.

@zspitz cela semble prometteur mais à mon avis, cela pourrait trop affecter les performances du compilateur, car la fonction n'est limitée par aucune règle et cela obligera TS à calculer certaines expressions trop compliquées ou même dépendantes de certaines ressources qui ne sont pas disponibles au moment de la compilation.

@Igmat

parce que la fonction n'est limitée par aucune règle

Avez-vous des exemples précis en tête? Il est peut-être possible de limiter la syntaxe de validation à un sous-ensemble "sûr"/connu au moment de la compilation de Typescript.

que diriez-vous de faire des gardes de type définis par l'utilisateur pour définir un nouveau type ?

// type guard that introduces new nominal type int
function isInt(value: number): value is type int { return /^\d+$/.test(value.toString()); }
// -------------------------------------^^^^ add type keyword here
function printNum(value: number) { console.log(value); }
function printInt(value: int) { console.log(value); }
const num = 123;
printNum(num); // ok
printInt(num); // error
if (isInt(num)) {
    printNum(num); // ok
    printInt(num); // ok
}

@disjukr a l' air sympa, mais qu'en est-il de l'extension de type ?

Doivent-ils absolument être extensibles ? Existe-t-il un principe de conception TypeScript qui l'exige ? Sinon, je préférerais avoir les types nominaux suggérés par @disjukr que rien, même s'ils ne sont pas extensibles.

Nous aurions besoin de quelque chose d'assez créatif pour obtenir l'extensibilité IMHO - nous ne pouvons pas déterminer si une garde de type arbitraire (fonction) est un sous-ensemble d'une autre garde de type arbitraire.

Nous pourrions obtenir une "extensibilité" rudimentaire en utilisant une mentalité d'assertion de type (je ne dis pas que c'est quelque chose de "assez créatif" - je dis que voici un palliatif jusqu'à ce que quelqu'un propose quelque chose d'assez créatif):

function isInt(value: number): value is type int { return /^\d+$/.test(value.toString()); }
// assert that biggerInt extends int. No compiler or runtime check that it actually does extend.
function isBiggerInt(value: number): value is type biggerInt extends int { return /^\d{6,}$/.test(value.toString()); }
// -----------------------------------------------------------^^^^ type extension assertion
function printNum(value: number) { console.log(value); }
function printInt(value: int) { console.log(value); }
function printBiggerInt(value: biggerInt) {console.log(value); }

const num = 123;
printNum(num); // ok
printInt(num); // error
printBiggerInt(num); // error
if (isInt(num)) {
    printNum(num); // ok
    printInt(num); // ok
    printBiggerInt(num); // error
}
if (isBiggerInt(num)) {
    printNum(num); // ok
    printInt(num); // ok
    printBiggerInt(num); // ok
}

Ce qui peut être utile même si ce n'est pas du son. Mais comme je l'ai dit au début, avons-nous besoin qu'il soit extensible, ou pouvons-nous l'implémenter comme suggéré par @disjukr ? (Si ce dernier, je suggère que nous l'implémentions de la manière non extensible de

Sorte de hors-sujet, répondez au premier commentaire de @DanielRosenwasser :
Pour les listes séparées par des virgules, vous devez utiliser les ancres ^ et $ (c'est pertinent dans la plupart des cas lorsque vous souhaitez valider une chaîne). Et les ancres aident à éviter les répétitions, pour votre exemple, l'expression rationnelle sera /^((dog|cat|fish)(,|$))+$/

Autoriser les types de chaînes à être des expressions régulières /#[0-9]{6}/ et autoriser les types d'imbrication dans des expressions régulières ${TColor} :

type TColor = 'red' | 'blue' | /#[0-9]{6}/;
type TBorderValue = /[0-9]+px (solid|dashed) ${TColor}/

Résultat:

let border1: TBorderValue = '1px solid red'; // OK
let border2: TBorderValue = '1px solid yellow'; // TSError: .....

Cas d'utilisation : il existe une bibliothèque dédiée à l'écriture de styles CSS "type-safe" dans TypeScript typestyle . La fonctionnalité proposée ci-dessus aiderait grandement, car la bibliothèque doit exposer des méthodes à utiliser au moment de l'exécution, les types de regex de chaîne proposés seraient à la place capables de vérifier le code au moment de la compilation et donneraient aux développeurs une grande intellisense.

@DanielRosenwasser @alexanderbird @Igmat : OMI, cette proposition changerait la donne pour TypeScript et le développement Web. Qu'est-ce qui l'empêche actuellement d'être mis en œuvre ?

Je suis d'accord que l'extensibilité et l'émission de type ne devraient pas gêner le reste de la fonctionnalité. S'il n'y a pas de chemin clair sur ces aspects, mettez-les en œuvre plus tard quand il y en aura.

Je suis arrivé ici car je cherche à avoir un type UUID et non une chaîne, donc avoir une expression régulière définissant la chaîne serait génial dans ce cas + un moyen de vérifier la validité du type (exemple Email.test) serait également utile.

@skbergam J'essaie de l'implémenter moi-même une fois de plus. Mais le projet TS est vraiment énorme et j'ai aussi du travail, donc il n'y a quasiment pas d'avancées (je n'ai réussi qu'à créer des tests pour cette nouvelle fonctionnalité). Si quelqu'un a plus d'expérience dans l'extension de TS, toute aide serait grandement appréciée...

Intéressant que cela crée effectivement un type nominal, car nous serions incapables d'établir des relations de sous-type/assignabilité entre deux expressions rationnelles non identiques

@RyanCavanaugh Plus tôt @maiermic a commenté

Edit : Il semble que ce problème soit résolu en temps polynomial (voir Le problème d'inclusion pour les expressions régulières).

Mais ce n'est peut-être pas assez bon ? On espère certainement qu'il n'y a pas beaucoup de relations d'expression régulière, mais on ne sait jamais.

En ce qui concerne les vérifications de type, si nous n'aimons pas dupliquer les expressions rationnelles et que typeof un const n'est pas assez bon (par exemple, les fichiers .d.ts), que pense TS de valueof e , qui émet la valeur littérale de e ssi e est un littéral, sinon une erreur (et émet quelque chose comme undefined ) ?

@maxlk Également hors sujet, mais j'ai pris votre regex et l'ai améliorée pour qu'elle ne corresponde pas aux virgules de fin sur une entrée par ailleurs valide : /^((dog|cat|fish)(,(?=\b)|$))+$/ avec test https://regex101.com/r/AuyP3g/1. Cela utilise une anticipation positive pour un caractère de mot après la virgule, forçant l'avant à revalider de manière DRY.

Salut!
Quel est le statut de ceci?
Ajouterez-vous cette fonctionnalité dans un proche avenir ? Je ne trouve rien à ce sujet dans la feuille de route.

@lgmat Que diriez-vous de limiter la syntaxe aux fonctions fléchées sur une seule ligne, en utilisant uniquement les définitions disponibles dans lib.d.ts ?

Ces améliorations impressionnantes sont-elles disponibles ? Peut-être au moins en version alpha ?

Les types validés par regex sont parfaits pour écrire des tests, la validation des entrées codées en dur serait formidable.

+1. Notre cas d'utilisation est très courant, nous devons avoir un format de date de chaîne comme 'jj/mm/AAAA'.

bien que, comme proposé, ce serait une fonctionnalité extrêmement intéressante, il manque le potentiel :

  • la sortie est toujours une piqûre (bien que le temps de compilation soit vérifié), il n'y a aucun moyen d'obtenir un objet structuré
  • la grammaire est limitée à ce que les expressions rationnelles peuvent faire, et elles ne peuvent pas faire grand-chose, le problème des expressions régulières est qu'elles sont régulières, leur résultat est une liste, pas un arbre, elles ne peuvent pas exprimer de longs niveaux arbitraires imbriqués
  • l'analyseur doit être exprimé en termes de grammaire dactylographiée, qui est limitée et non extensible

le meilleur moyen serait d'externaliser l'analyse et l'émission vers un plugin, comme proposé dans #21861, de cette façon, tout ce qui précède n'est pas un problème au prix d'une courbe d'apprentissage plus raide, mais bon ! la vérification de l'expression régulière peut être implémentée en plus de cela afin que la proposition d'origine soit toujours valable, venant de machines plus avancées

donc comme je l'ai dit, une manière plus générale serait des fournisseurs de syntaxe personnalisés pour n'importe quel littéral : #21861

exemples:

const uri: via URIParserAndEmitter = http://google.com; 
console.log(uri); // --> { protocol: 'http', host: 'google.com', path: undefined, query: undefined, hash: undefined }

const a: via PositiveNumberParser = 10; // --> 10
const b: via PositiveNumberParser = -10; // --> error

const date: via DateParser = 1/1/2019; // --> new Date(2019, 1, 1)


@lgmat Que diriez-vous de limiter la syntaxe aux fonctions fléchées sur une seule ligne, en utilisant uniquement les définitions disponibles dans lib.d.ts ?

@zspitz qui rendrait beaucoup de gens mécontents, comme ils le verraient, que c'est possible, mais interdit pour eux, essentiellement pour leur sécurité.

Ces améliorations impressionnantes sont-elles disponibles ? Peut-être au moins en version alpha ?

Autant que je sache, cela a encore besoin d'une proposition. @gtamas , @AndrewEastwood

Aussi, je pense que #11152 affecterait cela.

@Igmat Votre proposition limite la validation à n'être utile qu'avec les types de chaîne. Que pensez-vous de la proposition de @rylphs ? Cela permettrait une validation plus générique pour tous les types primitifs :

type ColorLevel = (n:number) => n>0 && n<= 255
type RGB = {red:ColorLevel, green:ColorLevel, blue:ColorLevel};
let redColor:RGB = {red:255, green:0, blue:0}   //OK
let wrongColor:RGB = {red:255, green:900, blue:0} //wrong

Je soupçonne cependant qu'étendre ce mécanisme au-delà des primitifs aux types non primitifs serait trop.

Le principal problème que je vois avec cela concerne les problèmes de sécurité, imaginez un code malveillant, qui utiliserait des tampons pour récupérer la mémoire de l'utilisateur lors de la vérification du type. Nous aurions à implémenter beaucoup de sandboxing autour de cela. Je préférerais voir 2 solutions différentes, une pour les chaînes et une pour les nombres.

RegExp est à l'abri de cela jusqu'à certaines extensions, car la seule façon de l'utiliser de manière malveillante est de faire une expression de retour en arrière . Cela étant dit, certains utilisateurs peuvent le faire par inadvertance, il devrait donc y avoir une sorte de protection. Je pense que la meilleure façon de le faire serait une minuterie.

Un point, le problème soulevé par @DanielRosenwasser - concernant les différentes implémentations du moteur regex - serait amplifié : selon le moteur Javascript sous lequel le compilateur Typescript s'exécute, la fonction de validation peut fonctionner différemment.

C'est vrai, c'est mauvais, mais nous pouvons le résoudre en spécifiant de quelle partie "moderne" de regExp nous avons besoin pour notre base de code. Il s'agirait par défaut d'une regexp normale (est-ce ES3?), qui fonctionne dans chaque nœud . Et une option pour activer de nouveaux indicateurs et des assertions lookbehind.

const unicodeMatcher = /\u{1d306}/u;
let value: typeof unicodeMatcher;
function(input: string) {
  value = input;  // Invalid
  if (input.match(unicodeMatcher)) {
    value = input;  // OK
  }
}

Si un utilisateur a désactivé le drapeau avec les drapeaux avancés.

let value: typeof unicodeMatcher = '𝌆';  // Warning, string literal isn't checked, because `variable` is of type `/\u{1d306}/u`.

TypeScript n'évaluerait pas RegExp avancé, s'il n'y est pas invité. Mais je suggérerais que cela devrait donner un avertissement, expliquant ce qui se passe et comment activer la vérification RegExp avancée.

Si l'utilisateur a activé un indicateur avec des indicateurs avancés et que son nœud le prend en charge.

let value: typeof unicodeMatcher = '𝌆';  // OK

Si un utilisateur a activé un indicateur avec des indicateurs avancés et que son nœud le prend en charge.

let value: typeof unicodeMatcher = '𝌆';  
// Error, NodeJS does not support advanced RegExp, upgrade NodeJS to version X.Y.Z, or disable advanced RegExp checking.

Je pense que c'est une façon raisonnable de procéder.
Les équipes de programmeurs ont généralement la même version de NodeJS ou peuvent facilement mettre à niveau puisque toute leur base de code fonctionne pour quelqu'un avec une version plus récente.
Les programmeurs solo peuvent s'adapter facilement à la volée,

Quel est l'état actuel de ce problème ? C'est vraiment dommage de voir que TypeScript a un potentiel aussi énorme et des dizaines de propositions impressionnantes, mais ils n'attirent pas beaucoup l'attention des développeurs…

AFAIK, la proposition originale était bonne, à l'exception de la vue d'ensemble Emit qui est interdite et pas vraiment nécessaire, elle ne devrait donc pas bloquer la proposition.

Le problème qu'il essaie de résoudre pourrait être résolu par l'introduction de littéraux regex (ce qui ne devrait pas être difficile, car ils sont effectivement équivalents aux littéraux de chaîne et de nombre) et d'un opérateur de type patternof (similaire à typeof et keyof ), qui prendrait un type littéral regex et renverrait un type _chaîne validé_. Voici comment il pourrait être utilisé :

type letterExpression = /[a-zA-Z]/;
let exp: letterExpression;
exp = /[a-zA-Z]/; // works
exp = /[A-Za-z]/; // error, the expressions do not match

type letter = patternof letterExpression;
type letter = patternof /[a-zA-Z]/; // this is equivalent

let a: letter;
a = 'f'; // works
a = '0'; // error
const email = /some-long-email-regex/;
type email = patternof typeof email;

declare let str: string;
if (str.match(email)) {
  str // typeof str === email
} else {
  str // typeof str === string
}

@m93a Je n'ai pas pensé à une telle solution avec un opérateur de type supplémentaire, lorsque je travaillais sur la proposition initiale.

J'aime cette approche consistant à supprimer l'impact des émissions causées par les types, même si cela semble plus verbeux.

Et cela m'a amené à imaginer comment étendre cette proposition afin d'ignorer l'ajout de nouveaux mots clés (comme vous le suggérez) - OMI, nous en avons déjà une assez grande quantité et n'avons pas d'impact d'émission du système de type (comme dans ma proposition).

Cela prendra 4 étapes:

  1. ajoutez regexp-validated string literal tapez :
    TypeScript type Email = /some-long-email-regex/;
  2. Modifions l'interface RegExp dans la bibliothèque principale en générique :
    TypeScript interface RegExp<T extends string = string> { test(stringToTest: string): stringToTest is T; }
  3. Changer le type d'inférence pour les littéraux regex dans le code réel :
    TypeScript const Email = /some-long-email-regex/; // infers to `RegExp</some-long-email-regex/>`
  4. Ajoutez un assistant de type en utilisant la fonctionnalité conditional types , comme InstanceType :
    TypeScript type ValidatedStringType<T extends RegExp> = T extends RegExp<infer V> ? V : string;

Exemple d'utilisation :

const Email = /some-long-email-regex/;
type Email = ValidatedStringType<typeof Email>;

const email: Email = `[email protected]`; // correct
const email2: Email = `emexample.com`; // compile time error

let userInput: string;
if (Email.test(userInput)) {
    // `userInput` here IS of type `Email`
} else {
    // and here it is just `string`
}

@Igmat Cool. Votre proposition semble plus naturelle pour TypeScript et nécessite moins de modifications du compilateur, c'est probablement une bonne chose. Le seul avantage de ma proposition était que les littéraux regex auraient la même sensation que les littéraux de chaîne et de nombre, cela pourrait être déroutant pour certains :

let a: 'foo' = 'foo'; // works
let b: 42 = 42; // works
let c: /x/ = /x/; // error

Mais je pense que la simplicité de votre proposition l'emporte sur le seul inconvénient.

Edit : je n'aime pas vraiment la longueur de ValidatedStringType<R> . Si nous décidions d'appeler des modèles de chaînes validés , nous pourrions utiliser PatternOf<R> après tout. Je ne dis pas que votre type prend plus de temps à taper, la plupart des gens tapent simplement les trois premières lettres et appuient sur la tabulation. Il a juste un impact plus important sur la spatification du code.

@Igmat Votre solution est excellente du point de vue du développement, mais en ce qui concerne la lisibilité, il serait bien mieux d'avoir la possibilité proposée par @m93a . Je pense qu'il pourrait être représenté en interne de la même manière, mais il devrait être présenté à l'utilisateur aussi simplement que possible.

@Akxe Je ne pense pas que les devs s'ajouter un autre mot - clé de fantaisie qui n'a qu'un seul cas d'utilisation très spécifique.

@RyanCavanaugh Pourriez-vous nous proposition originale et les quatre derniers commentaires (à l'exclusion de celui-ci).) Merci ! :+1:

que diriez-vous d'avoir un argument générique pour la chaîne, qui par défaut est .* ?

let a: 'foo' = 'foo'; // works
let b: 42 = 42; // works
let c: /x/ = /x/; // works
let d: string<x.> = 'xa'; // works

Le littéral de chaîne 'foo' peut être considéré comme un sucre pour string<foo>

Je n'aime pas vraiment la longueur de ValidatedStringType<R> . Si nous décidions d'appeler des chaînes validées _patterns_, nous pourrions utiliser PatternOf<R> après tout.

@m93a , IMO, dans ce cas, il serait préférable de les appeler PatternType<R> pour être cohérent avec les aides InstanceType et ReturnType déjà existantes.

@amir-arad, intéressant. À quoi ressemblera interface RegExp dans ce cas ?

@RyanCavanaugh Je pourrais réécrire la proposition originale avec un moyen nouvellement trouvé si cela peut aider. Devrais-je?

@amir-arad Votre syntaxe proposée est en conflit avec le reste de TypeScript. Désormais, vous ne pouvez transmettre des types qu'en tant qu'argument générique, et non en tant qu'expression arbitraire. La syntaxe que vous proposez serait extrêmement déroutante.

Pensez aux types génériques comme à des fonctions qui prennent un type et renvoient un type. Les deux morceaux de code suivants sont très proches à la fois du point de vue du sens et de la syntaxe :

function foo(str: string) {
  return str === 'bar' ? true : false
}

type foo<T extends string> = T extends 'bar' ? true : false;

Votre nouvelle syntaxe revient à proposer que l'expression régulière en JavaScript soit écrite let all = String(.*) ce qui serait un vilain abus de la syntaxe d'appel de fonction. Par conséquent, je ne pense pas que votre proposition ait beaucoup de sens.

@m93a ma suggestion était pour les annotations de type, pas pour javascript.

@Igmat du haut de ma tête, que diriez-vous de :

interface RegExp {
    test(stringToTest: string): stringToTest is string<this>;
}

@amir-arad, désolé, je ne peux pas ajouter de détails plus précieux à votre suggestion, mais à première vue, cela ressemble à un changement très important dans l'ensemble du compilateur TS, car string est une primitive très basique.

Même si je ne vois pas de problèmes évidents, je pense qu'une telle proposition devrait être beaucoup plus détaillée et couvrir un grand nombre de scénarios existants ainsi qu'une justification appropriée de son objectif.
Votre proposition ajoute un type et modifie un type primitif, tandis que la mienne n'en ajoute qu'un seul.

Malheureusement, je ne suis pas prêt à consacrer beaucoup de temps à la création d'une proposition pour une telle fonctionnalité (vous avez peut-être remarqué que toutes les propositions dans TS n'ont pas été mises en œuvre sans délai), mais si vous travaillez là-dessus, je ' Je serai heureux de vous fournir mes commentaires si nécessaire.

Si ces types d'expressions rationnelles étaient de vraies expressions régulières (pas des expressions régulières de type Perl qui ne sont pas régulières), nous pourrions les traduire en FSM déterministe et utiliser la construction de produits cartésiens sur celles-ci pour obtenir toutes les conjonctions et disjonctions. Les expressions régulières sont fermées par des opérations booléennes.

De plus, si les types littéraux de chaîne n'étaient pas atomiques, mais représentés sous forme de listes de caractères à la compilation, cela permettrait d'implémenter tous les opérateurs dans les bibliothèques. Cela ne ferait qu'empirer un peu les performances.

Edit : Corrige une erreur.

Passer pour noter que Mithril pourrait vraiment les utiliser, et être sûr de type dans le cas général est presque impossible sans cela. C'est le cas à la fois avec l'hyperscript et la syntaxe JSX. (Nous soutenons les deux.)

  • Nos crochets de cycle de vie, oninit , oncreate , onbeforeupdate , onupdate , onbeforeremove , et onremove , ont leurs propres prototypes spéciaux.
  • Les gestionnaires d'événements sur les nœuds virtuels DOM sont littéralement tout ce qui commence par on , et nous prenons en charge à la fois les fonctions d'écoute d'événement et les objets d'écoute d'événement (avec les méthodes handleEvent ), en s'alignant sur addEventListener et removeEventListener .
  • Nous prenons en charge les clés et les références, le cas échéant.
  • Tout le reste est traité comme un attribut ou une propriété, selon leur existence sur le nœud DOM de support lui-même.

Ainsi, avec un type de chaîne validé par regex + type negation , nous pourrions procéder comme suit pour les nœuds virtuels DOM :

interface BaseAttributes {
    // Lifecycle attributes
    oninit(vnode: Vnode<this, Vnode<Attributes, []>>): void;
    oncreate(vnode: Vnode<this, Vnode<Attributes, []>>): void;
    onbeforeupdate(
        vnode: Vnode<this, Vnode<Attributes, []>>,
        old: Vnode<this, Vnode<Attributes, []>>
    ): void;
    onupdate(vnode: Vnode<this, Vnode<Attributes, []>>): void;
    onbeforeremove(vnode: Vnode<this, Vnode<Attributes, []>>): void | Promise<void>;
    onremove(vnode: Vnode<this, Vnode<Attributes, []>>): void;

    // Control attributes
    key: PropertyKey;
}

interface DOMAttributes extends BaseAttributes {
    // Event handlers
    [key: /^on/ & not keyof BaseAttributes]: (
        ((this: Element, ev: Event) => void | boolean) |
        {handleEvent(ev: Event): void}
    );

    // Other attributes
    [key: keyof HTMLElement & not keyof BaseAttributes & not /^on/]: any;
    [key: string & not keyof BaseAttributes & not /^on/]: string;
}

interface ComponentAttributes extends BaseAttributes {
    // Nothing else interesting unless components define them.
}

(Ce serait aussi bien de pouvoir extraire des groupes de ces expressions régulières, mais je ne vais pas retenir mon souffle là-dessus.)

Edit : Clarifiez quelques détails critiques dans la proposition.
Edit 2: Corrigez le bit technique pour qu'il soit mathématiquement précis.
Edit 3: Ajout de la prise en charge de la mise en vedette générique des unions à un seul caractère

Voici une proposition concrète pour tenter de résoudre ce problème de manière beaucoup plus réaliste : les types de littéraux de modèle.

De plus, je pense que les expressions régulières complètes ne sont probablement pas une bonne idée, car il devrait être raisonnablement facile de les fusionner avec d'autres types. Peut-être que cela pourrait être mieux : les types de littéraux de modèle.

  • `value` - C'est littéralement équivalent à "value"
  • `value${"a" | "b"}` - C'est littéralement équivalent à "valuea" | "valueb"
  • `value${string}` - Ceci est fonctionnellement équivalent à l'expression rationnelle /^value/ , mais "value" , "valuea" et "valueakjsfbf aflksfief fskdf d" sont tous assignables.
  • `foo${string}bar` - Ceci est fonctionnellement équivalent à l'expression rationnelle /^foo.*bar$/ , mais est un peu plus facile à normaliser.
  • Il peut bien sûr y avoir plusieurs interpolations. `foo${string}bar${string}baz` est un type de littéral de modèle valide.
  • Les interpolations doivent s'étendre sur string , et elles ne doivent pas être récursives. (La deuxième condition est pour des raisons techniques.)
  • Un type de littéral de modèle A est attribuable à un type de littéral de modèle B si et seulement si l'ensemble de chaînes attribuable à A est un sous-ensemble de l'ensemble de chaînes attribuable à B .

En plus de ce qui précède, il existerait starof T type spécial T doit être composé que de types littéraux de chaîne à un seul caractère. string existerait en tant qu'alias de type de starof (...) , où ... est l'union de tous les littéraux de chaîne de caractères UCS-2 simples de U+0000 à U+FFFF, y compris lone substituts. Cela vous permet de définir la grammaire complète pour les littéraux numériques ES en base 10, par exemple :

type DecimalDigit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type Decimal = `${DecimalDigit}{starof DecimalDigit}`

type Numeric = `${(
    | Decimal
    | `${Decimal}.${starof DecimalDigit}`
    | `.${Decimal}`
)}${"" | (
    | `E${Decimal}` | `E+${Decimal}` | `E-${Decimal}`
    | `e${Decimal}` | `e+${Decimal}` | `e-${Decimal}`
)}`

Et de même, certaines méthodes intégrées peuvent être ajustées pour renvoyer de tels types :

  • Number.prototype.toString(base?) - Cela peut renvoyer le type Numeric ci-dessus ou une variante de celui-ci pour les base s statiquement connus.
  • +x , x | 0 , parseInt(x) , et similaires - Lorsque x est connu pour être un Numeric tel que défini ci-dessus, le type résultant peut être déduit de manière appropriée comme un type de nombre littéral.

Et enfin, vous pouvez extraire les groupes correspondants comme ceci : Key extends `on${infer EventName}` ? EventTypeMap[TagName][EventName] : never . L'extraction de modèle suppose qu'elle fonctionne toujours avec des noms complets, vous devez donc utiliser explicitement des interpolations ${string} pour rechercher une inclusion arbitraire. Ce n'est pas gourmand, donc ` "foo.bar.baz" extends ${infer T}.${infer U} ? [T, U] : never renvoie ["foo", "bar.baz"] , pas ["foo.bar", "baz"] .


D'un point de vue technique, c'est beaucoup plus réalisable à implémenter que les expressions régulières brutes. JS regexps ne sont pas même réguliers - ils deviennent contextuelle avec références arrière, et ils impliquent beaucoup de complexité sous la forme de Tant que vous bloquez récursion avec ces derniers , les types littéraux de modèle génèrent une seule langue régulière chacune, qui s'aligne très étroitement sur la théorie sous-jacente (mais n'en supporte qu'un sous-ensemble).

  • Langue vide : ""
  • Syndicat : "a" | "b"
  • Concaténation : `${a}${b}`
  • Étoile Kleene (partielle) : starof T ( T ne peut contenir que des caractères et des unions.)

Cela peut rendre le sous-typage de chaîne vérifiant un sous-ensemble du problème d'isomorphisme de sous-graphe dans le pire des cas, mais il y a quelques grands facteurs rédempteurs ici :

  1. Le cas courant est de loin les unions de petites chaînes finies, quelque chose que vous pouvez modéliser avec des arbres. C'est relativement évident de travailler avec. (Je ne recommande pas d'essayer de les joindre en tant que cordes, car cela compliquerait l'algorithme de correspondance ci-dessus, mais il est parfaitement normal de normaliser les unions à caractère unique et similaires en une seule division + jointure.)

  2. Vous pouvez modéliser l'intégralité du type unifié sous la forme d'un graphe orienté, où :

    1. Les unions étoilées de ces caractères sont des sous-graphes où le nœud parent a des bords à la fois vers chaque caractère et chaque nœud enfant du sous-graphe, et chaque caractère a des bords vers tous les autres caractères et tous les nœuds enfants du sous-graphe.
    2. Le reste du graphe contient une structure arborescente dirigée représentant toutes les autres possibilités.

    Selon ce chat Math.SE dans lequel j'étais brièvement (en commençant approximativement ici ), j'ai trouvé que ce graphe résultant aurait à la fois un genre borné (c'est-à-dire avec un nombre fini de sauts sur d'autres arêtes*) et, en l'absence de tout starof types, un degré borné. Cela signifie que l' égalité des types réduit cela à un problème de temps polynomial et en supposant que vous normalisiez les unions, ce n'est pas non plus très lent car ce n'est qu'un peu plus rapide que l'égalité des arbres. Je soupçonne fortement que le cas général de toute cette proposition (un sous-ensemble du problème d'isomorphisme de sous-graphe) est également en temps polynomial avec des coefficients raisonnables. (L'article Wikipedia lié ci-dessus contient quelques exemples dans les sections "Algorithmes" et références où une casse spéciale peut s'appliquer.)


  3. Aucune de ces clés n'est susceptible d'être volumineuse , de sorte que la plupart des coûts d'exécution réels sont ici amortis en pratique par d'autres éléments. Tant que c'est rapide pour les petites touches, c'est suffisant.


  4. Tous les sous-graphes qui seraient comparés partagent au moins un nœud : le nœud racine. (Cela représente le début de la chaîne.) Cela réduirait donc considérablement l'espace du problème à lui seul et garantirait un contrôle temporel polynomial.


Et bien sûr, l' intersection entre de tels types n'est pas triviale , mais je pense que des facteurs de rédemption similaires existent simplement en raison des restrictions ci-dessus. En particulier, la dernière restriction rend évidemment le temps polynomial à faire.

* Mathématiquement, le genre est défini de manière un peu contre-intuitive pour nous programmeurs (le nombre minimum de trous que vous devez percer dans une surface pour tracer le graphique sans aucun saut), mais un genre limité (nombre limité de trous) implique un nombre limité de sauts .

En utilisant cette proposition concrète, voici comment mon exemple de ce commentaire se traduit :

// This would work as a *full* type implementation mod implementations of `HTMLTypeMap` +
// `HTMLEventMap`
type BaseAttributes = {
    // Lifecycle attributes
    oninit(vnode: Vnode<this, Vnode<Attributes, []>>): void;
    oncreate(vnode: Vnode<this, Vnode<Attributes, []>>): void;
    onbeforeupdate(
        vnode: Vnode<this, Vnode<Attributes, []>>,
        old: Vnode<this, Vnode<Attributes, []>>
    ): void;
    onupdate(vnode: Vnode<this, Vnode<Attributes, []>>): void;
    onbeforeremove(vnode: Vnode<this, Vnode<Attributes, []>>): void | Promise<void>;
    onremove(vnode: Vnode<this, Vnode<Attributes, []>>): void;

    // Control attributes
    key: PropertyKey;
}

interface HTMLTypeMap {
    // ...
}

interface HTMLEventMap {
    // ...
}

// Just asserting a simple constraint
type _Assert<T extends true> = never;
type _Test0 = _Assert<
    keyof HTMLTypeMap[keyof HTMLTypeMap] extends `on${string}` ? false : true
>;

type EventHandler<Event> =
    ((this: Element, ev: Event) => void | boolean) |
    {handleEvent(ev: Event): void};

type Optional<T> = {[P in keyof T]?: T[P] | null | undefined | void}

type DOMAttributes<T extends keyof HTMLAttributeMap> = Optional<(
    & BaseAttributes
    & {[K in `on${keyof HTMLEventMap[T]}` & not keyof BaseAttributes]: EventHandler<(
        K extends `on${infer E}` ? HTMLEventMap[E] : never
    )>}
    & Record<
        keyof `on${string & not keyof HTMLEventMap}` & not keyof BaseAttributes,
        EventHandler<Event>
    >
    & Pick<HTMLTypeMap[T], (
        & keyof HTMLTypeMap[T]
        & not `on${string}`
        & not keyof BaseAttributes
    )>
    & Record<(
        & string
        & not keyof HTMLTypeMap[T]
        & not keyof BaseAttributes
        & not `on${string}`
    ), string | boolean>
)>;

Edit: Cela permettrait également de taper correctement 90 % de la méthode _.get de Lodash et des méthodes associées en utilisant son raccourci de propriété, comme sa méthode _.property(path) et son raccourci _.map(coll, path) . Il y a probablement plusieurs autres auxquels je ne pense pas non plus, mais c'est probablement le plus gros auquel je puisse penser. (Je vais laisser la mise en œuvre de ce type comme exercice au lecteur, mais je peux vous assurer que c'est possible avec une combinaison de cela et l'astuce habituelle des types conditionnels avec un enregistrement immédiatement indexé, quelque chose comme {0: ..., 1: ...}[Path extends "" ? 0 : 1] , pour traiter la chaîne de chemin statique.)

Ma recommandation est que nous concentrions nos efforts sur l'implémentation de fournisseurs de types, qui pourraient être utilisés pour implémenter des types regex.

Pourquoi taper des fournisseurs au lieu d'implémenter directement des types regex ? Parce que

  1. C'est une solution plus générique qui ajoute de nombreuses nouvelles possibilités à TypeScript, ce qui facilite l'obtention du support d'un groupe plus large de développeurs au-delà de ceux qui voient la valeur des types de chaînes regex.
  2. Les propriétaires de repo dactylographiés semblent ouverts à cette idée et attendent la bonne proposition. Voir #3136

F# a un fournisseur de type regex open source.

Quelques infos sur les fournisseurs de type : https://link.medium.com/0wS7vgaDQV

On pourrait imaginer qu'une fois les fournisseurs de type implémentés et le fournisseur de type regex implémenté en tant que bibliothèque open source, on l'utiliserait comme suit :

type PhoneNumber = RegexProvider</^\d{3}-\d{3}-\d{4}$/>
const acceptableNumber: PhoneNumber = "123-456-7890"; //  no compiler error
const unacceptableNumber: PhoneNumber = "hello world"; // compiler error

@AlexLeung Je ne suis pas convaincu que ce soit la bonne voie à suivre, du moins pas pour cette demande.

  • TypeScript est structurellement typé, pas nominalement typé, et pour la manipulation littérale de chaîne, je souhaite conserver cet esprit structurel. Les fournisseurs de types comme celui-ci créeraient un sous-type nominal stringRegexProvider</^foo$/> ne serait "foo" , mais un sous-type nominal de celui-ci. De plus, RegexProvider</^foo$/> et RegexProvider</^fo{2}$/> seraient traités comme deux types distincts, et c'est quelque chose dont je ne suis pas fan. Ma proposition s'intègre plutôt directement aux chaînes en leur cœur, directement informées par la théorie de la reconnaissance formelle du langage pour s'assurer qu'elle s'intègre naturellement.
  • Avec le mien, vous pouvez non seulement concaténer des chaînes, mais extraire des parties de chaînes via Key extends `on${infer K}` ? K : never ou même Key extends `${Prefix}${infer Rest}` ? Rest : never . Les fournisseurs de types n'offrent pas cette fonctionnalité, et il n'y a aucun moyen clair de savoir comment cela devrait être fait si une telle fonctionnalité devait être ajoutée.
  • Le mien est considérablement plus simple au niveau conceptuel : je suggère simplement d'ajouter des types de concaténation de chaînes et, pour le RHS des types conditionnels, la possibilité d'extraire son inverse. Je propose également qu'il s'intègre avec string lui-même pour prendre la place d'une regexp /.*/ . Il ne nécessite aucune modification de l'API, et à part les deux parties théoriquement complexes qui sont pour la plupart découplées du reste de la base de code, calculer si un type littéral de modèle est assignable à un autre et extraire une tranche d'une chaîne est similaire, sinon plus simple , implémenter.

BTW, ma proposition pourrait toujours taper cet exemple PhoneNumber aussi. C'est un peu plus détaillé, mais j'essaie de modéliser des données qui sont déjà dans TS land, pas des données qui existent ailleurs (à quoi les fournisseurs de type F# sont-ils les plus utiles). (Il convient de noter que cela s'étendrait techniquement à la liste complète des numéros de téléphone possibles ici.)

type D = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type PhoneNumber = `${D}${D}${D}-${D}${D}${D}-${D}${D}${D}${D}`;

RegexProvider^foo$/> et RegexProvider^fo{2}$/> seraient traités comme deux types distincts

Les fournisseurs de type peuvent nécessiter l'implémentation d'une méthode equals ou compare , de sorte que l'auteur du fournisseur de type d'un fournisseur de type regex puisse définir que les deux cas ci-dessus sont des types équivalents. L'auteur du fournisseur de types peut implémenter un typage structurel ou nominal à sa guise.

Il serait peut-être possible d'implémenter également votre type littéral de chaîne en tant que fournisseur de type. Je ne pense pas que la syntaxe puisse être la même, mais vous pourriez vous rapprocher d'un fournisseur de type qui prend en compte un nombre variable d'arguments.

type D = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type PhoneNumber = StringTemplateMatcherProvider<D, D, D, "-", D, D, D, "-", D, D, D, D>;

@AlexLeung Mais le type "123-456-7890" attribuable à votre type ? (Si c'est le cas, cela compliquera la mise en œuvre et ralentira beaucoup le vérificateur.)

Semi-lié à la discussion en cours, que se passe-t-il si le type n'est pas d'une longueur fixe (comme un numéro de téléphone) ? Une situation où j'aurais aimé l'utiliser récemment est pour stocker un nom de pièce, au format thread_{number} .

L'expression régulière pour correspondre à une telle valeur est thread_[1-9]\d* . Avec ce qui est proposé, il ne semble pas faisable (ni même possible) de faire correspondre un tel format. La partie numérique de la valeur pourrait être de longueur _n'importe quelle_ supérieure à zéro dans cette situation.

@jhpratt J'ai révisé ma proposition pour tenir compte de cela, sous la forme de starof ("0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")/^\d*$/ , car cela ne nécessitait qu'une petite modification. Il optimise de la même manière que string optimise /^[\u0000-\uFFFF]*$/ , j'ai donc décidé d'aller de l'avant et de généraliser cela.

Je ne veux pas étendre starof plus loin que cela, comme accepter des unions non récursives arbitraires, en raison de problèmes de complexité de calcul : vérifier si deux expressions régulières arbitraires* sont équivalentes peut être effectué dans un espace polynomial ou un très lentes en pratique et AFAICT vous ne pouvez pas avoir les deux. Ajoutez la prise en charge de la mise au carré (comme a{2} ), et c'est fondamentalement infaisable (complexité exponentielle) . Ceci est uniquement pour l'équivalence, et vérifier si une expression rationnelle correspond à un sous-ensemble des chaînes qu'une autre expression rationnelle correspond, requis pour vérifier l'assignabilité, va évidemment être encore plus compliqué.

* Expressions régulières au sens mathématique : je n'inclus que les caractères simples, () , (ab) , (a|b) , et (a*) , où a et b sont (potentiellement différents) chacun des membres de cette liste.

C'est probablement une question stupide, mais... pourquoi n'est-il pas assez facile, si suffisamment limité, de prendre en charge une fonction de validation (lambda ou nommée) ?

Par exemple, supposons que nous utilisions ":" pour indiquer que l'élément suivant est un validateur (remplacez ce que vous voulez par ":" si vous avez une opinion à ce sujet) :

type email = string : (s) => { return !!s.match(...) }
type phone_number = string : (n) => { return !!String(n).match(...) }
type excel_worksheet_name = string : (s) => { return (s != "History") && s.length <= 31 && ... }

Au départ, typescript ne pouvait accepter que les fonctions de validation qui :

  • avoir un seul argument, qui est obligatoire/supposé être du type "base"
  • uniquement les variables de référence qui sont définies dans la fonction de validation
  • renvoie une valeur (qui sera forcée à bool dans le processus de validation)

Les conditions ci-dessus semblent faciles à vérifier pour le compilateur dactylographié, et une fois ces conditions assumées, une grande partie de la complexité de l'implémentation disparaîtrait.

De plus, si nécessaire pour restreindre la portée initiale à une taille gérable :

  • les fonctions de validation ne peuvent être ajoutées qu'à un sous-ensemble de types natifs (chaîne, nombre)

Je ne pense pas que cette dernière restriction serait tout à fait nécessaire, mais s'il y a la moindre question quant à savoir si ce serait le cas, je ne pense pas non plus qu'il vaudrait la peine de passer beaucoup de temps à en débattre, car une solution avec la limitation ci-dessus résoudrait toujours une vaste gamme de cas d'utilisation réels. De plus, je vois peu d'inconvénients aux limitations ci-dessus, car les relâcher plus tard serait une extension simple et naturelle qui ne nécessiterait aucun changement dans la syntaxe de base et élargirait simplement l'étendue de la prise en charge du langage par le compilateur.

@mewalig Cela signifierait que quelque chose qui ressemble à une fonction d'exécution ne s'exécuterait ne pouvaient accéder à rien depuis le runtime (variables, fonctions), ce qui serait assez gênant.

De plus, vous ne voulez généralement pas que le compilateur exécute tout ce que vous lui lancez, en particulier des fonctions mal optimisées ou des while(true){} carrément malveillants. Si vous voulez une méta-programmation, vous devez la concevoir intelligemment. Autoriser au hasard le code d'exécution à s'exécuter au moment de la compilation serait la "manière PHP" de le faire.

Enfin, la syntaxe que vous proposez change le modèle habituel

let runtime: types = runtime;

(c'est-à-dire les types après le côlon) à l'envers, étant effectivement

type types = types: runtime;

ce qui est horrible. Alors merci pour votre proposition, mais c'est définitivement une mauvaise idée.

Ces fonctions ne pouvaient accéder à rien depuis le runtime (variables, fonctions), ce qui serait assez gênant.

Bien sûr, ils le pourraient , si le compilateur dispose d'un runtime ECMAScript ( tsc fait, BTW !). Vous avez évidemment un problème d'ambiguïté avec la sémantique au moment de la compilation, par exemple fetch() rapport à la sémantique à l'exécution, mais c'est le but de l'itération.

Autoriser au hasard le code d'exécution à s'exécuter au moment de la compilation serait la "manière PHP" de le faire.

C'est assez similaire aux fonctions C++ constexpr , qui sont très bien. La solution est de dire que constexpr ne peut utiliser que constexpr , mais tout peut utiliser constexpr . Ensuite, vous pourriez avoir des versions équivalentes de constexpr du système de fichiers pour le système de fichiers à la compilation, ce qui pourrait être assez puissant.

La syntaxe me semble également à peu près correcte : le LHS est un type, bien sûr le RHS est aussi un type quelconque. Mon problème concerne davantage la façon dont vous composez des types au-delà du type "de base", mais tout cela peut également être résolu.

Alors merci pour votre proposition, mais c'est définitivement une mauvaise idée.

Cela peut finir par être une mauvaise idée, mais pour l'instant, je ne vois qu'une idée très sous-spécifiée qui nécessitera probablement de s'éloigner trop des objectifs du tapuscrit. Cela ne veut pas dire qu'il n'y a peut-être pas une bonne idée qui lui ressemble !

La discussion sur cette fonctionnalité semble s'arrêter pour l'instant ( PR est fermé et selon l'équipe Design notes _ne veut pas s'engager à ce sujet jusqu'à ce que nous ayons des types nominaux et des signatures d'index généralisées, et nous devrions savoir à quoi ils ressemblent._).

Quoi qu'il en soit, je souhaite proposer une autre extension hypothétique aux relations publiques actuelles qui prendrait en charge l'extraction de modèles regex ( @isiahmeadows a présenté sa propre proposition, mais pour être honnête, je ne peux pas en

J'aime beaucoup les relations publiques actuelles et je baserais ma proposition sur cela. Je voudrais proposer la syntaxe basée sur l'inférence d'arguments de type générique que nous avons pour les fonctions (et les types conditionnels avec le mot-clé infer ). Tout simplement parce que les gens ont déjà l'intuition que dans une fonction générique, vous pouvez "extraire" des types à partir d'objets littéraux passés.

Par exemple, nous avons ce type.

type Prop1 = /(\w)\.(\w)/

et nous pouvons utiliser ce type pour tester les types littéraux

const goodLiteral = "foo.bar";
const badLiteral = "foo";
const regextTest: Prop1 = goodLiteral; //no error
const regextTest: Prop1 = badLiteral; //compiler error

function funProp1(prop: Prop1) { } 

funProp1(goodLiteral); //no error
funProp1(badLiteral); //error

Cependant, lorsque nous utilisons le type Regex dans le paramètre de fonction, nous pouvons utiliser la syntaxe des crochets angulaires pour signifier que nous voulons déduire les chaînes correspondantes. Par exemple

type Prop1 = /(\w)\.(\w)/
const Prop1 = /(\w)\.(\w)/

const goodLiteral = "foo.bar";
const badLiteral = "foo";

function funProp1<M1 extends string, M2 extends string>(prop: Prop1<M1, M2>) : [M1, M2] 
{
    const m = prop.match(Prop1);
    return [m[1], m[2]];
} 

const res1 = funProp1(goodLiteral); //no error. Function signature inferred to be funProp<"foo", "bar">(prop: Prop1<"foo", "bar">) : ["foo", "bar"]
const res2 = funProp1(badLiteral); //compiler error

notez que le type inféré de res1 est ["foo", "bar"]

Est-ce que c'est utile ?

  1. Ember.js/lodash obtenir la fonction

Vous pouvez implémenter un getter "chemin de chaîne" sécurisé pour que ce code fonctionne :

const deep = get(objNested, "nested.very.deep")

Mais il faudrait probablement résoudre ce problème si nous voulons éviter de nombreuses surcharges pour un nombre maximum fixe de "profondeur" de get possibles.

  1. Utilisez les paramètres extraits dans les types mappés.

Par exemple, si nous pouvions faire quelque chose comme ceci https://github.com/Microsoft/TypeScript/issues/12754. Ensuite, nous pourrions avoir la possibilité d'inverser la fonction (supprimer certains préfixes/suffixes de toutes les propriétés d'un type donné). Celui-ci devrait probablement introduire une forme plus généralisée de syntaxe typée mappée pour choisir une nouvelle clé pour la propriété (par exemple une syntaxe comme { [ StripAsyncSuffix<P> for P in K ] : T[P] } , quelqu'un a déjà proposé quelque chose comme ça)

Il y aurait probablement aussi d'autres cas d'utilisation. Mais je suppose que la plupart conviendraient à ces deux types (1. déterminer le type approprié en fonction du littéral de chaîne fourni, 2. transformer les noms de propriété du type d'entrée en nouveaux noms de propriété du nouveau type défini)

C'est quelque chose que nous pourrions faire avec.

Je construis actuellement des règles de lint personnalisées afin de pouvoir valider les URL - cependant, ce serait beaucoup plus facile si nous pouvions définir les paramètres facultatifs - ce qui nécessite une regex afin de pouvoir valider nos identifiants

En général, cela nous donnerait beaucoup plus de pouvoir pour affirmer la validité des accessoires dans notre base de code

Y a-t-il un mouvement sur les fournisseurs de type, les littéraux de chaîne de modèle ou d'autres suggestions ? Ce serait un si bon outil.

Ma solution de contournement consiste actuellement à utiliser une interface de marqueur comme celle-ci .

interface TickerSymbol extends String {}

Le seul problème est que lorsque je veux l'utiliser comme clé d'index, je dois le convertir en string .

interface TickerSymbol extends String {}
var symbol: TickerSymbol = 'MSFT';
// declare var tickers: {[symbol: TickerSymbol]: any}; // Error: index key must be string or number
declare var tickers: {[symbol: string]: any};
// tickers[symbol]; // Type 'TickerSymbol' cannot be used as an index type
tickers[symbol as string]; // OK

Cependant, JavaScript semble convenir avec le type d'index String (avec un S majuscule).

var obj = { one: 1 }
var key = new String('one');
obj[key]; // TypeScript Error: Type 'String' cannot be used as an index type.
// but JS gives expected output:
// 1

@DanielRosenwasser J'ai une proposition ici , et une proposition distincte a été créée fin 2016 , alors les étiquettes pour cela pourraient-elles être mises à jour ?

Nous avons examiné les propositions ci-dessus et avons quelques questions et commentaires.

Aspects problématiques des propositions à ce jour

Types Création Émission

Nous nous engageons à garder le système de types entièrement effacé, donc les propositions qui nécessitent des alias de type pour produire du code émis sont hors de portée. Je vais mettre en évidence quelques exemples dans ce fil où cela s'est produit peut-être d'une manière qui n'est pas évidente :

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -22080091 - crée une fonction et un type en même temps

type Integer(n:number) => String(n).macth(/^[0-9]+$/)

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -261519733 - fait également cela

type CssColor = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
// ... later
setFontColorFromString(color: string) {
    fontColor = color;// compile time error
    if (CssColor.test(color)) {
    //  ^^^^^^^^ no value declaration of 'CssColor' !
        fontColor = color;// correct
    }
}

Je le répète : c'est un non-starter . Les types dans TypeScript sont composables et l'émission de JS à partir de types n'est pas possible dans ce monde. La proposition la plus longue à ce jour comporte de nombreux types d'émissions; ce n'est pas réalisable. Par exemple, cela nécessiterait une émission étendue dirigée par type :

type Matcher<T extends number | boolean> = T extends number ? /\d+/ : /true|false/;
function fn<T extends number | boolean(arg: T, s: Matcher<T>) {
  type R = Matcher<T>
  if (R.test(arg)) {
      // ...
  }
}
fn(10, "10");
fn(false, "false");

Interdictions d'intersections

En fait, les types courants et les types validés par regex sont vraiment différents, nous avons donc besoin de règles pour gérer correctement leurs unions et leurs intersections.

type Regex_1 = / ... /;
type Regex_2 = / ... /;
type NonRegex = { ... };
type test_4 = Regex_1 & NonRegex;// compile time error

TypeScript ne peut pas faire d'erreur sur les instanciations d'intersections, donc cela ne ferait partie d'aucune conception finale.

Ergonomie

Dans l'ensemble, notre conclusion la plus importante est que nous voulons quelque chose où vous n'écrivez pas deux fois

Compte tenu des préoccupations ci-dessus concernant le type d'émission, la solution la plus réaliste consiste à écrire l'expression dans l'espace de valeurs :

// Probably put this in lib.d.ts
type PatternOf<T extends RegExp> = T extends { test(s: unknown): s is infer P } ? P : never;

const ZipCode = /^\d\d\d\d\d$/;
function map(z: PatternOf<typeof ZipCode>) {
}

map('98052'); // OK
map('Redmond'); // Error

Vous pouvez toujours écrire le RegExp dans l'espace de type, bien sûr, mais il n'y aurait pas de validation d'exécution disponible et toute utilisation non littérale nécessiterait un nouveau test ou une assertion :

function map(z: /^\d\d\d\d\d$/) { }
map('98052'); // OK
map('Redmond'); // Error

function fn(s: string) {
    map(s); // Error
    // typo
    if (/^\d\d\d\d$/.test(s)) {
        // Error, /^\d\d\d\d$/ is not assignable to /^\d\d\d\d\d$/
        map(s);
    }

    if (/^\d\d\d\d\d$/.test(s)) {
        // OK
        map(s);
    }
}

Collecte et clarification des cas d'utilisation

Pour un nouveau type de type, nous aimerions idéalement voir plusieurs exemples où :

  • Le problème en cours de résolution n'a pas de meilleure alternative (y compris des alternatives plausibles qui ne sont pas encore dans la langue)
  • Le problème se produit avec une fréquence significative dans les bases de code réelles
  • La solution proposée permet de résoudre ce problème bien

Validation au moment de la compilation des littéraux

Ce fil implique une grande variété de cas d'utilisation; les exemples concrets ont été plus rares. Fait troublant, beaucoup de ces exemples ne semblent pas complets - ils utilisent une RegExp qui rejetterait les entrées valides.

  • Couleur de police - AFAIK, tout ce qui accepte les couleurs hexadécimales accepte également, par exemple, "blanc" ou "bleu ciel". Cela rejette également à tort la syntaxe rgb(255, 0, 0) .
  • SSN, Zip, etc. - OK, mais pourquoi y a-t-il des SSN ou des codes postaux littéraux dans votre code ? Est-ce réellement un besoin de types nominaux ? Que se passe-t-il si vous avez une sous-classe de chaînes qui ne peut pas être décrite avec précision par une RegExp ? Voir « Propositions concurrentes »

    • Entier - rejette par erreur "3e5"

    • E - Ceci est généralement considéré comme une mauvaise idée . Encore une fois, il y a des littéraux de chaîne d'adresse e-mail dans votre code ?

    • Spécifications CSS Border - Je pourrais croire qu'une bibliothèque autonome pourrait fournir un RegEx précis pour décrire le DSL qu'elle prend elle-même en charge

    • Ecrire des tests - c'est là que les entrées codées en dur ont un sens, bien que ce soit presque un contrepoint car votre code de test devrait probablement fournir beaucoup d'entrées invalides

    • Formats de date - comment/pourquoi ? Date a un constructeur pour cela ; si l'entrée vient de l'extérieur du runtime, vous voulez juste un type nominal

    • URI - vous pouvez imaginer que fetch spécifierait host pour ne pas être avec http(s?):

À FAIRE : Aidez-nous en identifiant de vraies fonctions de bibliothèque qui pourraient bénéficier des types RegExp, et l'expression réelle que vous utiliseriez.

Une préoccupation est la "précision" - que se passe-t-il lorsque quelqu'un se présente utilement à DefinitelyTyped et ajoute des types RegExp à chaque fonction d'une bibliothèque, brisant ainsi chaque invocation non littérale ? Pire, les auteurs du fichier de définition devront se mettre d'accord exactement avec les consommateurs de ce qu'est la "bonne orthographe" d'une RegExp de validation.
Il semble que cela nous met rapidement sur la voie d'une situation de tour de Babel où chaque bibliothèque a sa propre version de ce qui est qualifié d'URL, de nom d'hôte, de courrier électronique, etc., et toute personne connectant deux bibliothèques doit insérer des assertions de type ou copier des expressions régulières pour satisfaire le compilateur.

Application des contrôles d'exécution

Il y a eu des discussions sur les vérifications où nous voulons nous assurer que les arguments d'une fonction ont été validés par une regex précédente, comme fn dans la section précédente sur l' ergonomie . Cela semble simple et utile, si le RegEx qui doit être testé est bien connu. C'est un gros "si", cependant - dans mon souvenir, je ne me souviens pas d'une seule bibliothèque qui fournit des expressions régulières de validation. Il peut fournir des fonctions de validation - mais cela implique que la fonctionnalité à fournir soit des types nominaux ou étiquetés, et non des types regex.

Les contre-preuves à cette évaluation sont les bienvenues.

Clés de propriété / Indexeurs de chaîne Regex

Certaines bibliothèques traitent les objets en fonction des noms de propriété. Par exemple, dans React, nous voulons appliquer des types à tout accessoire dont le nom commence par aria- :

interface IntrinsicElements {
    // ....
    [attributeName: /aria-\w+/]: number | string | boolean;
}

Il s'agit en fait d'un concept orthogonal (nous pourrions ajouter des types Regex sans ajouter de clés de propriété Regex, et vice versa).

À FAIRE (moi ou n'importe qui): Ouvrez un problème séparé pour cela.

Propositions concurrentes

Types nominaux ou étiquetés

Disons que nous avions des types nominaux/étiquetés d'une certaine sorte :

type ZipCode = make_unique_type string;

Vous pouvez alors écrire une fonction

function asZipCode(s: string): ZipCode | undefined {
    return /^\d\d\d\d\d$/.test(s) ? (s as ZipCode) : undefined;
}

À ce stade, auriez-vous vraiment besoin de types RegExp ? Reportez-vous à la section de vérification "au moment de la compilation" pour plus de réflexions.

Inversement, disons que nous avons des types RegExp et non des types nominaux. Il devient assez tentant de commencer à les (ab)utiliser pour des scénarios de non-validation :

type Password = /(IsPassword)?.*/;
type UnescapedString = /(Unescaped)?.*/;
declare function hash(p: Password): string;

const p: Password = "My security is g00d"; // OK
const e: UnescapedString = "<div>''</div>"; // OK
hash(p); // OK
hash(e); // Error
hash("correct horse battery staple"); // OK

Une chose courante dans le thread est que ces expressions régulières aideraient à valider le code de test, car même si dans les scénarios de production, le code s'exécuterait sur des chaînes fournies par l'exécution plutôt que sur des littéraux codés en dur, vous voudriez toujours une validation que vos chaînes de test étaient " correct". Cela semblerait être un argument pour les chaînes nominales/marquées/marquées à la place, puisque vous écririez la fonction de validation de toute façon, et l'avantage des tests est que vous savez qu'ils s'exécutent de manière exhaustive (ainsi toute erreur dans les entrées de test serait être signalés au début du cycle de développement).

Non-problèmes

Nous avons discuté des aspects suivants et les considérons comme n'étant pas des bloqueurs

Capacités de l'hôte

Les runtimes plus récents prennent en charge plus de syntaxe RegExp que les runtimes plus anciens. Selon l'endroit où le compilateur TypeScript s'exécute, certains codes peuvent être valides ou non en fonction des capacités du runtime à analyser les nouvelles fonctionnalités RegExp. En pratique, la plupart des nouvelles fonctionnalités de RegExp sont assez ésotériques ou liées à la correspondance de groupe, ce qui ne semble pas correspondre à la plupart des cas d'utilisation ici.

Performance

Les RegEx peuvent effectuer une quantité de travail illimitée et la correspondance avec une grande chaîne peut effectuer une quantité de travail arbitrairement importante. Les utilisateurs peuvent déjà DOS eux-mêmes par d'autres moyens, et il est peu probable qu'ils écrivent une RegExp malveillante et inefficace.

Sous-typage ( /\d*/ -> /.*/ ?), Union, Intersection et Inhabitabilité

En théorie, /\d+/ est un sous-type connaissable de /.+/ . Soi-disant, des algorithmes existent pour déterminer si une RegExp correspond à un sous-ensemble pur d'un autre (sous certaines contraintes), mais cela nécessiterait évidemment l'analyse de l'expression. En pratique, nous sommes à 100% d'accord avec RegExpes qui ne forment pas de relations de sous-type implicites basées sur ce qu'elles correspondent ; c'est probablement même préférable.

Les opérations d'union et d'intersection fonctionneraient "prêts à l'emploi" tant que les relations d'assignabilité étaient définies correctement.

Dans TypeScript, lorsque deux types primitifs "entrent en collision" dans une intersection, ils se réduisent à never . Lorsque deux RegExpes sont intersectées, nous conserverons simplement cela comme /a/ & /b/ plutôt que d'essayer de produire une nouvelle RegExp correspondant à l'intersection des deux expressions. Il n'y aurait pas de réduction à never nous aurions besoin d'un algorithme pour prouver qu'aucune chaîne ne peut satisfaire les deux côtés (c'est un problème parallèle à celui décrit plus haut concernant le sous-typage).

Prochaines étapes

Pour résumer, les prochaines étapes sont :

  • Déposer un problème distinct pour les clés de propriété nommées Regex AKA regex string indexers
  • Obtenez des cas d'utilisation concrets et plausibles pour la validation au moment de la compilation des littéraux de chaîne

    • Exemple : Identifiez les fonctions dans DefinitelyTyped ou d'autres bibliothèques qui en bénéficieraient grandement

  • Comprendre si les types nominaux/marqués/marqués sont une solution plus flexible et plus largement applicable pour la validation non littérale
  • Identifiez les bibliothèques qui fournissent déjà des RegEx de validation

Cas d'utilisation : fonctions similaires à Hyperscript (https://github.com/hyperhype/hyperscript)
Une fonction hyperscript est généralement appelée comme h('div#some-id')
Un matcher de modèle regex-ish permettrait de déterminer le type de retour de h qui serait HTMLDivElement dans le cas de l'exemple.

Si le système de types était capable d'ajouter des littéraux de chaîne, alors fondamentalement, toute propriété CSS pourrait être de type sécurisé

declare let width: number;
declare let element: HTMLElement;

element.style.height = `${width}px`;
// ...or
element.style.height = `${width}%`;

Les sélecteurs CSS pourraient également être validés ( element.class#id - valide, div#.name - invalide)

Si la capture de groupes fonctionnait (d'une manière ou d'une autre), la méthode get Lodash pourrait être de type sécurisé

var object = { 'a': [{ 'b': { 'c': 3 } }] };

_.get(object, 'a[0].b.c');

Cela pourrait être une chose aussi :

interface IOnEvents {
  [key: PatternOf</on[a-z]+/>]: (event: Event) => void;
}

interface IObservablesEndsOn$ {
  [key: PatternOf</\$$/>]: Observable<any>;
}

Cas d'utilisation : fonctions de type hyperscript (hyperhype/hyperscript)

À quoi ressemblerait cette regex ou quelle validation fournirait-elle ? Est-ce pour une surcharge de fonction basée sur les expressions régulières ?

FWIW La bibliothèque accepte les noms de balises avec espace de noms et fonctionne également sur des noms de balises arbitraires

> require("hyperscript")("qjz").outerHTML
'<qjz></qjz>'

Il accepte également un mélange illimité de valeurs de classe et d'identifiant

> require("hyperscript")("baz.foo#bar.qua").outerHTML
'<baz class="foo qua" id="bar"></baz>'

Les sélecteurs CSS pourraient également être validés

Les sélecteurs CSS ne peuvent pas être validés par une expression régulière

À quoi ressemblerait cette regex ou quelle validation fournirait-elle ? Est-ce pour une surcharge de fonction basée sur les expressions régulières ?

Pas l'OP, mais je suppose, oui, quelque chose comme les surcharges HTMLDocument#createElement() , par exemple :

// ...
export declare function h(query: /^canvas([\.#]\w+)*$/): HTMLCanvasElement;
// ...
export declare function h(query: /^div([\.#]\w+)*$/): HTMLDivElement;
// ...

Je suis sûr que les RE sont incomplètes. Notez qu'il s'agit d'un cas particulier de validation des sélecteurs CSS, qui sont utilisés de manière régulière dans de nombreux contextes. Par exemple, il est parfaitement acceptable que HTMLDocument.querySelector() renvoie HTMLElement comme solution de repli si vous utilisez un sélecteur complexe.

Je suis curieux de savoir s'il existe des exemples sans surcharge qui sont à la fois réalisables et utiles.

À FAIRE : Aidez-nous en identifiant de vraies fonctions de bibliothèque qui pourraient bénéficier des types RegExp, et l'expression réelle que vous utiliseriez.

Mon cas d'utilisation est celui que j'ai expliqué dans ce commentaire dans la bibliothèque CCXT où j'ai des chaînes qui représentent des TickerSymbol s. Je me fiche qu'ils soient vérifiés pour un modèle regex, mais je veux qu'ils soient traités comme des sous-types de string afin que j'obtienne des affectations plus strictes, une vérification du type de paramètre, etc. être très utile lorsque je fais de la programmation fonctionnelle, avec cela, je peux facilement suivre les symboles Ticker, les devises, les actifs, etc. au moment de la compilation où, au moment de l'exécution, ce ne sont que des chaînes normales.

@omidkrad Il semble que vous ayez besoin de types nominaux , pas de types validés par regex.

@m93a Dans mon cas, les types nominaux me

Les sélecteurs CSS pourraient également être validés

Les sélecteurs CSS ne peuvent pas être validés par une expression régulière

Eh bien, si l'expression régulière nous permettait de les assembler, nous pourrions copier les expressions régulières CSS..., n'est-ce pas ?

Le modèle d'objet typé CSS (en projet)

https://drafts.css-houdini.org/css-typed-om/

https://developers.google.com/web/updates/2018/03/cssom

Atténue potentiellement le désir d'utiliser le modèle CSS de type chaîne.

el.attributeStyleMap.set('padding', CSS.px(42));
const padding = el.attributeStyleMap.get('padding');
console.log(padding.value, padding.unit); // 42, 'px'

@RyanCavanaugh Pour Mithril en particulier, le nom de la balise est extrait via le groupe de capture dans ^([^#\.\[\]]+) (par défaut à "div" ), mais la correspondance de ^(${htmlTagNames.join("|")}) serait suffisante pour nos besoins. Et donc en utilisant ma proposition , ce serait suffisant pour mes besoins :

type SelectorAttrs = "" | `#${string}` | `.${string}`;

type GetTagName<T extends string> =
    T extends SelectorAttrs ? "div" :
    T extends `${keyof HTMLElementTagNameMap & (infer Tag)}${SelectorAttrs}` ? T :
    string;

En ce qui concerne les événements et les attributs, nous pourrions passer à ce terrain de types une fois nié :

type EventsForElement<T extends Element> =
    T extends {addEventListener(name: infer N, ...args: any[]): any} ? N : never;

type MithrilEvent<E extends string> =
    (E extends EventsForElement<T> ? HTMLElementEventMap[E] : Event) &
    {redraw?: boolean};

type Attributes<T extends Element> =
    LifecycleAttrs<T> &
    {[K in `on${string}` & not LifecycleAttrs<T>](
        ev: K extends `on${infer E}` ? MithrilEvent<E> : never
    ): void | boolean} &
    {[K in keyof T & not `on${string}`]: T[K]} &
    {[K in string & not keyof T & not `on${string}`]: string};

BTW, cette intégration transparente et cet évitement de la complexité est la raison pour laquelle je préfère toujours ma proposition aux expressions rationnelles littérales.


Cependant, je ne connais aucun moyen de le faire avec des types d'expression rationnelle purs. Je tiens à le souligner.

À FAIRE : Aidez-nous en identifiant de vraies fonctions de bibliothèque qui pourraient bénéficier des types RegExp, et l'expression réelle que vous utiliseriez.

bent a un type de retour différent basé sur ce qui est donné comme chaîne qui décrit le type de réponse attendu, par exemple

bent('json')('https://google.com') // => Promise<JSON>
bent('buffer')('https://google.com') // => Promise<Buffer | ArrayBuffer>
bent('string')('https://google.com') // => Promise<String>

Il accepte également d'autres arguments, tels que la méthode et l'url en tant que chaînes, mais ceux-ci peuvent apparaître dans n'importe quelle position, donc si nous essayons d'utiliser des unions pour décrire tout le type de retour ( 'json' | 'buffer' | 'string' ), ce serait plutôt stupide à seulement string lorsqu'il est combiné avec les types d'URL et de méthode dans l'union, ce qui signifie que nous ne pouvons pas automatiquement déduire le type de retour en fonction du type donné lors du premier appel.

@Ovyerus, comment les types regex vous aideraient-ils là-bas? Qu'attendriez-vous d'écrire ? Vous pouvez modéliser quelque chose de similaire au comportement de bend avec des surcharges ou des types conditionnels.

type BentResponse<Encoding> = Promise<
    Encoding extends "json" ? MyJsonType :
    Encoding extends "buffer" ? Buffer | ArrayBuffer :
    Encoding extends "string" ? string :
    Response
>;

declare function bent<T extends string>(urlOrEncoding: T): (url: string) => BentResponse<T>;

http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAQhB2wBKEDOYD29UQDwFF4BjDAEwEt4BzAPigF4oAFAJwwFtydcBYAKCiCohEhWpQIAD2AJSqKACIAVqiwKoAfigBZEAClV8ACrhoALn5DhxMpSoTps + QoBGAVwBmHiC3VaYnt4sUAA + UACCLCwAhiABXj5QFgJCIrbiUjLwcoqowCx2flB5BeLJVijoWDj8NADc-PykEEQANtEs0B5uxMDkWFAuCMC4Rg5ZOSV2NAAUbiytAPIsaWJUZlBGAJQbcwsbU9RbDHRwiJWY2HhG9Y18lDIsHtFE0PFBUADeUAD67gksDbReAgKAAX34Dx8z1eOn0hhMkC + vxUWCBIPBdxm0VQIGIUBmx3odE + liErQgwCgkg2ugMWER0EY0QA7tFyFShogZspDAotjyABbAYBgVBmAD0Eqk0XYYApADoSOx + Q0 + GCBVsgA

Oh, je n'étais pas clair désolé, je crois que mon problème était plutôt de faire correspondre http(s): au début d'une chaîne pour détecter l'URL de base.

La signature de Bent s'apparente davantage à

type HttpMethods = 'GET' | 'PATCH' | ...
type StatusCode = number;
type BaseUrl = string; // This is where I would ideally need to see if a string matches http(s):
type Headers = { [x: string]: any; };

type Options = HttpMethods | StatusCode | BaseUrl | Headers;

function bent(...args: Options[]): RequestFunction<RawResponse>
function bent(...args: (Options | 'json')[]): RequestFunction<JSON>
// and so on

Cependant, avoir BaseUrl tant que chaîne absorbe les HttpMethods et les unions de type de retour, qui se terminent par simplement string . L'avoir juste comme une chaîne correspond également "improprement" au fonctionnement de bent, car il vérifie la présence de ^http: ou ^https: afin de déterminer ce qu'il doit utiliser comme URL de base.

Si nous avions des types regex, je pourrais définir BaseUrl comme type BaseUrl = /^https?:/ , et cela vérifierait idéalement correctement les chaînes qui ne sont pas une méthode HTTP ou un codage de réponse, ainsi que de ne pas les absorber dans le string taper.

Exactement, je suis pareil.

--
Prokop Simek

Le 20 octobre 2019 à 03:23:30, Michael Mitchell ([email protected])
a écrit:

Oh, je n'étais pas clair désolé, je crois que mon problème était plus du genre
http(s) correspondant(s) : au début d'une chaîne pour détecter l'URL de base.

La signature de Bent s'apparente davantage à

tapez HttpMethods = 'GET' | 'PATCH' | ...type StatusCode = nombre;type BaseUrl = chaîne; // C'est ici que j'aurais idéalement besoin de voir si une chaîne correspond à http(s):type Headers = { [x: string]: any; } ;
type Options = HttpMethods | Code d'état | URL de base | En-têtes ;
function bent(...args: Options[]): RequestFunctionfunction bent(...args: (Options | 'json')[]): RequestFunction// etc

Cependant, avoir BaseUrl en tant que chaîne absorbe les HttpMethods et renvoie
type unions, qui se termine par une simple chaîne. L'avoir juste comme une chaîne
correspond également « incorrectement » au fonctionnement de Bnt, car il vérifie la présence
de ^http: ou ^https: afin de déterminer ce qu'il doit utiliser comme base
URL.

Si nous avions des types regex, je pourrais définir BaseUrl comme type BaseUrl = /^https?:/,
et cela idéalement vérifierait correctement les chaînes qui ne sont pas une méthode HTTP ou
codage de réponse, ainsi que de ne pas les absorber dans le type de chaîne.

-
Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/microsoft/TypeScript/issues/6579?email_source=notifications&email_token=ABJ3U4JNK3V5MV4DJH73ZU3QPOXJFA5CNFSM4BZLAVSKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMV54AB42KWSHJDN ,
ou se désinscrire
https://github.com/notifications/unsubscribe-auth/ABJ3U4PHBXO4766LK7P7UXDQPOXJFANCNFSM4BZLAVSA
.

La pensée que j'avais d'un cas d'utilisation était de détecter les types de paramètres d'une fonction.

Fondamentalement, j'ai un format regex bien défini d'une chaîne représentant un identifiant. Je pourrais utiliser des décorateurs, mais un type de chaîne étendu me permettrait d'utiliser un type pour représenter l'identifiant passé à la fonction.

Pour réitérer, nous avons besoin d'exemples de code JavaScript que vous souhaitez écrire de manière typée - sinon nous ne pouvons que deviner ce que vous essayez de modéliser (et s'il existe déjà un moyen de le modéliser).

@DanielRosenwasser Vous lequel nous aimerions forcer la saisie. http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAqjCSARKBeKBnYAnAlgOwHMBYAKFIGMAbAQ3XVnQiygG9SoOoxcA3a4aJn45yULBGoATAPZ5KIKAFtq + GIywAuWAmRoA5NQCcEAAwBGQwCMArAFoAZtYBMAdlsAWcvYActw2ZM7JzNJF28TCCcANgtLPXZOAHpErl5 + QWBhUXEpWXklFTw1Ji04JFQoMycAZihkqAA5AHkAFSgAQQAZTqaAdQBRRASOeu4cPgEMTOARMQkZOQVlVXVSnQq9PGlgW2pbAFd9nEk9OpSAZQAJJphO5Ga2gCF + ju6 + wah ++ BBL-oAlYZQciyTBYfbkYDSLAACkBnC4 + 0slFmxzWSAANHDOGBEcjRJYsNQ8JItKD8ARMSR4fCcUjZuocNRKFo8PtFJYmJTqdjcbNyDkBJJHiA0boGEwAHTLIrqACUrFICQAvqQVSQgA

@yannickglt il semble que vous vouliez un type nominal, pas un type RegExp ? Vous ne vous attendez pas à ce que les appelants se présentent avec des invocations aléatoires validées par le site comme celle-ci :

// OK
someFunc('a9e019b5-f527-4cf8-9105-21d780e2619b');
// Also OK, but probably really bad
someFunc('a9e019b5-f527-4cf8-9106-21d780e2619b');
// Error
someFunc('bfe91246-8371-b3fa-3m83-82032713adef');

IOW le fait que vous puissiez décrire un UUID avec une expression régulière est un artefact du format de la chaîne elle-même, alors que ce que vous essayez d'exprimer est que les UUID sont un type spécial dont le format de support se trouve être une chaîne .

Ainsi, la combinaison de Assertion Functions de 3.7 et de la fonctionnalité nominal peut le faire (?)

nominal UUID = string

function someFunc(uuid: any): asserts uuid is UUID {
  if (!UUID_REGEX.test(uuid)) {
    throw new AssertionError("Not UUID!")
  }
}

class User {
  private static readonly mainUser: UUID = someFunc('a9e019b5-f527-4cf8-9105-21d780e2619b')
  // private static readonly mainUser: UUID = someFunc(123) // assertion fails
  // private static readonly mainUser: UUID = someFunc('not-a-uuid') // assertion fails
  constructor(
    public id: UUID,
    public brand: string,
    public serial: number,
    public createdBy: UUID = User.mainUser) {

  }
}

Cela échouera-t-il aussi ?

new User('invalid-uuid', 'brand', 1) // should fail
new User('invalid-uuid' as UUID, 'brand', 1) // 🤔 

Après avoir réfléchi un moment, je vois un problème avec ma solution proposée 🤔
Le asserts ne déclenche une erreur qu'à l' exécution -> 👎
Le Regex-Validation pourrait déclencher une erreur de compilation -> 👍
Sinon, cette proposition n'a aucun sens

Éditer:
Autre problème : someFunc(uuid: any): asserts uuid is UUID ne renvoie pas d'UUID, il renvoie ou renvoie is UUID -> true .
Je ne peux donc pas utiliser cette fonction pour attribuer un UUID de cette manière à mainUser

@RyanCavanaugh Nous voulons que ceux-ci soient correctement saisis pour Mithril :

// <div id="hello"></div>
m("div#hello", {
    oncreate(vnode) { const dom: HTMLDivElement = vnode.dom },
})

// <section class="container"></section>
m("section.container", {
    oncreate(vnode) { const dom: HTMLElement = vnode.dom },
})

// <input type="text" placeholder="Name">
m("input[type=text][placeholder=Name]", {
    oncreate(vnode) { const dom: HTMLInputElement = vnode.dom },
})

// <a id="exit" class="external" href="https://example.com">Leave</a>
m("a#exit.external[href='https://example.com']", {
    oncreate(vnode) { const dom: HTMLAnchorElement = vnode.dom },
}, "Leave")

// <div class="box box-bordered"></div>
m(".box.box-bordered", {
    oncreate(vnode) { const dom: HTMLDivElement = vnode.dom },
})

// <details></details> with `.open = true`
m("details[open]", {
    oncreate(vnode) { const dom: HTMLDetailsElement = vnode.dom },
})

// alias for `m.fragment(attrs, ...children)`
m("[", {
    oncreate(vnode) { const dom: HTMLElement | SVGElement = vnode.dom },
}, ...children)

Nous voulons les rejeter statiquement :

// selector must be non-empty
m("")

// incomplete class
m("div.")

// incomplete ID
m("div#")

// incomplete attribute
m("div[attr=")

// not special and doesn't start /[a-z]/i
m("@foo")

Idéalement, nous voudrions également les rejeter statiquement, mais ce n'est pas une priorité aussi élevée et nous pouvons survivre sans eux :

// event handers must be functions
m("div[onclick='return false']")

// `select.selectedIndex` is a number
m("select[selectedIndex='not a number']")

// `input.form` is read-only
m("input[type=text][form='anything']")

// `input.spellcheck` is a boolean, this evaluates to a string
// (This is a common mistake, actually.)
m("input[type=text][spellcheck=false]")

// invalid tag name for non-custom element
m("sv")

Cela nécessiterait une définition de type beaucoup plus compliquée, dans laquelle nous aurions besoin d'un message d'échec de vérification de type personnalisé pour aider les utilisateurs à comprendre pourquoi il n'a pas réussi à vérifier le type.

D'autres bibliothèques d'hyperscripts et frameworks basés sur des hyperscripts, tels que react-hyperscript, ont également des préoccupations similaires.

J'espère que cela t'aides!

@isiahmeadows est une meilleure façon pour vous d'utiliser une forme de constructeur de chaîne de sélection, qui renverra une chaîne de marque, avec des frappes correctes. Comme:

m(mt.div({ attr1: 'val1' }))

@ anion155 Il existe également d'autres moyens d'y parvenir, mais il s'agit de taper une bibliothèque dont l'API a été conçue par son auteur d'origine en 2014. Si je concevais son API maintenant, j'utiliserais probablement m("div", {...attrs}, ...children) avec aucun du sucre hyperscript (plus facile à taper, beaucoup plus simple à traiter), mais il est beaucoup trop tard maintenant pour faire grand-chose à ce sujet.

J'ai BEAUCOUP à dire. Cependant, je suis impatient. Donc, je vais libérer mes pensées un peu à la fois.

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -542405537

Concernant la "précisionite" (mec, j'adore ce mot),
Je pense qu'il ne faut pas trop s'en préoccuper.

Le système de types est déjà terminé.
Cela signifie essentiellement que nous pouvons être très précis sur beaucoup de choses.
(Comme, modéliser tout SQL ? Plug sans vergogne = P)

Mais vous ne voyez pas (trop) de personnes aller à fond et utiliser tous les opérateurs de type de manière folle qui empêchent les bibliothèques d'être compatibles les unes avec les autres. J'aime à penser que les auteurs de bibliothèques ont tendance à être assez pondérés... N'est-ce pas ?

Ce n'est pas souvent que j'ai souhaité des types de modèle de chaîne/types de chaîne validés par regex, mais ils auraient certainement contribué à augmenter la sécurité de type de ma base de code.


Cas d'utilisation

Du haut de ma tête, je peux penser à un exemple récent. (Il y en a plein d'autres mais je suis un être oublieux)

Lors de l'intégration avec l'API de Stripe (une plate-forme de traitement des paiements), ils utilisent ch_ pour les identifiants liés à charge , re_ pour les identifiants liés à refund , etc.

Il aurait été bien de les encoder avec PatternOf</^ch_.+/> et PatternOf</^re_.+/> .

De cette façon, lorsque vous faites des fautes de frappe comme,

charge.insertOne({ stripeChargeId : someObj.refundId });

j'aurais une erreur,

Cannot assign `PatternOf</^re_.+/>` to `PatternOf</^ch_.+/>`

Autant j'aime les types nominaux/étiquetés, autant ils sont
Je considère toujours les types nominaux/marqués en dernier recours , car cela signifie qu'il y a quelque chose que le système de type TS ne peut tout simplement pas modéliser.

De plus, les types étiquetés sont parfaits pour les types fantômes.
Les types nominaux ne sont fondamentalement jamais utiles.
(D'accord, je suis peut-être partial. Ils ne sont utiles qu'à cause de unique symbol Mais j'aime penser que je ne me trompe pas complètement.)

Le modèle de validation "ValueObject" est encore pire et je ne prendrai pas la peine d'en parler.


Comparaison

Ci-dessous, je vais comparer ce qui suit,

  • Types de modèle de chaîne/types de chaîne validés par regex
  • Types nominaux
  • Types d'étiquettes structurelles

Nous pouvons tous convenir que le modèle "ValueObject" est la pire solution, et ne pas s'en soucier dans les comparaisons, n'est-ce pas ?


Types de motif de chaîne

const stripeChargeIdRegex = /^ch_.+/;
const stripeRefundIdRegex = /^re_.+/;

type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;
type StripeRefundId = PatternOf<typeof stripeRefundIdRegex>;

declare function takesStripeChargeId (stripeChargeId : StripeChargeId) : void;

declare const str : string;
takesStripeChargeId(str); //Error
if (stripeChargeIdRegex.test(str)) {
  takesStripeChargeId(str); //OK
}
if (stripeRefundIdRegex.test(str)) {
  takesStripeChargeId(str); //Error
}

declare const stripeChargeId : StripeChargeId;
declare const stripeRefundId : StripeRefundId;
takesStripeChargeId(stripeChargeId); //OK
takesStripeChargeId(stripeRefundId); //Error

takesStripeChargeId("ch_hello"); //OK
takesStripeChargeId("re_hello"); //Error

Regarde ça.

  • Parfait pour les littéraux de chaîne.
  • Pas trop mal pour les string non-littéraux.

Types nominaux...

const stripeChargeIdRegex = /^ch_.+/;
const stripeRefundIdRegex = /^re_.+/;

type StripeChargeId = make_unique_type string;
type StripeRefundId = make_unique_type string;

function isStripeChargeId (str : string) : str is StripeChargeId {
  return stripeChargeIdRegex.test(str);
}
function isStripeRefundId (str : string) : str is StripeRefundId {
  return stripeRefundIdRegex.test(str);
}

declare function takesStripeChargeId (stripeChargeId : StripeChargeId) : void;

declare const str : string;
takesStripeChargeId(str); //Error
if (isStripeChargeId(str)) {
  takesStripeChargeId(str); //OK
}
if (isStripeRefundId(str)) {
  takesStripeChargeId(str); //Error
}

declare const stripeChargeId : StripeChargeId;
declare const stripeRefundId : StripeRefundId;
takesStripeChargeId(stripeChargeId); //OK
takesStripeChargeId(stripeRefundId); //Error

takesStripeChargeId("ch_hello"); //Error? Ughhhh
takesStripeChargeId("re_hello"); //Error

takesStripeChargeId("ch_hello" as StripeChargeId); //OK, BUT UNSAFE
takesStripeChargeId("re_hello" as StripeChargeId); //OK, BUT WAIT! I MESSED UP

const iKnowThisIsValid = "ch_hello";
if (isStripeChargeId(iKnowThisIsValid)) {
  takesStripeChargeId(iKnowThisIsValid); //OK
} else {
  throw new Error(`Wat? This should be valid`);
}

function assertsStripeChargeId (str : string) : asserts str is StripeChargeId {
  if (!isStripeChargeId(str)) {
    throw new Error(`Expected StripeChargeId`);
  }
}
assertsStripeChargeId(iKnowThisIsValid);
takesStripeChargeId(iKnowThisIsValid); //OK

function makeStripeChargeIdOrError (str : string) : StripeChargeId {
  assertsStripeChargeId(str);
  return str;
}
takesStripeChargeId(makeStripeChargeIdOrError("ch_hello")); //OK
takesStripeChargeId(makeStripeChargeIdOrError("re_hello")); //OK, compiles, throws during run-time... Not good

Regarde ça.

  • TERRIBLE pour les littéraux de chaîne.
  • Après avoir surmonté l'obstacle de la chaîne littérale, ce n'est pas trop mal... N'est-ce pas ?

Mais le principal cas d'utilisation de cette proposition concerne les littéraux de chaîne.
Donc, c'est une alternative terrible.


Types d'étiquettes structurelles...

Les types d'étiquettes structurelles ne sont pas très différents des types nominaux...

const stripeChargeIdRegex = /^ch_.+/;
const stripeRefundIdRegex = /^re_.+/;

type StripeChargeId = string & tag { stripeChargeId : void };
type StripeRefundId = string & tag { stripeRefundId : void };

function isStripeChargeId (str : string) : str is StripeChargeId {
  return stripeChargeIdRegex.test(str);
}
function isStripeRefundId (str : string) : str is StripeRefundId {
  return stripeRefundIdRegex.test(str);
}

declare function takesStripeChargeId (stripeChargeId : StripeChargeId) : void;

declare const str : string;
takesStripeChargeId(str); //Error
if (isStripeChargeId(str)) {
  takesStripeChargeId(str); //OK
}
if (isStripeRefundId(str)) {
  takesStripeChargeId(str); //Error
}

declare const stripeChargeId : StripeChargeId;
declare const stripeRefundId : StripeRefundId;
takesStripeChargeId(stripeChargeId); //OK
takesStripeChargeId(stripeRefundId); //Error

takesStripeChargeId("ch_hello"); //Error? Ughhhh
takesStripeChargeId("re_hello"); //Error

takesStripeChargeId("ch_hello" as StripeChargeId); //OK, BUT UNSAFE
takesStripeChargeId("re_hello" as StripeChargeId); //OK, BUT WAIT! I MESSED UP

const iKnowThisIsValid = "ch_hello";
if (isStripeChargeId(iKnowThisIsValid)) {
  takesStripeChargeId(iKnowThisIsValid); //OK
} else {
  throw new Error(`Wat? This should be valid`);
}

function assertsStripeChargeId (str : string) : asserts str is StripeChargeId {
  if (!isStripeChargeId(str)) {
    throw new Error(`Expected StripeChargeId`);
  }
}
assertsStripeChargeId(iKnowThisIsValid);
takesStripeChargeId(iKnowThisIsValid); //OK

function makeStripeChargeIdOrError (str : string) : StripeChargeId {
  assertsStripeChargeId(str);
  return str;
}
takesStripeChargeId(makeStripeChargeIdOrError("ch_hello")); //OK
takesStripeChargeId(makeStripeChargeIdOrError("re_hello")); //OK, compiles, throws during run-time... Not good

Regarde ça.

  • TERRIBLE pour les littéraux de chaîne.
  • Après avoir surmonté l'obstacle de la chaîne littérale, ce n'est pas trop mal... N'est-ce pas ?

Mais le principal cas d'utilisation de cette proposition concerne les littéraux de chaîne.
Donc, c'est une alternative terrible.

De plus, cet exemple de type de balise structurelle est un copier-coller littéral (ha, calembour) de l'exemple de type nominal .

La seule différence réside dans la façon dont les types StripeChargeId et StripeRefundId sont déclarés.

Même si le code est fondamentalement le même, les types structurels sont meilleurs que les types nominaux. (Je clarifierai cela dans le prochain post, je le jure).


Conclusion

Ceci n'est qu'une conclusion pour ce commentaire ! Pas une conclusion à mes pensées générales!

Les types de modèle de chaîne/les types de chaîne validés par regex sont plus ergonomiques que les types de balises nominales/structurelles. Espérons que mes exemples simples n'étaient pas trop artificiels l' ont suffisamment démontré.


Conclusion (Supplément)

Autant que possible, les manières de prendre le sous-ensemble d'un type primitif doivent toujours être préférées aux types nominaux/structurels d'étiquette/valeur-objet.

Exemples de prise du sous-ensemble des types primitifs,

  • string littéraux
  • number littéraux (hors NaN, Infinity, -Infinity )
  • boolean littéraux
  • bigint littéraux
  • Même unique symbol prend juste un sous-ensemble de symbol

Parmi les exemples ci-dessus, seul boolean est « assez fini ». Il n'a que deux valeurs.
Les développeurs sont satisfaits d'avoir des littéraux true et false car il n'y a pas grand-chose d'autre à demander.


Le type number est fini mais il a tellement de valeurs que nous pourrions aussi bien le considérer comme infini.
Il y a aussi des trous dans les littéraux que nous pouvons spécifier.

C'est pourquoi le type de numéro de plage et les problèmes NaN, Infinity, -Infinity sont si populaires et continuent d'apparaître. Être capable de spécifier un petit ensemble fini de valeurs, à partir d'un ensemble infini n'est pas suffisant.

Spécifier une plage est l'une des idées les plus courantes/naturelles à venir à quelqu'un lorsqu'il a besoin de spécifier un grand sous-ensemble fini/infini d'un ensemble infini.


Le type bigint est fondamentalement infini, limité uniquement par la mémoire.

Cela contribue également à la popularité de la question du type de numéro de plage.


Le type string est fondamentalement infini, limité uniquement par la mémoire.

Et c'est pourquoi ce problème de type de chaîne/type de chaîne validé par regex est si populaire.

Spécifier une regex est l'une des idées les plus courantes/naturelles à venir à quelqu'un lorsqu'il a besoin de spécifier un grand sous-ensemble fini/infini d'un ensemble infini.


Le type symbol ... C'est aussi infini. Et aussi illimité, à peu près.

Mais les éléments du type symbol sont tous à peu près indépendants les uns des autres, de presque toutes les manières. Et, donc, personne n'a posé de problème pour demander : "Puis-je avoir un moyen de spécifier un grand sous-ensemble fini/infini de symbol ?".

Pour la plupart des gens, cette question n'a même pas de sens. Il n'y a pas de moyen significatif de le faire (n'est-ce pas ?)


Cependant, le simple fait de pouvoir déclarer des sous-ensembles de primitives n'est pas très utile. Nous avons aussi besoin de,

  • Les littéraux du bon type doivent être assignables sans autre travail

Heureusement, TS est suffisamment sain d'esprit pour permettre cela.

Imaginez être incapable de passer false à (arg : false) => void !

  • Manières intégrées de rétrécissement

    Pour le moment, pour ces littéraux, nous avons == & === comme moyens intégrés de rétrécissement.

    Imaginez avoir besoin d'écrire un nouveau type de garde pour chaque littéral !

Le problème avec les types nominaux/structurels d'étiquette/d'objet de valeur est qu'ils ne remplissent fondamentalement pas les deux critères ci-dessus. Ils transforment les types primitifs en types maladroits qui ne sont pas tout à fait des types d'objets, mais qui doivent de toute façon être traités comme des types d'objets.

Ergonomie

D'accord, voici plus d'explications sur les types d'étiquettes à motif de chaîne par rapport aux types d'étiquettes nominales ou structurelles.

Ces arguments s'appliquent également à https://github.com/microsoft/TypeScript/issues/15480 .


Compatibilité entre bibliothèques

Les types nominaux sont les pires en termes de compatibilité entre bibliothèques.
C'est comme utiliser unique symbol dans deux bibliothèques et essayer de les faire interagir.
Ça ne peut tout simplement pas être fait.
Vous devez utiliser une protection de type passe-partout, ou le trust-me-operator ( as ).

Vous aurez également besoin de plus de passe-partout pour un garde d'assertion.

Si le type ne nécessite pas de compatibilité entre bibliothèques, l'utilisation de types nominaux convient parfaitement...
Même si très peu ergonomique (voir exemple ci-dessus).


Pour les types structurels, si la bibliothèque A possède,

//Lowercase 'S'
type StripeChargeId = string & tag { stripeChargeId : void };

Et la bibliothèque B a,

//Uppercase 'S'
type StripeChargeId = string & tag { StripeChargeId : void };

//Or
type StripeChargeId = string & tag { isStripeChargeId : true };

//Or
type StripeChargeId = string & tag { stripe_charge_id : void };

Ensuite, vous aurez besoin d'une protection de type passe-partout ou de l'opérateur trust-me ( as ).

Vous aurez également besoin de plus de passe-partout pour un garde d'assertion.

Si le type ne nécessite pas de compatibilité entre bibliothèques, l'utilisation de types structurels convient parfaitement...
Même si très peu ergonomique (voir exemple ci-dessus).


Pour les types de motif de chaîne, si la bibliothèque A a,

type stripeChargeIdRegex = /^ch_.+/;
type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;

Et la bibliothèque B a,

//Extra dollar sign at the end
type stripeChargeIdRegex = /^ch_.+$/;
type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;

//Or,
type stripeChargeIdRegex =/^ch_[a-zA-Z0-9]$/;
type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;

//Or,
type stripeChargeIdRegex =/^ch_[A-Za-z0-9]$/;
type StripeChargeId = PatternOf<typeof stripeChargeIdRegex>;

Supposons que les deux bibliothèques produisent toujours des chaînes pour StripeChargeId qui satisferont les exigences des deux bibliothèques. La bibliothèque A est juste "paresseuse" avec sa validation. Et la bibliothèque B est "plus stricte" avec sa validation.

Ensuite, c'est un peu agaçant. Mais pas trop mal.
Parce que vous pouvez simplement utiliser libraryB.stripeChargeIdRegex.test(libraryA_stripeChargeId) comme protection de type. Pas besoin d'utiliser le trust-me-operator ( as ).

Cependant, vous aurez toujours besoin d'un passe-partout pour les gardes d'assertion.

Si le type ne nécessite pas de compatibilité entre bibliothèques, l'utilisation de types à motif de chaîne est parfaite et également très ergonomique.


Si vous avez besoin d'une compatibilité entre bibliothèques, les types de motifs de chaîne sont toujours meilleurs que les types de balises structurelles ! Écoutez-moi.

Si le domaine modélisé est bien compris, il est très probable que plusieurs auteurs de bibliothèque isolés finissent par écrire la même regex. Avec les types de balises structurelles, ils pourraient tous simplement écrire les propriétés et les types qu'ils souhaitent dans les balises.

S'il existe une norme spécifiant les formats de chaîne pour tout ce qui est modélisé, alors il est fondamentalement garanti que tous les auteurs de bibliothèque écriront la même regex ! S'ils écrivent une regex différente, ils ne suivent pas vraiment la norme. Voulez-vous utiliser leur bibliothèque? Avec les types de balises structurelles, ils pouvaient toujours écrire n'importe quoi. (À moins que quelqu'un ne commence une norme de type de balise structurelle dont tout le monde se souciera? Lol)


Compatibilité entre les versions

Comme d'habitude, les types nominaux sont les pires en termes de compatibilité entre les versions.
Oh, vous avez mis un patch ou une version mineure dans votre bibliothèque ?
Le type de décalaration est toujours le même ?
Le code est toujours le même ?
Nan. Ce sont des types différents.

image


Les types de balises structurelles sont toujours assignables, à travers les versions (même les versions majeures), tant que le type de balise est structurellement le même.


Les types de modèle de chaîne sont toujours assignables, à travers les versions (même les versions majeures), tant que l'expression régulière est la même.

Ou nous pourrions simplement exécuter un algorithme PSPACE-complet pour déterminer si les regex sont les mêmes ? Nous pouvons également déterminer quelles sous-classes de regex sont les plus courantes et exécuter des algorithmes d'équivalence optimisés pour celles-ci... Mais cela semble beaucoup d'efforts.

Les vérifications de sous-type Regex seraient intéressantes à avoir et rendraient certainement l'utilisation de types de motif de chaîne plus ergonomique. Tout comme les vérifications de sous-type de plage bénéficieraient à la proposition de type de plage de numéros.

[Éditer]
Dans ce commentaire,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment-243338433

Quelqu'un lié à,
https://bora.uib.no/handle/1956/3956

Intitulé "Le problème d'inclusion pour les expressions régulières"
[/Éditer]


Chaudronnerie

À FAIRE (Mais nous pouvons voir que les types de motif de chaîne ont le moins de passe-partout)

Invocation littérale

À FAIRE (Mais nous pouvons voir que les types de motif de chaîne prennent le mieux en charge l'invocation littérale)

Invocation non littérale

À FAIRE (Mais nous pouvons voir que les types de motif de chaîne prennent le mieux en charge l'invocation non littérale)

En savoir plus sur https://github.com/microsoft/TypeScript/issues/6579#issuecomment -542405537

TypeScript ne peut pas faire d'erreur sur les instanciations d'intersections, donc cela ne ferait partie d'aucune conception finale.

Je ne sais pas pourquoi les gens voulaient interdire les intersections, mais vous avez tout à fait raison de dire que l'interdire n'a pas de sens.


brisant ainsi toute invocation non littérale ?

Eh bien, pas toutes les invocations non littérales.

declare function foo (arg : PatternOf</a+/>) : void;
function bar (arg : PatternOf</a+/>) : void {
  //non-literal and does not break.
  foo(arg);
}
bar("aa"); //OK
bar("bb"); //Error
bar("" as string); //Error, I know this is what you meant by non-literal invocation

function baz (arg : "car"|"bar"|"tar") : void {
  bar(arg); //OK
}

Rompre sur une invocation non littérale, où il ne peut pas prouver qu'il correspond à l'expression régulière, n'est pas nécessairement une mauvaise chose. C'est juste une question de sécurité de type.

C'est un peu comme dire que les littéraux de chaîne sont mauvais parce que maintenant les invocations non littérales échouent.
Les types de modèle de chaîne/types de chaîne validés par regex vous permettent simplement de définir des unions d'un nombre infini de littéraux de chaîne.


toute utilisation non littérale nécessiterait un nouveau test ou une affirmation :

Je ne vois pas du tout cela comme un problème.
C'est la même chose avec les types nominaux/étiquetés en ce moment.
Ou essayer de passer un string à une fonction attendant des littéraux de chaîne.
Ou essayer de passer un type plus large à un type plus étroit.

Dans ce cas particulier, vous avez montré que const ZipCode = /^\d\d\d\d\d$/; et ZipCode.test(s) peuvent agir comme une protection de type. Cela aidera certainement avec l'ergonomie.


  • Le problème en cours de résolution n'a pas de meilleure alternative (y compris des alternatives plausibles qui ne sont pas encore dans la langue)

Eh bien, j'espère avoir montré que les types de balises nominaux/structurels ne sont pas la meilleure alternative. Ils sont en fait assez mauvais.

  • Le problème se produit avec une fréquence significative dans les bases de code réelles

Euh... Laissez-moi vous revenir là-dessus...

  • La solution proposée résout bien ce problème

Le type de motif de chaîne proposé semble être assez bon.


À FAIRE : Aidez-nous en identifiant de vraies fonctions de bibliothèque qui pourraient bénéficier des types RegExp, et l'expression réelle que vous utiliseriez.

Votre point de vue est que les types nominaux/étiquetés sont assez bons pour une utilisation non littérale.
Ainsi, tout cas d'utilisation présenté qui montre une utilisation non littérale n'est pas suffisant, car les types nominaux/étiquetés le couvrent.

Cependant, nous avons vu que, même pour une utilisation non littérale,

  • Les types de balises nominaux/structurels souffrent de problèmes de compatibilité entre bibliothèques/versions
  • La quantité de passe-partout pour les types d'étiquettes nominaux/structurels est nettement supérieure à celle du passe-partout pour les types de chaîne

En outre, il semble que les cas d'utilisation littéraux évoqués ne vous aient pas satisfait, car ils essaient de faire des choses stupides comme la validation des e-mails ou utilisent des expressions régulières qui ne sont pas assez précises.


Ecrire des tests - c'est là que les entrées codées en dur ont un sens, bien que ce soit presque un contrepoint car votre code de test devrait probablement fournir beaucoup d'entrées invalides

Un bon cas d'utilisation a été l'écriture de tests d' exécution . Et vous avez raison, ils devraient également y envoyer beaucoup d'entrées invalides pour les tests d'exécution.

Mais ce n'est pas une raison pour ne pas prendre en charge les types de modèle de chaîne. Il se peut qu'ils veuillent tester des entrées valides dans un certain fichier et donnent accidentellement une entrée invalide.

Mais, parce qu'ils doivent utiliser un type guard ou trust-me-operator ( as ) ou un objet value, ils obtiendront maintenant une erreur d'exécution, au lieu de savoir que le test échouera à l'avance .

L'utilisation de l'opérateur trust-me- ( as ) pour les tests d'exécution ne doit être réservée qu'au test des entrées non valides. Lorsque vous souhaitez tester des entrées valides, il est plus clair de ne pas avoir besoin de hacks pour attribuer des littéraux à un type de balise nominal/structural.

S'ils changent un jour l'expression régulière à l'avenir, ce serait bien que leurs tests échouent même maintenant à s'exécuter, en raison de problèmes d'assignabilité. S'ils n'utilisent que as partout dans leurs tests, ils ne le sauront pas tant qu'ils n'auront pas exécuté les tests.

Et si l'auteur de la bibliothèque n'utilise que as partout lorsqu'il crée sa propre bibliothèque... Qu'en est-il des consommateurs en aval ? Ne seront-ils pas également tentés d'utiliser as partout et de rencontrer des problèmes d'exécution lors de la mise à niveau vers une nouvelle version ?

Avec les types de modèle de chaîne, il y a moins de raisons d'utiliser as partout et l'auteur de la bibliothèque et les consommateurs en aval connaîtront plus facilement les modifications avec rupture.

(Un peu long mais j'espère que certains de mes points sont passés).


De plus, j'écris beaucoup de tests de compilation (et je sais que l'équipe TS le fait aussi).

Ce serait bien si je pouvais tester qu'un certain littéral string échouera/passera une vérification d'expression régulière dans mes tests de compilation. Pour le moment, je ne peux pas avoir de tests de compilation pour ces choses et j'ai besoin d'utiliser un test d'exécution à la place.

Et s'il échoue / réussit mes tests de compilation, alors je serai sûr que les consommateurs en aval peuvent utiliser ces littéraux de chaîne (ou similaires) et s'attendre à ce qu'ils se comportent correctement.


Il semble que cela nous met rapidement sur la voie d'une situation de tour de Babel...

C'est encore plus vrai avec l'utilisation de types de balises nominaux/structurels, en fait. Comme les exemples ci-dessus l'ont montré, ils font terriblement pour la compatibilité entre bibliothèques/versions...

Cependant, les types regex/string-pattern ont une chance décente de ne pas tomber dans ce problème (espérons-le, grâce à la normalisation et aux auteurs de bibliothèques sensés).


ÉDITER

Une chose courante dans le thread est que ces expressions régulières aideraient à valider le code de test, car même si dans les scénarios de production, le code s'exécuterait sur des chaînes fournies par l'exécution plutôt que sur des littéraux codés en dur, vous voudriez toujours une validation que vos chaînes de test étaient " correct". Cela semblerait être un argument pour les chaînes nominales/marquées/marquées à la place, puisque vous écririez la fonction de validation de toute façon, et l'avantage des tests est que vous savez qu'ils s'exécutent de manière exhaustive (ainsi toute erreur dans les entrées de test serait être signalés au début du cycle de développement).

Ah... j'aurais dû tout lire avant d'écrire ça...

Quoi qu'il en soit, j'ai quelques exemples avec moi, où les types de motif de chaîne sont utiles.


Bibliothèque de déclaration de route HTTP

Avec cette bibliothèque, vous pouvez créer des objets de déclaration de route HTTP. Cette déclaration est utilisée à la fois par le client et le serveur.

/*snip*/
createTestCard : f.route()
    .append("/platform")
    .appendParam(s.platform.platformId, /\d+/)
    .append("/stripe")
    .append("/test-card")
/*snip*/

Ce sont les contraintes pour .append() ,

  • Littéraux de chaîne uniquement (Impossible d'appliquer cela pour le moment, mais si vous utilisez des non-littéraux, le générateur de déclaration de route devient inutile)
  • Doit commencer par une barre oblique ( / )
  • Ne doit pas se terminer par une barre oblique ( / )
  • Ne doit pas contenir de caractère conlon ( : ); il est réservé aux paramètres
  • Ne doit pas contenir au moins deux barres obliques consécutives ( // )

Pour le moment, je n'ai que des contrôles d'exécution pour ceux-ci, qui génèrent des erreurs. J'aimerais que les consommateurs en aval aient à suivre ces contraintes sans avoir besoin de lire certains commentaires Github README ou JSDoc. Écrivez simplement le chemin et voyez les lignes ondulées rouges.


D'autres choses

J'ai aussi des expressions régulières pour les chaînes hexadécimales, les chaînes alphanumériques.

j'ai aussi ça,

const floatingPointRegex = /^([-+])?([0-9]*\.?[0-9]+)([eE]([-+])?([0-9]+))?$/;

Je vois ça,

Entier - rejette à tort "3e5"

J'ai aussi ceci, qui n'est pas une expression rationnelle entière mais utilise le floatingPointRegex ,

function parseFloatingPointString (str : string) {
    const m = floatingPointRegex.exec(str);
    if (m == undefined) {
        return undefined;
    }
    const rawCoefficientSign : string|undefined = m[1];
    const rawCoefficientValue : string = m[2];
    const rawExponentSign : string|undefined = m[4];
    const rawExponentValue : string|undefined = m[5];

    const decimalPlaceIndex = rawCoefficientValue.indexOf(".");
    const fractionalLength = (decimalPlaceIndex < 0) ?
        0 :
        rawCoefficientValue.length - decimalPlaceIndex - 1;

    const exponentValue = (rawExponentValue == undefined) ?
        0 :
        parseInt(rawExponentValue) * ((rawExponentSign === "-") ? -1 : 1);

    const normalizedFractionalLength = (fractionalLength - exponentValue);
    const isInteger = (normalizedFractionalLength <= 0) ?
        true :
        /^0+$/.test(rawCoefficientValue.substring(
            rawCoefficientValue.length-normalizedFractionalLength,
            rawCoefficientValue.length
        ));
    const isNeg = (rawCoefficientSign === "-");

    return {
        isInteger,
        isNeg,
    };
}

J'ai aussi ce commentaire, cependant,

/**
    Just because a string is in integer format does not mean
    it is a finite number.

    ```ts
    const nines_80 = "99999999999999999999999999999999999999999999999999999999999999999999999999999999";
    const nines_320 = nines_80.repeat(4);
    //This will pass, 320 nines in a row is a valid integer format
    integerFormatString()("", nines_320);
    //Infinity
    parseFloat(nines_320);
    ```
*/

Constructeur RegExp

Curieusement, le constructeur RegExp bénéficiera des types de chaînes validés par regex !

En ce moment, c'est,

new(pattern: string, flags?: string): RegExp

Cependant, nous aurions pu,

new(pattern: string, flags?: PatternOf</^[gimsuy]*$/>): RegExp

TL; DR (Veuillez le lire, cependant, j'ai fait beaucoup d'efforts pour cela :cry: )

  • Les types de motif de chaîne sont plus ergonomiques que les types de balises nominaux/structurels

    • Moins de passe-partout

  • Les types de modèle de chaîne sont moins susceptibles que les types d'étiquettes nominaux/structurels de devenir une situation de la tour de Babel

    • Surtout avec les contrôles de sous-type regex

  • Les types à motif de chaîne sont le moyen le plus naturel de définir de grands sous-ensembles finis/infinis du type string

    • L'introduction de cette fonctionnalité pourrait même amener les gens à réfléchir de plus près aux formats de chaîne valides pour leurs bibliothèques !

  • Les types de modèle de chaîne permettent une sécurité de compilation renforcée pour certaines bibliothèques (Permettez-moi de vous revenir sur la prévalence... s'enfuit )

    • Constructeur RegExp, chaînes hexadécimales/alphanumériques, déclarations de chemin de route, identifiants de chaîne pour les bases de données, etc.


Pourquoi tes regex sont-elles si mauvaises ?

Un tas de cas d'utilisation évoqués par d'autres voulaient introduire des types de motifs de chaîne pour s'adapter aux bibliothèques

Souvent, j'ai l'impression que ces bibliothèques existantes n'utilisent même pas beaucoup d'expressions régulières pour valider leur entrée. Ou, ils utilisent une expression régulière pour effectuer une simple validation. Ensuite, ils utilisent un analyseur plus compliqué pour effectuer la validation réelle.

Mais il s'agit d'un cas d'utilisation réellement valide pour les types de motif de chaîne !


Types de modèle de chaîne pour valider des sur-ensembles de valeurs de chaîne valides

Bien sûr, une chaîne qui commence par / , ne se termine pas par / , ne contient pas de / consécutifs et ne contient pas : passera le " regex de chemin HTTP". Mais cela signifie simplement que l'ensemble de valeurs qui passent cette expression régulière est un sur -

Plus bas, nous avons un véritable analyseur de chemin d'URL qui vérifie que ? n'est pas utilisé, # n'est pas utilisé, certains caractères sont échappés, etc.

Mais avec ce type de motif de chaîne simple, nous avons déjà éliminé une grande partie des problèmes courants qu'un utilisateur de la bibliothèque peut rencontrer ! Et nous l'avons également éliminé au moment de la compilation !

Ce n'est pas souvent qu'un utilisateur utilise ? dans ses chemins HTTP, car la plupart sont suffisamment expérimentés pour savoir que ? est le début d'une chaîne de requête.


Je viens de réaliser que vous connaissez déjà ce cas d'utilisation.

Ce fil implique une grande variété de cas d'utilisation; les exemples concrets ont été plus rares. Fait troublant, beaucoup de ces exemples ne semblent pas être complets - ils utilisent une RegExp qui rejetterait les entrées valides.

Donc, bien sûr, beaucoup des regex proposées ne sont pas "complètes".
Mais tant qu'ils ne rejettent pas les entrées valides, ça devrait aller, n'est-ce pas ?

Ce n'est pas grave s'ils autorisent une entrée non valide, n'est-ce pas ?
Puisque nous pourrions avoir un "vrai" analyseur pendant l'exécution, gérer la validation complète.
Et une vérification au moment de la compilation peut éliminer de nombreux problèmes courants pour les utilisateurs en aval, augmentant ainsi la productivité.

Ces exemples qui rejettent les entrées valides devraient être assez faciles à modifier, de sorte qu'ils ne rejettent pas les entrées valides, mais autorisent les entrées invalides.


Types de motif de chaîne et intersections

Quoi qu'il en soit, les types d'intersection sur les types de motif de chaîne seraient super utiles !

Mon exemple .append() pourrait être écrit comme,

append (str : (
  //Must start with forward slash
  & PatternOf</^\//>
  //Must not end with forward slash
  & PatternOf</[^/]$/>
  //Must not have consecutive forward slashes anywhere
  & not PatternOf</\/\//>
  //Must not contain colon
  & PatternOf</^[^:]+$/>
)) : SomeReturnType;

Le not PatternOf</\/\//> pourrait aussi être,
PatternOf</^((([/])(?!\3))|[^/])+$/> mais c'est tellement plus compliqué

Merci @AnyhowStep pour les nombreuses démonstrations. Je voulais te reprocher de me faire autant lire, mais ça s'est avéré très utile !

J'ai souvent du mal à taper mon api interne plein de paramètres de chaîne, et je me retrouve inévitablement avec beaucoup de conditions qui se lancent au moment de l'exécution. Inévitablement, mes consommateurs doivent dupliquer ces vérifications de modèles, car ils ne veulent pas d'exception, ils veulent un moyen spécial de gérer l'échec.

// Today
function createServer(id: string, comment: string) {
  if (id.match(/^[a-z]+-[0-9]+$/)) throw new Error("Server id does not match the format");
  // work
}

// Nicer
function createServer(id: PatternOf</^[a-z]+-[0-9]+$/>, comment: string) {
  // work immediately
}

Dans le monde des chaînes et des modèles, un string générique est à peu près le même que unknown , supprimant beaucoup de sécurité de type en faveur des vérifications d'exécution et causant des inconvénients pour mes développeurs consommateurs.

Pour certains des cas d'utilisation mentionnés, seul un petit sous-ensemble de Regex serait requis, par exemple la correspondance de préfixe.

Potentiellement, cela pourrait être fait avec des fonctionnalités de langage TS plus générales comme Variadic Kinds #5453 et l'inférence de type lors de la diffusion de types littéraux de chaîne.

Spéculation future :

const x: ['a', 'b', 'c'] = [...'abc'] as const;

type T = [...'def']; // ['d', 'e', 'f'];
type Guard<T extends string> =
  [...T] extends [...'https://', ...any[]] ? Promise<any> : never;

declare function secureGET<
  T extends string
>(url: T): Guard<T>;

const x = secureGET('https://a.com');
x.then(...) // okay

const z = secureGET('http://z.com');
z.then(...); // error
type NaturalNumberString<T extends string> =
  [...T] extends ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9')[] ? T : never;

Pour certains des cas d'utilisation mentionnés, seul un petit sous-ensemble de Regex serait requis, par exemple la correspondance de préfixe.

Je maintiens toujours ma proposition , qui offre ceci + quelques autres choses, fondamentalement un très petit sur - ensemble de

Le problème avec les langages sans étoiles est, comme leur nom l'indique, que vous ne pouvez pas utiliser d'étoiles, ce qui rend difficile la validation de choses comme les URL. En outre, la plupart des gens voudront probablement des étoiles et utiliseront simplement un nombre arbitraire de séquences répétées pour les émuler, ce qui rendrait difficile la recherche de sous-ensembles.

Et les performances de la plupart des regex représentables DFA normales ne sont pas si mauvaises, et il est possible de les vérifier pour les sous/super-ensembles.

Cependant, vous pouvez toujours obtenir en quelque sorte * .

const str : PatternOf</ab+c/> | PatternOf</ac/>

@TijmenW Lisez ma proposition d'un peu plus près - il y a des justifications cachées et quelques petites fonctionnalités qui la rendent réellement pratique. Ce n'est pas directement limité à la spécification de grammaires sans étoiles, mais à un petit sur-ensemble étendu avec juste assez pour le rendre pratiquement utile pour mon cas d'utilisation semi-avancé. En particulier, vous pouvez faire starof ('a' | 'b' | ...) pour des caractères individuels et vous pouvez utiliser string comme équivalent à starof UnionOfAllCodePoints (ce qui en fait plus une primitive en théorie).

De plus, vérifier si un langage régulier correspond à un sous-ensemble de ce qu'un autre langage régulier correspond est NP-complet et équivalent au problème général d' starof , pour essayer de réduire la complexité de calcul théorique.

À FAIRE : Aidez-nous en identifiant de vraies fonctions de bibliothèque qui pourraient bénéficier des types RegExp, et l'expression réelle que vous utiliseriez.

Prenez cela avec un grain de sel, car il s'agit d'une toute nouvelle bibliothèque, mais toute bibliothèque comme https://github.com/ostrowr/ts-json-validator serait beaucoup plus utile avec quelque chose comme un type regex.

Le but de la bibliothèque est de générer des paires type Typescript/schéma JSON <T, s> telles que

  1. Tout type que s peut valider est assignable à T
  2. Le moins de types possibles pouvant être attribués à T échouent à la validation lorsqu'ils sont exécutés sur s .

Un type regex améliorerait la rigueur de (2) en permettant au type validé d'être plus strict sur au moins les mots-clés suivants :

  • format
  • patternProperties
  • propertyNames

À FAIRE : Aidez-nous en identifiant de vraies fonctions de bibliothèque qui pourraient bénéficier des types RegExp, et l'expression réelle que vous utiliseriez.

Toutes les bibliothèques d'interface Excel peuvent utiliser la validation de type comme A1 ou A5:B7 .

Clés de propriété / Indexeurs de chaîne Regex

Certaines bibliothèques traitent les objets en fonction des noms de propriété. Par exemple, dans React, nous voulons appliquer des types à tout accessoire dont le nom commence par aria- :

interface IntrinsicElements {
    // ....
    [attributeName: /aria-\w+/]: number | string | boolean;
}

Il s'agit en fait d'un concept orthogonal (nous pourrions ajouter des types Regex sans ajouter de clés de propriété Regex, et vice versa).

Je sais que c'est un peu orthogonal à tout ce qui se passe ici, mais Wesley a pensé que vous pourriez utiliser notre contribution. Cela revient sans cesse dans Fabric pour plusieurs raisons. En tant que bibliothèque de composants, nous voulons pouvoir élever une interface d'accessoires de composant qui reflète avec précision l'interface de composant React autorisée par TypeScript, y compris les attributs data- et aria- . Sans cela, nous ne pouvons pas élever des interfaces précises à nos consommateurs à utiliser pour ces attributs. Cela devient un problème plus important avec la prochaine version de Fabric où nous examinons des implémentations enfichables telles que des emplacements et devons définir et autoriser ces attributs sur des interfaces arbitraires.

S'il y a quelque chose que nous pouvons faire pour vous aider, faites-le moi savoir! ??

Aire de jeux TS :

import * as React from 'react';

// Want to reflect the same aria- and data- attributes here that JSX compiler allows in this interface:
interface TestComponentProps {
    someProp?: number;
}

const TestComponent: React.FunctionComponent<TestComponentProps> = () => {
    return null;
}

const ConsumerComponent: React.FunctionComponent = () => {
    // The React component interface allows for 'data-' and 'aria-' attributes, but we don't have any typesafe way of
    // elevating that interface or instantiating props objects that allow the same attributes. We just want to be able to 
    // define component interfaces that match what the React component interface allows without opening it up to 'any' and 
    // giving up all type safety on that interface.
    const testComponentProps: TestComponentProps = {
        someProp: 42,
        'data-attribute-allowed': 'test'
    };

    return (
        <TestComponent
            someProp={42}
            // 'data-' and 'aria-' attributes are only allowed here:
            data-attribute-allowed={'data-value'}
            aria-attribute-allowed={'aria-value'}
            {...testComponentProps}
        />
    )
}

À FAIRE : Aidez-nous en identifiant de vraies fonctions de bibliothèque qui pourraient bénéficier des types RegExp, et l'expression réelle que vous utiliseriez

Cron emplois. (très surpris que cela ne soit pas mentionné)

^((\*|\d+((\/|\-|,){0,1}(\d+))*)\s*){6}$

Je donne juste mes deux cents ici - je travaille sur un projet React où nous aimerions valider un accessoire qui sera utilisé comme attribut HTML id . Cela signifie qu'il doit respecter les règles suivantes, sinon un comportement inattendu se produira :

  1. Avoir au moins un personnage
  2. Ne pas avoir d'espaces

En d'autres termes:

interface Props {
  id: PatternOf</[^ ]+/>;
}

Autre exemple : sanctuary-type-identifiers avec des chaînes attendues au format '<namespace>/<name>[@<version>]'

Cas d'utilisation : API DOM de type chaîne comme Navigator.registerProtocolHandler() .

Citant MDN :

Pour des raisons de sécurité, registerProtocolHandler() limite les schémas pouvant être enregistrés.

Un schéma personnalisé peut être enregistré tant que :

  • Le nom du schéma personnalisé commence par web+
  • Le nom du schéma personnalisé comprend au moins 1 lettre après le préfixe web+
  • Le schéma personnalisé n'a que des lettres ASCII minuscules dans son nom.

En d'autres termes, Navigator.registerProtocolHandler() attend soit un string bien connu, soit un string mais uniquement s'il est conforme à un schéma spécifique.

CSS Custom Properties for CSSType est un autre cas d'utilisation pour fournir des types fermés pour toutes les propriétés, à l'exception de celles préfixées par -- .

interface Properties {
    // ....
    [customProperty: /--[a-z][^\s]*/]: number | string;
}`

Connexe https://github.com/frenic/csstype/issues/63

Quelqu'un peut-il me dire si c'est la même chose que les types de raffinement? https://github.com/microsoft/TypeScript/issues/7599

@ gautam1168 C'est théoriquement juste un sous-ensemble, où il affine spécifiquement les types de chaînes. (Les types numériques ont leurs propres préoccupations, bien sûr.)

Pour certains des cas d'utilisation mentionnés, seul un petit sous-ensemble de Regex serait requis, par exemple la correspondance de préfixe.

Je maintiens toujours ma proposition , qui offre ceci + quelques autres choses, fondamentalement un très petit sur - ensemble de

Dans ce commentaire,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment-243338433

Quelqu'un lié à,
https://bora.uib.no/handle/1956/3956

Intitulé "Le problème d'inclusion pour les expressions régulières"


Cependant,

  • Si l'expression de droite est 1-non ambiguë, l'algorithme donne la bonne réponse.
  • Sinon, il peut donner la bonne réponse, ou pas de réponse.

https://www.sciencedirect.com/science/article/pii/S0022000011001486

(Bien sûr, les expressions régulières JS ne sont pas régulières)

@AnyhowStep Cela pourrait fonctionner - je starof et modifierais cette restriction en conséquence. Je voudrais une meilleure façon de caractériser la restriction, puisque le calcul est un peu abstrait et on ne sait pas comment il s'appliquerait concrètement dans la pratique (pas tout le monde qui utiliserait ces types sont bien versés dans des langues officielles).

De plus, séparément, j'aimerais vraiment une meilleure alternative à starof en tant qu'opérateur pour modéliser ce genre de chose.

Je suis curieux : est-il possible de décider de l'inclusion/du confinement des expressions régulières ? Selon do wikipedia , c'est décidable. Cependant, cela prend-il également en compte les expressions régulières dans JS ? Je pense qu'ils ont plus de fonctionnalités que les RE standard (par exemple, les références arrière). Si c'est décidable, est-ce que c'est faisable sur le plan informatique ?
Cela affecterait cette fonctionnalité (rétrécissement) :

if (Gmail.test(candidate)) {
    // candidate is also an Email
}

@nikeee Décidable ne suffit pas pour que cela soit réaliste. Même le temps quadratique est généralement trop lent à cette échelle. Pas TS, mais j'ai quelques connaissances sur des problèmes similaires.

Face aux références arrière, je soupçonne que c'est toujours décidable, mais probablement exponentiel sinon pire. Juste une supposition éclairée, cependant.

Merci d'avoir clarifié ça !

Même le temps quadratique est généralement trop lent à cette échelle.

C'est pourquoi j'ai également demandé si c'était faisable sur le plan informatique, donc je suppose que ce n'est pas le cas.

Si la même chose s'applique à l'égalité, cela ne signifie-t-il pas que presque toutes les propriétés de cette caractéristique sont infaisables ? Corrigez-moi si je me trompe, mais il semble que la seule chose qui reste soit l'adhésion. Je ne pense pas que cela seul serait utile.

@nikeee Il convient de garder à l'esprit que ce modèle sera vérifié par rapport à chaque propriété de chaque type auquel ne devez calculer si une expression rationnelle correspond à un sous - ensemble des autres matches, regexp une bête plutôt compliquée en elle - même.

Ce n'est pas impossible, juste difficile , et il faut être restrictif si on veut que ce soit faisable. (D'une part, les expressions régulières JS ne fonctionneraient pas - elles ne sont pas seulement pas assez extensibles, mais aussi trop flexibles.)

Edit : Je tiens à réitérer ceci : je ne fais

Hmm, alors peut-être que vous ne pouvez prendre en charge qu'un sous-ensemble "limité" de RegEx "habituel". Compte tenu des cas d'utilisation, les regex étaient assez simples jusqu'à présent… (couleurs, numéros de téléphone, etc.)

Comment pourrions-nous concevoir l'UX de ne prendre en charge qu'un sous-ensemble ? Il n'est peut-être pas très clair pour l'utilisateur que la fonctionnalité X de RegEx fonctionne, mais pas Y.

eh bien … ne l'appelez pas "regex" - pour commencer. Peut-être juste "pattern matching" ou alors :see_no_ evil:. Mais oui, ce n'est probablement pas une tâche facile…

Qu'en est-il d'une syntaxe non-regex comme celle-ci :

type TLD = 'com' | 'net' | 'org';
type Domain = `${string}.${TLD}`;
type URL = `${'http'|'https'}://${Domain}`;

const good: URL = 'https://google.com'; // ✔️
const bad: URL = 'ftp://example.com'; // ✖️ TypeError: 'ftp' is not assignable to type 'http' | 'https'

Dans mon esprit, cela s'intégrerait assez bien dans le système de types. Vous pouvez ajouter une syntaxe différente pour des choses comme les correspondances facultatives :

type SubDomain = `${string}.`;
type Domain = `${SubDomain}?${string}.${TLD}`;

Ajoutez la prise en charge des quantificateurs, de l'opérateur gourmand et vous obtenez quelque chose d'assez robuste, je pense que ce serait probablement suffisant pour la majorité des cas d'utilisation pour lesquels les développeurs pourraient vouloir l'utiliser.

Je pense que cette approche serait plus conviviale. Cependant, elle semble être équivalente aux opérations arithmétiques sur les types.
Selon https://github.com/microsoft/TypeScript/issues/15645#issuecomment -299917814 et https://github.com/microsoft/TypeScript/issues/15794#issuecomment -301170109, c'est une décision de conception à ne pas faire arithmétique sur les types.
Si je ne me trompe pas, cette approche peut facilement créer un type énorme. Envisager:

type TLD = 'com' | 'net' | 'org' | 'ly' | 'a' | 'b' | 'c' | 'd';
type Foo = `${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}`;
type Bar = `${Foo}${Foo}${Foo}${Foo}${Foo}`

(cela suppose que l'implémentation utiliserait des types d'union. Cela peut fonctionner avec une implémentation différente / plus complexe)

Avis de non-responsabilité : je ne fais pas partie de l'équipe TS et je ne travaille pas sur TS. Juste mon 2c.

@rozzzly @nikeee C'est plus ou moins l'essence de ma proposition , juste avec quelques petites fonctionnalités manquantes. J'ai basé le mien sur un grand sous-ensemble de langages réguliers (le concept de langage formel), et non sur des expressions régulières au sens des littéraux d'expression régulière et autres, donc c'est beaucoup moins puissant que ceux-là mais assez puissant pour faire le travail.

Je pense que cette approche serait plus conviviale. Cependant, elle semble être équivalente aux opérations arithmétiques sur les types.

Les mathématiques disent que valider si un type est un sous-type d'un autre est équivalent en termes de calcul à vérifier si une chaîne est contenue dans un langage formel donné.

La validation de domaine en particulier est en fait une chose assez compliquée à faire si vous vérifiez également la validité du TLD/ /[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+/ + contenant au plus 255 caractères, mais même cela est très compliqué à saisir à moins que vous n'utilisiez des grammaires régulières complètes comme le montre l'expression rationnelle ci-dessus. Vous pouvez générer le type par programme assez simplement (je le laisserai comme exercice pour le lecteur) en utilisant uniquement des chaînes de @rozzzly ou de ma proposition, mais le résultat final est encore assez compliqué.

@isiahmeadows

C'est plus ou moins l'essence de ma proposition , juste avec quelques petites fonctionnalités manquantes.

La dernière fois que j'ai lu tout ce fil, c'était il y a plus d'un an. J'étais en pause et j'ai vu une notification, j'ai lu le commentaire de Eh bien… ne l'appelez pas "regex" - pour commencer"_ ce qui m'a fait réfléchir... Je n'avais pas réalisé que quelqu'un l'avait déjà fait a présenté une proposition considérablement plus détaillée pour essentiellement la même _(/une idée très similaire)_.

... même cela est très compliqué à taper à moins que vous n'utilisiez des grammaires régulières complètes, comme le montre l'expression rationnelle ci-dessus. Vous pouvez générer le type par programme assez simplement (je le laisserai comme exercice pour le lecteur) en utilisant uniquement des chaînes de @rozzzly ou de ma proposition, mais le résultat final est encore assez compliqué.

Dans mon esprit, une certaine facilité de correspondance de motifs limitée telle que j'ai suggéré d'autoriser serait extrêmement utile pour un typage très simpliste et _nécessairement pas rigoureusement strict_. L'exemple que j'ai donné est loin d'être précis et ne ferait pas exploser le compilateur.

Mais comme @nikeee et vous le soulignez tous les deux, cela pourrait être @types/some-popular-project contenant :

type MixedCaseAlphaNumeric = (
    | 'a'
    | 'b'
    | 'c'
    // and so on
);

type StrWithLengthBeteen1And64<Charset extends string> = (
    | `${Charset}`
    | `${Charset}|${Charset}`
    | `${Charset}|${Charset}|${Charset}`
    // and so on
);

function updatePassword(userID: number, password: StrWithLengthBetween1And64<MixedCaseAlphaNumeric>): void {
    // ...
}

Pour mettre cela en perspective, cette union consiste en types distincts qui sont plus que des atomes dans l'univers observable .

Maintenant, j'ai vu des erreurs d'assignabilité terriblement longues, mais imaginez (l'erreur non tronquée) pour cela ....

Type '"😢"' is not assignable to type '"a"|"b"|"c"..........."'.ts(2322)'

Alors oui.. il y a des problèmes là-bas

@rozzzly Qu'est-ce qui rend ce type différent (en termes de faisabilité) d'un TupleWithLengthBeteen1And64<Charset> ?
Le compilateur n'est pas obligé d'étendre chaque type à une forme normalisée, il exploserait rapidement sur des types assez normaux s'il le faisait.
Je ne dis pas que je pense que ce problème a du sens en tapuscrit pour le moment, si même "un nombre entier entre 3 et 1024" (pensez aux longueurs d'allocation de tampon de message) est considéré comme hors de portée.

@simonbuchan Au moins les types de préfixe et de suffixe doivent exister, sinon rien d'autre. Cela est lui-même requis pour de nombreuses bibliothèques et frameworks DOM.

Je sais que cela a été battu à mort et que de bonnes propositions ont déjà été faites. Mais je voulais juste ajouter des trucs supplémentaires que certains pourraient trouver légèrement intéressants.

Face aux références arrière, je soupçonne que c'est toujours décidable, mais probablement exponentiel sinon pire. Juste une supposition éclairée, cependant.

Les références arrière peuvent faire en sorte qu'une expression rationnelle décrive une grammaire contextuelle, un sur-ensemble de grammaires sans contexte. Et l'égalité linguistique pour les CFG est indécidable. C'est donc encore pire pour les CSG, qui sont équivalents à des automates bornés linéairement.


En supposant que toutes les expressions régulières pouvant être converties en DFA soient utilisées dans une expression rationnelle (concat, union, étoile, intersection, complément, etc.), la conversion d'une expression rationnelle en NFA est O(n), obtenant le produit de deux NFAs est O(m*n), alors la traversée du graphe résultant pour les états acceptés est O(m*n). Ainsi, vérifier l'égalité du langage/le sous-ensemble de deux expressions rationnelles régulières est également O(m*n).

Le problème est que l'alphabet est vraiment grand ici. Les manuels scolaires se limitent généralement aux alphabets de taille 1 à 5, lorsqu'ils parlent d'expressions DFA/NFA/régulières. Mais avec les expressions rationnelles JS, nous avons tout l'unicode comme alphabet. Certes, il peut exister des moyens efficaces de représenter les fonctions de transition à l'aide de tableaux clairsemés et d'autres astuces et optimisations intelligentes pour les tests d'égalité/sous-ensembles...

Je suis convaincu qu'il est possible d'effectuer une vérification de type pour une affectation régulière à régulière de manière assez efficace.

Ensuite, toutes les affectations non régulières peuvent simplement nécessiter des assertions de type explicites.

J'ai récemment travaillé sur un petit projet d'automate fini, donc l'info est encore fraîche dans ma tête =x

Si je ne me trompe pas, cette approche peut facilement créer un type énorme. Envisager:

type TLD = 'com' | 'net' | 'org' | 'ly' | 'a' | 'b' | 'c' | 'd';
type Foo = `${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}${TLD}`;
type Bar = `${Foo}${Foo}${Foo}${Foo}${Foo}`

(cela suppose que l'implémentation utiliserait des types d'union. Cela peut fonctionner avec une implémentation différente / plus complexe)

Curieusement, c'est exactement ce qui est possible avec les nouveaux types de littéraux de chaîne de modèle. Ce cas est évité en ayant un seuil pour les types d'union, semble-t-il.

Les références arrière

Edit : précision

J'ai confirmé que ce commentaire de @rozzzly fonctionne avec TS 4.1.0 tous les soirs !

type TLD = 'com' | 'net' | 'org';
type Domain = `${string}.${TLD}`;
type Url = `${'http'|'https'}://${Domain}`;

const success: Url = 'https://example.com';
const fail: Url = 'example.com';
const domain: Domain = 'example.com';

Essayez-le dans la cour de récréation et voyez que fail a une erreur de compilation 🤩


Mise à jour : après avoir joué un peu avec cette fonctionnalité, elle ne couvrira pas beaucoup de cas d'utilisation. Par exemple, cela ne fonctionne pas pour une chaîne de couleur hexadécimale.

type HexChar = '0' | '1' | '2' | '3' | '4' | '5' | '6'| '7' | '8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
type HexColor = `#${HexChar}${HexChar}${HexChar}${HexChar}${HexChar}${HexChar}`;
let color: HexColor = '#123456';

Aujourd'hui, cela échoue avec "L'expression produit un type d'union trop complexe à représenter. (2590)"

J'ai confirmé que ce commentaire de @rozzzly fonctionne avec TS 4.1.0 tous les soirs !

type TLD = 'com' | 'net' | 'org';
type Domain = `${string}.${TLD}`;
type Url = `${'http'|'https'}://${Domain}`;

const success: Url = 'https://example.com';
const fail: Url = 'example.com';
const domain: Domain = 'example.com';

Essayez-le dans la cour de récréation et voyez que fail a une erreur de compilation 🤩

Cela résoudrait le problème de données ou d'air auquel la plupart d'entre nous sont confrontés dans les bibliothèques UX s'il peut être appliqué aux index.

En gros, mais cela ne fonctionne évidemment pas car TS n'autorise que les chaînes | numéro. Puisqu'il s'agit essentiellement d'une chaîne, peut-elle être activée ?
https://www.typescriptlang.org/play?target=99&ts=4.1.0-dev.20201001#code/LAKALgngDgpgBAEQIZicgzgCzgXjgAwBIBvAcgBMUkBaUgXxPTACcBLAOwHM78BuUDmBjMAZkgDG8AJIAVGEzjFQcFXADalVBkwAFZgHsoALmRakWALpGmbLvxB1QocfvYL0AV3GT06I3Fl5MFxFCipqISZSI1JIsHpeIA

_Mise à jour_ : après avoir joué un peu avec cette fonctionnalité, elle ne couvrira pas beaucoup de cas d'utilisation. Par exemple, cela ne fonctionne pas pour une chaîne de couleur hexadécimale.

type HexChar = '0' | '1' | '2' | '3' | '4' | '5' | '6'| '7' | '8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F';
type HexColor = `#${HexChar}${HexChar}${HexChar}${HexChar}${HexChar}${HexChar}`;
let color: HexColor = '#123456';

Aujourd'hui, cela échoue avec "L'expression produit un type d'union trop complexe à représenter. (2590)"

Il y avait une référence à cette limitation dans les notes de version. Il crée une liste de toutes les combinaisons valides possibles, dans ce cas, il créerait une union avec 16 777 216 membres (c'est-à-dire 16^6).

C'est une excellente idée... Igmat a fait des articles incroyables en 2016 qui ont l'air bien sur le papier de toute façon.

J'ai trouvé cela parce que je voulais m'assurer que les clés d'un littéral d'objet passé dans ma fonction étaient des noms de classe CSS valides. Je peux facilement vérifier au moment de l'exécution... mais il me semble tellement évident que le script devrait pouvoir le faire au moment de la compilation, en particulier dans les situations où je ne fais que coder en dur des objets littéraux et où le script ne devrait pas avoir à déterminer si MonUnionÉtenduExotiquetype satisfait SomeArbitraryRegexType.

Peut-être qu'un jour je serai suffisamment informé pour apporter une contribution plus productive :/

J'ai confirmé que ce commentaire de @rozzzly fonctionne avec TS 4.1.0 tous les soirs !

Wow. Honnêtement, je ne m'attendais pas à ce que cela soit mis en œuvre, du moins pas de sitôt.

@chadlavi-casebook

Il y avait une référence à cette limitation dans les notes de version. Il crée une liste de toutes les combinaisons valides possibles, dans ce cas, il créerait une union avec 16 777 216 membres (c'est-à-dire 16^6).

Je serais curieux de voir à quel point ce syndicat pourrait devenir grand avant qu'il ne devienne un problème de performance. L'exemple de @styfle montre à quel point il est facile d'atteindre ce plafond. Il y aura évidemment un certain degré de rendements décroissants de l'utilité des types complexes par rapport aux performances.

@thehappycheese

Je voulais m'assurer que les clés d'un littéral d'objet passé dans ma fonction étaient des noms de classe CSS valides

Je suis assez confiant pour dire que ce n'est pas possible avec la mise en œuvre actuelle. S'il existait une prise en charge des quantificateurs et des plages, vous obtiendriez probablement la validation des noms de classe de style BEM. L'expression régulière js standard pour cela n'est pas _trop_ terrible :
^\.[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$
Vous abandonneriez également les ancres car, dans l'état actuel de l'implémentation, il s'agit soit d'une correspondance de bout en bout, soit de rien, donc ^ et $ sont implicites. C'est maintenant une expression régulière relativement simple pour un sous-ensemble étroit de ce qui est un sélecteur CSS valide. Par exemple : ಠ_ಠ est un nom de classe valide. Je ne plaisante pas.

Je suis désolé. Je devais faire ça.

J'ai implémenté des langages réguliers dans TypeScript.

Plus précisément, j'ai implémenté un automate fini déterministe simple en utilisant TS 4.1

Je veux dire, nous pouvons déjà implémenter des machines de Turing dans TS. Ainsi, les DFA et les PDA sont "faciles", comparés à cela.

Et les chaînes de modèle rendent cela plus utilisable.


Les types de noyaux sont en fait simples et s'adaptent à < 30 LOC,

type Head<StrT extends string> = StrT extends `${infer HeadT}${string}` ? HeadT : never;

type Tail<StrT extends string> = StrT extends `${string}${infer TailT}` ? TailT : never;

interface Dfa {
    startState : string,
    acceptStates : string,
    transitions : Record<string, Record<string, string>>,
}

type AcceptsImpl<
    DfaT extends Dfa,
    StateT extends string,
    InputT extends string
> =
    InputT extends "" ?
    (StateT extends DfaT["acceptStates"] ? true : false) :
    AcceptsImpl<
        DfaT,
        DfaT["transitions"][StateT][Head<InputT>],
        Tail<InputT>
    >;

type Accepts<DfaT extends Dfa, InputT extends string> = AcceptsImpl<DfaT, DfaT["startState"], InputT>;

C'est la spécification des automates qui est la partie difficile.

Mais je suis presque sûr que quelqu'un peut créer une expression régulière vers le générateur TypeScript DFA™ ...


Je voudrais également souligner que l'exemple "chaîne hexadécimale de longueur 6" montre que vous pouvez faire en sorte que les paramètres de fonction n'acceptent que les chaînes correspondant à l'expression régulière à l'aide de ugly hackery,

declare function takesOnlyHex<StrT extends string> (
    hexString : Accepts<HexStringLen6, StrT> extends true ? StrT : {__err : `${StrT} is not a hex-string of length 6`}
) : void;

//OK
takesOnlyHex("DEADBE")

//Error: Argument of type 'string' is not assignable to parameter of type '{ __err: "DEADBEEF is not a hex-string of length 6"; }'.
takesOnlyHex("DEADBEEF")

//OK
takesOnlyHex("01A34B")

//Error: Argument of type 'string' is not assignable to parameter of type '{ __err: "01AZ4B is not a hex-string of length 6"; }'.
takesOnlyHex("01AZ4B")

Voici un terrain de jeu bonus ; il implémente la regex /^hello .*/

Et une autre aire de jeux ; il implémente la regex / world$/

Un dernier exemple, Playground ; c'est une expression régulière de chaîne à virgule flottante !

@AnyhowStep Eh bien, j'ai utilisé votre idée DFA pour implémenter une regex simple [abc]{4} qui signifie les lettres abc dans n'importe quel ordre avec manquant mais exactement la longueur de 4. (aaaa, abcc, bbcc, etc...).
Terrain de jeux

https://cyberzhg.github.io/toolbox/min_dfa?regex=ZCgoYmQqYiopKmMpKg==

https://github.com/CyberZHG/toolbox

Si j'avais plus de volonté, je prendrais quelque chose comme ci-dessus et l'utiliserais pour transformer les expressions régulières en TS DFA ™ lol

OK, je viens de créer un prototype,

https://glitch.com/~sassy-valiant-heath

[Modifier] https://glitch.com/~efficacious-valley-repair <-- Cela produit une bien meilleure sortie pour les expressions régulières plus compliquées

[Modifier] Il semble que Glitch archivera les projets gratuits qui sont inactifs trop longtemps. Alors, voici un dépôt git avec les fichiers,
https://github.com/AnyhowStep/efficacious-valley-repair/tree/main/app

Étape 1, saisissez votre regex ici,
image

Étape 2, cliquez sur convertir,
image

Étape 3, cliquez sur l'URL du terrain de jeu TS généré,
image

Étape 4, faites défiler jusqu'à InLanguage_0 ,
image

Étape 5, jouez avec les valeurs d'entrée,
image

image

Bravo à https://www.npmjs.com/package/regex2dfa , pour avoir fait le gros du travail de la conversion

Au cas où quelqu'un aurait besoin de quelque chose d'un peu plus puissant, voici une machine de Turing 😆

Terrain de jeux

Ce fil est devenu trop long à lire et de nombreux commentaires sont soit adressés par des types littéraux de modèle, soit hors sujet. J'ai créé un nouveau numéro 41160 pour discuter des cas d'utilisation restants qui pourraient être activés par cette fonctionnalité. N'hésitez pas à continuer à discuter des analyseurs de système de type ici 😀

Cette page vous a été utile?
0 / 5 - 0 notes