Typescript: Vorschlag: Regex-validierter String-Typ

Erstellt am 22. Jan. 2016  ·  146Kommentare  ·  Quelle: microsoft/TypeScript

Es gibt Fälle, in denen eine Eigenschaft nicht nur eine beliebige Zeichenfolge (oder ein Satz von Zeichenfolgen) sein kann, sondern einem Muster entsprechen muss.

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

In JavaScript ist es gängige Praxis, Farbwerte in CSS-Notation zu speichern, z. B. in der CSS-Stilreflexion von DOM-Knoten oder verschiedenen Bibliotheken von Drittanbietern.

Was denken Sie?

Literal Types Needs Proposal Suggestion

Hilfreichster Kommentar

Designvorschlag

Es gibt viele Fälle , in denen Entwickler mehr angegebenen Wert benötigen dann nur eine Zeichenfolge, aber kann sie nicht als Vereinigung von einfachem Stringliterale zB CSS Farben, E - Mails, Telefonnummern, ZipCode, aufzuzählen Prahlerei Erweiterungen etc. Auch json Schemaspezifikation , die häufig verwendet zum Beschreiben des Schemas von JSON-Objekten hat pattern und patternProperties , die in Bezug auf das TS-Typsystem regex-validated string type und regex-validated string type of index genannt werden könnten.

Ziele

Bieten Sie Entwicklern ein Typsystem, das dem von ihnen häufig verwendeten JSON-Schema einen Schritt näher kommt, und verhindern Sie, dass sie bei Bedarf die Überprüfung der Zeichenfolgenvalidierung vergessen.

Syntaktische Übersicht

Die Implementierung dieser Funktion besteht aus 4 Teilen:

Regex-validierter Typ

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;

Regex-validierter Variablentyp

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

und das gleiche, aber besser lesbar

let fontColor: CssColor;

Regex-validierter variabler Indextyp

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;
}

und das gleiche, aber besser lesbar

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

Typschutz für Variablentyp

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

und das gleiche

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

und Verwendung eines definierten Typs für eine bessere Lesbarkeit

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

gleich wie

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

Typenschutz für Indextyp

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
    }
}

gleich wie

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
}

und Verwendung eines definierten Typs für eine bessere Lesbarkeit

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

gleich wie

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

Semantische Übersicht

Zuordnungen

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

Leider können wir aufgrund dieses Artikels nicht überprüfen, ob ein Regex ein Untertyp eines anderen ist, ohne dass die Leistung stark beeinträchtigt wird. Es sollte also eingeschränkt werden. Aber es gibt folgende Workarounds:

// 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

Leider sollte die Zuweisung von string Variablen zu regex-validated Variablen ebenfalls eingeschränkt werden, da es in der Kompilierzeit keine Garantie dafür gibt, dass sie mit Regex übereinstimmt.

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

Aber wir sind in der Lage, explizite Cast- oder Typeguards zu verwenden, wie hier gezeigt. Zweite wird empfohlen.
Glücklicherweise ist dies kein Fall für String-Literale, denn während wir sie verwenden, können wir überprüfen, ob ihr Wert mit Regex übereinstimmt.

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

Typeinschränkung für Indizes

Für einfache Fälle von regex-validated type des Indexes siehe Typ gurard für den Indextyp .
Aber es könnte kompliziertere Fälle geben:

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

Literale haben kein solches Problem:

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

Aber für Variablen ist die beste Option die Verwendung von Typwächtern wie in den nächsten realistischeren Beispielen:

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
    }
}

Aber wenn wir eine bessere Definition für den Typ Gmail verwenden, würde es eine andere Typeinschränkung geben:

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
    }
}

Gewerkschaften und Kreuzungen

Tatsächlich sind gängige Typen und regex-validated Typen wirklich unterschiedlich, daher brauchen wir Regeln, wie ihre Vereinigungen und Schnittmengen korrekt behandelt werden.

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
}

Generika

Es gibt keine Sonderfälle für Generika, daher könnte der Typ regex-validated mit Generika auf die gleiche Weise wie übliche Typen verwendet werden.
Für Generika mit Einschränkungen wie unten verhält sich der Typ regex-validated wie ein String:

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

Übersicht ausgeben

Im Gegensatz zu den üblichen Typen haben regex-validated einen gewissen Einfluss auf die Emission:

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 {
    /* ... */
}

wird kompilieren zu:

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 {
    /* ... */
}

Kompatibilitätsübersicht

Diese Funktion hat keine Kompatibilitätsprobleme, da es nur einen Fall gibt, der sie beschädigen könnte und der damit zusammenhängt, dass der Typ regex-validated Gegensatz zum üblichen Typ Auswirkungen hat. Dies ist also gültiger TS-Code:

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

wenn der folgende Code nicht ist:

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

Aber das zweite WAR bereits ungültig, aber aus einem anderen Grund (Typdeklaration war falsch).
Also müssen wir jetzt die Deklaration von Variablen mit gleichem Namen auf Typ beschränken, falls dieser Typ regex-validated .

PS

Fühlen Sie sich frei, auf Dinge hinzuweisen, die ich wahrscheinlich übersehen habe. Wenn Ihnen dieser Vorschlag gefällt, könnte ich versuchen, Tests zu erstellen, die ihn abdecken, und sie als PR hinzufügen.

Alle 146 Kommentare

Ja, ich habe gesehen, wie das DefinitelyTyped durchkämmt, . Sogar wir könnten so etwas mit ScriptElementKind in der Diensteschicht verwenden , wo wir diese idealerweise als eine durch Kommas getrennte Liste bestimmter Zeichenfolgen beschreiben könnten.

Die Hauptprobleme sind:

  • Es ist nicht klar, wie man diese gut komponiert. Wenn ich eine durch Kommas getrennte Liste von "cat" , "dog" und "fish" möchte, muss ich etwas wie /dog|cat|fish(,(dog|cat|fish))*/ schreiben.

    • Wenn ich bereits Typen habe, die String-Literaltypen für "cat" , "dog " und "fish" , wie integriere ich sie in diese Regex?

    • Offensichtlich gibt es hier Wiederholungen, die unerwünscht sind. Vielleicht würde die Behebung des vorherigen Problems dies einfacher machen.

  • Nicht standardmäßige Erweiterungen machen diese Art fragwürdig.

Riesiges +1 dafür, ZipCode, SSN, ONet, viele andere Anwendungsfälle dafür.

Ich hatte das gleiche Problem und sehe, dass es noch nicht implementiert ist. Vielleicht ist diese Problemumgehung hilfreich:
http://stackoverflow.com/questions/37144672/guid-uuid-type-in-typescript

Wie @mhegazy vorgeschlagen habe, werde ich meinen Vorschlag (#8665) hier platzieren. Wie wäre es mit einfachen Validierungsfunktionen in Typdeklarationen? Sowas in der Art:

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

Der Wert, den der Typ akzeptieren kann, würde durch den Funktionsparametertyp und durch die Funktionsauswertung selbst bestimmt. Das würde auch #7982 lösen.

@rylphs +1 das würde TypeScript extrem mächtig machen

Wie funktioniert die Subtyping mit _regex-validierten String-Typen_?

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?

wobei RegExType_1 und RegExType_2 _regex-validierte Zeichenfolgentypen_ sind.

Bearbeiten: Es sieht so aus, als ob dieses Problem in polynomieller Zeit lösbar ist (siehe Das Einschlussproblem für reguläre Ausdrücke ).

Würde auch bei TypeStyle helfen: https://github.com/typestyle/typestyle/issues/5 :rose:

In JSX haben @RyanCavanaugh und ich gesehen, wie Leute aria- (und möglicherweise data- ) Attribute hinzugefügt haben. Jemand hat in DefinitelyTyped tatsächlich eine Zeichenfolgenindexsignatur als Catch-All hinzugefügt. Eine neue Indexsignatur dafür wäre hilfreich gewesen.

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

Designvorschlag

Es gibt viele Fälle , in denen Entwickler mehr angegebenen Wert benötigen dann nur eine Zeichenfolge, aber kann sie nicht als Vereinigung von einfachem Stringliterale zB CSS Farben, E - Mails, Telefonnummern, ZipCode, aufzuzählen Prahlerei Erweiterungen etc. Auch json Schemaspezifikation , die häufig verwendet zum Beschreiben des Schemas von JSON-Objekten hat pattern und patternProperties , die in Bezug auf das TS-Typsystem regex-validated string type und regex-validated string type of index genannt werden könnten.

Ziele

Bieten Sie Entwicklern ein Typsystem, das dem von ihnen häufig verwendeten JSON-Schema einen Schritt näher kommt, und verhindern Sie, dass sie bei Bedarf die Überprüfung der Zeichenfolgenvalidierung vergessen.

Syntaktische Übersicht

Die Implementierung dieser Funktion besteht aus 4 Teilen:

Regex-validierter Typ

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;

Regex-validierter Variablentyp

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

und das gleiche, aber besser lesbar

let fontColor: CssColor;

Regex-validierter variabler Indextyp

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;
}

und das gleiche, aber besser lesbar

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

Typschutz für Variablentyp

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

und das gleiche

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

und Verwendung eines definierten Typs für eine bessere Lesbarkeit

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

gleich wie

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

Typenschutz für Indextyp

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
    }
}

gleich wie

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
}

und Verwendung eines definierten Typs für eine bessere Lesbarkeit

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

gleich wie

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

Semantische Übersicht

Zuordnungen

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

Leider können wir aufgrund dieses Artikels nicht überprüfen, ob ein Regex ein Untertyp eines anderen ist, ohne dass die Leistung stark beeinträchtigt wird. Es sollte also eingeschränkt werden. Aber es gibt folgende Workarounds:

// 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

Leider sollte die Zuweisung von string Variablen zu regex-validated Variablen ebenfalls eingeschränkt werden, da es in der Kompilierzeit keine Garantie dafür gibt, dass sie mit Regex übereinstimmt.

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

Aber wir sind in der Lage, explizite Cast- oder Typeguards zu verwenden, wie hier gezeigt. Zweite wird empfohlen.
Glücklicherweise ist dies kein Fall für String-Literale, denn während wir sie verwenden, können wir überprüfen, ob ihr Wert mit Regex übereinstimmt.

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

Typeinschränkung für Indizes

Für einfache Fälle von regex-validated type des Indexes siehe Typ gurard für den Indextyp .
Aber es könnte kompliziertere Fälle geben:

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

Literale haben kein solches Problem:

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

Aber für Variablen ist die beste Option die Verwendung von Typwächtern wie in den nächsten realistischeren Beispielen:

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
    }
}

Aber wenn wir eine bessere Definition für den Typ Gmail verwenden, würde es eine andere Typeinschränkung geben:

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
    }
}

Gewerkschaften und Kreuzungen

Tatsächlich sind gängige Typen und regex-validated Typen wirklich unterschiedlich, daher brauchen wir Regeln, wie ihre Vereinigungen und Schnittmengen korrekt behandelt werden.

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
}

Generika

Es gibt keine Sonderfälle für Generika, daher könnte der Typ regex-validated mit Generika auf die gleiche Weise wie übliche Typen verwendet werden.
Für Generika mit Einschränkungen wie unten verhält sich der Typ regex-validated wie ein String:

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

Übersicht ausgeben

Im Gegensatz zu den üblichen Typen haben regex-validated einen gewissen Einfluss auf die Emission:

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 {
    /* ... */
}

wird kompilieren zu:

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 {
    /* ... */
}

Kompatibilitätsübersicht

Diese Funktion hat keine Kompatibilitätsprobleme, da es nur einen Fall gibt, der sie beschädigen könnte und der damit zusammenhängt, dass der Typ regex-validated Gegensatz zum üblichen Typ Auswirkungen hat. Dies ist also gültiger TS-Code:

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

wenn der folgende Code nicht ist:

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

Aber das zweite WAR bereits ungültig, aber aus einem anderen Grund (Typdeklaration war falsch).
Also müssen wir jetzt die Deklaration von Variablen mit gleichem Namen auf Typ beschränken, falls dieser Typ regex-validated .

PS

Fühlen Sie sich frei, auf Dinge hinzuweisen, die ich wahrscheinlich übersehen habe. Wenn Ihnen dieser Vorschlag gefällt, könnte ich versuchen, Tests zu erstellen, die ihn abdecken, und sie als PR hinzufügen.

Ich habe vergessen, auf einige Fälle für Schnittmengen und Vereinigungen von regex-validated Typen hinzuweisen, aber ich habe sie im neuesten Testfall beschrieben. Sollte ich Design proposal aktualisieren, um diese geringfügige Änderung widerzuspiegeln?

@Igmat , Frage zu Ihrem Designvorschlag: Könnten Sie die Emissionsübersicht

@alexanderbird , ja, jeder andere Typ hat keinen Einfluss auf die Emission. Zuerst dachte ich, dass regex-validated auch tun würde, also habe ich angefangen, den Vorschlag zu erstellen und mit der vorgeschlagenen Syntax zu spielen.
Der erste Ansatz war so:

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

und das:

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

Es ist in Ordnung und es müssen keine Änderungen ausgegeben werden, da "#000" in der Kompilierzeit überprüft werden könnte.
Aber wir müssen auch die Eingrenzung von string auf regex-validated handhaben, um es nützlich zu machen. Also habe ich mir das für beide vorherigen Setups überlegt:

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

Es hat also auch keinen Einfluss auf emit und sieht ok aus, außer dass Regex nicht sehr lesbar ist und an allen Stellen kopiert werden muss, so dass dem Benutzer leicht ein Fehler unterlaufen kann. Aber in diesem speziellen Fall scheint es immer noch besser zu sein, als die Funktionsweise von type ändern.
Aber dann wurde mir klar, dass dieses Zeug:

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

ist ein Albtraum. Und das sogar ohne Überschneidungen und Gewerkschaften. Um zu vermeiden, dass solche Dinge passieren, müssen wir type ausgeben, wie im Vorschlag gezeigt, leicht ändern.

@DanielRosenwasser , könnten Sie bitte Feedback zu diesem Vorschlag geben? Und wenn möglich auch für hier referenzierte Tests?
Ich möchte wirklich bei der Implementierung dieser Funktion helfen, aber es erfordert viel Zeit ( tsc ist ein wirklich kompliziertes Projekt und ich muss noch daran arbeiten, zu verstehen, wie es im Inneren funktioniert) und ich weiß es nicht Dieser Vorschlag ist zur Implementierung bereit, oder Sie werden diese auf diese Weise implementierte Funktion aufgrund einer anderen Sprachdesignvision oder aus anderen Gründen ablehnen.

Hey @Igmat , ich denke, es gibt ein paar Dinge, nach denen ich anfangs hätte fragen sollen

Um zu beginnen, verstehe ich immer noch nicht, warum Sie irgendeine Art von Änderung zum Ausgeben benötigen, und ich glaube nicht, dass irgendeine Art von Emission basierend auf Typen akzeptabel wäre. Sehen Sie sich hier unsere Nicht-Ziele an .

Ein weiteres Problem, das ich hätte ansprechen sollen, ist das Problem der regulären Ausdrücke, die Rückverweise verwenden. Mein Verständnis (und meine Erfahrung) ist, dass Rückverweise in einem regulären Ausdruck dazu führen können, dass ein Test in der Zeit exponentiell zu seiner Eingabe ausgeführt wird. Ist das ein Eckfall? Vielleicht, aber das würde ich im Allgemeinen lieber vermeiden. Dies ist besonders wichtig, da in Editor-Szenarien eine Typprüfung an einem Standort möglichst wenig Zeit in Anspruch nehmen sollte.

Ein weiteres Problem besteht darin, dass wir uns entweder auf die Engine verlassen müssen, auf der der TypeScript-Compiler ausgeführt wird, oder eine benutzerdefinierte Engine für reguläre Ausdrücke erstellen müssen, um diese Dinge auszuführen. TC39 wird beispielsweise um ein neues s Flag erweitert, damit . mit Zeilenumbrüchen übereinstimmen kann. Es würde eine Diskrepanz zwischen ESXXXX und älteren Laufzeiten geben, die dies unterstützen.

@igmat - Für @DanielRosenwasser gesagt hat, würde sie wahrscheinlich sowieso nicht genehmigt werden). Du sagtest

Aber wir müssen auch die Einengung von String auf Regex-validierten Typ handhaben, um es nützlich zu machen

Ich denke, dies ist nur der Fall, wenn wir von einem dynamischen String auf einen regex-validierten Typ einschränken wollen. Das wird sehr kompliziert. Auch in diesem einfachen Fall:

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

Wir können nicht sicher sein, ob die Typen übereinstimmen - was ist, wenn die Zahl negativ ist? Und je komplizierter die Regexe werden, desto unordentlicher wird es. Wenn wir das wirklich wollten, erlauben wir vielleicht "Typ-Interpolation: type Baz = /prefix:{number}/ ... aber ich weiß nicht, ob es sich lohnt, dorthin zu gehen.

Stattdessen könnten wir das Ziel nur halbwegs erreichen, wenn wir nur die Zuweisung von String-Literalen zu regex-validierten Typen zulassen würden.

Folgendes berücksichtigen:

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'

Halten Sie das für eine praktikable Alternative?

@DanielRosenwasser , ich habe Design Goals sorgfältig gelesen und wenn ich dich richtig verstehe, ist das Problem die Verletzung von Non-Goals#5.
Aber es erscheint mir nicht als Verstoß, sondern als Syntaxverbesserung. Früher hatten wir zum Beispiel:

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
}

Mit diesem umgesetzten Vorschlag würde es so aussehen:

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
}

Wie Sie sehen, ist der Code fast derselbe - es ist eine gängige einfache Verwendung von Regex. Aber der zweite Fall ist viel ausdrucksvoller und verhindert, dass der Benutzer versehentliche Fehler macht, z.
Zweitens können wir ohne eine solche Typeinschränkung normalerweise keinen Regex-validierten Typ in Indizes verwenden, da solche Indexfelder in den meisten Fällen mit einer Variablen arbeiten, die zur Laufzeit nicht überprüft werden kann, wie dies bei Literalen der Fall wäre .

@alexanderbird , ich schlage nicht vor, diesen Code gültig zu machen oder einige versteckte Überprüfungen sowohl zur Laufzeit als auch zur Kompilierzeit hinzuzufügen.

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

Dieser Code muss aufgrund meines Vorschlags einen Fehler auslösen. Aber dieses:

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

oder dieses:

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

richtig wäre und keinen Einfluss auf den ausgegebenen Code hätte.

Und wie ich im vorherigen Kommentar gezeigt habe, würden Literale selbst für allgemeine Anwendungsfälle definitiv nicht ausreichen, da wir oft mit Stings aus Benutzereingaben oder anderen Quellen arbeiten müssen. Ohne diese Emissionswirkung zu implementieren, müssten Benutzer mit diesem Typ auf folgende Weise arbeiten:

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
}

oder für Kreuzungen:

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
}

Ich glaube nicht, dass es kein guter Weg ist, Benutzer zu zwingen, Code zu duplizieren und explizite Umwandlungen zu verwenden, wenn dies vom Compiler leicht gehandhabt werden könnte. Die Auswirkungen von Emitting sind wirklich sehr gering und vorhersehbar. Ich bin mir sicher, dass dies die Benutzer nicht überraschen oder zu falsch verstandenen Funktionen oder schwer zu lokalisierenden Fehlern führen wird, während diese Funktion ohne Änderungen definitiv implementiert wird.

Zusammenfassend möchte ich sagen, dass der Typ regex-validated sowohl eine bereichsbezogene Variable als auch ein Compilertyp ist.

@DanielRosenwasser und @alexanderbird ok, dafür habe ich noch eine Idee. Was ist mit Syntax wie dieser:

const type Email = /email-regex/;

In diesem Fall muss der Benutzer explizit definieren, dass er/sie dies sowohl als type als const möchte, so dass das tatsächliche Typsystem keine Änderungen ausgibt, es sei denn, es wird mit einem solchen Modifikator verwendet. Aber wenn es damit verwendet wird, sind wir immer noch in der Lage, viele Fehler, Casts und Duplizierung von Code zu vermeiden, indem wir denselben emit hinzufügen wie für:

const Email = /email-regex/;

Dies scheint für diesen Vorschlag noch größer zu sein als nur eine Verbesserung, da dies wahrscheinlich so etwas ermöglichen könnte (Beispiel stammt aus einem Projekt mit Redux ):

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

wird umgewandelt in

export const type SOME_ACTION = 'SOME_ACTION';

Ich habe versucht, einen ähnlichen Vorschlag zu finden, war aber nicht erfolgreich. Wenn es ein Workaround sein könnte und wenn Sie eine solche Idee mögen, kann ich einen Designvorschlag und Tests dafür erstellen.

@DanielRosenwasser , zu Ihrem zweiten Problem - ich glaube nicht, dass dies jemals passieren würde, da der Compiler in meinem Vorschlag Regex nur für Literale ausführt und es nicht so aussieht, als würde jemand so etwas tun:

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

Wie auch immer, wir könnten testen, wie lange Literal sein sollte, um die Echtzeitleistung zu beeinträchtigen, und eine Heuristik erstellen, die den Benutzer warnt, wenn wir es in einigen Editor-Szenarien nicht überprüfen können, während er diesen Umständen ausgesetzt ist, aber wir würden es überprüfen, wenn er die kompiliert Projekt. Oder es könnte einige andere Problemumgehungen geben.

Bei der dritten Frage bin ich mir nicht sicher, ob ich alles richtig verstehe, aber es scheint, dass die Regex-Engine abhängig von target von tsconfig wenn sie unterschiedliche Implementierungen haben. Braucht noch mehr Nachforschungen.

@DanielRosenwasser gibt es irgendwelche Gedanken? 😄 Über den ersten Vorschlag und über den letzten. Vielleicht muss ich mir einen genaueren Überblick über den zweiten machen, oder?

@Igmat Ihr Vorschlag schränkt die Validierung so ein, dass sie nur bei Zeichenfolgentypen nützlich ist. Was denkst du über Vorschlag ? Dies würde eine allgemeinere Validierung für alle primitiven Typen ermöglichen:

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

Ich vermute jedoch, dass es zu viel wäre, diesen Mechanismus über Primitive hinaus auf nicht-primitive Typen auszudehnen.
Ein Punkt, das von @DanielRosenwasser angesprochene

@zspitz es sieht vielversprechend aus, aber meiner Meinung nach könnte es die Compilerleistung zu stark beeinträchtigen, da die Funktion nicht durch Regeln eingeschränkt ist und TS zwingt, einige Ausdrücke zu berechnen, die zu kompliziert sind oder sogar von einigen Ressourcen abhängen, die nicht verfügbar sind in der Kompilierzeit.

@Igmat

weil die Funktion durch keine Regeln eingeschränkt ist

Haben Sie konkrete Beispiele im Kopf? Vielleicht ist es möglich, die Validierungssyntax auf eine "sichere"/Kompilierungszeit-bekannte Teilmenge von Typescript zu beschränken.

Wie wäre es mit benutzerdefinierten Typwächtern, die einen neuen Typ definieren?

// 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 sieht gut aus, aber was ist mit der

Müssen sie unbedingt erweiterbar sein? Gibt es ein TypeScript-Entwurfsprinzip, das dies verlangt? Wenn nicht, hätte ich lieber die nominalen Typen, die @disjukr vorschlägt, als nichts, obwohl sie nicht erweiterbar sind.

Wir bräuchten IMHO etwas ziemlich Kreatives, um die Erweiterbarkeit zu erreichen - wir können nicht feststellen, ob ein Wächter (Funktion) eines beliebigen Typs eine Teilmenge eines anderen Wächters eines beliebigen Typs ist.

Wir könnten eine rudimentäre "Erweiterbarkeit" mit einer Typ-Assertion-Mentalität erreichen (ich sage nicht, dass dies etwas "ziemlich Kreatives" ist - ich sage, hier ist eine Notlösung, bis jemand etwas ziemlich Kreatives findet):

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
}

Was nützlich sein könnte, auch wenn es nicht klingt. Aber, wie eingangs gesagt, verlangen wir, dass es erweiterbar ist oder können wir es wie von @disjukr vorgeschlagen implementieren? (Wenn letzteres gilt, schlage ich vor, dass wir es auf die nicht erweiterbare Weise von @disjukr implementieren.)

Irgendwie offtopic, antworte auf @DanielRosenwasser ersten Kommentar:
Für eine durch Kommas getrennte Liste müssen Sie die Anker ^ und $ (dies ist in den meisten Fällen relevant, wenn Sie einen String validieren möchten). Und Anker helfen dabei, Wiederholungen zu vermeiden, in Ihrem Beispiel lautet der Regexp /^((dog|cat|fish)(,|$))+$/

Erlaube Stringtypen als reguläre Ausdrücke /#[0-9]{6}/ und lasse Verschachtelungstypen in reguläre Ausdrücke ${TColor} :

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

Ergebnis:

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

Anwendungsfall: Es gibt eine Bibliothek zum Schreiben von "typsicheren" CSS-Stilen in TypeScript typestyle . Die oben vorgeschlagene Funktionalität würde sehr hilfreich sein, da die Bibliothek Methoden bereitstellen muss, die zur Laufzeit verwendet werden sollen, die vorgeschlagenen String-Regex-Typen stattdessen in der Lage wären, Code zur Kompilierzeit zu überprüfen und Entwicklern eine große Intelligenz zu bieten.

@DanielRosenwasser @alexanderbird @Igmat : IMO wäre dieser Vorschlag

Ich stimme zu, dass Erweiterbarkeit und Typenemission dem Rest der Funktion nicht im Wege stehen sollten. Wenn es keinen klaren Weg zu diesen Aspekten gibt, implementieren Sie sie später, wenn dies der Fall ist.

Ich bin hier angekommen, da ich einen UUID-Typ und keinen String suche, daher wäre es in diesem Fall großartig, eine Regex zu haben, die den String definiert, + eine Möglichkeit, die Gültigkeit des Typs zu überprüfen (Email.test-Beispiel) wäre ebenfalls hilfreich.

@skbergam Ich versuche es noch einmal selbst umzusetzen. Aber das TS-Projekt ist wirklich riesig und ich habe auch eine Arbeit, daher gibt es fast keine Fortschritte (ich habe es nur geschafft, Tests für dieses neue Feature zu erstellen). Wenn jemand mehr Erfahrung mit der Erweiterung von TS hat, wäre jede Hilfe sehr dankbar ...

Interessant, dass dies effektiv einen nominalen Typ erstellt, da wir keine Subtyp-/Zuweisbarkeitsbeziehungen zwischen zwei nicht identischen Regexps herstellen könnten

@RyanCavanaugh Früher @maiermic kommentiert

Bearbeiten: Es sieht so aus, als ob dieses Problem in polynomieller Zeit lösbar ist (siehe Das Inklusionsproblem für reguläre Ausdrücke).

Aber das ist vielleicht nicht gut genug? Man hofft sicherlich, dass es nicht zu viele Regexp-Beziehungen gibt, aber man weiß nie.

In Bezug auf Typprüfungen, wenn wir es nicht mögen, Regexps zu duplizieren und typeof eine Konstante nicht gut genug ist (zB .d.ts-Dateien), wie denkt TS über valueof e , das ausgibt der Literalwert von e wenn e ein Literal ist, andernfalls ein Fehler (und etwas wie undefined )?

@maxlk Auch off-topic, aber ich habe Ihre Regex genommen und verbessert, um /^((dog|cat|fish)(,(?=\b)|$))+$/ mit Test https://regex101.com/r/AuyP3g/1. Dies verwendet einen positiven Lookahead für ein Wortzeichen nach dem Komma und erzwingt eine DRY-Neuvalidierung des Vorherigen.

Hi!
Wie ist der Stand davon?
Werden Sie diese Funktion in naher Zukunft hinzufügen? In der Roadmap ist dazu nichts zu finden.

@lgmat Wie wäre es, die Syntax auf einzeilige lib.d.ts verfügbar sind?

Sind diese großartigen Verbesserungen verfügbar? Vielleicht zumindest im Alpha-Release?

Regex-validierte Typen eignen sich hervorragend zum Schreiben von Tests, die Validierung hartcodierter Eingaben wäre großartig.

+1. Unser Anwendungsfall ist sehr häufig, wir benötigen ein String-Datumsformat wie 'TT/MM/JJJJ'.

Obwohl es wie vorgeschlagen ein extrem cooles Feature wäre, fehlt ihm das Potenzial:

  • die Ausgabe ist immer ein Sting (obwohl die Kompilierungszeit überprüft wurde), es gibt keine Möglichkeit, ein strukturiertes Objekt zu erhalten
  • Grammatik ist auf das beschränkt, was Regexps tun können, und sie können nicht viel tun. Das Problem von regulären Ausdrücken ist, dass sie regulär sind, ihr Ergebnis ist eine Liste, kein Baum, sie können nicht beliebig lange verschachtelte Ebenen ausdrücken
  • der Parser muss in maschinenschriftlicher Grammatik ausgedrückt werden, die begrenzt und nicht erweiterbar ist

besser wäre es, das Parsen und Emittieren an ein Plugin auszulagern, wie in #21861 vorgeschlagen. Auf diese Weise ist all das oben Genannte kein Problem um den Preis einer steileren Lernkurve, aber hey! Die Regexp-Prüfung kann darüber hinaus implementiert werden, so dass der ursprüngliche Vorschlag noch steht und von fortgeschritteneren Maschinen auf den Markt kommt

wie gesagt, ein allgemeinerer Weg wären benutzerdefinierte Syntaxanbieter für alle Literale: #21861

Beispiele:

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 Wie wäre es, die Syntax auf einzeilige lib.d.ts verfügbar sind?

@zspitz das würde viele Leute unglücklich machen, da sie sehen würden, dass es möglich, aber für sie verboten ist, im Grunde zu ihrer Sicherheit.

Sind diese großartigen Verbesserungen verfügbar? Vielleicht zumindest im Alpha-Release?

Soweit ich weiß, bräuchte das noch einen Vorschlag. @gtamas , @AndrewEastwood

Ich denke auch, dass #11152 dies beeinflussen würde.

@Igmat Ihr Vorschlag schränkt die Validierung so ein, dass sie nur bei Zeichenfolgentypen nützlich ist. Was denkst du über Vorschlag ? Dies würde eine allgemeinere Validierung für alle primitiven Typen ermöglichen:

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

Ich vermute jedoch, dass es zu viel wäre, diesen Mechanismus über Primitive hinaus auf nicht-primitive Typen auszudehnen.

Das Hauptproblem, das ich dabei sehe, sind Sicherheitsbedenken. Stellen Sie sich einen bösartigen Code vor, der Puffer verwendet, um den Speicher des Benutzers zu erfassen, während er nach Typ sucht. Dafür müssten wir viel Sandboxing implementieren. Ich würde eher 2 verschiedene Lösungen sehen, eine für Strings und eine für Zahlen.

RegExp ist in gewisser Weise dagegen immun, da Sie dies nur böswillig verwenden können, indem Sie einen Backtracking-Ausdruck erstellen . Davon abgesehen können einige Benutzer dies unbeabsichtigt tun, daher sollte es eine Art Schutz geben. Ich denke, der beste Weg, dies zu tun, wäre ein Timer.

Ein Punkt, das von @DanielRosenwasser angesprochene

Das ist wahr, das ist schlecht, aber wir können es lösen, indem wir angeben, welchen "modernen" Teil von regExp wir für unsere Codebasis benötigen. Es würde standardmäßig normal (ist es ES3?) regexp sein, das in jedem Knoten funktioniert. Und Option zum Aktivieren neuer Flags und Lookbehind-Assertionen.

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

Wenn ein Benutzer das Flag mit erweiterten Flags deaktiviert hat.

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

TypeScript würde Advanced RegExp nicht auswerten, wenn es nicht dazu aufgefordert wird. Aber ich würde vorschlagen, dass dies eine Warnung ausgeben sollte, die erklärt, was passiert und wie die erweiterte RegExp-Prüfung aktiviert wird.

Wenn der Benutzer das Flag mit erweiterten Flags

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

Wenn ein Benutzer Flag mit erweiterten Flags aktiviert hat und sein Knoten dies unterstützt.

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

Ich denke, das ist ein vernünftiger Weg.
Programmierteams haben in der Regel die gleiche Version von NodeJS oder sind leicht in der Lage, ein Upgrade durchzuführen, da ihre gesamte Codebasis für jemanden mit einer neueren Version funktioniert.
Solo-Programmierer können sich im Handumdrehen anpassen,

Wie ist der aktuelle Status dieses Problems? Es ist wirklich schade zu sehen, dass TypeScript ein so großes Potenzial und Dutzende von großartigen Vorschlägen hat, aber sie erhalten nicht viel Aufmerksamkeit von den Entwicklern…

AFAIK der ursprüngliche Vorschlag war gut, abgesehen von der Emit-Übersicht, die ein No-Go ist und nicht wirklich benötigt wird, also sollte er den Vorschlag nicht blockieren.

Das Problem, das es zu lösen versucht, könnte durch die Einführung von Regex-Literalen (was nicht schwierig sein sollte, da sie effektiv String- und Zahlenliteralen entsprechen) und einem Typoperator patternof (ähnlich wie typeof und keyof ), die einen Regex-Literaltyp annehmen und einen _validierten string_-Typ zurückgeben würden. So könnte es verwendet werden:

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 Ich habe nicht über eine solche Lösung mit zusätzlichem

Ich mag diesen Ansatz, den durch Typen verursachten Emissionseinfluss zu entfernen, auch wenn dies ausführlicher zu sein scheint.

Und das führte mich zu der Idee, wie man diesen Vorschlag erweitern kann, um das Hinzufügen neuer Schlüsselwörter zu überspringen (wie Sie vorschlagen) - IMO haben wir bereits eine ziemlich große Menge davon und haben keine Auswirkungen vom Typsystem (wie in meinem Vorschlag).

Es dauert 4 Schritte:

  1. regexp-validated string literal hinzufügen Typ:
    TypeScript type Email = /some-long-email-regex/;
  2. Lassen Sie uns die RegExp Schnittstelle in der Kernbibliothek auf generisch ändern:
    TypeScript interface RegExp<T extends string = string> { test(stringToTest: string): stringToTest is T; }
  3. Ändern Sie den Ableitungstyp für Regex-Literale im tatsächlichen Code:
    TypeScript const Email = /some-long-email-regex/; // infers to `RegExp</some-long-email-regex/>`
  4. Fügen Sie einen Typhelfer mit der Funktion conditional types , z. B. InstanceType :
    TypeScript type ValidatedStringType<T extends RegExp> = T extends RegExp<infer V> ? V : string;

Anwendungsbeispiel:

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. Ihr Vorschlag fühlt sich für TypeScript natürlicher an und erfordert weniger Änderungen am Compiler, das ist wahrscheinlich eine gute Sache. Der einzige Vorteil meines Vorschlags war, dass sich Regex-Literale genauso anfühlen würden wie String- und Zahlenliterale, dies könnte für einige verwirrend sein:

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

Aber ich denke, dass die Einfachheit Ihres Vorschlags den einen Nachteil aufwiegt.

Bearbeiten: Ich mag die Länge von ValidatedStringType<R> nicht wirklich. Wenn wir validierte Strings Muster nennen beschlossen, könnten wir verwenden PatternOf<R> , nachdem alle. Ich sage nicht, dass Ihre Eingabe länger dauert, die meisten Leute würden einfach die ersten drei Buchstaben eingeben und die Tabulatortaste drücken. Es hat nur einen größeren Einfluss auf die Code-Spagetifizierung.

@Igmat Ihre Lösung ist vom Entwicklungsstandpunkt her ausgezeichnet, aber was die Lesbarkeit @m93a vorgeschlagen. Ich denke, es könnte intern ähnlich dargestellt werden, aber es sollte dem Benutzer so einfach wie möglich präsentiert werden.

@Akxe Ich glaube nicht, dass die Entwickler Lust haben, ein weiteres Schlüsselwort hinzuzufügen, das nur einen ganz bestimmten Anwendungsfall hat.

@RyanCavanaugh Könnten Sie uns bitte Ihre Meinung dazu sagen? (Besonders der ursprüngliche Vorschlag und die vier letzten Kommentare (außer diesem).) Vielen Dank! :+1:

Wie wäre es mit einem generischen Argument für String, das standardmäßig .* ?

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

String-Literal 'foo' kann als Zucker für string<foo>

Ich mag die Länge von ValidatedStringType<R> nicht wirklich. Wenn wir uns entscheiden, validierte Strings _patterns_ zu nennen, könnten wir immerhin PatternOf<R> .

@m93a , IMO, in diesem Fall wäre es besser, sie PatternType<R> zu nennen, um mit bereits vorhandenen InstanceType und ReturnType Helfern konsistent zu sein.

@amir-arad, interessant. Wie wird interface RegExp in diesem Fall aussehen?

@RyanCavanaugh Ich könnte den ursprünglichen Vorschlag mit einem neu gefundenen Weg umschreiben, wenn es hilft. Sollte ich?

@amir-arad Ihre vorgeschlagene Syntax steht in Konflikt mit dem Rest von TypeScript. Jetzt können Sie Typen nur als generisches Argument übergeben, nicht als willkürlicher Ausdruck. Ihre vorgeschlagene Syntax wäre äußerst verwirrend.

Stellen Sie sich generische Typen wie Funktionen vor, die einen Typ annehmen und einen Typ zurückgeben. Die beiden folgenden Codeteile sind in Bedeutung und Syntax sehr ähnlich:

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

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

Ihre neue Syntax ist wie der Vorschlag, dass Regex in JavaScript let all = String(.*) was ein hässlicher Missbrauch der Funktionsaufrufsyntax wäre. Daher finde ich deinen Vorschlag wenig sinnvoll.

@ m93a Mein Vorschlag war für

@Igmat aus dem Kopf, wie wäre es mit:

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

@amir-arad, tut mir leid, ich kann Ihrem Vorschlag keine weiteren wertvollen Details hinzufügen, aber auf den ersten Blick sieht es nach einer sehr bedeutenden Änderung des gesamten TS-Compilers aus, da string sehr einfach primitiv ist.

Auch wenn ich keine offensichtlichen Probleme sehe, denke ich, dass ein solcher Vorschlag viel detaillierter sein und viele bestehende Szenarien sowie eine angemessene Begründung seines Zwecks abdecken sollte.
Ihr Vorschlag fügt einen Typ hinzu und ändert einen primitiven Typ, während meiner nur einen Typ hinzufügt.

Leider bin ich nicht bereit, viel Zeit für die Erstellung eines Vorschlags für ein solches Feature zu verwenden (auch Sie haben vielleicht bemerkt, dass nicht jeder Vorschlag in TS ohne erhebliche Verzögerung implementiert wurde), aber wenn Sie daran arbeiten, werde ich Gerne gebe ich Ihnen bei Bedarf mein Feedback.

Wenn diese Regexp-Typen echte reguläre Ausdrücke wären (keine Perl-ähnlichen regulären Ausdrücke, die nicht regulär sind), könnten wir sie in deterministisches FSM übersetzen und auf diese kartesische Produktkonstruktion anwenden, um alle Konjunktionen und Disjunktionen zu erhalten. Reguläre Ausdrücke werden unter booleschen Operationen geschlossen.

Auch wenn String-Literaltypen nicht atomar wären, sondern als Zeichenlisten zur Kompilierzeit dargestellt würden, könnten alle Operatoren in Bibliotheken implementiert werden. Das würde die Leistung nur etwas verschlechtern.

Edit: Fehler beheben.

Schauen Sie vorbei, um festzustellen, dass Mithril diese wirklich verwenden könnte, und ohne sie ist es fast unmöglich, im allgemeinen Fall typsicher zu sein. Dies ist sowohl bei Hyperscript- als auch bei JSX-Syntax der Fall. (Wir unterstützen beide.)

  • Unsere Lifecycle-Hooks, oninit , oncreate , onbeforeupdate , onupdate , onbeforeremove und onremove haben ihre eigenen spezielle Prototypen.
  • Event-Handler auf DOM-Vnodes sind buchstäblich alles andere, was mit on beginnt, und wir unterstützen sowohl Event-Listener-Funktionen als auch Event-Listener-Objekte (mit handleEvent Methoden), ausgerichtet auf addEventListener und removeEventListener .
  • Wir unterstützen Schlüssel und Referenzen nach Bedarf.
  • Alles andere wird als Attribut oder Eigenschaft behandelt, abhängig von ihrer Existenz auf dem unterstützenden DOM-Knoten selbst.

Mit einem regex-validierten String-Typ + Typ negation könnten wir also für DOM-Vnodes Folgendes tun:

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.
}

(Es wäre auch schön, Gruppen aus solchen Regexes extrahieren zu können, aber ich werde dabei nicht den Atem anhalten.)

Bearbeiten: Klären Sie einige kritische Details im Vorschlag.
Bearbeiten 2: Korrigieren Sie das technische Bit, um tatsächlich mathematisch genau zu sein.
Bearbeiten 3: Unterstützung für generische Markierungen von Einzelzeichen-Vereinigungen hinzufügen

Hier ist ein konkreter Vorschlag, um dies viel praktikabler zu lösen: Vorlagen-Literaltypen.

Außerdem bin ich der Meinung, dass vollständige Regexps wahrscheinlich keine gute Idee sind, da sie relativ einfach mit anderen Typen zusammengeführt werden können. Vielleicht ist dies besser: Vorlagen-Literaltypen.

  • `value` - Dies entspricht wörtlich "value"
  • `value${"a" | "b"}` - Dies entspricht wörtlich "valuea" | "valueb"
  • `value${string}` - Dies ist funktional äquivalent zu dem Regexp /^value/ , aber "value" , "valuea" und "valueakjsfbf aflksfief fskdf d" sind alle zuweisbar.
  • `foo${string}bar` - Dies entspricht funktional dem Regexp /^foo.*bar$/ , ist aber etwas einfacher zu normalisieren.
  • Natürlich können auch mehrere Interpolationen erfolgen. `foo${string}bar${string}baz` ist ein gültiger Vorlagenliteraltyp.
  • Interpolationen müssen string und dürfen nicht rekursiv sein. (Die zweite Bedingung ist aus technischen Gründen.)
  • Ein Vorlagenliteraltyp A ist einem Vorlagenliteraltyp B nur dann zuweisbar, wenn die Menge der Zeichenketten, die A zuweisbar sind, eine Teilmenge der Zeichenkettenmenge ist, die B zuweisbar ist

Zusätzlich zu den oben genannten würde es einen speziellen starof T Typ geben, wobei T nur aus einstelligen String-Literaltypen bestehen darf. string würde als Typ-Alias ​​von starof (...) , wobei ... die Vereinigung aller einzelnen UCS-2-Zeichenkettenliterale von U+0000 bis U+FFFF ist, einschließlich lone Stellvertreter. Auf diese Weise können Sie die vollständige Grammatik für numerische Literale der ES-Basis 10 definieren, zum Beispiel:

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}`
)}`

Ebenso können bestimmte integrierte Methoden angepasst werden, um solche Typen zurückzugeben:

  • Number.prototype.toString(base?) - Dies kann den obigen Numeric Typ oder eine Variante davon für statisch bekannte base s zurückgeben.
  • +x , x | 0 , parseInt(x) und ähnliches - Wenn x Numeric wie oben definiert als

Und schließlich können Sie übereinstimmende Gruppen wie folgt extrahieren: Key extends `on${infer EventName}` ? EventTypeMap[TagName][EventName] : never . Bei der Vorlagenextraktion wird davon ausgegangen, dass sie immer mit vollständigen Namen arbeitet, daher müssen Sie explizit ${string} Interpolationen verwenden, um nach beliebigen Einschlüssen zu suchen. Dies ist nicht gierig, daher gibt ` "foo.bar.baz" extends ${infer T}.${infer U} ? [T, U] : never ["foo", "bar.baz"] , nicht ["foo.bar", "baz"] .


Aus technischer Sicht ist dies viel einfacher zu implementieren als rohe Regexps. JS-Regexps sind nicht einmal regulär - sie werden mit Rückverweisen kontextsensitiv, und sie beinhalten eine Menge Komplexität in Form von Solange Sie die Rekursion mit diesen blockieren, generieren Template-Literaltypen jeweils eine einzige reguläre Sprache , die stimmt sehr gut mit der zugrunde liegenden Theorie überein (unterstützt aber nur eine Teilmenge davon).

  • Leere Sprache: ""
  • Gewerkschaft: "a" | "b"
  • Verkettung: `${a}${b}`
  • Kleene-Stern (teilweise): starof T ( T darf nur einzelne Zeichen und Vereinigungen enthalten.)

Dies kann dazu führen, dass String-Subtyping eine Teilmenge des Untergraphen-Isomorphismus-Problems im schlimmsten Fall überprüft, aber es gibt hier ein paar große erlösende Faktoren:

  1. Der weit verbreitete Fall sind Vereinigungen kleiner endlicher Strings, die Sie mit Bäumen modellieren können. Damit zu arbeiten ist relativ offensichtlich. (Ich empfehle nicht, sie als Seile zu verbinden, da dies den obigen Abgleichsalgorithmus verkompliziert, aber es ist völlig in Ordnung, Vereinigungen mit einem einzigen Zeichen und ähnlichem zu einem einzigen Split + Join zu normalisieren.)

  2. Sie können den gesamten vereinheitlichten Typ als gerichteten Graphen modellieren, wobei:

    1. Sternförmige Vereinigungen solcher Zeichen sind Untergraphen, bei denen der Elternknoten Kanten sowohl zu jedem Zeichen als auch zu jedem Kindknoten des Teilgraphen hat und jedes Zeichen Kanten sowohl zu allen anderen Zeichen als auch zu allen Kindknoten des Teilgraphen hat.
    2. Der Rest des Graphen enthält eine gerichtete baumartige Struktur, die alle anderen Möglichkeiten repräsentiert.

    Gemäß diesem Math.SE-Chat, in dem ich kurz war (ungefähr hier beginnend ), stellte ich fest, dass dieser resultierende Graph sowohl eine begrenzte Gattung (dh mit einer endlichen Anzahl von Sprüngen über andere Kanten*) als auch ohne starof Typen, ein begrenzter Grad. Dies bedeutet, dass die Typgleichheit dies auf ein Polynomialzeitproblem reduziert, und wenn Sie Unions normalisieren, ist dies auch nicht sehr langsam, da es nur etwas schneller als die Baumgleichheit ist. Ich vermute stark, dass der allgemeine Fall für diesen gesamten Vorschlag (eine Teilmenge des Teilgraphen-Isomorphismus-Problems) ebenfalls Polynomialzeit mit vernünftigen Koeffizienten ist. (Der oben verlinkte Wikipedia-Artikel enthält einige Beispiele in den "Algorithmen" und verweist auf Abschnitte, in denen eine spezielle Groß-/Kleinschreibung gelten könnte.)


  3. Keiner dieser Schlüssel dürfte groß sein , so dass sich der größte Teil der tatsächlichen Laufzeitkosten hier in der Praxis durch andere Dinge amortisiert. Solange es für kleine Tasten schnell ist, ist es gut genug.


  4. Alle zu vergleichenden Untergraphen teilen sich mindestens einen Knoten: den Wurzelknoten. (Dies stellt den Anfang des Strings dar.) Dies würde also allein den Problemraum drastisch reduzieren und eine polynomielle Zeitprüfung garantieren.


Und natürlich ist die Überschneidung zwischen solchen Typen nicht trivial , aber ich glaube, dass ähnliche erlösende Faktoren einfach aufgrund der oben genannten Einschränkungen existieren. Insbesondere die letzte Einschränkung macht es offensichtlich polynomial-time zu tun.

* Mathematisch ist Gattung für uns Programmierer etwas kontraintuitiv definiert (die minimale Anzahl von Löchern, die Sie in eine Oberfläche stechen müssen, um den Graphen ohne Sprünge zu zeichnen), aber eine begrenzte Gattung (begrenzte Anzahl von Löchern) impliziert eine begrenzte Anzahl von Sprüngen .

Mit diesem konkreten Vorschlag übersetzt sich mein Beispiel aus diesem Kommentar folgendermaßen :

// 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>
)>;

Bearbeiten: Dies würde auch die korrekte Eingabe von 90% der _.get Methode von Lodash und verwandter Methoden mit ihrer Eigenschaftskurzschrift ermöglichen, wie der _.property(path) Methode und der _.map(coll, path) Kurzschrift. Es gibt wahrscheinlich noch einige andere, an die ich nicht denke, aber das ist wahrscheinlich der größte, der mir einfällt. (Ich überlasse die Implementierung dieses Typs dem Leser als Übung, aber ich kann Ihnen versichern, dass dies mit einer Kombination daraus und dem üblichen Trick bedingter Typen mit einem sofort indizierten Datensatz möglich ist, etwa {0: ..., 1: ...}[Path extends "" ? 0 : 1] , um die statische Pfadzeichenfolge zu verarbeiten.)

Meine Empfehlung ist, dass wir unsere Bemühungen auf die Implementierung von Typanbietern konzentrieren, die zur Implementierung von Regex-Typen verwendet werden könnten.

Warum Typanbieter, anstatt Regex-Typen direkt zu implementieren? Weil

  1. Es ist eine allgemeinere Lösung, die TypeScript viele neue Möglichkeiten hinzufügt, was es einfacher macht, Unterstützung von einer breiteren Gruppe von Entwicklern zu erhalten, die über den Wert von Regex-String-Typen hinausgehen.
  2. Die Typoskript-Repo-Besitzer scheinen dieser Idee gegenüber aufgeschlossen zu sein und warten auf den richtigen Vorschlag. Siehe #3136

F# verfügt über einen Open-Source-Regex-Typanbieter.

Einige Informationen zu Typanbietern: https://link.medium.com/0wS7vgaDQV

Man könnte sich vorstellen, dass, sobald Typanbieter implementiert sind und der Regex-Typanbieter als Open-Source-Bibliothek implementiert ist, man ihn so verwenden würde:

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 Ich bin nicht überzeugt, dass dies der richtige Weg ist, zumindest nicht für diese Anfrage.

  • TypeScript ist strukturell typisiert, nicht nominell typisiert, und für die Manipulation von String-Literalen möchte ich diesen strukturellen Geist beibehalten. Typanbieter wie dieser würden einen nominalen string Subtyp erstellen, wobei RegexProvider</^foo$/> nicht als äquivalent zu "foo" , sondern als ein nominaler Subtyp davon. Außerdem würden RegexProvider</^foo$/> und RegexProvider</^fo{2}$/> als zwei verschiedene Typen behandelt, und das ist etwas, wovon ich kein Fan bin. Mein Vorschlag integriert stattdessen direkt in seinen Kern Strings, die direkt von der Theorie der formalen Spracherkennung inspiriert sind, um sicherzustellen, dass er sich natürlich einfügt.
  • Mit meinem können Sie nicht nur Strings verketten, sondern auch Teile von Strings über Key extends `on${infer K}` ? K : never oder sogar Key extends `${Prefix}${infer Rest}` ? Rest : never extrahieren. Typ - Provider bieten diese Funktionalität nicht, und es gibt keine klare Art und Weise , wie es sollte , wenn eine solche Funktionalität versetzt werden.
  • Meins ist auf konzeptioneller Ebene erheblich einfacher: Ich schlage nur vor, dass wir String-Verkettungstypen hinzufügen und für das RHS von bedingten Typen die Möglichkeit, seine Umkehrung zu extrahieren. Ich schlage auch vor, dass es in string selbst integriert wird, um den Platz eines regexp /.*/ einzunehmen. Es erfordert keine API-Änderungen, und abgesehen von den beiden theoretisch komplexen Teilen, die größtenteils vom Rest der Codebasis entkoppelt sind, ist die Berechnung, ob ein Vorlagenliteraltyp einem anderen zuweisbar ist, und das Extrahieren eines Slice aus einem String ähnlich, wenn nicht sogar einfacher , implementieren.

Übrigens, mein Vorschlag könnte auch das Beispiel PhoneNumber eingeben. Es ist etwas ausführlicher, aber ich versuche, Daten zu modellieren, die sich bereits im TS-Land befinden, nicht Daten, die anderswo vorhanden sind (wofür die Typanbieter von F# am nützlichsten sind). (Es ist erwähnenswert, dass dies technisch auf die vollständige Liste der möglichen Telefonnummern hier erweitert würde.)

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$/> und RegexProvider^fo{2}$/> würden als zwei verschiedene Typen behandelt

Typanbieter könnten die Implementierung einer equals oder compare Methode erfordern, sodass der Typanbieter-Autor eines Regex-Typanbieters definieren könnte, dass beide oben genannten Fälle äquivalente Typen sind. Der Autor des Typanbieters kann die strukturelle oder nominale Typisierung nach Belieben implementieren.

Vielleicht wäre es auch möglich, Ihren String-Literaltyp als Typanbieter zu implementieren. Ich glaube nicht, dass die Syntax gleich sein könnte, aber Sie könnten mit einem Typanbieter, der eine variable Anzahl von Argumenten aufnimmt, nahe kommen.

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 Aber ist der Typ "123-456-7890" Ihrem Typ zuordenbar? (Wenn ja, wird dies die Implementierung erschweren und den Checker stark verlangsamen.)

Semi-bezogen auf die vorliegende Diskussion, was ist, wenn der Typ keine feste Länge hat (wie eine Telefonnummer)? Eine Situation, in der ich dies in letzter Zeit gerne verwendet hätte, ist das Speichern eines Raumnamens im Format thread_{number} .

Die Regex für einen solchen Wert ist thread_[1-9]\d* . Mit dem, was vorgeschlagen wird, scheint es nicht machbar (oder sogar möglich), ein solches Format zu erreichen. Der numerische Teil des Werts könnte in dieser Situation _jede_ Länge größer als Null sein.

@jhpratt Ich habe meinen Vorschlag überarbeitet, um dies in Form von starof ("0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")/^\d*$/ berücksichtigen , da nur eine kleine Änderung erforderlich war. Es optimiert auf die gleiche Weise wie string wie /^[\u0000-\uFFFF]*$/ , also habe ich beschlossen, das zu verallgemeinern.

Ich möchte starof weiter ausdehnen, wie das Akzeptieren beliebiger nicht-rekursiver Vereinigungen aufgrund von Bedenken hinsichtlich der Rechenkomplexität: Die Überprüfung, ob zwei beliebige reguläre Ausdrücke* äquivalent sind, kann im polynomialen Raum oder in polynomieller Zeit durchgeführt werden ( konvertieren Sie beide in minimalen DFA und vergleichen Sie - der übliche Weg, aber in der Praxis sehr langsam), aber beide Wege sind in der Praxis sehr langsam und AFAICT können Sie nicht beide Wege haben. Fügen Sie Unterstützung für das Quadrieren hinzu (wie a{2} ), und es ist im Grunde nicht machbar (exponentielle Komplexität) . Dies dient nur der Äquivalenz, und die Prüfung, ob ein Regexp mit einer Teilmenge der Strings übereinstimmt, die mit einem anderen Regexp übereinstimmen, ist für die Prüfung der Zuweisbarkeit natürlich noch komplizierter.

* Reguläre Ausdrücke im mathematischen Sinne: Ich schließe nur einzelne Zeichen ein, () , (ab) , (a|b) und (a*) , wobei a und b sind (möglicherweise unterschiedlich) Mitglieder dieser Liste.

Dies ist wahrscheinlich eine dumme Frage, aber ... warum ist es nicht ziemlich einfach, wenn sie angemessen eingeschränkt ist, eine Validierungsfunktion (entweder Lambda oder benannt) zu unterstützen?

Angenommen, wir verwenden ":", um anzugeben, dass das nächste Element ein Validator ist (ersetzen Sie ":", wenn Sie eine Meinung dazu haben):

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 && ... }

Zu Beginn konnte Typescript nur Validierungsfunktionen akzeptieren, die:

  • ein einzelnes Argument haben, das vom Typ "Basis" benötigt/angenommen wird
  • nur Referenzvariablen, die in der Validator-Funktion definiert sind
  • einen Wert zurückgeben (der im Validierungsprozess zu bool gezwungen wird)

Die obigen Bedingungen scheinen für den Typoskript-Compiler leicht zu überprüfen, und wenn diese Bedingungen einmal angenommen werden, würde ein Großteil der Implementierungskomplexität verschwinden.

Zusätzlich ggf. um den anfänglichen Umfang auf ein überschaubares Maß zu beschränken:

  • Validierungsfunktionen können nur zu einer Teilmenge nativer Typen hinzugefügt werden (Zeichenfolge, Zahl)

Ich glaube nicht, dass diese letzte Einschränkung allzu notwendig wäre, aber wenn es eine Frage gibt, ob es so wäre, denke ich auch nicht, dass es sich lohnen würde, viel Zeit damit zu verbringen, darüber zu diskutieren, denn eine Lösung mit der obigen Einschränkung würde immer noch eine Vielzahl von realen Anwendungsfällen lösen. Außerdem sehe ich wenig Nachteil der oben genannten Einschränkungen, da eine spätere Lockerung eine einfache und natürliche Erweiterung wäre, die keine Änderung der grundlegenden Syntax erfordert und lediglich die Breite der Sprachunterstützung durch den Compiler erweitern würde.

@mewalig Das würde bedeuten, dass etwas, das wie eine Laufzeitfunktion aussieht, tatsächlich nicht zur Laufzeit ausgeführt wird, sondern zur Kompilierzeit (und jedes Mal, wenn Sie die Zuweisbarkeit überprüfen möchten). Diese Funktionen könnten aus der Laufzeit auf nichts zugreifen (Variablen, Funktionen), was sich ziemlich umständlich anfühlen würde.

Außerdem möchten Sie im Allgemeinen nicht, dass der Compiler alles ausführt, was Sie ihm vorwerfen, insbesondere nicht schlecht optimierte Funktionen oder völlig bösartige while(true){} . Wenn Sie Meta-Programmierung wollen, müssen Sie sie intelligent gestalten. Nur zufällig zuzulassen, dass Laufzeitcode zur Kompilierzeit ausgeführt wird, wäre der "PHP-Weg", dies zu tun.

Schließlich ändert die von Ihnen vorgeschlagene Syntax das übliche Muster

let runtime: types = runtime;

(d. h. Typen nach dem Doppelpunkt) von innen nach außen, effektiv sein

type types = types: runtime;

was schrecklich ist. Also danke für deinen Vorschlag, aber es ist definitiv eine schlechte Idee.

Diese Funktionen könnten aus der Laufzeit auf nichts zugreifen (Variablen, Funktionen), was sich ziemlich umständlich anfühlen würde.

Natürlich könnten sie tsc tut es übrigens!). Sie haben offensichtlich ein Mehrdeutigkeitsproblem mit der Kompilierzeitsemantik von zB fetch() vs. Laufzeitsemantik, aber darum geht es bei der Iteration.

Nur zufällig zuzulassen, dass Laufzeitcode zur Kompilierzeit ausgeführt wird, wäre der "PHP-Weg", dies zu tun.

Es ist den constexpr Funktionen von constexpr nur constexpr , aber alles kann constexpr . Dann könnten Sie constexpr -äquivalente Versionen des Dateisystems für das Dateisystem zur Kompilierzeit haben, das ziemlich mächtig sein könnte.

Auch die Syntax sieht für mich in etwa gut aus: Der LHS ist ein Typ, der RHS ist natürlich auch ein Typ. Bei meinem Problem geht es mehr darum, wie Sie Typen über den "Basistyp" hinaus erstellen würden, aber das ist auch alles lösbar.

Also danke für deinen Vorschlag, aber es ist definitiv eine schlechte Idee.

Es mag am Ende eine schlechte Idee sein, aber im Moment sehe ich nur eine sehr unterspezifizierte Idee, die es wahrscheinlich erfordern wird, zu weit von den Zielen des Typoskripts abzuweichen. Das bedeutet nicht, dass es keine ähnliche gute Idee gibt!

Die Diskussion über dieses Feature scheint vorerst zu stoppen ( PR ist geschlossen und laut Design Notes Team _will mich nicht darauf festlegen, bis wir nominale Typen und verallgemeinerte Indexsignaturen haben und wir wissen sollten, wie diese aussehen._).

Wie auch immer, ich möchte eine weitere hypothetische Erweiterung der aktuellen PR vorschlagen, die die Extraktion von Regex-Mustern unterstützen würde ( @isiahmeadows präsentierte seinen eigenen Vorschlag, aber um ehrlich zu sein, kann ich mich jetzt nicht

Ich mag aktuelle PR sehr und würde meinen Vorschlag darauf aufbauen. Ich möchte die Syntax vorschlagen, die auf der Inferenz von generischen Typargumenten basiert, die wir für Funktionen (und bedingte Typen mit dem Schlüsselwort infer ) haben. Einfach, weil die Leute bereits eine gewisse Intuition haben, dass Sie in generischen Funktionen Typen aus übergebenen Literalobjekten "extrahieren" können.

Wir haben zum Beispiel diesen Typ.

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

und wir können diesen Typ verwenden, um Literaltypen zu testen

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

Wenn wir jedoch den Regex-Typ im Funktionsparameter verwenden, können wir die Syntax mit spitzen Klammern verwenden, um zu bedeuten, dass wir übereinstimmende Zeichenfolgen ableiten möchten. Zum Beispiel

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

Beachten Sie, dass der abgeleitete Typ von res1 ["foo", "bar"]

Ist es nützlich?

  1. Ember.js/lodash Get-Funktion

Sie könnten einen typsicheren Getter "String Path" implementieren, damit dieser Code funktioniert:

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

Aber wahrscheinlich müsste dies gelöst werden, wenn wir viele Überladungen für eine feste maximale Anzahl möglicher get "Tiefe" vermeiden wollen.

  1. Verwenden Sie extrahierte Parameter in zugeordneten Typen.

Zum Beispiel, wenn wir so etwas tun könnten https://github.com/Microsoft/TypeScript/issues/12754. Dann könnten wir die Möglichkeit haben, die Funktion umzukehren (einige Präfixe/Suffixe von allen Eigenschaften des gegebenen Typs zu entfernen). Dieser müsste wahrscheinlich eine allgemeinere Form der zugeordneten typisierten Syntax einführen, um einen neuen Schlüssel für die Eigenschaft auszuwählen (zum Beispiel Syntax wie { [ StripAsyncSuffix<P> for P in K ] : T[P] } , jemand hat so etwas bereits

Wahrscheinlich gäbe es auch andere Anwendungsfälle. Aber ich denke, die meisten würden in diese beiden Typen passen (1. den richtigen Typ basierend auf dem bereitgestellten String-Literal herausfinden, 2. die Eigenschaftsnamen des Eingabetyps in neue Eigenschaftsnamen des neu definierten Typs umwandeln)

Damit könnten wir etwas anfangen.

Ich baue derzeit benutzerdefinierte Lint-Regeln, um URLs validieren zu können – dies wäre jedoch viel einfacher, wenn wir die optionalen Parameter definieren könnten – was eine Regex erfordert, um unsere IDs validieren zu können

Im Allgemeinen würde uns dies viel mehr Möglichkeiten geben, die Gültigkeit von Requisiten in unserer Codebasis zu behaupten

Gibt es Änderungen bei den Typanbietern, Vorlagenzeichenfolgenliteralen oder anderen Vorschlägen? Das wäre so ein tolles Werkzeug.

Meine Problemumgehung dafür besteht derzeit darin, eine Markierungsschnittstelle wie diese zu verwenden .

interface TickerSymbol extends String {}

Das einzige Problem ist, dass ich ihn in string umwandeln muss, wenn ich ihn als Indexschlüssel verwenden möchte.

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

JavaScript scheint jedoch mit dem Indextyp String (mit Großbuchstaben S) in Ordnung zu sein.

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 Ich habe hier einen Vorschlag , und ein separater Vorschlag wurde Ende 2016 erstellt. Könnten die Labels dafür aktualisiert werden?

Wir haben die obigen Vorschläge überprüft und haben einige Fragen und Kommentare.

Problematische Aspekte der bisherigen Vorschläge

Typen erzeugen Emit

Wir sind bestrebt, das Typsystem vollständig gelöscht zu halten, sodass Vorschläge, die Typaliase erfordern, um ausgegebenen Code zu erzeugen, nicht in den Geltungsbereich fallen. Ich werde in diesem Thread einige Beispiele hervorheben, bei denen dies möglicherweise auf eine nicht offensichtliche Weise passiert ist:

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -22010091 - erstellt gleichzeitig eine Funktion und einen Typ

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

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -261519733 - macht das auch

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
    }
}

Ich wiederhole: Dies ist ein Nichtstarter . Typen in TypeScript sind zusammensetzbar und das Ausgeben von JS von Typen ist in dieser Welt nicht möglich. Der bisher längste Vorschlag weist umfangreiche Emit-von-Typen auf; das ist nicht machbar. Dies würde beispielsweise eine umfangreiche typgesteuerte Emission erfordern:

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");

Kreuzungsverbote

Tatsächlich sind gängige Typen und regex-validierte Typen wirklich unterschiedlich, daher brauchen wir Regeln, wie ihre Vereinigungen und Schnittmengen korrekt gehandhabt werden.

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

TypeScript kann keine Fehler bei der Instanziierung von Schnittpunkten verursachen, daher wäre dies kein Teil eines endgültigen Designs.

Ergonomie

Insgesamt ist unsere wichtigste Erkenntnis, dass wir etwas wollen, bei dem Sie nicht zweimal dieselbe RegExp schreiben (einmal im Werteraum, einmal im Typraum).

Angesichts der obigen Bedenken bezüglich des Typs emit besteht die realistischste Lösung darin, den Ausdruck in den Werteraum zu schreiben:

// 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

Sie könnten die RegExp natürlich immer noch im Typraum schreiben, aber es wäre keine Laufzeitvalidierung verfügbar und jede nicht wörtliche Verwendung würde einen erneuten Test oder eine Assertion erfordern:

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);
    }
}

Erhebung und Klärung von Use Cases

Für eine neue Art von Typ möchten wir idealerweise mehrere Beispiele sehen, in denen:

  • Das zu lösende Problem hat keine bessere Alternative (einschließlich plausibler Alternativen, die noch nicht in der Sprache vorhanden sind)
  • Das Problem tritt mit sinnvoller Häufigkeit in realen Codebasen auf
  • Die vorgeschlagene Lösung löst dieses Problem gut

Kompilierzeitvalidierung von Literalen

Dieser Thread impliziert eine Vielzahl von Anwendungsfällen; konkrete Beispiele waren seltener. Beunruhigenderweise scheinen viele dieser Beispiele nicht vollständig zu sein - sie verwenden einen RegExp, der gültige Eingaben ablehnt.

  • Schriftfarbe - AFAIK alles, was Hex-Farben akzeptiert, akzeptiert auch zB "weiß" oder "himmelblau". Dies weist auch fälschlicherweise die rgb(255, 0, 0) Syntax zurück.
  • SSN, Zip usw. - OK, aber warum enthält Ihr Code wörtliche SSNs oder Postleitzahlen? Ist dies tatsächlich eine Notwendigkeit für nominale Typen? Was passiert, wenn Sie eine Unterklasse von Strings haben, die von einem RegExp nicht genau beschrieben werden kann? Siehe "Konkurrierende Vorschläge"

    • Integer - lehnt fälschlicherweise "3e5"

    • E-MailDies wird normalerweise als schlechte Idee angesehen . Aber auch hier gibt es Zeichenfolgenliterale für E-Mail-Adressen in Ihrem Code ?

    • CSS Border-Spezifikationen - Ich könnte glauben, dass eine eigenständige Bibliothek einen genauen RegEx liefern könnte, um die DSL zu beschreiben, die sie selbst unterstützt

    • Tests schreiben – hier machen hartcodierte Eingaben Sinn, obwohl dies fast ein Kontrapunkt ist, da Ihr Testcode wahrscheinlich viele ungültige Eingaben bereitstellen sollte

    • Datumsformate - wie/warum? Date hat dafür einen Konstruktor; Wenn die Eingabe von außerhalb der Laufzeit kommt, möchten Sie nur einen nominalen Typ

    • URI - Sie können sich vorstellen, dass fetch host angeben würde, um nicht mit http(s?):

TODO: Bitte helfen Sie uns, indem Sie echte Bibliotheksfunktionen identifizieren, die von RegExp-Typen profitieren könnten, und den tatsächlichen Ausdruck, den Sie verwenden würden.

Ein Problem ist "Präzision" - was passiert, wenn jemand hilfreich bei DefinitelyTyped auftaucht und jeder Funktion in einer Bibliothek RegExp-Typen hinzufügt und so jeden nicht wörtlichen Aufruf unterbricht? Schlimmer noch, die Autoren der Definitionsdatei müssen mit den Verbrauchern genau übereinstimmen
Es scheint, dass uns dies schnell auf den Weg zu einer Situation im Turm von Babel bringt, in der jede Bibliothek ihre eigene Version davon hat, was als URL, was als Hostname, was als E-Mail usw. gilt, und jeder, der zwei Bibliotheken verbindet muss Typzusicherungen einfügen oder Regexes kopieren, um den Compiler zufriedenzustellen.

Durchsetzung von Laufzeitprüfungen

Es gab einige Diskussionen über Prüfungen, bei denen sichergestellt werden soll, dass die Argumente einer Funktion von einer früheren Regex validiert wurden, wie z. B. fn im früheren Abschnitt Ergonomie . Dies erscheint einfach und wertvoll, wenn der RegEx, gegen den getestet werden muss, bekannt ist. Das ist jedoch ein großes "Wenn" - in meiner Erinnerung kann ich mich an keine einzige Bibliothek erinnern, die Validierungs-Regexes bereitstellt. Es kann Validierungsfunktionen bieten - aber dies bedeutet , dass die Funktion zur Verfügung gestellt nominal oder getaggten Typen werden nicht regex - Typen.

Gegenbeweise zu dieser Einschätzung werden begrüßt.

Eigenschaftsschlüssel / Regex-String-Indexer

Einige Bibliotheken behandeln Objekte nach den Eigenschaftsnamen. In React möchten wir beispielsweise Typen auf jede Requisite anwenden, deren Name mit aria- beginnt:

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

Dies ist praktisch ein orthogonales Konzept (wir könnten Regex-Typen hinzufügen, ohne Regex-Eigenschaftsschlüssel hinzuzufügen, und umgekehrt).

TODO (ich oder irgendjemand): Öffnen Sie dafür eine separate Ausgabe.

Konkurrierende Vorschläge

Nominale oder getaggte Typen

Nehmen wir an, wir hätten eine Art nominale/getaggte Typen:

type ZipCode = make_unique_type string;

Du könntest dann eine Funktion schreiben

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

Brauchen Sie zu diesem Zeitpunkt wirklich noch RegExp-Typen? Weitere Gedanken finden Sie im Abschnitt zur Überprüfung der Kompilierungszeit.

Nehmen wir umgekehrt an, wir hätten RegExp-Typen und keine nominalen Typen. Es ist ziemlich verlockend , sie für Nicht-Validierungsszenarien (ab) zu verwenden:

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

Eine allgemeine Sache im Thread ist, dass diese Regexes helfen, Testcode zu validieren, denn obwohl der Code in Produktionsszenarien für von der Laufzeit bereitgestellte Zeichenfolgen und nicht für hartcodierte Literale ausgeführt wird, möchten Sie immer noch eine Überprüfung, ob Ihre Testzeichenfolgen " Korrekt". Dies scheint jedoch ein Argument für nominale/getaggte/gebrandete Zeichenfolgen zu sein, da Sie die Validierungsfunktion so oder so schreiben würden und der Vorteil von Tests darin besteht, dass Sie wissen, dass sie erschöpfend ausgeführt werden (daher würden alle Fehler in den Testeingaben früh im Entwicklungszyklus gekennzeichnet werden).

Nicht-Probleme

Wir haben die folgenden Aspekte diskutiert und betrachten sie nicht als Blocker

Host-Funktionen

Neuere Laufzeiten unterstützen mehr RegExp-Syntax als ältere Laufzeiten. Je nachdem, wo der TypeScript-Compiler ausgeführt wird, kann bestimmter Code gültig oder ungültig sein, je nach den Fähigkeiten der Laufzeit, neuere RegExp-Features zu analysieren. In der Praxis sind die meisten der neuen RegExp-Funktionen ziemlich esoterisch oder beziehen sich auf den Gruppenabgleich, der nicht mit den meisten Anwendungsfällen hier übereinstimmt.

Leistung

RegExes können eine unbegrenzte Menge an Arbeit erledigen, und der Abgleich mit einer großen Zeichenfolge kann eine beliebig große Menge an Arbeit erledigen. Benutzer können sich bereits auf andere Weise selbst DOS machen, und es ist unwahrscheinlich, dass sie einen böswillig ineffizienten RegExp schreiben.

Subtyping ( /\d*/ -> /.*/ ?), Union, Kreuzung und Unbewohnbarkeit

Theoretisch ist /\d+/ ein bekannter Untertyp von /.+/ . Angeblich gibt es Algorithmen, um zu bestimmen, ob ein RegExp mit einer reinen Teilmenge eines anderen übereinstimmt (unter bestimmten Einschränkungen), aber offensichtlich müsste der Ausdruck geparst werden. In der Praxis sind wir zu 100% damit einverstanden, dass RegExpes keine impliziten Subtyp-Beziehungen basierend auf ihren Übereinstimmungen bildet; dies ist wahrscheinlich sogar vorzuziehen.

Unions- und Intersection-Operationen würden "out of the box" funktionieren, solange die Zuweisungsbeziehungen korrekt definiert wurden.

Wenn in TypeScript zwei primitive Typen in einer Schnittmenge "kollidieren", reduzieren sie sich auf never . Wenn sich zwei RegExpes schneiden, würden wir das einfach als /a/ & /b/ belassen, anstatt zu versuchen, einen neuen RegExp zu erzeugen, der der Schnittmenge der beiden Ausdrücke entspricht. Es gäbe keine Reduzierung auf never wir bräuchten einen Algorithmus, um zu beweisen, dass kein String beide Seiten befriedigen kann (dies ist ein paralleles Problem zu dem zuvor beschriebenen Problem: Subtyping).

Nächste Schritte

Zusammenfassend sind die nächsten Schritte:

  • Melden Sie ein separates Problem für Regex-benannte Eigenschaftsschlüssel AKA Regex-String-Indexer
  • Erhalten Sie konkrete und plausible Anwendungsfälle für die Validierung von Zeichenfolgenliteralen zur Kompilierzeit

    • Beispiel: Identifizieren Sie Funktionen in DefinitelyTyped oder anderen Bibliotheken, die davon stark profitieren würden

  • Verstehen Sie, ob nominale/getaggte/gekennzeichnete Typen eine flexiblere und allgemein anwendbare Lösung für die nicht wörtliche Validierung sind
  • Identifizieren Sie Bibliotheken, die bereits Validierungs-RegExes bereitstellen

Anwendungsfall: Hyperscript (https://github.com/hyperhype/hyperscript) ähnliche Funktionen
Eine Hyperscript-Funktion wird normalerweise wie h('div#some-id') aufgerufen
Ein Mustervergleicher für Regex würde es ermöglichen, den Rückgabetyp von h zu bestimmen, der im Beispielfall HTMLDivElement wäre.

Wenn das Typsystem in der Lage wäre, String-Literale hinzuzufügen, dann könnte im Grunde jede CSS-Eigenschaft typsicher sein

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

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

CSS-Selektoren könnten auch validiert werden ( element.class#id - gültig, div#.name - ungültig)

Wenn das Erfassen von Gruppen (irgendwie) funktionieren würde, könnte die Methode get Lodash typsicher sein

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

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

Das könnte auch was sein:

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

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

Anwendungsfall: Hyperscript (Hyperhype/Hyperscript) ähnliche Funktionen

Wie würde diese Regex aussehen oder welche Validierung würde sie bieten? Ist dies für das Überladen von Regex-basierten Funktionen?

FWIW Die Bibliothek akzeptiert Tag-Namen mit Namensraum und funktioniert auch mit beliebigen Tag-Namen

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

Es akzeptiert auch eine unbegrenzte Mischung von Klassen- und ID-Werten

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

CSS-Selektoren könnten auch validiert werden

CSS-Selektoren können nicht durch einen regulären Ausdruck validiert werden

Wie würde diese Regex aussehen oder welche Validierung würde sie bieten? Ist dies für das Überladen von Regex-basierten Funktionen?

Nicht das OP, aber ich nehme an, ja, so etwas wie die HTMLDocument#createElement() Überladungen, zB:

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

Ich bin sicher, dass die REs unvollständig sind. Beachten Sie, dass dies ein Sonderfall der Validierung von CSS-Selektoren ist, die in vielen Kontexten regelmäßig verwendet werden. Zum Beispiel ist es völlig in Ordnung, wenn HTMLDocument.querySelector() HTMLElement als Fallback zurückgibt, wenn Sie einen komplexen Selektor verwenden.

Ich bin jedoch gespannt, ob es nicht überladene Beispiele gibt, die sowohl machbar als auch nützlich sind.

TODO: Bitte helfen Sie uns, indem Sie echte Bibliotheksfunktionen identifizieren, die von RegExp-Typen profitieren könnten, und den tatsächlichen Ausdruck, den Sie verwenden würden.

Mein Anwendungsfall ist der, den ich in diesem Kommentar in der CCXT-Bibliothek erläutert habe, in dem ich Zeichenfolgen habe, die TickerSymbol s darstellen. Es ist mir egal, ob sie auf ein Regex-Muster überprüft werden, aber ich möchte, dass sie als Untertypen von string damit ich strengere Zuweisungen, Parametertypprüfungen usw. bekomme. Ich fand es zu sehr nützlich sein, wenn ich funktionale Programmierung mache, damit ich TickerSymbols, Währungen, Assets usw. zur Kompilierzeit leicht verfolgen kann, wo sie zur Laufzeit nur normale Zeichenfolgen sind.

@omidkrad Das hört sich so an, als ob Sie nominale Typen benötigen, keine regex-validierten Typen.

@m93a In meinem Fall zurecht , aber für den gleichen Anwendungsfall könnten Sie regex-validierte Typen für eine strengere Typprüfung und

CSS-Selektoren könnten auch validiert werden

CSS-Selektoren können nicht durch einen regulären Ausdruck validiert werden

Nun, wenn der Regexp es uns ermöglichen würde, sie zusammenzufügen, könnten wir CSS-Regexes kopieren ..., richtig?

Das (Entwurf) CSS Typed Object Model

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

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

Mildert möglicherweise den Wunsch, das stringly-typisierte CSS-Modell zu verwenden.

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

@RyanCavanaugh Insbesondere für Mithril wird der Tag-Name über die Capture-Gruppe in ^([^#\.\[\]]+) extrahiert (standardmäßig "div" ), aber für unsere Zwecke würde die Übereinstimmung mit ^(${htmlTagNames.join("|")}) ausreichen. Mit meinem Vorschlag wäre dies für meine Zwecke ausreichend:

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

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

Was Ereignisse und Attribute betrifft, könnten wir zu diesen einmal negierten Typen Land wechseln:

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};

Übrigens, diese nahtlose Integration und Vermeidung von Komplexität ist der Grund, warum ich meinen Vorschlag immer noch den wörtlichen Regexps vorziehe.


Ich kenne jedoch keine Möglichkeit, dies mit reinen Regexp-Typen zu tun. Ich möchte darauf hinweisen.

TODO: Bitte helfen Sie uns, indem Sie echte Bibliotheksfunktionen identifizieren, die von RegExp-Typen profitieren könnten, und den tatsächlichen Ausdruck, den Sie verwenden würden.

Bent hat einen anderen Rückgabetyp basierend auf dem, was als String angegeben wird, der den erwarteten Antworttyp beschreibt, z

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

Es akzeptiert auch einige andere Argumente wie method und url als Strings, aber diese können an jeder Position erscheinen. Wenn wir also versuchen, Unions zu verwenden, um den gesamten Rückgabetyp ( 'json' | 'buffer' | 'string' ) zu beschreiben, wäre dies stattdessen dumm auf nur string wenn es mit den URL- und Methodentypen in der Union kombiniert wird, was bedeutet, dass wir den Rückgabetyp nicht automatisch basierend auf dem beim ersten Aufruf angegebenen Typ ableiten können.

@Ovyerus wie würden Ihnen Regex-Typen dabei helfen? Was würden Sie erwarten zu schreiben? Sie können mit Überladungen oder bedingten Typen etwas Ähnliches wie das Verhalten von Bent modellieren.

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, ich war unklar, Entschuldigung, ich glaube, mein Problem war eher das Abgleichen von http(s): am Anfang einer Zeichenfolge, um die Basis-URL zu erkennen.

Bents Signatur ist eher in der Richtung von

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

Wenn jedoch BaseUrl als Zeichenfolge verwendet wird, werden die HttpMethods- und Rückgabetyp-Unions absorbiert, was am Ende nur string ergibt. Wenn es nur als Zeichenfolge verwendet wird, stimmt dies auch "falsch" mit der Funktionsweise von Bent überein, da es auf das Vorhandensein von ^http: oder ^https: prüft, um zu bestimmen, was als Basis-URL verwendet werden soll.

Wenn wir Regex-Typen hätten, könnte ich BaseUrl als type BaseUrl = /^https?:/ , und dies würde idealerweise Zeichenfolgen, die keine HTTP-Methode oder Antwortcodierung sind, ordnungsgemäß überprüfen und sie nicht in die string Typ.

Genau, mir geht es genauso.

--
Prokop Simek

Am 20. Oktober 2019 um 03:23:30, Michael Mitchell ([email protected])
schrieb:

Oh, ich war unklar, sorry, ich glaube, mein Problem lag eher in der Richtung von
übereinstimmende http(s): am Anfang einer Zeichenfolge, um die Basis-URL zu erkennen.

Bents Signatur ist eher in der Richtung von

type HttpMethods = 'GET' | 'PATCH' | ...Typ StatusCode = Zahl;Typ BaseUrl = Zeichenfolge; // Hier müsste ich idealerweise sehen, ob ein String mit http(s):type Headers = { [x: string]: any; };
type Optionen = HttpMethods | Statuscode | BaseURL | Kopfzeilen;
function Bent(...args: Options[]): RequestFunctionfunction Bent(...args: (Optionen | 'json')[]): RequestFunction// und so weiter

Wenn jedoch BaseUrl als Zeichenfolge verwendet wird, werden die HttpMethods absorbiert und zurückgegeben
type unions, die nur als String enden. Habe es nur als Schnur
passt auch "falsch" zur Funktionsweise von Bent, da es auf Anwesenheit prüft
von ^http: oder ^https: um zu bestimmen, was als Basis verwendet werden soll
url.

Wenn wir Regex-Typen hätten, könnte ich BaseUrl als Typ BaseUrl = /^https?:/, definieren.
und dies würde idealerweise Zeichenfolgen, die keine HTTP-Methode sind, ordnungsgemäß überprüfen oder
Antwortcodierung, sowie sie nicht in den String-Typ aufzunehmen.


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/microsoft/TypeScript/issues/6579?email_source=notifications&email_token=ABJ3U4JNK3V5MV4DJH73ZU3QPOXJFA5CNFSM4BZLAVSKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXGOZ111com ,
oder abmelden
https://github.com/notifications/unsubscribe-auth/ABJ3U4PHBXO4766LK7P7UXDQPOXJFANCNFSM4BZLAVSA
.

Der Gedanke, den ich von einem Anwendungsfall hatte, war, Parametertypen für eine Funktion zu erkennen.

Grundsätzlich habe ich ein gut definiertes Regex-Format einer Zeichenfolge, die einen Bezeichner darstellt. Ich könnte Dekoratoren verwenden, aber ein erweiterter String-Typ würde es mir ermöglichen, einen Typ zu verwenden, um den an die Funktion übergebenen Bezeichner darzustellen.

Um es noch einmal zu wiederholen, wir benötigen Beispiele für JavaScript-Code, den Sie typisiert schreiben möchten - andernfalls können wir nur raten, was Sie modellieren möchten (und ob es bereits eine Möglichkeit gibt, es zu modellieren).

@DanielRosenwasser Unten ist ein Beispiel für Code, für den wir das Tippen erzwingen möchten. http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAqjCSARKBeKBnYAnAlgOwHMBYAKFIGMAbAQ3XVnQiygG9SoOoxcA3a4aJn45yULBGoATAPZ5KIKAFtq + GIywAuWAmRoA5NQCcEAAwBGQwCMArAFoAZtYBMAdlsAWcvYActw2ZM7JzNJF28TCCcANgtLPXZOAHpErl5 + QWBhUXEpWXklFTw1Ji04JFQoMycAZihkqAA5AHkAFSgAQQAZTqaAdQBRRASOeu4cPgEMTOARMQkZOQVlVXVSnQq9PGlgW2pbAFd9nEk9OpSAZQAJJphO5Ga2gCF + ju6 + Wah ++ BBL-oAlYZQciyTBYfbkYDSLAACkBnC4 + 0slFmxzWSAANHDOGBEcjRJYsNQ8JItKD8ARMSR4fCcUjZuocNRKFo8PtFJYmJTqdjcbNyDkBJJHiA0boGEwAHTLIrqACUrFICQAvqQVSQgA

@yannickglt es scheint, als ob Sie einen nominalen Typ möchten, keinen RegExp-Typ? Sie erwarten nicht, dass Anrufer mit zufälligen Site-validierten Aufrufen wie diesem erscheinen:

// 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 die Tatsache , dass Sie in der Lage sind , eine UUID mit einem regulären Ausdruck zu beschreiben , ist ein Artefakt des Formats der Zeichenfolge selbst, während das, was Sie versuchen , dass UUIDs ist Format eine besondere Art von Typ , deren Unterstützung auszudrücken , ist zufällig ein String sein .

Die Kombination aus Assertion Functions von 3.7 und der Funktion nominal kann dies (?)

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) {

  }
}

Wird das auch scheitern?

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

Nach längerem Nachdenken sehe ich ein Problem mit meinem Lösungsvorschlag 🤔
Die asserts lösen nur zur Laufzeit einen Fehler aus ->
Das Regex-Validation könnte einen Kompilierungsfehler auslösen ->
Ansonsten macht dieser Vorschlag keinen Sinn

Bearbeiten:
Ein weiteres Problem: someFunc(uuid: any): asserts uuid is UUID gibt keine UUID zurück, es wirft oder gibt is UUID -> true .
Ich kann diese Funktion also nicht verwenden, um mainUser auf diese Weise eine UUID zuzuweisen

@RyanCavanaugh Wir möchten, dass diese für Mithril richtig eingegeben werden:

// <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)

Diese wollen wir statisch ablehnen:

// 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")

Im Idealfall würden wir diese auch statisch ablehnen, aber das hat nicht so hohe Priorität und wir können ohne sie überleben:

// 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")

Dies würde eine viel kompliziertere Typdefinition erfordern, bei der wir eine benutzerdefinierte Fehlermeldung bei der Typprüfung benötigen, um Benutzern zu helfen, herauszufinden, warum die Typprüfung fehlgeschlagen ist.

Auch andere Hyperscript-Bibliotheken und hyperscript-basierte Frameworks wie React-Hyperscript haben ähnliche Bedenken.

Hoffe das hilft!

@isiahmeadows eine bessere Möglichkeit für Sie, eine Form des Selektor-String-Builders zu verwenden, der Markenzeichenfolgen mit korrekten Eingaben zurückgibt. Mögen:

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

@anion155 Es gibt auch andere Möglichkeiten, dorthin zu gelangen, aber hier geht es darum, eine Bibliothek m("div", {...attrs}, ...children) mit verwenden nichts von dem hyperskripten Zucker (einfacher zu tippen, viel einfacher zu verarbeiten), aber jetzt ist es viel zu spät, um viel dagegen zu unternehmen.

Ich habe VIEL zu sagen. Allerdings bin ich ungeduldig. Also werde ich meine Gedanken nach und nach freigeben.

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

In Bezug auf "Präzisionsitis" (Mann, ich liebe dieses Wort),
Ich denke, wir sollten uns nicht zu viele Sorgen machen.

Das Typensystem ist bereits vollständig.
Das bedeutet im Grunde, dass wir in vielen Dingen
(Zum Beispiel, die gesamte SQL zu modellieren? Schamloser Stecker =P)

Aber Sie sehen nicht (zu viele) Leute, die alles geben und alle Typoperatoren auf verrückte Weise verwenden, die Bibliotheken daran hindern, miteinander kompatibel zu sein. Ich denke gerne, dass Bibliotheksautoren dazu neigen, besonnen genug zu sein ... Richtig?

Es kommt nicht oft vor, dass ich mir Zeichenfolgenmustertypen / Regex-validierte Zeichenfolgentypen gewünscht habe, aber sie hätten definitiv dazu beigetragen, die Typsicherheit meiner Codebasis zu erhöhen.


Anwendungsfall

Aus dem Kopf fällt mir ein aktuelles Beispiel ein. (Es gibt noch eine Menge mehr, aber ich bin ein vergessliches Wesen)

Bei der Integration mit Stripes API (einer Zahlungsabwicklungsplattform) verwenden sie ch_ für charge -bezogene Identifikatoren, re_ für refund -bezogene Identifikatoren usw.

Es wäre schön gewesen, sie mit PatternOf</^ch_.+/> und PatternOf</^re_.+/> zu kodieren.

Auf diese Weise können Sie bei Tippfehlern wie

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

Ich würde einen Fehler bekommen,

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

So sehr ich nominelle/getaggte Typen liebe, sie sind weitaus unergonomischer und fehleranfälliger.
Ich sehe nominale/getaggte Typen immer als letzten Ausweg , weil dies bedeutet, dass das TS-Typsystem einfach nicht modellieren kann.

Außerdem eignen sich Typen mit Tags hervorragend für Phantomtypen.
Nominaltypen sind grundsätzlich nie sinnvoll.
(Okay, ich bin vielleicht voreingenommen. Sie sind nur nützlich, weil unique symbol Aber ich denke gerne, dass ich nicht völlig falsch liege.)

Das Muster "ValueObject" für die Validierung ist noch schlimmer und ich werde nicht darüber sprechen.


Vergleich

Im Folgenden vergleiche ich Folgendes,

  • String-Mustertypen/Regex-validierte String-Typen
  • Nenntypen
  • Strukturelle Tag-Typen

Wir können uns alle einig sein, dass das "ValueObject"-Muster die schlechteste Lösung ist und uns bei den Vergleichen nicht darum kümmern, oder?


String-Mustertypen

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

Sieh dir das an.

  • Perfekt für String-Literale.
  • Nicht schlecht für string Nicht-Literale.

Nenntypen...

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

Sieh dir das an.

  • TERRIBLE für String-Literale.
  • Nach dem Überwinden der buchstäblichen Hürde ist es nicht so schlimm... oder?

Der Hauptanwendungsfall für diesen Vorschlag sind jedoch Zeichenfolgenliterale.
Das ist also eine schreckliche Alternative.


Strukturelle Tag-Typen...

Strukturelle Tag-Typen unterscheiden sich nicht wesentlich von nominalen Typen...

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

Sieh dir das an.

  • TERRIBLE für String-Literale.
  • Nach dem Überwinden der buchstäblichen Hürde ist es nicht so schlimm... oder?

Der Hauptanwendungsfall für diesen Vorschlag sind jedoch Zeichenfolgenliterale.
Das ist also eine schreckliche Alternative.

Außerdem ist dieses strukturelle Tag-Typ- Beispiel ein wörtliches (ha, Wortspiel) Copy-Paste des nominalen Typ- Beispiels.

Der einzige Unterschied besteht darin, wie die Typen StripeChargeId und StripeRefundId deklariert werden.

Obwohl der Code im Grunde gleich ist, sind strukturelle Typen besser als nominale Typen. (Ich werde das im nächsten Beitrag klären, ich schwöre).


Abschluss

Dies ist nur eine Schlussfolgerung für diesen Kommentar! Kein Fazit zu meinen Gesamtüberlegungen!

String-Mustertypen/Regex-validierte String-Typen sind ergonomischer als nominale/strukturelle Tag-Typen. Hoffentlich waren meine einfachen Beispiele nicht zu konstruiert, die das hinreichend bewiesen haben.


Fazit (Extra)

So weit wie möglich sollten Methoden, die Teilmenge eines primitiven Typs zu verwenden, immer den nominalen/strukturellen Tag/Wert-Objekt-Typen vorgezogen werden.

Beispiele für die Untermenge der primitiven Typen,

  • string Literale
  • number Literale (außer NaN, Infinity, -Infinity )
  • boolean Literale
  • bigint Literale
  • Sogar unique symbol nimmt nur eine Teilmenge von symbol

Von den obigen Beispielen ist nur boolean "endlich genug". Es hat nur zwei Werte.
Entwickler sind mit den Literalen true und false zufrieden, denn es gibt nicht viel mehr zu verlangen.


Der Typ number ist endlich, aber er hat so viele Werte, dass wir ihn genauso gut als unendlich betrachten können.
Es gibt auch Lücken in den Literalen, die wir angeben können.

Aus diesem Grund sind der Bereichsnummerntyp und NaN, Infinity, -Infinity Probleme so beliebt und tauchen immer wieder auf. In der Lage zu sein, eine kleine endliche Menge von Werten aus einer unendlichen Menge zu spezifizieren, ist nicht gut genug.

Die Angabe eines Bereichs ist eine der gebräuchlichsten/natürlichsten Ideen, die jemandem einfällt, wenn er eine große endliche/unendliche Teilmenge einer unendlichen Menge angeben muss.


Der Typ bigint ist im Grunde unendlich und nur durch den Speicher begrenzt.

Es trägt auch zur Popularität des Bereichsnummerntypproblems bei.


Der Typ string ist im Grunde unendlich und nur durch den Speicher begrenzt.

Und deshalb ist dieses Problem mit dem String-Mustertyp/dem Regex-validierten String-Typ so beliebt.

Die Angabe einer Regex ist eine der häufigsten/natürlichsten Ideen, die jemandem einfällt, wenn er eine große endliche/unendliche Teilmenge einer unendlichen Menge angeben muss.


Der Typ symbol ... Er ist auch unendlich. Und auch grenzenlos, so ziemlich.

Aber die Elemente des Typs symbol haben in fast jeder Hinsicht keinen Bezug zueinander. Und so hat sich niemand die Frage gestellt: "Kann ich eine Möglichkeit haben, eine große endliche/unendliche Teilmenge von symbol anzugeben?".

Für die meisten Leute macht diese Frage nicht einmal Sinn. Es gibt keinen sinnvollen Weg, dies zu tun (oder?)


Es ist jedoch nicht sehr nützlich, nur Teilmengen von Primitiven deklarieren zu können. Wir brauchen auch,

  • Literale des richtigen Typs müssen ohne weitere Arbeit zuweisbar sein

Zum Glück ist TS vernünftig genug, um dies zuzulassen.

Stellen Sie sich vor, Sie könnten false an (arg : false) => void !

  • Eingebaute Möglichkeiten zur Verengung

    Im Moment haben wir für diese Literale == & === als eingebaute Möglichkeiten zur Eingrenzung.

    Stellen Sie sich vor, Sie müssten für jedes Literal einen neuen Typschutz schreiben!

Das Problem bei nominalen/strukturellen Tag/Wert-Objekttypen besteht darin, dass sie die oben genannten beiden Kriterien grundsätzlich nicht erfüllen. Sie verwandeln primitive Typen in klobige Typen, die zwar keine Objekttypen sind, aber trotzdem wie Objekttypen behandelt werden müssen.

Ergonomie

Okay, hier sind mehr Erläuterungen zu String-Mustern vs. nominalen vs. strukturellen Tag-Typen.

Diese Argumente gelten auch für https://github.com/microsoft/TypeScript/issues/15480 .


Cross-Library-Kompatibilität

Nominaltypen sind am schlechtesten in
Es ist, als würde man unique symbol in zwei Bibliotheken verwenden und versuchen, sie zur Interoperabilität zu bringen.
Es ist einfach nicht möglich.
Sie müssen einen Wächter vom Typ Boilerplate oder den Trust-me-Operator ( as ) verwenden.

Sie werden auch mehr Boilerplate für einen Assertion Guard benötigen.

Wenn der Typ keine bibliotheksübergreifende Kompatibilität erfordert, ist die Verwendung von nominalen Typen in Ordnung ...
Wenn auch sehr unergonomisch (siehe Beispiel oben).


Für Strukturtypen, wenn die Bibliothek A hat,

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

Und die Bibliothek B hat,

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

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

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

Dann benötigen Sie einen Boilerplate-Wächter oder den Trust-me-Operator ( as ).

Sie werden auch mehr Boilerplate für einen Assertion Guard benötigen.

Wenn der Typ keine bibliotheksübergreifende Kompatibilität erfordert, ist die Verwendung von Strukturtypen in Ordnung ...
Wenn auch sehr unergonomisch (siehe Beispiel oben).


Für Zeichenfolgenmustertypen, wenn die Bibliothek A hat,

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

Und die Bibliothek B hat,

//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>;

Angenommen, beide Bibliotheken produzieren immer Strings für StripeChargeId , die die Anforderungen beider Bibliotheken erfüllen. Die Bibliothek A ist mit ihrer Validierung einfach "fauler". Und die Bibliothek B ist mit ihrer Validierung "strenger".

Dann ist es irgendwie nervig. Aber nicht so schlimm.
Weil Sie einfach libraryB.stripeChargeIdRegex.test(libraryA_stripeChargeId) als Typeguard verwenden können. Es ist nicht erforderlich, den Trust-me-Operator ( as ) zu verwenden.

Sie benötigen jedoch immer noch Boilerplate für Assertion Guards.

Wenn der Typ keine bibliotheksübergreifende Kompatibilität erfordert, ist die Verwendung von Zeichenfolgenmustertypen perfekt und auch sehr ergonomisch.


Wenn Sie bibliotheksübergreifende Kompatibilität benötigen, sind String-Muster-Typen immer noch besser als strukturelle Tag-Typen! Lass mich ausreden.

Wenn die zu modellierende Domäne gut verstanden ist, ist es sehr wahrscheinlich, dass mehrere, isolierte Bibliotheksautoren am Ende dieselbe Regex schreiben. Mit strukturellen Tag-Typen könnten sie alle einfach die gewünschten Eigenschaften und Typen in die Tags schreiben.

Wenn es einen Standard gibt , der String-Formate für alles, was modelliert wird, spezifiziert, dann ist im Grunde garantiert, dass alle Bibliotheksautoren dieselbe Regex schreiben! Wenn sie eine andere Regex schreiben, folgen sie nicht wirklich dem Standard. Möchten Sie ihre Bibliothek nutzen? Mit strukturellen Tag-Typen könnten sie immer noch alles schreiben, was auch immer. (Es sei denn, jemand startet einen Standard für strukturelle Tag-Typen, der für alle wichtig ist? Lol)


Versionsübergreifende Kompatibilität

Wie üblich sind nominale Typen die schlechteste Versionskompatibilität.
Oh, Sie haben Ihrer Bibliothek einen Patch oder eine Nebenversion hinzugefügt?
Ist die Typenbezeichnung immer noch die gleiche?
Der Code ist immer noch derselbe?
Nö. Sie sind verschiedene Typen.

image


Strukturelle Tag-Typen sind weiterhin versionenübergreifend (auch Hauptversionen) zuweisbar, solange der Tag-Typ strukturell gleich ist.


String-Muster-Typen sind immer noch versionenübergreifend (sogar Hauptversionen) zuweisbar, solange die Regex gleich ist.

Oder könnten wir einfach einen PSPACE-vollständigen Algorithmus ausführen, um festzustellen, ob die Regexes gleich sind? Wir können auch bestimmen, welche Unterklassen von Regexes die gebräuchlichsten sind und für diese optimierte Äquivalenzalgorithmen ausführen... Aber das klingt nach viel Aufwand.

Regex-Untertypprüfungen wären cool und würden die Verwendung von Zeichenfolgenmustertypen definitiv ergonomischer machen. Genauso wie die Prüfung von Bereichssubtypen von dem Vorschlag für Nummernkreistypen profitieren würde.

[Bearbeiten]
In diesem Kommentar,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Jemand verlinkt mit,
https://bora.uib.no/handle/1956/3956

Mit dem Titel "Das Inklusionsproblem für reguläre Ausdrücke"
[/Bearbeiten]


Kesselplatte

TODO (Aber wir können sehen, dass String-Pattern-Typen am wenigsten Boilerplate haben)

Wörtliche Anrufung

TODO (Aber wir können sehen, dass Zeichenfolgenmustertypen den wörtlichen Aufruf am besten unterstützen)

Nicht-wörtlicher Aufruf

TODO (Aber wir können sehen, dass Zeichenfolgenmustertypen nicht-literale Aufrufe am besten unterstützen)

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

TypeScript kann keine Fehler bei der Instanziierung von Schnittpunkten verursachen, daher wäre dies kein Teil eines endgültigen Designs.

Ich weiß nicht, warum die Leute Kreuzungen verbieten wollten, aber Sie haben absolut Recht, dass es keinen Sinn macht, sie zu verbieten.


damit jeden nicht wörtlichen Aufruf brechen?

Nun, nicht jeder nicht wörtliche Aufruf.

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
}

Bei einem nicht wörtlichen Aufruf zu unterbrechen, bei dem nicht nachgewiesen werden kann, dass er mit der Regex übereinstimmt, ist nicht unbedingt eine schlechte Sache. Es ist nur eine Sache der Typsicherheit.

Das ist so, als würde man sagen, dass String-Literale schlecht sind, weil jetzt nicht-literale Aufrufe fehlschlagen.
Mit String-Mustertypen/Regex-validierten String-Typen können Sie nur Vereinigungen einer unendlichen Anzahl von String-Literalen definieren.


jede nicht wörtliche Verwendung würde eine erneute Prüfung oder Bestätigung erfordern:

Ich sehe das überhaupt nicht als Problem.
Genauso verhält es sich im Moment mit nominalen/getaggten Typen.
Oder versuchen, string an eine Funktion zu übergeben, die Zeichenfolgenliterale erwartet.
Oder versuchen, einen breiteren Typ an einen schmaleren Typ zu übergeben.

In diesem speziellen Fall haben Sie gezeigt, dass const ZipCode = /^\d\d\d\d\d$/; und ZipCode.test(s) Typwächter fungieren können. Dies wird sicherlich der Ergonomie helfen.


  • Das zu lösende Problem hat keine bessere Alternative (einschließlich plausibler Alternativen, die noch nicht in der Sprache vorhanden sind)

Nun, hoffentlich habe ich gezeigt, dass nominale/strukturelle Tag-Typen nicht die bessere Alternative sind. Sie sind eigentlich ziemlich schlecht.

  • Das Problem tritt mit sinnvoller Häufigkeit in realen Codebasen auf

Ähh... Lassen Sie mich darauf zurückkommen...

  • Die vorgeschlagene Lösung löst dieses Problem gut

Der vorgeschlagene String-Muster-Typ scheint ziemlich gut zu sein.


TODO: Bitte helfen Sie uns, indem Sie echte Bibliotheksfunktionen identifizieren, die von RegExp-Typen profitieren könnten, und den tatsächlichen Ausdruck, den Sie verwenden würden.

Ihrer Ansicht nach sind nominale/getaggte Typen gut genug für die nicht wörtliche Verwendung.
Daher ist jeder Anwendungsfall, der eine nicht wörtliche Verwendung zeigt, nicht gut genug, da nominale/getaggte Typen ihn abdecken.

Wir haben jedoch gesehen, dass selbst für den nicht wörtlichen Gebrauch

  • Nominale/strukturelle Tag-Typen leiden unter Problemen mit der bibliotheksübergreifenden/Versionskompatibilität
  • Die Menge der Boilerplate für nominale/strucutrale Tag-Typen ist deutlich höher als die Boilerplate für String-Pattern-Typen

Es scheint auch, dass die buchstäblichen Anwendungsfälle für Sie unbefriedigend waren, weil sie versuchen, dumme Dinge wie die E-Mail-Validierung zu tun oder Regexes zu verwenden, die nicht genau genug sind.


Tests schreiben – hier machen hartcodierte Eingaben Sinn, obwohl dies fast ein Kontrapunkt ist, da Ihr Testcode wahrscheinlich viele ungültige Eingaben bereitstellen sollte

Ein guter Anwendungsfall war das Schreiben von Laufzeittests . Und Sie haben Recht, dass sie auch für Laufzeittests viele ungültige Eingaben durchführen sollten.

Aber das ist kein Grund, Zeichenfolgenmustertypen nicht zu unterstützen. Es kann sein, dass sie gültige Eingaben in einer bestimmten Datei testen möchten und versehentlich ungültige Eingaben machen.

Da sie jedoch einen Typwächter oder einen Trust-me-Operator ( as ) oder ein Wertobjekt verwenden müssen, erhalten sie jetzt einen Laufzeitfehler, anstatt zu wissen, dass der Test vorzeitig fehlschlägt .

Die Verwendung des Trust-me-Operators ( as ) für Laufzeittests sollte nur dem Testen ungültiger Eingaben vorbehalten sein. Wenn Sie gültige Eingaben testen möchten, ist es klarer , dass keine Hacks erforderlich sind, um Literale einem nominalen/strukturellen Tag-Typ zuzuweisen.

Wenn sie die Regex in Zukunft jemals ändern, wäre es schön, wenn ihre Tests jetzt aufgrund von Problemen mit der Zuweisung nicht einmal ausgeführt werden. Wenn sie in ihren Tests überall nur as verwenden, werden sie es erst wissen, wenn sie die Tests durchführen.

Und wenn der Bibliotheksautor überall nur as , wenn er seine eigene Bibliothek dogfood macht... Was ist mit den nachgelagerten Verbrauchern? Werden sie nicht auch versucht sein, as überall zu verwenden und beim Upgrade auf eine neue Version Laufzeitprobleme zu bekommen?

Bei Zeichenfolgenmustertypen gibt es weniger Grund, as überall zu verwenden, und sowohl Bibliotheksautoren als auch nachgelagerte Benutzer werden leichter von Breaking Changes wissen.

(Irgendwie langatmig, aber ich hoffe, dass einige meiner Punkte durchgekommen sind).


Außerdem schreibe ich viele Kompilierzeittests (und ich weiß, dass das TS-Team das auch tut).

Es wäre schön, wenn ich testen könnte, ob ein bestimmtes string Literal eine Regex-Prüfung in meinen Kompilierzeit-Tests fehlschlägt/besteht. Im Moment kann ich für diese Dinge keine Kompilierzeittests durchführen und muss stattdessen einen Laufzeittest verwenden.

Und wenn es meine Kompilierzeit-Tests fehlschlägt/besteht, dann bin ich zuversichtlich, dass nachgeschaltete Verbraucher diese Zeichenfolgenliterale (oder ähnliche) verwenden und erwarten können, dass sie sich richtig verhalten.


Es scheint, als würde uns dies schnell auf den Weg zu einer Situation im Turmbau zu Babel bringen...

Dies gilt sogar noch mehr für die Verwendung von nominalen/strukturellen Tag-Typen. Wie die obigen Beispiele gezeigt haben, sind sie für die bibliotheksübergreifende / Versionskompatibilität schrecklich...

Regexes/String-Pattern-Typen haben jedoch eine gute Chance, nicht in dieses Problem zu geraten (hoffentlich dank der Standardisierung und vernünftiger Bibliotheksautoren).


BEARBEITEN

Eine allgemeine Sache im Thread ist, dass diese Regexes helfen, Testcode zu validieren, denn obwohl der Code in Produktionsszenarien für von der Laufzeit bereitgestellte Zeichenfolgen und nicht für hartcodierte Literale ausgeführt wird, möchten Sie immer noch eine Überprüfung, ob Ihre Testzeichenfolgen " Korrekt". Dies scheint jedoch ein Argument für nominale/getaggte/gebrandete Zeichenfolgen zu sein, da Sie die Validierungsfunktion so oder so schreiben würden und der Vorteil von Tests darin besteht, dass Sie wissen, dass sie erschöpfend ausgeführt werden (daher würden alle Fehler in den Testeingaben früh im Entwicklungszyklus gekennzeichnet werden).

Ah... ich hätte zuerst alles lesen sollen, bevor ich das schreibe...

Wie auch immer, ich habe einige Beispiele bei mir, wo String-Muster-Typen nützlich sind.


HTTP-Routen-Deklarationsbibliothek

Mit dieser Bibliothek können Sie HTTP-Route-Deklarationsobjekte erstellen. Diese Deklaration wird sowohl vom Client als auch vom Server verwendet.

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

Dies sind die Einschränkungen für .append() ,

  • Nur String-Literale (Kann dies im Moment nicht erzwingen, aber wenn Sie Nicht-Literale verwenden, wird der Builder für die Routendeklaration zu Müll)
  • Muss mit führendem Schrägstrich beginnen ( / )
  • Darf nicht mit einem nachgestellten Schrägstrich enden ( / )
  • Darf kein Konlonzeichen enthalten ( : ); es ist für Parameter reserviert
  • Darf nicht zwei oder mehr Schrägstriche hintereinander enthalten ( // )

Im Moment habe ich nur Laufzeitprüfungen für diese, die Fehler auslösen. Ich möchte, dass nachgelagerte Verbraucher diese Einschränkungen befolgen müssen, ohne einen Kommentar zu Github README oder JSDoc lesen zu müssen. Schreiben Sie einfach den Pfad und sehen Sie rote, verschnörkelte Linien.


Andere Sachen

Ich habe auch Regexes für hexadezimale Zeichenfolgen, alphanumerische Zeichenfolgen.

Das habe ich auch,

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

Ich sehe es,

Integer - lehnt "3e5" fälschlicherweise ab

Ich habe auch dies, das keine Integer-Regex ist, sondern das 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,
    };
}

Ich habe aber auch diesen Kommentar,

/**
    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);
    ```
*/

RegExp-Konstruktor

Lustigerweise profitiert der RegExp Konstruktor von Regex-validierten String-Typen!

Im Moment ist es,

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

Wir könnten jedoch

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

TL;DR (Bitte lesen Sie es, ich habe mir viel Mühe gegeben :cry: )

  • String-Mustertypen sind ergonomischer als nominale/strukturelle Tag-Typen

    • Weniger Boilerplate

  • String-Muster-Typen werden weniger wahrscheinlich als nominale/strukturelle Tag-Typen zu einer Situation im Turm von Babel

    • Besonders bei Regex-Untertypprüfungen

  • String-Muster-Typen sind die natürlichste Art, große endliche/unendliche Teilmengen des Typs string

    • Die Einführung dieser Funktion könnte die Leute sogar dazu bringen, genauer über gültige Zeichenfolgenformate für ihre Bibliotheken nachzudenken!

  • String-Pattern-Typen ermöglichen eine stärkere Kompilierzeitsicherheit für einige Bibliotheken (Lassen Sie mich auf die Verbreitung zurückkommen ... rennt weg )

    • RegExp-Konstruktor, Hex-/alphanumerische Zeichenfolgen, Routenpfaddeklarationen, Zeichenfolgenbezeichner für Datenbanken usw.


Warum sind deine Regexes so schlecht?

Eine Reihe von Anwendungsfällen, die von anderen angesprochen wurden, wollten String-Mustertypen einführen, die zu bestehenden Bibliotheken passen; und es scheint das TS-Team nicht zu überzeugen.

Oft habe ich das Gefühl, dass diese vorhandenen Bibliotheken nicht einmal so häufig Regexes verwenden, um ihre Eingaben zu überprüfen. Oder sie verwenden eine Regex, um eine einfache Validierung durchzuführen. Dann verwenden sie einen komplizierteren Parser, um die eigentliche Validierung durchzuführen.

Dies ist jedoch ein tatsächlich gültiger Anwendungsfall für Zeichenfolgenmustertypen!


String-Mustertypen zum Validieren von Obermengen gültiger String-Werte

Sicher, eine Zeichenfolge, die mit / beginnt, nicht mit / endet, keine aufeinanderfolgenden / und nicht : wird das " HTTP-Pfad-Regex". Dies bedeutet jedoch nur, dass die Menge von Werten, die diese Regex übergeben, eine Obermenge gültiger HTTP-Pfade ist.

Weiter unten haben wir einen tatsächlichen URL-Pfad-Parser, der überprüft, ob ? nicht verwendet wird, # nicht verwendet wird, einige Zeichen mit Escapezeichen versehen sind usw.

Aber mit diesem einfachen Zeichenfolgenmustertyp haben wir bereits eine große Klasse der häufigsten Probleme beseitigt, auf die ein Benutzer der Bibliothek stoßen kann! Und wir haben es auch während der Kompilierzeit eliminiert!

Es kommt nicht oft vor, dass ein Benutzer ? in seinen HTTP-Pfaden verwendet, da die meisten erfahren genug sind, um zu wissen, dass ? der Anfang einer Abfragezeichenfolge ist.


Ich habe gerade festgestellt, dass Sie diesen Anwendungsfall bereits kennen.

Dieser Thread impliziert eine Vielzahl von Anwendungsfällen; konkrete Beispiele waren seltener. Beunruhigenderweise scheinen viele dieser Beispiele nicht vollständig zu sein - sie verwenden einen RegExp, der gültige Eingaben ablehnt.

Sicher, viele der vorgeschlagenen Regexes sind nicht "vollständig".
Aber solange sie keine gültige Eingabe ablehnen, sollte es in Ordnung sein, oder?

Es ist in Ordnung, wenn sie ungültige Eingaben zulassen, oder?
Da wir während der Laufzeit einen "echten" Parser haben könnten, der die vollständige Validierung übernimmt.
Und eine Überprüfung zur Kompilierzeit kann viele häufige Probleme für nachgeschaltete Benutzer beseitigen und die Produktivität steigern.

Diese Beispiele, die gültige Eingaben ablehnen, sollten einfach genug zu ändern sein, damit sie keine gültige Eingabe ablehnen, sondern eine ungültige Eingabe zulassen.


String-Mustertypen und Schnittmengen

Wie auch immer, Kreuzungstypen auf String-Mustertypen wären super nützlich!

Mein .append() Beispiel könnte geschrieben werden als:

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;

Das not PatternOf</\/\//> könnte auch sein,
PatternOf</^((([/])(?!\3))|[^/])+$/> aber das ist so viel komplizierter

Vielen Dank, @AnyhowStep , für die umfangreichen Demonstrationen. Ich wollte dich dafür kritisieren, dass du mich so oft zum Lesen gebracht hast, aber es hat sich als sehr hilfreich herausgestellt!

Ich habe oft Probleme damit, meine interne API voller Zeichenfolgenparameter einzugeben, und am Ende habe ich unweigerlich viele Bedingungen, die zur Laufzeit ausgelöst werden. Unweigerlich müssen meine Verbraucher diese Musterprüfungen duplizieren, da sie keine Ausnahme wünschen, sondern eine spezielle Methode zur Behandlung des Fehlers.

// 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
}

In der Welt der Strings und Muster ist ein generisches string ziemlich dasselbe wie unknown , was viel Typsicherheit zugunsten von Laufzeitüberprüfungen entfernt und meinen verbrauchenden Entwicklern Unannehmlichkeiten bereitet.

Für einige der genannten Anwendungsfälle wäre nur eine kleine Teilmenge von Regex erforderlich, zB Präfix-Matching.

Potenziell könnte dies mit allgemeineren TS-Sprachfunktionen wie Variadic Kinds #5453 und Typinferenz beim Verteilen von String-Literaltypen erreicht werden.

Zukunftsspekulation:

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;

Für einige der genannten Anwendungsfälle wäre nur eine kleine Teilmenge von Regex erforderlich, zB Präfix-Matching.

Ich stehe immer noch zu meinem Vorschlag , der dies + ein paar andere Dinge bietet, im Grunde eine sehr kleine Obermenge von sternenfreien Sprachen, die Sie noch einigermaßen effizient auf Untergruppen und Gleichheit überprüfen können. Und bis jetzt habe ich keinen anderen Vorschlag gesehen, der versucht, den Leistungsaspekt willkürlicher regulärer Ausdrücke anzusprechen, die größte Sorge des TS-Teams.

Das Problem mit sternfreien Sprachen ist, wie der Name schon sagt, dass Sie keine Sterne verwenden können, was es schwierig macht, Dinge wie URLs zu validieren. Außerdem werden die meisten Leute wahrscheinlich Sterne wollen und einfach eine beliebige Anzahl von sich wiederholenden Sequenzen verwenden, um sie zu emulieren, was es schwierig machen würde, nach Teilmengen zu suchen.

Und die Leistung der meisten normalen darstellbaren DFA-Regexe ist nicht so schlecht, und es ist möglich, diese auf Unter-/Übermengen zu überprüfen.

Sie können jedoch immer noch * .

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

@TijmenW Lesen Sie meinen Vorschlag etwas genauer - dort gibt es einige versteckte starof ('a' | 'b' | ...) für einzelne Zeichen ausführen und Sie können string als Äquivalent zu starof UnionOfAllCodePoints (was es in der Theorie praktisch nicht mehr primitiv macht).

Auch die Überprüfung, ob eine reguläre Sprache mit einer Teilmenge dessen übereinstimmt, was eine andere reguläre Sprache entspricht, ist NP-vollständig und äquivalent zum allgemeinen Teilgraphen-Isomorphismusproblem . Aus diesem Grund können Sie nicht einfach reguläre Standardsprachen erstellen, und ich habe versucht, starof so weit wie möglich zu begrenzen, um zu versuchen, die theoretische Rechenkomplexität gering zu halten.

TODO: Bitte helfen Sie uns, indem Sie echte Bibliotheksfunktionen identifizieren, die von RegExp-Typen profitieren könnten, und den tatsächlichen Ausdruck, den Sie verwenden würden.

Nehmen Sie dies mit Vorsicht, da es sich um eine brandneue Bibliothek handelt, aber jede Bibliothek wie https://github.com/ostrowr/ts-json-validator wäre mit so etwas wie einem Regex-Typ viel nützlicher.

Das Ziel der Bibliothek ist es, Typescript-Typ/JSON-Schema-Paare <T, s> so zu generieren, dass

  1. Jeder Typ, den s validieren kann, kann T
  2. So wenige Typen wie möglich, die T zugewiesen werden können, schlagen bei der Ausführung gegen s fehl.

Ein Regex-Typ würde die Strenge von (2) verbessern, indem er dem validierten Typ erlaubt, mindestens die folgenden Schlüsselwörter strenger zu sein:

  • format
  • patternProperties
  • propertyNames

TODO: Bitte helfen Sie uns, indem Sie echte Bibliotheksfunktionen identifizieren, die von RegExp-Typen profitieren könnten, und den tatsächlichen Ausdruck, den Sie verwenden würden.

Alle Excel-Schnittstellenbibliotheken können die Typvalidierung als A1 oder A5:B7 .

Eigenschaftsschlüssel / Regex-String-Indexer

Einige Bibliotheken behandeln Objekte nach den Eigenschaftsnamen. In React möchten wir beispielsweise Typen auf jede Requisite anwenden, deren Name mit aria- beginnt:

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

Dies ist praktisch ein orthogonales Konzept (wir könnten Regex-Typen hinzufügen, ohne Regex-Eigenschaftsschlüssel hinzuzufügen, und umgekehrt).

Ich weiß, dass dies ein bisschen orthogonal zu allem ist, was hier vor sich geht, aber Wesley dachte, Sie könnten unseren Beitrag gebrauchen. Dies taucht in Fabric aus mehreren Gründen immer wieder auf. Als Komponentenbibliothek möchten wir in der Lage sein, eine Komponentenprops-Schnittstelle zu erhöhen, die die von TypeScript erlaubte React-Komponentenschnittstelle genau widerspiegelt, einschließlich der Attribute data- und aria- . Ohne sie können wir unseren Verbrauchern keine

Wenn wir etwas tun können, um zu helfen, lassen Sie es mich bitte wissen! 😄

TS-Spielplatz :

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}
        />
    )
}

TODO: Bitte helfen Sie uns, indem Sie echte Bibliotheksfunktionen identifizieren, die von RegExp-Typen profitieren könnten, und den tatsächlichen Ausdruck, den Sie verwenden würden

Zeitgesteuerte Aufgaben. (sehr überrascht, dass das nicht erwähnt wurde)

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

Geben Sie hier nur meine zwei Cent ein - ich arbeite an einem React-Projekt, bei dem wir eine Requisite validieren möchten, die als HTML-Attribut id wird. Dies bedeutet, dass es die folgenden Regeln erfüllen muss, sonst tritt ein unerwartetes Verhalten auf:

  1. Mindestens einen Charakter haben
  2. Keine Leerzeichen vorhanden

Mit anderen Worten:

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

Ein weiteres Beispiel: Sanctuary-Type-Identifiers mit erwarteten Strings im Format '<namespace>/<name>[@<version>]'

Anwendungsfall: stringly-typisierte DOM-APIs wie Navigator.registerProtocolHandler() .

Zitat von MDN:

Aus Sicherheitsgründen schränkt registerProtocolHandler() , welche Schemata registriert werden können.

Ein benutzerdefiniertes Schema kann registriert werden, solange:

  • Der Name des benutzerdefinierten Schemas beginnt mit web+
  • Der Name des benutzerdefinierten Schemas enthält mindestens einen Buchstaben nach dem Präfix web+
  • Das benutzerdefinierte Schema enthält nur ASCII-Kleinbuchstaben im Namen.

Mit anderen Worten, Navigator.registerProtocolHandler() erwartet entweder ein bekanntes string oder ein benutzerdefiniertes string aber nur, wenn es einem bestimmten Schema entspricht.

Benutzerdefinierte CSS-Eigenschaften für CSSType ist ein weiterer Anwendungsfall, um geschlossene Typen für alle Eigenschaften bereitzustellen, außer denen mit dem Präfix -- .

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

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

Kann mir jemand sagen, ob dies mit Verfeinerungstypen identisch ist? https://github.com/microsoft/TypeScript/issues/7599

@gautam1168 Es ist theoretisch nur eine Teilmenge, in der Zeichenfolgentypen speziell verfeinert werden. (Numerische Typen haben natürlich ihre eigenen Bedenken.)

Für einige der genannten Anwendungsfälle wäre nur eine kleine Teilmenge von Regex erforderlich, zB Präfix-Matching.

Ich stehe immer noch zu meinem Vorschlag , der dies + ein paar andere Dinge bietet, im Grunde eine sehr kleine Obermenge von sternenfreien Sprachen, die Sie noch einigermaßen effizient auf Untergruppen und Gleichheit überprüfen können. Und bis jetzt habe ich keinen anderen Vorschlag gesehen, der versucht, den Leistungsaspekt willkürlicher regulärer Ausdrücke anzusprechen, die größte Sorge des TS-Teams.

In diesem Kommentar,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Jemand verlinkt mit,
https://bora.uib.no/handle/1956/3956

Mit dem Titel "Das Inklusionsproblem für reguläre Ausdrücke"


Jedoch,

  • Wenn der rechte Ausdruck 1-eindeutig ist, gibt der Algorithmus die richtige Antwort.
  • Andernfalls kann es die richtige Antwort oder keine Antwort geben.

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

(Natürlich sind reguläre JS-Ausdrücke nicht regulär)

@AnyhowStep Das könnte funktionieren - ich würde einfach die Einschränkung starof aufheben und diese Einschränkung entsprechend ändern. Ich hätte gerne eine bessere Möglichkeit, die Einschränkung zu charakterisieren, da die Mathematik etwas abstrakt ist und unklar ist, wie sie konkret in der Praxis angewendet werden würde (nicht jeder, der diese Typen verwenden würde, ist mit formalen Sprachen vertraut).

Außerdem würde ich mir sehr stark eine bessere Alternative zu starof als Operator wünschen, um so etwas zu modellieren.

Ich bin neugierig: Ist es möglich, die Aufnahme/Einschließung von regulären Ausdrücken zu entscheiden? Laut Wikipedia ist es entscheidbar. Berücksichtigt dies jedoch auch reguläre Ausdrücke in JS? Ich denke, sie haben mehr Funktionen als Standard-REs (zB Rückverweise). Wenn es entscheidbar ist, ist es rechnerisch machbar?
Dies würde sich auf diese Funktion auswirken (Einschränkung):

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

@nikeee Decidable ist nicht genug, um realistisch zu sein. Selbst die quadratische Zeit ist in dieser Größenordnung im Allgemeinen zu langsam. Nicht TS, aber ich habe einige Hintergrundinformationen zu ähnlichen Problemen.

Angesichts von Rückverweisen vermute ich, dass es immer noch entscheidbar ist, aber wahrscheinlich exponentiell, wenn nicht sogar schlimmer. Allerdings nur eine begründete Vermutung.

Danke für die Klärung!

Selbst die quadratische Zeit ist in dieser Größenordnung im Allgemeinen zu langsam.

Aus diesem Grund habe ich auch gefragt, ob es rechnerisch machbar ist, also vermute ich, dass es nicht so ist.

Wenn das Gleiche für Gleichheit gilt, bedeutet das nicht, dass fast jede Eigenschaft dieses Merkmals nicht durchführbar ist? Korrigiert mich, wenn ich falsch liege, aber anscheinend bleibt nur noch die Mitgliedschaft. Ich glaube nicht, dass dies allein sinnvoll wäre.

@nikeee Es ist zu beachten, dass dieses Muster buchstäblich mit jeder Eigenschaft jedes Typs verglichen wird, mit dem es verglichen wird . Und für die Typen mit regexp Eigenschaften, Sie berechnen müssen , ob ein regulärer Ausdruck passt eine Teilmenge von dem, was andere regexp Streichhölzer, ein ziemlich kompliziertes Tier in sich selbst.

Es ist nicht unmöglich, nur schwierig , und man muss einschränkend sein, wenn man es machbar haben will. (Zum einen würden JS-Regexps nicht funktionieren - sie sind nicht nur nicht erweiterbar genug, sondern auch zu flexibel.)

Edit: Ich möchte dies wiederholen: Ich bin nicht im TS-Team, nur um das klarzustellen. Ich habe nur einen anständigen Hintergrund im Design von CS-Algorithmen.

Hmm, vielleicht können Sie also nur eine "begrenzte" Untermenge von "üblichen" RegEx unterstützen. Angesichts der Anwendungsfälle waren die Regex's bisher recht einfach… (Farben, Telefonnummern usw.)

Wie könnten wir die UX so gestalten, dass sie nur eine Teilmenge unterstützt? Es mag für den Benutzer nicht ganz klar sein, dass Feature X von RegEx funktioniert, Y jedoch nicht.

na ja … nenne es nicht „regex“ – für den Anfang. Vielleicht nur "Musterabgleich" oder so :see_no_ evil:. Aber ja, wahrscheinlich keine leichte Aufgabe…

Was ist mit einer Nicht-Regex-Syntax wie dieser:

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'

Das würde meiner Meinung nach ganz gut in das Typensystem passen. Sie können eine andere Syntax für Dinge wie optionale Übereinstimmungen hinzufügen:

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

Fügen Sie Unterstützung für Quantoren und gierigen Operatoren hinzu, und Sie haben etwas ziemlich Robustes, das wahrscheinlich für die Mehrheit der Anwendungsfälle ausreichen würde, für die Entwickler dies verwenden möchten.

Ich denke, dieser Ansatz wäre benutzerfreundlicher. Es scheint jedoch äquivalent zu arithmetischen Operationen auf Typen zu sein.
Laut https://github.com/microsoft/TypeScript/issues/15645#issuecomment -299917814 und https://github.com/microsoft/TypeScript/issues/15794#issuecomment -301170109 ist es eine Designentscheidung, dies nicht zu tun Arithmetik auf Typen.
Wenn ich mich nicht irre, kann dieser Ansatz leicht einen riesigen Typ erzeugen. Erwägen:

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}`

(Dies setzt voraus, dass die Implementierung Union-Typen verwendet. Es kann mit einer anderen / komplexeren Implementierung funktionieren)

Haftungsausschluss: Ich gehöre nicht zum TS-Team und arbeite nicht an TS. Nur meine 2c.

@rozzzly @nikeee Das ist mehr oder weniger die Essenz meines Vorschlags , nur dass ein paar kleinere Funktionen fehlen. Ich basierte auf einer großen Teilmenge regulärer Sprachen (dem formalen Sprachkonzept), nicht auf regulären Ausdrücken im Sinne von Regexp-Literalen und dergleichen, daher ist es viel weniger mächtig als diese, aber mächtig genug, um die Arbeit zu erledigen.

Ich denke, dieser Ansatz wäre benutzerfreundlicher. Es scheint jedoch äquivalent zu arithmetischen Operationen auf Typen zu sein.

Math sagt, dass die Überprüfung, ob ein Typ ein Untertyp eines anderen ist, rechnerisch gleichwertig ist mit der Überprüfung, ob ein String in einer bestimmten formalen Sprache enthalten ist.

Insbesondere die Domänenvalidierung ist eigentlich eine ziemlich komplizierte Sache, wenn Sie auch die Gültigkeit von TLDs / öffentlichen Suffixen überprüfen. Generische Domänen selbst laut RFC sind so einfach wie /[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+/ + mit höchstens 255 Zeichen, aber selbst dies ist sehr kompliziert zu tippen, es sei denn, Sie verwenden vollständige reguläre Grammatiken, wie der obige Regexp demonstriert. Sie könnten den Typ ziemlich einfach programmgesteuert generieren (ich belasse es als Übung für den Leser), indem Sie nur Zeichenfolgen von

@isiahmeadows

Das ist mehr oder weniger die Essenz meines Vorschlags , nur dass ein paar kleinere Funktionen fehlen.

Das letzte Mal habe ich diesen ganzen Thread vor über einem Jahr gelesen. Ich war in meiner Pause und sah eine Benachrichtigung, las @rugks Kommentar zu _"

...selbst dies ist sehr kompliziert zu tippen, es sei denn, Sie verwenden vollständige reguläre Grammatiken, wie der obige Regexp demonstriert. Sie könnten den Typ ziemlich einfach programmgesteuert generieren (ich belasse es als Übung für den Leser), indem Sie nur Zeichenfolgen von

Meiner Meinung nach wäre eine Möglichkeit für einen begrenzten Mustervergleich, wie ich sie vorgeschlagen habe, für sehr vereinfachte und _notwendigerweise nicht streng strenge_ Eingaben äußerst nützlich. Das Beispiel, das ich gegeben habe, ist alles andere als präzise und würde den Compiler nicht sprengen.

Aber wie @nikeee und Sie beide darauf hinweisen, könnte dies in gefährliche Extreme @types/some-popular-project , das Folgendes enthielt:

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 {
    // ...
}

Um das in die richtige Perspektive zu rücken, besteht diese Union aus verschiedene Typen, die mehr als Atome im beobachtbaren Universum sind .

Jetzt habe ich einige schrecklich lange Zuweisungsfehler gesehen, aber stellen Sie sich den (ungekürzten) Fehler dafür vor ....

Type '"😢"' is not assignable to type '"a"|"b"|"c"..........."'.ts(2322)'

Also ja.. da gibt es einige Probleme

@rozzzly Was unterscheidet diesen Typ (in Bezug auf die Durchführbarkeit) von einem TupleWithLengthBeteen1And64<Charset> ?
Der Complier ist nicht gezwungen, jeden Typ in eine normalisierte Form zu erweitern, er würde dann schnell bei ziemlich normalen Typen explodieren.
Ich sage nicht, dass ich dieses Problem im Moment in Typoskript für sinnvoll halte, wenn sogar "Ganzzahl zwischen 3 und 1024" (denken Sie an die Zuweisungslängen des Nachrichtenpuffers) als außerhalb des Gültigkeitsbereichs betrachtet wird.

@simonbuchan Zumindest Präfix- und Suffix-Typen müssen vorhanden sein, wenn nichts anderes. Das ist selbst für viele DOM-Bibliotheken und -Frameworks erforderlich.

Ich weiß, dass dies zu Tode geprügelt wurde und es wurden bereits einige gute Vorschläge gemacht. Aber ich wollte nur zusätzliche Dinge hinzufügen, die einige vielleicht leicht interessant finden.

Angesichts von Rückverweisen vermute ich, dass es immer noch entscheidbar ist, aber wahrscheinlich exponentiell, wenn nicht sogar schlimmer. Allerdings nur eine begründete Vermutung.

Rückverweise können dazu führen, dass ein Regexp eine kontextsensitive Grammatik beschreibt, eine Obermenge kontextfreier Grammatiken. Und Sprachgleichheit für CFGs ist unentscheidbar. Für CSGs, die linear-beschränkten Automaten äquivalent sind, ist es also noch schlimmer.


Angenommen, nur alle regulären Ausdrücke, die in einen DFA umgewandelt werden können, werden in einem Regexp verwendet (Konkat, Union, Stern, Schnittmenge, Komplement usw.), ist die Umwandlung eines Regexp in einen NFA O(n), was das Produkt von zwei ergibt NFAs ist O(m*n), dann ist die Durchquerung des resultierenden Graphen für Akzeptanzzustände O(m*n). Die Überprüfung der Sprachgleichheit/Teilmenge von zwei regulären Regexps ist also auch O(m*n).

Das Problem ist, dass das Alphabet hier wirklich groß ist. Lehrbücher beschränken sich normalerweise auf Alphabete der Größe 1-5, wenn es um DFAs/NFAs/reguläre Ausdrücke geht. Aber mit JS-Regexps haben wir Unicode als Alphabet. Zugegeben, es gibt effiziente Möglichkeiten, Übergangsfunktionen mit spärlichen Arrays und anderen cleveren Hacks und Optimierungen für Gleichheits-/Teilmengentests darzustellen ...

Ich bin zuversichtlich, dass es möglich ist, die Typprüfung für regelmäßige Zuweisungen einigermaßen effizient durchzuführen.

Dann können alle nicht regulären Zuweisungen nur explizite Typzusicherungen erfordern.

Ich habe vor kurzem an einem kleinen endlichen Automatenprojekt gearbeitet, daher sind die Informationen noch frisch in meinem Kopf =x

Wenn ich mich nicht irre, kann dieser Ansatz leicht einen riesigen Typ erzeugen. Erwägen:

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}`

(Dies setzt voraus, dass die Implementierung Union-Typen verwendet. Es kann mit einer anderen / komplexeren Implementierung funktionieren)

Lustigerweise ist genau dies mit den neuen Vorlagen-String-Literaltypen möglich. Dieser Fall wird anscheinend durch einen Schwellenwert für Unionstypen vermieden.

@AnyhowStep JS-Rückverweise sind die einzige kontextsensitive Produktion (und noch dazu eine ziemlich einfache und begrenzte - nur bis zu 9 Gruppen können so referenziert werden), und der Rest der Regexp-Grammatik ist regulär, deshalb vermute ich es ist entscheidbar. Aber trotzdem denke ich, dass wir uns einig sind, dass es in keiner Weise praktikabel ist. 🙂

Edit: Genauigkeit

Ich habe bestätigt, dass dieser Kommentar von @rozzzly jeden Abend mit TS 4.1.0 funktioniert!

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';

Probieren Sie es auf dem Spielplatz aus und sehen Sie, dass fail einen Kompilierzeitfehler hat 🤩


Update : Nachdem Sie ein wenig mit dieser Funktion gespielt haben, deckt sie nicht viele Anwendungsfälle ab. Beispielsweise funktioniert es nicht für eine Hex-Farbzeichenfolge.

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';

Heute scheitert das mit "Ausdruck erzeugt einen Unionstyp, der zu komplex für die Darstellung ist.(2590)"

Ich habe bestätigt, dass dieser Kommentar von @rozzzly jeden Abend mit TS 4.1.0 funktioniert!

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';

Probieren Sie es auf dem Spielplatz aus und sehen Sie, dass fail einen Kompilierzeitfehler hat 🤩

Dies würde das Daten- oder Arienproblem lösen, mit dem die meisten von uns in UX-Bibliotheken konfrontiert sind, wenn es auf Indizes angewendet werden kann.

Grundsätzlich funktioniert dies aber offensichtlich nicht, da TS nur Strings zulässt | Nummer. Da dies im Wesentlichen eine Zeichenfolge ist, kann sie aktiviert werden?
https://www.typescriptlang.org/play?target=99&ts=4.1.0-dev.20201001#code/LAKALgngDgpgBAEQIZicgzgCzgXjgAwBIBvAcgBMUkBaUgXxPTACcBLAOwHM78BuUDmBjMAZkgDG8AJIAVGEzjFQcFXADalVBkwAFZgHsoALmRakWALpGmbLvxB1QocfvYL0AV3GT06I3Fl5MFxFCipqISZSI1JIsHpeIA

_Update_: Nachdem Sie ein wenig mit dieser Funktion gespielt haben, deckt sie nicht viele Anwendungsfälle ab. Beispielsweise funktioniert es nicht für eine Hex-Farbzeichenfolge.

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';

Heute scheitert das mit "Ausdruck erzeugt einen Unionstyp, der zu komplex für die Darstellung ist.(2590)"

In den Versionshinweisen wurde auf diese Einschränkung hingewiesen. Es erstellt eine Liste aller möglichen gültigen Kombinationen, in diesem Fall würde es eine Union mit 16.777.216 (dh 16^6) Mitgliedern erstellen.

Das ist eine großartige Idee... Igmat hat 2016 einige unglaubliche Posts gemacht, die auf dem Papier sowieso gut aussehen.

Ich habe dies gefunden, weil ich sicherstellen wollte, dass die Schlüssel eines an meine Funktion übergebenen Objektliterals gültige CSS-Klassennamen sind. Ich kann leicht zur Laufzeit überprüfen ... aber mir scheint es so offensichtlich zu sein, dass Typskript dies zur Kompilierzeit tun kann, insbesondere in Situationen, in denen ich nur Objektliterale hart kodiere und Typskript nicht herausfinden muss, ob MyUnionExtendedExotictype erfüllt SomeArbitraryRegexType.

Vielleicht werde ich eines Tages sachkundig genug sein, um einen produktiveren Beitrag zu leisten :/

Ich habe bestätigt, dass dieser Kommentar von @rozzzly jeden Abend mit TS 4.1.0 funktioniert!

Beeindruckend. Ich habe ehrlich gesagt nicht erwartet, dass dies implementiert wird, zumindest nicht in absehbarer Zeit.

@chadlavi-casebook

In den Versionshinweisen wurde auf diese Einschränkung hingewiesen. Es erstellt eine Liste aller möglichen gültigen Kombinationen, in diesem Fall würde es eine Union mit 16.777.216 (dh 16^6) Mitgliedern erstellen.

Ich wäre gespannt, wie groß diese Union werden könnte, bevor sie zu einem leistungsmäßigen Problem wurde. Das Beispiel von

@thehappycheese

Ich wollte sicherstellen, dass die Schlüssel eines an meine Funktion übergebenen Objektliterals gültige CSS-Klassennamen sind

Ich bin ziemlich zuversichtlich, dass dies mit der aktuellen Implementierung nicht möglich ist. Wenn es Unterstützung für Quantoren und Bereiche gäbe, würden Sie wahrscheinlich eine Validierung für Klassennamen im BEM-Stil erhalten. Die Standard-js-Regex dafür ist nicht _zu_ schrecklich:
^\.[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$
Sie würden auch die Anker wegwerfen, da es sich bei der Implementierung entweder um eine End-to-End-Übereinstimmung oder nichts handelt, sodass ^ und $ impliziert werden. Das ist nun eine vergleichsweise einfache Regex für eine schmale Teilmenge eines gültigen CSS-Selektors. Beispiel: ಠ_ಠ ist ein gültiger Klassenname. Ich scherze nicht.

Es tut mir Leid. Ich musste dies tun.

Ich habe reguläre Sprachen in TypeScript implementiert.

  • Spielplatz #1 , Anzahl der Einsen ist durch 3 teilbar, aber nicht durch 2

Genauer gesagt habe ich einen einfachen deterministischen endlichen Automaten mit TS 4.1 implementiert

Ich meine, wir können Turing-Maschinen bereits in TS implementieren. DFAs und PDAs sind also im Vergleich dazu "einfach".

Und Vorlagenzeichenfolgen machen dies besser nutzbar.


Die Kerntypen sind eigentlich einfach und passen in < 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>;

Es ist der schwierige Teil, die Automaten zu spezifizieren.

Aber ich bin mir ziemlich sicher, dass jemand eine Regex für den TypeScript DFA™ -Generator erstellen kann...


Ich möchte auch hervorheben, dass das Beispiel "Hex-String der Länge 6" zeigt, dass Sie Funktionsparameter nur mit hässlichen Hackern akzeptieren können, die mit der Regex übereinstimmen.

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")

Hier ist ein Bonus-Spielplatz ; es implementiert die Regex /^hello .*/

Und noch ein Spielplatz ; es implementiert die Regex / world$/

Ein letztes Beispiel, Spielplatz ; Dies ist eine Regex mit Gleitkomma-String !

@AnyhowStep Nun, ich habe Ihre DFA-Idee verwendet, um eine einfache Regex [abc]{4} zu implementieren, was bedeutet, dass die Buchstaben abc in beliebiger Reihenfolge fehlen, aber genau die Länge 4 haben (aaaa, abcc, bbcc, etc...).
Spielplatz

https://cyberzhg.github.io/toolbox/min_dfa?regex=ZCgoYmQqYiopKmMpKg==

https://github.com/CyberZHG/toolbox

Wenn ich mehr Willenskraft hätte, würde ich etwas wie das oben genannte nehmen und damit Regexes in TS DFAs ™ umwandeln lol

Okay, ich habe gerade einen Prototyp zusammengeworfen,

https://glitch.com/~sassy-valiant-heath

[Bearbeiten] https://glitch.com/~efficacious-valley-repair <-- Dies erzeugt eine viel bessere Ausgabe für kompliziertere Regexe

[Bearbeiten] Es scheint, als würde Glitch kostenlose Projekte archivieren, die zu lange inaktiv sind. Also, hier ist ein Git-Repo mit den Dateien,
https://github.com/AnyhowStep/efficacious-valley-repair/tree/main/app

Schritt 1, geben Sie hier Ihre Regex ein.
image

Schritt 2, klicken Sie auf Konvertieren,
image

Schritt 3, klicken Sie auf die generierte TS-Playground-URL,
image

Schritt 4, scrollen Sie nach unten bis InLanguage_0 ,
image

Schritt 5, spielen Sie mit Eingabewerten,
image

image

Gruß an @kpdyer , Autor von https://www.npmjs.com/package/regex2dfa , für die schwere Arbeit bei der Konvertierung

Falls jemand etwas Mächtigeres braucht, hier eine Turing-Maschine 😆

Spielplatz

Dieser Thread ist zu lang geworden, um ihn zu lesen, und viele der Kommentare werden entweder von Vorlagen-Literaltypen angesprochen oder gehören nicht zum Thema. Ich habe eine neue Ausgabe Nr. 41160 erstellt, um zu diskutieren, welche verbleibenden Anwendungsfälle durch diese Funktion aktiviert werden könnten. Fühlen Sie sich frei, hier weiter über Typsystem-Parser zu diskutieren 😀

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

Roam-Cooper picture Roam-Cooper  ·  3Kommentare

blendsdk picture blendsdk  ·  3Kommentare

weswigham picture weswigham  ·  3Kommentare

DanielRosenwasser picture DanielRosenwasser  ·  3Kommentare

Antony-Jones picture Antony-Jones  ·  3Kommentare