Typescript: Sugerencia: tipo de cadena validada por expresiones regulares

Creado en 22 ene. 2016  ·  146Comentarios  ·  Fuente: microsoft/TypeScript

Hay casos en los que una propiedad no puede ser simplemente una cadena (o un conjunto de cadenas), sino que debe coincidir con un patrón.

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

Es una práctica común en JavaScript almacenar valores de color en notación css, como en el reflejo del estilo css de los nodos DOM o varias bibliotecas de terceros.

¿Qué piensas?

Literal Types Needs Proposal Suggestion

Comentario más útil

Propuesta de diseño

Hay muchos casos en los que los desarrolladores necesitan un valor más especificado que solo una cadena, pero no pueden enumerarlos como una unión de cadenas literales simples, por ejemplo, colores CSS, correos electrónicos, números de teléfono, ZipCode, extensiones swagger , etc.Incluso la especificación del esquema json que comúnmente utilizado para describir el esquema del objeto JSON tiene patrón y Propiedades que en términos de sistema de tipo TS podrían llamarse regex-validated string type y regex-validated string type of index .

Metas

Proporcione a los desarrolladores un sistema de tipos que esté un paso más cerca del JSON Schema, que comúnmente usan y también evite que se olviden de las comprobaciones de validación de cadenas cuando sea necesario.

Resumen sintáctico

La implementación de esta función consta de 4 partes:

Tipo de expresión regular validado

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;

Tipo de variable validada por expresiones regulares

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

y lo mismo, pero más legible

let fontColor: CssColor;

Tipo de índice de variable validada por expresiones regulares

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

y lo mismo, pero más legible

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

Protector de tipo para tipo variable

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

y lo mismo

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

y el uso de tipo definido para una mejor legibilidad

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

igual que

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

Escriba gurard para el tipo de índice

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

igual que

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

y el uso de tipo definido para una mejor legibilidad

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

igual que

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

Descripción semántica

Asignaciones

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

Desafortunadamente, no podemos verificar si una expresión regular es un subtipo de otra sin un gran impacto en el rendimiento debido a este artículo . Por lo que debería estar restringido. Pero hay próximas soluciones:

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

Desafortunadamente, la asignación de la variable string variable regex-validated también debería estar restringida, porque no hay garantía en el tiempo de compilación de que coincidirá con las expresiones regulares.

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

Pero podemos usar protectores de tipo o de conversión explícitos como se muestra aquí . Se recomienda el segundo.
Afortunadamente, no es un caso para cadenas literales, porque mientras las usamos PODEMOS verificar que su valor coincida con regex.

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

Reducción de tipos para índices

Para casos simples de regex-validated type de índice, consulte Tipo gurard para el tipo de índice .
Pero podría haber casos más complicados:

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

Los literales no tienen tal problema:

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

Pero para las variables, la mejor opción es usar guardias de tipo como en los siguientes ejemplos más realistas:

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

Pero si usamos una mejor definición para el tipo Gmail , tendría otro tipo de restricción:

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

Uniones e intersecciones

En realidad, los tipos comunes y los tipos regex-validated son realmente diferentes, por lo que necesitamos reglas sobre cómo manejar correctamente sus uniones e intersecciones.

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
}

Genéricos

No hay casos especiales para los genéricos, por lo que regex-validated type podría usarse con genéricos de la misma manera que los tipos habituales.
Para genéricos con restricciones como las siguientes, el tipo regex-validated comporta como una cadena:

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

Emitir descripción general

A diferencia de los tipos habituales, regex-validated tienen algún impacto en emit:

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

se compilará para:

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

Resumen de compatibilidad

Esta característica no tiene problemas de compatibilidad, porque solo hay un caso que podría romperla y está relacionada con que el tipo regex-validated tiene un impacto de emisión a diferencia del tipo habitual, por lo que este es un código TS válido:

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

cuando el código siguiente no es:

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

Pero el segundo ya era inválido, pero debido a otra razón (la declaración de tipo era incorrecta).
Así que ahora tenemos que restringir la declaración de la variable con el mismo nombre que el tipo, en caso de que este tipo sea regex-validated .

PD

Siéntete libre de señalar cosas que probablemente me he perdido. Si te gusta esta propuesta, podría intentar crear pruebas que la cubran y agregarlas como PR.

Todos 146 comentarios

Sí, he visto esto peinando DefinitelyTyped,. Incluso podríamos usar algo como esto con ScriptElementKind en la capa de servicios , donde idealmente podríamos describirlos como una lista separada por comas de cadenas específicas.

Los principales problemas son:

  • No está claro cómo componerlos bien. Si quiero una lista separada por comas de "cat" , "dog" y "fish" , entonces necesito escribir algo como /dog|cat|fish(,(dog|cat|fish))*/ .

    • Si ya tengo tipos que describen tipos de cadenas literales para "cat" , "dog "y "fish" , ¿cómo los integro en esta expresión regular?

    • Claramente hay repetición aquí, lo cual no es deseable. Quizás solucionar el problema anterior lo haría más fácil.

  • Las extensiones no estándar hacen que esto sea dudoso.

Enorme +1 en esto, ZipCode, SSN, ONet, muchos otros casos de uso para esto.

Enfrenté el mismo problema y veo que aún no está implementado, tal vez esta solución sea útil:
http://stackoverflow.com/questions/37144672/guid-uuid-type-in-typescript

Como sugirió @mhegazy , pondré mi sugerencia (# 8665) aquí. ¿Qué hay de permitir funciones de validación simples en declaraciones de tipo? Algo como eso:

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

El valor que el tipo puede aceptar vendría determinado por el tipo de parámetro de función y por la propia evaluación de la función. Eso también resolvería # 7982.

@rylphs +1 esto haría que TypeScript sea extremadamente poderoso

¿Cómo funciona el subtipo con _tipos de cadenas validados por _regex_?

let a: RegExType_1
let b: RegExType_2

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

donde RegExType_1 y RegExType_2 son _tipos de cadenas validados por expresiones regulares_.

Editar: Parece que este problema se puede resolver en tiempo polinomial (consulte El problema de inclusión para expresiones regulares ).

También ayudaría con TypeStyle: https://github.com/typestyle/typestyle/issues/5 : rose:

En JSX, @RyanCavanaugh y he visto a personas agregar aria- (y potencialmente data- ) atributos. Alguien realmente agregó una firma de índice de cadena en DefinitelyTyped como un comodín. Una nueva firma de índice para esto sería útil.

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

Propuesta de diseño

Hay muchos casos en los que los desarrolladores necesitan un valor más especificado que solo una cadena, pero no pueden enumerarlos como una unión de cadenas literales simples, por ejemplo, colores CSS, correos electrónicos, números de teléfono, ZipCode, extensiones swagger , etc.Incluso la especificación del esquema json que comúnmente utilizado para describir el esquema del objeto JSON tiene patrón y Propiedades que en términos de sistema de tipo TS podrían llamarse regex-validated string type y regex-validated string type of index .

Metas

Proporcione a los desarrolladores un sistema de tipos que esté un paso más cerca del JSON Schema, que comúnmente usan y también evite que se olviden de las comprobaciones de validación de cadenas cuando sea necesario.

Resumen sintáctico

La implementación de esta función consta de 4 partes:

Tipo de expresión regular validado

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;

Tipo de variable validada por expresiones regulares

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

y lo mismo, pero más legible

let fontColor: CssColor;

Tipo de índice de variable validada por expresiones regulares

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

y lo mismo, pero más legible

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

Protector de tipo para tipo variable

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

y lo mismo

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

y el uso de tipo definido para una mejor legibilidad

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

igual que

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

Escriba gurard para el tipo de índice

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

igual que

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

y el uso de tipo definido para una mejor legibilidad

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

igual que

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

Descripción semántica

Asignaciones

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

Desafortunadamente, no podemos verificar si una expresión regular es un subtipo de otra sin un gran impacto en el rendimiento debido a este artículo . Por lo que debería estar restringido. Pero hay próximas soluciones:

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

Desafortunadamente, la asignación de la variable string variable regex-validated también debería estar restringida, porque no hay garantía en el tiempo de compilación de que coincidirá con las expresiones regulares.

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

Pero podemos usar protectores de tipo o de conversión explícitos como se muestra aquí . Se recomienda el segundo.
Afortunadamente, no es un caso para cadenas literales, porque mientras las usamos PODEMOS verificar que su valor coincida con regex.

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

Reducción de tipos para índices

Para casos simples de regex-validated type de índice, consulte Tipo gurard para el tipo de índice .
Pero podría haber casos más complicados:

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

Los literales no tienen tal problema:

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

Pero para las variables, la mejor opción es usar guardias de tipo como en los siguientes ejemplos más realistas:

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

Pero si usamos una mejor definición para el tipo Gmail , tendría otro tipo de restricción:

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

Uniones e intersecciones

En realidad, los tipos comunes y los tipos regex-validated son realmente diferentes, por lo que necesitamos reglas sobre cómo manejar correctamente sus uniones e intersecciones.

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
}

Genéricos

No hay casos especiales para los genéricos, por lo que regex-validated type podría usarse con genéricos de la misma manera que los tipos habituales.
Para genéricos con restricciones como las siguientes, el tipo regex-validated comporta como una cadena:

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

Emitir descripción general

A diferencia de los tipos habituales, regex-validated tienen algún impacto en emit:

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

se compilará para:

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

Resumen de compatibilidad

Esta característica no tiene problemas de compatibilidad, porque solo hay un caso que podría romperla y está relacionada con que el tipo regex-validated tiene un impacto de emisión a diferencia del tipo habitual, por lo que este es un código TS válido:

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

cuando el código siguiente no es:

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

Pero el segundo ya era inválido, pero debido a otra razón (la declaración de tipo era incorrecta).
Así que ahora tenemos que restringir la declaración de la variable con el mismo nombre que el tipo, en caso de que este tipo sea regex-validated .

PD

Siéntete libre de señalar cosas que probablemente me he perdido. Si te gusta esta propuesta, podría intentar crear pruebas que la cubran y agregarlas como PR.

He olvidado señalar algunos casos de intersecciones y uniones de tipos regex-validated , pero los he descrito en el último caso de prueba. ¿Debo actualizar Design proposal para reflejar ese cambio menor?

@Igmat , pregunta sobre su propuesta de diseño: ¿Podría dar más detalles sobre la descripción general de la emisión? ¿Por qué es necesario emitir tipos validados por expresiones regulares? Por lo que puedo decir, otros tipos no admiten comprobaciones en tiempo de ejecución ... ¿me falta algo?

@alexanderbird , sí, cualquier otro tipo no tiene impacto en emit. Al principio, pensé que regex-validated lo haría, así que comencé a crear la propuesta y a jugar con la sintaxis propuesta.
El primer enfoque fue así:

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

y esto:

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

Está bien y no es necesario emitir cambios, porque "#000" podría comprobarse en tiempo de compilación.
Pero también tenemos que manejar la reducción de tipo string a regex-validated para que sea útil. Así que pensé en esto para ambas configuraciones anteriores:

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

Por lo tanto, tampoco tiene ningún impacto en la emisión y se ve bien, excepto que la expresión regular no es muy legible y debe copiarse en todos los lugares, por lo que el usuario podría cometer un error fácilmente. Pero en este caso particular, todavía parece ser mejor que cambiar cómo funciona type .
Pero luego me di cuenta de que estas cosas:

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

es una pesadilla. E incluso sin intersecciones y sindicatos. Entonces, para evitar que sucedan cosas como esta, tenemos que cambiar ligeramente type emit como se muestra en la propuesta.

@DanielRosenwasser , ¿podría, por favor,
Realmente quiero ayudar con la implementación de esta función, pero requiere mucho tiempo ( tsc es un proyecto realmente complicado y todavía tengo que trabajar para comprender cómo funciona por dentro) y no sé si esta propuesta está lista para implementar o rechazará esta característica implementada de esta manera debido a otra visión de diseño de lenguaje o cualquier otro motivo.

Hola @Igmat , creo que hay algunas cosas sobre las que debería haber preguntado inicialmente

Para empezar, todavía no entiendo por qué necesita algún tipo de cambio para emitir, y no creo que ningún tipo de emisión basada en tipos sea aceptable. Consulte nuestras no metas aquí .

Otro tema que debería haber planteado es el problema de las expresiones regulares que usan referencias inversas. Mi comprensión (y experiencia) es que las referencias inversas en una expresión regular pueden obligar a que una prueba se ejecute en el tiempo de forma exponencial a su entrada. ¿Es este un caso de esquina? Quizás, pero es algo que preferiría evitar en general. Esto es especialmente importante dado que en escenarios de editor, una verificación de tipo en una ubicación debería llevar una cantidad mínima de tiempo.

Otro problema es que tendríamos que depender del motor en el que se ejecuta el compilador de TypeScript o crear un motor de expresión regular personalizado para ejecutar estas cosas. Por ejemplo, TC39 se está moviendo para incluir una nueva bandera s para que . pueda coincidir con nuevas líneas. Habría una discrepancia entre ESXXXX y los tiempos de ejecución anteriores que admiten esto.

@igmat : no @DanielRosenwasser , probablemente no se aprobaría de todos modos). Usted dijo

Pero también tenemos que manejar el estrechamiento de una cadena a un tipo validado por expresiones regulares para que sea útil

Creo que este es solo el caso si vamos a reducir de una cadena dinámica a un tipo validado por expresiones regulares. Esto se vuelve muy complicado. Incluso en este simple caso:

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

No podemos estar seguros de que los tipos coincidan, ¿qué pasa si el número es negativo? Y a medida que las expresiones regulares se vuelven más complicadas, se vuelve más y más desordenado. Si realmente quisiéramos esto, tal vez permitiéramos "interpolación de tipos: type Baz = /prefix:{number}/ ... pero no sé si vale la pena ir allí.

En cambio, podríamos llegar parcialmente al objetivo si solo permitiéramos que se asignaran literales de cadena a tipos validados por expresiones regulares.

Considera lo siguiente:

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'

¿Crees que es una alternativa viable?

@DanielRosenwasser , he leído los Objetivos de diseño detenidamente y, si te entiendo correctamente, el problema es la infracción de los No objetivos n. ° 5.
Pero no me parece una violación, sino una mejora sintáctica. Por ejemplo, anteriormente teníamos:

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
}

Con esta propuesta implementada, se vería así:

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
}

Como puede ver, el código es casi el mismo: es un uso simple común de expresiones regulares. Pero el segundo caso es mucho más expresivo y evitará que el usuario cometa errores accidentales, como olvidarse de verificar la cadena antes de asignarla a la variable que debía ser validada por expresiones regulares.
En segundo lugar, sin este tipo de reducción, no podremos utilizar normalmente el tipo validado por expresiones regulares en los índices, porque en la mayoría de los casos, dichos campos de índice funcionan con alguna variable que no se puede verificar en tiempo de ejecución, como se podría hacer con literales. .

@alexanderbird , no sugiero hacer que este código sea válido o agregar algunas comprobaciones ocultas tanto en tiempo de ejecución como en tiempo de compilación.

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

Este código debe arrojar un error debido a mi propuesta. Pero esto:

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

o esto:

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

sería correcto e incluso no tendría ningún impacto en el código emitido.

Y como mostré en el comentario anterior, los literales definitivamente no serían suficientes incluso para casos de uso comunes, porque a menudo tenemos que trabajar con picaduras de la entrada del usuario u otras fuentes. Sin implementar este impacto de emisión, los usuarios tendrían que trabajar con este tipo de la siguiente manera:

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
}

o para intersecciones:

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
}

No creo que obligar a los usuarios a duplicar el código y usar la conversión explícita, cuando el compilador podría manejarlo fácilmente, no sea una buena manera de hacerlo. El impacto de la emisión es realmente muy pequeño y predecible, estoy seguro de que no sorprenderá a los usuarios ni dará lugar a alguna característica incomprendida o difícil de localizar errores, mientras que la implementación de esta característica sin emitir cambios definitivamente lo HARÁ.

En conclusión, quiero decir que, en términos simples, regex-validated type es tanto una variable de ámbito como un tipo de compilador.

@DanielRosenwasser y bien @alexanderbird, que tienen una idea más por eso. ¿Qué pasa con la sintaxis como esta:

const type Email = /email-regex/;

En este caso, el usuario tiene que definir explícitamente que quiere esto como type y const , por lo que el sistema de tipo real no tiene cambios de emisión a menos que se use con dicho modificador. Pero si se usa con él, aún podemos evitar muchos errores, conversiones y duplicación de código agregando la misma emisión que para:

const Email = /email-regex/;

Esto parece ser incluso más grande que una simple mejora para esta propuesta, porque esto probablemente podría permitir algo como esto (el ejemplo es del proyecto con Redux ):

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

siendo convertido a

export const type SOME_ACTION = 'SOME_ACTION';

Intenté encontrar alguna sugerencia similar pero no tuve éxito. Si pudiera ser una solución alternativa y si le gusta esa idea, puedo preparar la propuesta de diseño y las pruebas.

@DanielRosenwasser , sobre su segundo problema, no creo que suceda nunca, porque en mi sugerencia, el compilador ejecuta expresiones regulares solo para literales y no parece que alguien haga algo como esto:

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

De todos modos, podríamos probar cuánto tiempo debe ser literal para afectar el rendimiento en tiempo real y crear una heurística que advertirá al usuario si no podemos verificarlo mientras se enfrenta a estas circunstancias en algunos escenarios del editor, pero lo verificaríamos cuando compile el proyecto. O podría haber otras soluciones.

Acerca de la tercera pregunta, no estoy seguro de haber entendido todo correctamente, pero parece que el motor de expresiones regulares debería seleccionarse dependiendo de target de tsconfig si tienen implementaciones diferentes. Necesita más investigación.

@DanielRosenwasser ¿hay alguna idea? 😄 Sobre la propuesta inicial y sobre la última. Es posible que tenga que hacer una descripción más detallada del segundo, ¿verdad?

@Igmat Su propuesta limita la validación para que solo sea útil con tipos de cadenas. ¿Qué opinas de la propuesta de @rylphs ? Esto permitiría una validación más genérica para todos los tipos primitivos:

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

Sin embargo, sospecho que extender este mecanismo más allá de los primitivos a los tipos no primitivos sería demasiado.
Un punto, el problema que planteó @DanielRosenwasser , acerca de las diferentes implementaciones del motor de

@zspitz parece prometedor, pero en mi opinión podría afectar demasiado el rendimiento del compilador, porque la función no está limitada por ninguna regla y obligará a TS a calcular algunas expresiones que son demasiado complicadas o incluso que dependen de algunos recursos que no están disponibles. en tiempo de compilación.

@Igmat

porque la función no está limitada por ninguna regla

¿Tiene en mente algunos ejemplos específicos? Quizás sea posible limitar la sintaxis de validación a un subconjunto de Typecript "seguro" / conocido en tiempo de compilación.

¿Qué tal si los protectores de tipos definidos por el

// 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 se ve bien, pero ¿qué pasa con la extensión de tipo?

¿Es absolutamente necesario que sean ampliables? ¿Existe un principio de diseño de TypeScript que lo exija? De lo contrario, preferiría tener los tipos nominales que sugiere

Necesitaríamos algo bastante creativo para obtener extensibilidad en mi humilde opinión: no podemos determinar si una protección de tipo arbitraria (función) es un subconjunto de otra protección de tipo arbitraria.

Podríamos obtener una "extensibilidad" rudimentaria usando una mentalidad de afirmación de tipo (no estoy diciendo que esto sea algo "bastante creativo", estoy diciendo que aquí hay una solución provisional hasta que alguien presente algo bastante creativo):

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
}

Lo que podría ser útil aunque no sea sólido. Pero como dije al principio, ¿requerimos que sea extensible o podemos implementarlo como lo sugiere @disjukr? (Si es lo último, sugiero que lo implementemos de la forma no extensible de @disjukr ).

Algo fuera de tema, responde al primer comentario de @DanielRosenwasser :
Para la lista separada por comas, debe usar los anclajes ^ y $ (es relevante en la mayoría de los casos cuando desea validar alguna cadena). Y los anclajes ayudan a evitar la repetición, por ejemplo, la expresión regular será /^((dog|cat|fish)(,|$))+$/

Permitir que los tipos de cadenas sean expresiones regulares /#[0-9]{6}/ y permitir tipos anidados en expresiones regulares ${TColor} :

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

Resultado:

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

Caso de uso: hay una biblioteca dedicada a escribir estilos CSS "seguros para escribir" en TypeScript typestyle . La funcionalidad propuesta anteriormente sería de gran ayuda, ya que la biblioteca tiene que exponer los métodos que se usarán en tiempo de ejecución, los tipos de expresiones regulares de cadena propuestos podrían, en cambio, escribir código de verificación en tiempo de compilación y brindar a los desarrolladores una gran inteligencia.

@DanielRosenwasser @alexanderbird @Igmat: OMI esta propuesta sería que cambia el juego de mecanografiado y desarrollo web. ¿Qué impide actualmente su implementación?

Estoy de acuerdo en que la capacidad de ampliación y la emisión de tipos no deberían interferir con el resto de la función. Si no hay un camino claro en esos aspectos, impleméntelos más tarde cuando lo haya.

Llegué aquí porque estoy buscando tener un tipo UUID y no una cadena, por lo tanto, tener una expresión regular que defina la cadena sería increíble en este caso + una forma de verificar la validez del tipo (ejemplo de Email.test) también sería útil.

@skbergam Estoy tratando de implementarlo yo mismo una vez más. Pero el proyecto TS es realmente enorme y también tengo un trabajo, por lo que casi no hay progreso (solo he logrado crear pruebas para esta nueva característica). Si alguien tiene más experiencia en extender TS, cualquier ayuda será muy apreciada ...

Es interesante que esto crea efectivamente un tipo nominal, ya que no podríamos establecer ninguna relación de subtipo / asignabilidad entre dos expresiones regulares no idénticas

@RyanCavanaugh Anteriormente @maiermic comentó

Editar: Parece que este problema se puede resolver en tiempo polinomial (consulte El problema de inclusión para expresiones regulares).

¿Pero eso podría no ser lo suficientemente bueno? Uno ciertamente espera que no haya muchas relaciones de expresiones regulares, pero nunca se sabe.

Con respecto a las comprobaciones de tipo, si no nos gusta duplicar expresiones regulares y typeof una constante no es lo suficientemente buena (por ejemplo, archivos .d.ts), ¿cómo se siente TS con respecto a valueof e , que emite el valor literal de e iff e es un literal, de lo contrario un error (y emite algo como undefined )?

@maxlk También fuera de tema, pero tomé su expresión regular y la mejoré para que no coincida con las comas finales en una entrada válida: /^((dog|cat|fish)(,(?=\b)|$))+$/ con la prueba https://regex101.com/r/AuyP3g/1. Esto usa una búsqueda anticipada positiva para un carácter de palabra después de la coma, lo que obliga a la anterior a revalidar de manera SECA.

¡Hola!
¿Cuál es el estado de esto?
¿Agregará esta función en un futuro próximo? No puedo encontrar nada sobre esto en la hoja de ruta.

@lgmat ¿Qué tal limitar la sintaxis a funciones de flecha de una sola línea, usando solo las definiciones disponibles en lib.d.ts ?

¿Están disponibles esas increíbles mejoras? ¿Quizás en versión alfa al menos?

Los tipos validados de expresiones regulares son excelentes para escribir pruebas, validar las entradas codificadas sería excelente.

+1. Nuestro caso de uso es muy común, necesitamos tener un formato de fecha de cadena como 'dd / mm / AAAA'.

aunque, como se propone, sería una característica extremadamente interesante, carece del potencial:

  • la salida es siempre una picadura (aunque se verificó el tiempo de compilación), no hay forma de obtener un objeto estructurado
  • la gramática se limita a lo que las expresiones regulares pueden hacer, y no pueden hacer mucho, el problema de las expresiones regulares es que son regulares, su resultado es una lista, no un árbol, no pueden expresar niveles anidados arbitrarios largos
  • el analizador debe expresarse en términos de gramática mecanografiada, que es limitada y no extensible

Una mejor manera sería subcontratar el análisis y la emisión a un complemento, como se propone en # 21861, de esta manera todo lo anterior no es un problema al precio de una curva de aprendizaje más pronunciada, ¡pero bueno! la verificación de expresiones regulares se puede implementar además de eso para que la propuesta original siga en pie, surgiendo con maquinaria más avanzada

así que como dije, una forma más general sería proveedores de sintaxis personalizados para cualquier literal: # 21861

ejemplos:

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 ¿Qué tal limitar la sintaxis a funciones de flecha de una sola línea, usando solo las definiciones disponibles en lib.d.ts ?

@zspitz eso haría infeliz a mucha gente, como verían, que es posible, pero prohibido para ellos, básicamente por su seguridad.

¿Están disponibles esas increíbles mejoras? ¿Quizás en versión alfa al menos?

Hasta donde yo sé, esto todavía necesita una propuesta. @gtamas , @AndrewEastwood

También creo que el # 11152 estaría afectando esto.

@Igmat Su propuesta limita la validación para que solo sea útil con tipos de cadenas. ¿Qué opinas de la propuesta de @rylphs ? Esto permitiría una validación más genérica para todos los tipos primitivos:

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

Sin embargo, sospecho que extender este mecanismo más allá de los primitivos a los tipos no primitivos sería demasiado.

El principal problema que veo con esto son las preocupaciones de seguridad, imagina algún código malicioso, que usaría búferes para tomar la memoria del usuario mientras verifica el tipo. Tendríamos que implementar mucho sandboxing en torno a esto. Preferiría ver 2 soluciones diferentes, una para cadenas y otra para números.

La expresión regular es inmune a eso para algunas extensiones, ya que la única forma en que puede usar esto maliciosamente es hacer alguna expresión de retroceso . Dicho esto, algunos usuarios pueden hacerlo sin querer, por lo tanto, debería haber algún tipo de protección. Creo que la mejor forma de hacerlo sería con un temporizador.

Un punto, el problema que planteó @DanielRosenwasser , acerca de las diferentes implementaciones del motor de

Eso es cierto, esto es malo, pero podemos resolverlo especificando qué parte "moderna" de regExp necesitamos para nuestro código base. El valor predeterminado sería la expresión regular normal (¿es ES3?), Que funciona en todos los nodos . Y opción para habilitar nuevos indicadores y aserciones de búsqueda atrás.

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

Si un usuario ha desactivado la bandera con banderas avanzadas.

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

TypeScript no evaluaría RegExp avanzado, si no se le indica. Pero sugeriría que debería dar una advertencia, explicando lo que está sucediendo y cómo habilitar la comprobación avanzada de RegExp.

Si el usuario ha habilitado el indicador con indicadores avanzados y su nodo lo admite.

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

Si un usuario ha habilitado el indicador con indicadores avanzados y su nodo lo admite.

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

Creo que esta es una forma razonable de hacerlo.
Los equipos de programadores suelen tener la misma versión de NodeJS o pueden actualizarse fácilmente ya que todo su código base funciona para alguien con una versión más nueva.
Los programadores individuales pueden adaptarse fácilmente sobre la marcha,

¿Cuál es el estado actual de este problema? Es realmente una lástima ver que TypeScript tiene un potencial tan enorme y docenas de propuestas increíbles, pero no reciben mucha atención de los desarrolladores ...

AFAIK, la propuesta original era buena, aparte de la descripción general de Emit, que no es válida y no es realmente necesaria, por lo que no debería bloquear la propuesta.

El problema que está tratando de abordar podría resolverse mediante la introducción de literales regex (que no debería ser difícil, ya que son equivalentes a literales de cadena y número) y un operador de tipo patternof (similar a typeof y keyof ), que tomaría un tipo literal de expresión regular y devolvería un tipo de cadena _validada_. Así es como podría usarse:

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 No pensé en tal solución con un operador de tipo adicional, cuando estaba trabajando en la propuesta inicial.

Me gusta este enfoque de eliminar el impacto de emisión causado por los tipos, aunque parece ser más detallado.

Y esto me llevó a la idea de cómo extender esta propuesta para omitir la adición de una nueva palabra clave (como sugiere). En mi opinión, ya tenemos una gran cantidad de ellas y no tenemos un impacto de emisión del sistema de tipos (como en mi propuesta).

Tomará 4 pasos:

  1. agregar regexp-validated string literal tipo:
    TypeScript type Email = /some-long-email-regex/;
  2. Cambiemos la interfaz RegExp en core lib a genérica:
    TypeScript interface RegExp<T extends string = string> { test(stringToTest: string): stringToTest is T; }
  3. Cambiar el tipo inferir para los literales de expresiones regulares en el código real:
    TypeScript const Email = /some-long-email-regex/; // infers to `RegExp</some-long-email-regex/>`
  4. Agregue un ayudante de tipo usando la función conditional types , como InstanceType :
    TypeScript type ValidatedStringType<T extends RegExp> = T extends RegExp<infer V> ? V : string;

Ejemplo de uso:

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. Su propuesta se siente más natural para TypeScript y requiere menos cambios en el compilador, eso probablemente sea algo bueno. La única ventaja de mi propuesta fue que los literales de expresiones regulares se sentirían igual que los literales de cadena y números, esto podría ser confuso para algunos:

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

Pero creo que la sencillez de su propuesta supera la única desventaja.

Editar: Realmente no me gusta la longitud de ValidatedStringType<R> . Si decidimos llamar a patrones de cadenas validados, podríamos usar PatternOf<R> después de todo. No estoy diciendo que su tipo requiera más tiempo para escribir, la mayoría de las personas simplemente escribirían las primeras tres letras y presionarían el tabulador. Simplemente tiene un mayor impacto de espagetificación de código.

@Igmat Su solución es excelente desde el punto de desarrollo, pero en cuanto a legibilidad, sería mucho mejor tener la posibilidad como propuso @ m93a . Creo que podría representarse internamente de la misma manera, pero debería presentarse al usuario de la manera más simple posible.

@Akxe No creo que a los desarrolladores les guste agregar otra palabra clave que solo tenga un caso de uso muy específico.

@RyanCavanaugh ¿Podría decirnos su opinión sobre esto? (Específicamente la propuesta original y los cuatro últimos comentarios (excluyendo éste).) ¡Gracias! : +1:

¿Qué tal tener un argumento genérico para la cadena, que por defecto es .* ?

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

cadena literal 'foo' puede considerarse un azúcar por string<foo>

Realmente no me gusta la longitud de ValidatedStringType<R> . Si decidimos llamar a cadenas validadas _patterns_, podríamos usar PatternOf<R> después de todo.

@ m93a , IMO, en este caso sería mejor llamarlos PatternType<R> para ser coherentes con los ayudantes InstanceType y ReturnType ya existentes.

@ amir-arad, interesante. ¿Cómo se verá interface RegExp en este caso?

@RyanCavanaugh Podría reescribir la propuesta original con una forma recién encontrada si ayuda. ¿Debería?

@ amir-arad La sintaxis propuesta está en conflicto con el resto de TypeScript. Ahora solo puede pasar tipos como un argumento genérico, no como una expresión arbitraria. Su sintaxis propuesta sería extremadamente confusa.

Piense en tipos genéricos como si fueran funciones que toman un tipo y devuelven un tipo. Los dos siguientes fragmentos de código son muy parecidos tanto en significado como en sintaxis:

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

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

Su nueva sintaxis es como proponer que la expresión regular en JavaScript debe escribirse let all = String(.*) que sería un abuso desagradable de la sintaxis de llamada a la función. Por tanto, no creo que su propuesta tenga mucho sentido.

@ m93a mi sugerencia fue para anotaciones de tipo, no javascript.

@Igmat desde lo alto de mi cabeza, ¿qué tal:

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

@ amir-arad, lo siento, no puedo agregar más detalles valiosos a su sugerencia, pero a primera vista parece un cambio muy significativo en todo el compilador de TS, porque string es un primitivo muy básico.

Aunque no veo ningún problema obvio, creo que dicha propuesta debería ser mucho más detallada y cubrir una gran cantidad de escenarios existentes además de una justificación adecuada de su propósito.
Su propuesta agrega un tipo y cambia un tipo primitivo, mientras que la mía solo agrega un tipo.

Desafortunadamente, no estoy listo para dedicar mucho tiempo a crear una propuesta para dicha función (también, puede notar que no todas las propuestas en TS se han implementado sin una demora significativa), pero si trabaja en esto, yo ' Estaré encantado de proporcionarle mis comentarios si es necesario.

Si estos tipos de expresiones regulares fueran expresiones regulares reales (no expresiones regulares similares a Perl que no son regulares), podríamos traducirlas a FSM determinista y usar la construcción de productos cartesianos en ellas para obtener todas las conjunciones y disyunciones. Las expresiones regulares se cierran bajo operaciones booleanas.

Además, si los tipos literales de cadena no fueran atómicos, sino representados como listas de caracteres en tiempo de compilación, permitiría implementar todos los operadores en las bibliotecas. Eso solo empeoraría un poco el rendimiento.

Editar: corrige un error.

Pasar a notar que Mithril realmente podría usarlos, y ser seguro para los tipos en el caso general es casi imposible sin él. Este es el caso tanto de la sintaxis de Hypercript como de JSX. (Apoyamos a ambos).

  • Nuestros ganchos de ciclo de vida, oninit , oncreate , onbeforeupdate , onupdate , onbeforeremove y onremove , tienen los suyos prototipos especiales.
  • Los controladores de eventos en los vnodes DOM son literalmente cualquier otra cosa que comience con on , y admitimos tanto las funciones de escucha de eventos como los objetos de escucha de eventos (con métodos handleEvent ), alineados con addEventListener y removeEventListener .
  • Apoyamos claves y referencias según corresponda.
  • Todo lo demás se trata como un atributo o propiedad, dependiendo de su existencia en el propio nodo DOM de respaldo.

Entonces, con un tipo de cadena validado por expresiones regulares + negación de tipo , podríamos hacer lo siguiente para DOM vnodes:

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

(También sería bueno poder extraer grupos de tales expresiones regulares, pero no voy a contener la respiración en eso).

Editar: aclare algunos detalles críticos en la propuesta.
Edición 2: Corrija la parte técnica para que sea matemáticamente precisa.
Edición 3: agregue soporte para protagonismo genérico de uniones de un solo carácter

Aquí hay una propuesta concreta para intentar resolver esto de manera mucho más factible: tipos de literales de plantilla.

Además, creo que las expresiones regulares completas probablemente no sean una buena idea, porque debería ser razonablemente fácil fusionarlas con otros tipos. Quizás esto podría ser mejor: tipos de literales de plantilla.

  • `value` - Esto es literalmente equivalente a "value"
  • `value${"a" | "b"}` - Esto es literalmente equivalente a "valuea" | "valueb"
  • `value${string}` - Esto es funcionalmente equivalente a la expresión regular /^value/ , pero "value" , "valuea" y "valueakjsfbf aflksfief fskdf d" son todos asignables.
  • `foo${string}bar` : esto es funcionalmente equivalente a la expresión regular /^foo.*bar$/ , pero es un poco más fácil de normalizar.
  • Por supuesto, puede haber múltiples interpolaciones. `foo${string}bar${string}baz` es un tipo de literal de plantilla válido.
  • Las interpolaciones deben extenderse string y no deben ser recursivas. (La segunda condición es por razones técnicas).
  • Un tipo de literal de plantilla A se puede asignar a un tipo de literal de plantilla B si y solo si el conjunto de cadenas asignables a A es un subconjunto del conjunto de cadenas asignables a B .

Además de lo anterior, existiría un tipo especial starof T , donde T debe constar únicamente de tipos literales de cadena de un solo carácter. string existiría como un alias de tipo de starof (...) , donde ... es la unión de todos los literales de cadena de caracteres UCS-2 individuales desde U + 0000 a U + FFFF, incluido lone sustitutos. Esto le permite definir la gramática completa para literales numéricos en base 10 de ES, por ejemplo:

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

Y del mismo modo, ciertos métodos incorporados se pueden ajustar para devolver tales tipos:

  • Number.prototype.toString(base?) - Esto puede devolver el tipo Numeric o alguna variante del mismo para base s conocidos estáticamente.
  • +x , x | 0 , parseInt(x) , y similares - Cuando se sabe que x es un Numeric como se define arriba, el tipo resultante puede inferirse apropiadamente como un tipo de número literal.

Y finalmente, puede extraer grupos coincidentes así: Key extends `on${infer EventName}` ? EventTypeMap[TagName][EventName] : never . La extracción de plantillas asume que siempre funciona con nombres completos, por lo que debe usar explícitamente interpolaciones ${string} para buscar inclusión arbitraria. Esto no es codicioso, por lo que ` "foo.bar.baz" extends $ {inferir T}. $ {Inferir U} ? [T, U] : never devuelve ["foo", "bar.baz"] , no ["foo.bar", "baz"] .


Desde un punto de vista técnico, esto es mucho más factible de implementar que las expresiones regulares sin procesar. Las expresiones regulares de JS ni siquiera son regulares: se vuelven sensibles al contexto con referencias inversas e implican mucha complejidad en la forma de Siempre que bloquee la recursividad con estos, los tipos de literales de plantilla generan un solo lenguaje regular cada uno, uno que se alinea muy de cerca con la teoría subyacente (pero apoya solo un subconjunto de ella).

  • Idioma vacío: ""
  • Unión: "a" | "b"
  • Concatenación: `${a}${b}`
  • Estrella de Kleene (parcial): starof T ( T solo puede contener caracteres individuales y uniones).

Esto puede hacer que la verificación de subtipos de cadenas sea un subconjunto del problema del isomorfismo del subgrafo en el peor de los casos, pero hay algunos factores importantes que redimen aquí:

  1. El caso común, con mucho, son las uniones de pequeñas cadenas finitas, algo que se puede modelar con árboles. Es relativamente obvio trabajar con esto. (No recomiendo intentar unirlos como cuerdas, ya que eso complicará el algoritmo de coincidencia anterior, pero está perfectamente bien normalizar uniones de un solo carácter y similares en una sola división + unión).

  2. Puede modelar todo el tipo unificado como un gráfico dirigido, donde:

    1. Las uniones con estrellas de dichos caracteres son subgrafos en los que el nodo padre tiene bordes tanto para cada personaje como para cada nodo secundario del subgrafo, y cada personaje tiene bordes para todos los otros caracteres y todos los nodos secundarios del subgrafo.
    2. El resto del gráfico tiene una estructura en forma de árbol dirigida que representa todas las demás posibilidades.

    De acuerdo con este chat de Math.SE en el que estuve brevemente (comenzando aproximadamente aquí ), encontré que este gráfico resultante tendría un género acotado (es decir, con un número finito de saltos sobre otros bordes *) y, sin ningún starof tipos, un grado acotado. Esto significa que la igualdad de tipos reduce eso a un problema de tiempo polinomial y, suponiendo que normaliza las uniones, tampoco es muy lento, ya que solo es algo más rápido que la igualdad de árboles. Sospecho fuertemente que el caso general para toda esta propuesta (un subconjunto del problema de isomorfismo de subgrafo) es también polinomio-tiempo con coeficientes razonables. (El artículo de Wikipedia vinculado anteriormente tiene algunos ejemplos en las secciones de "Algoritmos" y referencias donde se pueden aplicar mayúsculas y minúsculas).


  3. Es probable que ninguna de estas claves sea grande , por lo que la mayor parte del costo de tiempo de ejecución real aquí se amortiza en la práctica con otras cosas. Siempre que sea rápido para teclas pequeñas, es lo suficientemente bueno.


  4. Todos los subgrafos que se compararían comparten al menos un nodo: el nodo raíz. (Esto representa el comienzo de la cadena). Por lo tanto, esto reduciría drásticamente el espacio del problema por sí solo y garantizaría una verificación de tiempo polinomial.


Y, por supuesto, la intersección entre estos tipos no es trivial , pero creo que existen factores redentores similares simplemente debido a las restricciones anteriores. En particular, la última restricción hace que sea obviamente polinomial-time to do.

* Matemáticamente, el género se define de forma un poco contradictoria para nosotros, los programadores (el número mínimo de agujeros que necesita hacer en una superficie para dibujar el gráfico sin saltos), pero un género acotado (número limitado de agujeros) implica un número limitado de saltos .

Usando esta propuesta concreta, así es como se traduce mi ejemplo de este comentario :

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

Editar: Esto también permitiría escribir correctamente el 90% del método _.get de Lodash y los métodos relacionados utilizando su taquigrafía de propiedad, como su método _.property(path) y su taquigrafía _.map(coll, path) . Probablemente también hay otros en los que no estoy pensando, pero ese es probablemente el más grande en el que puedo pensar. (Dejaré la implementación de ese tipo como ejercicio para el lector, pero puedo asegurarles que es posible con una combinación de eso y el truco habitual de tipos condicionales con un registro indexado inmediatamente, algo así como {0: ..., 1: ...}[Path extends "" ? 0 : 1] , para procesar la cadena de ruta estática).

Mi recomendación es que concentremos nuestros esfuerzos en implementar proveedores de tipos, que podrían usarse para implementar tipos de expresiones regulares.

¿Por qué tipos de proveedores en lugar de implementar directamente tipos de expresiones regulares? Porque

  1. Es una solución más genérica que agrega muchas posibilidades nuevas a TypeScript, lo que facilita la obtención de soporte de un grupo más amplio de desarrolladores más allá de aquellos que ven el valor en los tipos de cadenas de expresiones regulares.
  2. Los propietarios de repositorios mecanografiados parecen estar abiertos a esta idea y están esperando la propuesta adecuada. Ver # 3136

F # tiene un proveedor de tipo de expresión regular de código abierto.

Alguna información sobre proveedores de tipos: https://link.medium.com/0wS7vgaDQV

Uno podría imaginar que una vez que se implementan los proveedores de tipos y el proveedor de tipos de expresiones regulares se implementa como una biblioteca de código abierto, uno lo usaría así:

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 No estoy convencido de que sea el camino correcto a seguir, al menos no para esta solicitud.

  • TypeScript está escrito estructuralmente, no nominalmente escrito, y para la manipulación literal de cadenas, quiero conservar ese espíritu estructural. Los proveedores de tipos como ese crearían un subtipo string nominal donde RegexProvider</^foo$/> no se trataría como equivalente a "foo" , sino como un subtipo nominal del mismo. Además, RegexProvider</^foo$/> y RegexProvider</^fo{2}$/> serían tratados como dos tipos distintos, y eso es algo de lo que no soy fan. En cambio, mi propuesta se integra directamente con las cadenas en su núcleo, directamente informada por la teoría del reconocimiento del lenguaje formal para garantizar que encaje de forma natural.
  • Con el mío, no solo puedes concatenar cadenas, sino también extraer partes de cadenas a través de Key extends `on${infer K}` ? K : never o incluso Key extends `${Prefix}${infer Rest}` ? Rest : never . Los proveedores de tipos no ofrecen esta funcionalidad y no hay una forma clara de cómo debería hacerlo si se agregara dicha funcionalidad.
  • El mío es considerablemente más simple a nivel conceptual: solo estoy sugiriendo que agreguemos tipos de concatenación de cadenas y, para el RHS de los tipos condicionales, la capacidad de extraer su inverso. También propongo que se integre con string para tomar el lugar de una expresión regular /.*/ . No requiere cambios en la API y, aparte de las dos partes teóricamente complejas que en su mayoría están desacopladas del resto de la base del código, calcular si un tipo de literal de plantilla se puede asignar a otro y extraer un segmento de una cadena, es similar, si no más simple. , para implementar.

Por cierto, mi propuesta también podría escribir ese ejemplo de PhoneNumber . Es un poco más detallado, pero estoy tratando de modelar datos que ya están en la tierra de TS, no datos que existen en otros lugares (para qué son más útiles los proveedores de tipos de F #). (Vale la pena señalar que esto técnicamente se expandiría a la lista completa de posibles números de teléfono aquí).

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 $ /> y RegexProvider ^ fo {2} $ /> serían tratados como dos tipos distintos

Los proveedores de tipos podrían requerir la implementación de algún método equals o compare , de modo que el autor del proveedor de tipos de un proveedor de tipos de expresiones regulares podría definir que ambos casos anteriores son tipos equivalentes. El autor del proveedor de tipos podría implementar el tipo nominal o estructural a su gusto.

Quizás también sería posible implementar su tipo literal de cadena como proveedor de tipos. No creo que la sintaxis pueda ser la misma, pero podrías acercarte a un proveedor de tipos que acepte un número variable de argumentos.

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 Pero, ¿el tipo "123-456-7890" asignar a tu tipo? (Si es así, complicará la implementación y ralentizará mucho el corrector).

Semi-relacionado con la discusión en cuestión, ¿qué pasa si el tipo no es de una longitud fija (como un número de teléfono)? Una situación en la que me hubiera gustado usar esto recientemente es para almacenar un nombre de habitación, del formato thread_{number} .

La expresión regular para coincidir con dicho valor es thread_[1-9]\d* . Con lo que se propone, no parece factible (ni siquiera posible) igualar ese formato. La parte numérica del valor podría ser _cualquier_ longitud mayor que cero en esta situación.

@jhpratt Revisé mi propuesta para adaptarla, en la forma de starof ("0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")/^\d*$/ , ya que solo requería un pequeño cambio. Optimiza de la misma manera que string optimiza como /^[\u0000-\uFFFF]*$/ , así que decidí seguir adelante y generalizar eso.

No quiero extender starof más allá de eso, como aceptar uniones arbitrarias no recursivas, debido a preocupaciones de complejidad computacional: verificar si dos expresiones regulares arbitrarias * son equivalentes se puede hacer en el espacio polinomial o en el muy lentas en la práctica y AFAICT no puede tener ambas formas. Agregue soporte para cuadrar (como a{2} ), y es básicamente inviable (complejidad exponencial) . Esto es solo para la equivalencia, y verificar si una expresión regular coincide con un subconjunto de las cadenas que otra expresión regular coincide, requerida para verificar la asignabilidad, obviamente será aún más complicado.

* Expresiones regulares en el sentido matemático: solo incluyo caracteres individuales, () , (ab) , (a|b) y (a*) , donde a y b son (potencialmente diferentes) cada uno de los miembros de esta lista.

Esta es probablemente una pregunta tonta, pero ... ¿por qué no es bastante fácil, si está adecuadamente limitado, admitir una función de validación (ya sea lambda o con nombre)?

Por ejemplo, suponga que usamos ":" para indicar que el siguiente elemento es un validador (sustituya ":" por lo que quiera si tiene una opinión sobre esto):

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

Como comienzo inicial, el mecanografiado solo podía aceptar funciones de validación que:

  • tener un solo argumento, que se requiere / se supone que es del tipo "base"
  • solo las variables de referencia que están definidas en la función de validación
  • devolver un valor (que será obligado a bool en el proceso de validación)

Las condiciones anteriores parecen fáciles de verificar para el compilador mecanografiado, y una vez que se asumen esas condiciones, gran parte de la complejidad de la implementación desaparecerá.

Además, si es necesario para restringir el alcance inicial a un tamaño manejable:

  • Las funciones de validación solo se pueden agregar a un subconjunto de tipos nativos (cadena, número)

No creo que esta última restricción sea tan necesaria, pero si hay alguna duda sobre si lo sería, tampoco creo que valga la pena dedicar mucho tiempo a debatirla, porque una solución con la limitación anterior todavía resolvería una amplia gama de casos de uso del mundo real. Además, veo un pequeño inconveniente de las limitaciones anteriores porque relajarlas más tarde sería una extensión simple y natural que no requeriría ningún cambio en la sintaxis básica y simplemente expandiría la amplitud del soporte del lenguaje por parte del compilador.

@mewalig Eso significaría que algo que parece una función en tiempo de ejecución en realidad no se ejecutaría en tiempo de ejecución, sino en tiempo de compilación (y cada vez que desee verificar la asignabilidad). Estas funciones no podían acceder a nada desde el tiempo de ejecución (variables, funciones), lo que se sentiría bastante incómodo.

Además, generalmente no desea que el compilador ejecute cualquier cosa que le arroje, especialmente funciones mal optimizadas o completamente maliciosas while(true){} . Si desea metaprogramación, debe diseñarla de manera inteligente. Permitir que el código en tiempo de ejecución se ejecute aleatoriamente en tiempo de compilación sería la "forma PHP" de hacerlo.

Finalmente, la sintaxis que propones cambia el patrón habitual

let runtime: types = runtime;

(es decir, tipos después de dos puntos) de adentro hacia afuera, siendo efectivamente

type types = types: runtime;

lo cual es horrible. Así que gracias por tu propuesta, pero definitivamente es una mala idea.

Estas funciones no podían acceder a nada desde el tiempo de ejecución (variables, funciones), lo que se sentiría bastante incómodo.

Por supuesto que podrían , si el compilador tiene un tiempo de ejecución de ECMAScript disponible ( tsc tiene, ¡por cierto!). Obviamente, tiene un problema de ambigüedad con la semántica en tiempo de compilación de, por ejemplo, fetch() frente a la semántica en tiempo de ejecución, pero de eso se trata la iteración.

Permitir que el código en tiempo de ejecución se ejecute aleatoriamente en tiempo de compilación sería la "forma PHP" de hacerlo.

Es bastante similar a las funciones C ++ constexpr , que están bien. La solución es decir que constexpr solo puede usar constexpr , pero todo puede usar constexpr . Entonces podría tener constexpr -versiones equivalentes del sistema de archivos para el sistema de archivos en tiempo de compilación, lo cual podría ser bastante poderoso.

La sintaxis también me parece bastante buena: el LHS es un tipo, por supuesto, el RHS también es un tipo de algún tipo. Mi problema es más sobre cómo componer tipos más allá del tipo "base", pero eso también tiene solución.

Así que gracias por tu propuesta, pero definitivamente es una mala idea.

Puede terminar siendo una mala idea, pero por ahora solo veo una idea muy subespecificada que probablemente requerirá alejarse demasiado de los objetivos de la escritura mecanografiada. ¡No significa que no haya una buena idea que se parezca a ella!

La discusión sobre esta característica parece detenerse por ahora ( PR está cerrado y, de acuerdo con el equipo de notas de diseño , no quiero comprometerme con esto hasta que tengamos tipos nominales y firmas de índice generalizadas, y deberíamos saber cómo se ven).

De todos modos, quiero proponer otra extensión hipotética de las relaciones públicas actuales que admitiría la extracción de patrones de @isiahmeadows presentó su propia propuesta, pero para ser honesto, no puedo entenderlo ahora ...).

Realmente me gustan las relaciones públicas actuales y basaría mi propuesta en eso. Me gustaría proponer la sintaxis basada en la inferencia de argumentos de tipo genérico que tenemos para funciones (y tipos condicionales con infer palabra clave). Simplemente porque la gente ya tiene la intuición de que en la función genérica se pueden "extraer" tipos de objetos literales pasados.

Por ejemplo tenemos este tipo.

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

y podemos usar este tipo para probar tipos literales

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

Sin embargo, cuando usamos el tipo Regex en el parámetro de función, podemos usar la sintaxis de corchetes angulares para significar que queremos inferir cadenas coincidentes. Por ejemplo

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

observe que el tipo inferido de res1 es ["foo", "bar"]

¿Es útil?

  1. Función get de Ember.js / lodash

Puede implementar un captador de "ruta de cadena" de tipo seguro para que este código funcione:

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

Pero probablemente sería necesario resolver esto si queremos evitar muchas sobrecargas para un número máximo fijo de "profundidad" posible de get .

  1. Utilice parámetros extraídos en tipos mapeados.

Por ejemplo, si pudiéramos hacer algo como esto https://github.com/Microsoft/TypeScript/issues/12754. Entonces podríamos tener la posibilidad de invertir la función (quitar algún prefijo / sufijo de todas las propiedades del tipo dado). Este probablemente necesitaría introducir alguna forma más generalizada de sintaxis de tipo mapeado para elegir una nueva clave para la propiedad (por ejemplo, una sintaxis como { [ StripAsyncSuffix<P> for P in K ] : T[P] } , alguien ya propuso algo así)

Probablemente también habría otros casos de uso. Pero supongo que la mayoría encajaría en estos dos tipos (1. averiguar el tipo adecuado según el literal de cadena proporcionado, 2. transformar los nombres de propiedad del tipo de entrada en nuevos nombres de propiedad del nuevo tipo definido)

Esto es algo con lo que podríamos hacer.

Actualmente estoy creando reglas de pelusa personalizadas para poder validar las URL; sin embargo, esto sería mucho más fácil si pudiéramos definir los parámetros opcionales, lo que requiere una expresión regular para poder validar nuestros identificadores.

En general, esto nos proporcionaría mucho más poder para afirmar la validez de los accesorios en nuestra base de código.

¿Hay algún movimiento en los proveedores de tipos, literales de cadenas de plantillas u otras sugerencias? Esta sería una gran herramienta.

Mi solución para esto actualmente es usar una interfaz de marcador como esta .

interface TickerSymbol extends String {}

El único problema es que cuando quiero usarlo como clave de índice, tengo que convertirlo en string .

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

Sin embargo, JavaScript parece estar bien con el tipo de índice de String (con mayúscula S).

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 Tengo una propuesta aquí , y se creó una propuesta separada a fines de 2016 , entonces, ¿podrían actualizarse las etiquetas para esto?

Hemos revisado las propuestas anteriores y tenemos algunas preguntas y comentarios.

Aspectos problemáticos de las propuestas hasta ahora

Tipos de creación de emisión

Estamos comprometidos a mantener el sistema de tipos completamente borrado, por lo que las propuestas que requieren alias de tipos para producir código emitido están fuera de alcance. Destacaré algunos ejemplos en este hilo donde esto ha sucedido quizás de una manera que no es obvia:

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -220180091 - crea una función y un tipo al mismo tiempo

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

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -261519733 - también hace esto

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

Lo reiteraré: esto no es un comienzo . Los tipos en TypeScript se pueden componer y la emisión de JS a partir de tipos no es posible en este mundo. La propuesta más larga hasta la fecha tiene una gran cantidad de tipos de emisión; esto no es viable. Por ejemplo, esto requeriría una emisión dirigida de tipo extensa:

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

Prohibiciones en las intersecciones

En realidad, los tipos comunes y los tipos validados por expresiones regulares son realmente diferentes, por lo que necesitamos reglas sobre cómo manejar correctamente sus uniones e intersecciones.

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

TypeScript no puede generar errores en las instancias de intersecciones, por lo que esto no sería parte de ningún diseño final.

Ergonomía

En general, nuestra conclusión más destacada es que queremos algo en el que no escriba la misma expresión regular dos veces (una en el espacio de valor, una vez en el espacio de tipo).

Dadas las preocupaciones anteriores sobre el tipo emitir, la solución más realista es escribir la expresión en el espacio de valores:

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

Aún podría escribir la RegExp en el espacio de tipo, por supuesto, pero no habría validación en tiempo de ejecución disponible y cualquier uso no literal requeriría una nueva prueba o afirmación:

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

Recopilación y aclaración de casos de uso

Para un nuevo tipo de tipo, idealmente nos gustaría ver varios ejemplos donde:

  • El problema que se está resolviendo no tiene
  • El problema ocurre con una frecuencia significativa en bases de código reales.
  • Los resuelve solución propuesta que bien problema

Validación de literales en tiempo de compilación

Este hilo implica una amplia variedad de casos de uso; los ejemplos concretos han sido más raros. Es preocupante que muchos de estos ejemplos no parezcan estar completos : utilizan una expresión regular que rechazaría las entradas válidas.

  • Color de fuente : AFAIK, cualquier cosa que acepte colores hexadecimales también acepta, por ejemplo, "blanco" o "azul cielo". Esto también rechaza incorrectamente la sintaxis rgb(255, 0, 0) .
  • SSN, Zip, etc. - OK, pero ¿por qué hay SSN o códigos postales literales en su código? ¿Es esto realmente una necesidad de tipos nominales? ¿Qué sucede si tiene una subclase de cadenas que una expresión regular no puede describir con precisión? Ver "Propuestas competitivas"

    • Entero : rechaza incorrectamente "3e5"

    • Correo electrónico : por lo general , en su código ?

    • Especificaciones de borde de CSS : podría creer que una biblioteca independiente podría proporcionar una expresión regular precisa para describir el DSL que admite.

    • Pruebas de escritura : aquí es donde las entradas codificadas tienen algún sentido, aunque esto es casi un contrapunto porque su código de prueba probablemente debería proporcionar muchas entradas no válidas

    • Formatos de fecha : ¿cómo / por qué? Date tiene un constructor para esto; si la entrada proviene de fuera del tiempo de ejecución, solo desea un tipo nominal

    • URI : podría imaginar que fetch especificaría host para no estar con http(s?):

TODO: Ayúdenos identificando funciones de biblioteca reales que podrían beneficiarse de los tipos de RegExp y la expresión real que usaría.

Una preocupación es la "precisionitis": ¿qué sucede cuando alguien aparece de manera útil en DefinitelyTyped y agrega tipos RegExp a cada función en una biblioteca, rompiendo así todas las invocaciones no literales? Peor aún, los autores del archivo de definición tendrán que estar de acuerdo exactamente con los consumidores del mismo cuál es la "ortografía correcta" de una expresión regular de validación.
Parece que esto nos pone rápidamente en el camino hacia una situación de la Torre de Babel donde cada biblioteca tiene su propia versión de lo que califica como una URL, lo que califica como un nombre de host, lo que califica como un correo electrónico, etc., y cualquiera que conecte dos bibliotecas. tiene que insertar afirmaciones de tipo o copiar expresiones regulares para satisfacer al compilador.

Ejecución de verificaciones en tiempo de ejecución

Ha habido alguna discusión sobre las comprobaciones en las que queremos asegurarnos de que los argumentos de una función hayan sido validados por una expresión regular anterior, como fn en la sección anterior de Ergonomía . Esto parece sencillo y valioso, si la expresión regular con la que se debe realizar la prueba es bien conocida. Eso es un gran "si", sin embargo, en mi recuerdo, no puedo recordar una sola biblioteca que proporcione expresiones regulares de validación. Puede proporcionar funciones de validación, pero esto implica que la característica que se debe proporcionar son tipos nominales o etiquetados, no tipos de expresiones regulares.

Se agradecen las pruebas contrarias a esta evaluación.

Claves de propiedad / Indexadores de cadenas de expresiones regulares

Algunas bibliotecas tratan los objetos de acuerdo con los nombres de las propiedades. Por ejemplo, en React queremos aplicar tipos a cualquier prop cuyo nombre comience con aria- :

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

Este es efectivamente un concepto ortogonal (podríamos agregar tipos Regex sin agregar claves de propiedad Regex, y viceversa).

TODO (yo o cualquier persona): abra una edición separada para esto.

Propuestas competitivas

Tipos nominales o etiquetados

Digamos que tenemos tipos nominales / etiquetados de algún tipo:

type ZipCode = make_unique_type string;

Entonces podrías escribir una función

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

En este punto, ¿realmente necesitaría tipos de expresiones regulares? Consulte la sección de comprobación de "tiempo de compilación" para obtener más ideas.

Por el contrario, digamos que tenemos tipos de RegExp y no tipos nominales. Se vuelve bastante tentador comenzar (ab) a usarlos para escenarios de no validación:

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

Una cosa común en el hilo es que estas expresiones regulares ayudarían a validar el código de prueba, porque aunque en escenarios de producción el código se ejecutaría en cadenas proporcionadas en tiempo de ejecución en lugar de literales codificados, aún querría alguna validación de que sus cadenas de prueba eran " correcto". Sin embargo, esto parecería ser un argumento para cadenas nominales / etiquetadas / de marca, ya que estaría escribiendo la función de validación de cualquier manera, y el beneficio de las pruebas es que sabe que se ejecutan de manera exhaustiva (por lo tanto, cualquier error en las entradas de prueba sería ser marcado al principio del ciclo de desarrollo).

No cuestiones

Discutimos los siguientes aspectos y los consideramos no bloqueadores

Capacidades del anfitrión

Los tiempos de ejecución más nuevos admiten más sintaxis de RegExp que los tiempos de ejecución más antiguos. Dependiendo de dónde se ejecute el compilador de TypeScript, cierto código puede ser válido o no válido según las capacidades del tiempo de ejecución para analizar las características RegExp más nuevas. En la práctica, la mayoría de las nuevas funciones de RegExp son bastante esotéricas o se relacionan con la coincidencia de grupos, que no parecen coincidir con la mayoría de los casos de uso aquí.

Rendimiento

Las expresiones regulares pueden realizar una cantidad ilimitada de trabajo y la comparación con una cadena grande puede realizar una cantidad de trabajo arbitrariamente grande. Los usuarios ya pueden utilizar DOS a través de otros medios y es poco probable que escriban una expresión regular maliciosamente ineficaz.

Subtipado ( /\d*/ -> /.*/ ?), Unión, Intersección e Inhabitabilidad

En teoría, /\d+/ es un subtipo conocido de /.+/ . Supuestamente existen algoritmos para determinar si una expresión regular coincide con un subconjunto puro de otra (bajo ciertas restricciones), pero obviamente requeriría analizar la expresión. En la práctica , estamos 100% de acuerdo con que las expresiones regulares no formen relaciones de subtipo implícitas basadas en lo que coinciden; esto es probablemente incluso preferible.

Las operaciones de unión e intersección funcionarían "fuera de la caja" siempre que las relaciones de asignabilidad se definieran correctamente.

En TypeScript, cuando dos tipos primitivos "chocan" en una intersección, se reducen a never . Cuando se cruzan dos expresiones regulares, lo mantendríamos como /a/ & /b/ lugar de intentar producir una nueva expresión regular que coincida con la intersección de las dos expresiones. No habría ninguna reducción a never , necesitaríamos un algoritmo para demostrar que ninguna cadena podría satisfacer ambos lados (este es un problema paralelo al descrito anteriormente en re: subtipificación).

Próximos pasos

Para resumir, los siguientes pasos son:

  • Presentar un problema separado para las claves de propiedad con nombre Regex, también conocidas como indexadores de cadenas de expresiones regulares
  • Obtenga casos de uso concretos y plausibles para la validación de cadenas literales en tiempo de compilación

    • Ejemplo: identificar funciones en DefinitelyTyped u otras bibliotecas que se beneficiarían mucho de esto

  • Comprenda si los tipos nominales / etiquetados / de marca son una solución más flexible y de amplia aplicación para la validación no literal
  • Identificar bibliotecas que ya están proporcionando RegExes de validación

Caso de uso: funciones similares a Hyperscript (https://github.com/hyperhype/hyperscript)
Una función de hiperescripto generalmente se llama como h('div#some-id')
Un comparador de patrones de expresiones regulares permitiría determinar el tipo de retorno de h que sería HTMLDivElement en el caso de ejemplo.

Si el sistema de tipos pudiera agregar cadenas literales, entonces básicamente cualquier propiedad CSS podría ser segura para los tipos.

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

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

Los selectores de CSS también se pueden validar ( element.class#id - válido, div#.name - no válido)

Si la captura de grupos funciona (de alguna manera), entonces el método get Lodash podría ser seguro para los tipos

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

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

Esto también podría ser una cosa:

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

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

Caso de uso: funciones similares a Hyperscript (hyperhype / hyperscript)

¿Cómo se vería esa expresión regular o qué validación proporcionaría? ¿Es esto para la sobrecarga de funciones basadas en expresiones regulares?

FWIW La biblioteca acepta nombres de etiquetas con espacios de nombres y también funciona con nombres de etiquetas arbitrarios

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

También acepta una mezcla ilimitada de valores de identificación y clase.

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

Los selectores de CSS también se pueden validar

Los selectores CSS no se pueden validar con una expresión regular

¿Cómo se vería esa expresión regular o qué validación proporcionaría? ¿Es esto para la sobrecarga de funciones basadas en expresiones regulares?

No el OP, pero supongo, sí, algo como las sobrecargas HTMLDocument#createElement() , por ejemplo:

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

Estoy seguro de que los RE están incompletos. Tenga en cuenta que este es un caso especial de validación de selectores CSS, que se utilizan en muchos contextos de forma regular. Por ejemplo, está perfectamente bien que HTMLDocument.querySelector() devuelva HTMLElement como alternativa si está utilizando un selector complejo.

Sin embargo, tengo curiosidad por saber si hay ejemplos que no se sobrecarguen que sean factibles y útiles.

TODO: Ayúdenos identificando funciones de biblioteca reales que podrían beneficiarse de los tipos de RegExp y la expresión real que usaría.

Mi caso de uso es el que expliqué en este comentario en la biblioteca CCXT donde tengo cadenas que representan TickerSymbol s. Realmente no me importa si se verifican en busca de un patrón de expresión regular, pero quiero que se traten como subtipos de string para obtener asignaciones más estrictas, verificación de tipo de parámetro, etc. será muy útil cuando estoy haciendo programación funcional, con eso puedo rastrear fácilmente TickerSymbols, Monedas, Activos, etc.en tiempo de compilación donde en tiempo de ejecución son solo cadenas normales.

@omidkrad Parece que necesita tipos nominales , no tipos validados por expresiones regulares.

@ m93a En mi caso, estaré bien con los tipos nominales, pero para el mismo caso de uso, podría usar tipos validados por expresiones regulares para una verificación de tipos más estricta y autodocumentar los tipos de cadenas.

Los selectores de CSS también se pueden validar

Los selectores CSS no se pueden validar con una expresión regular

Bueno, si la expresión regular nos permitiera unirlos, podríamos copiar expresiones regulares CSS ..., ¿verdad?

El modelo de objetos con tipo CSS (borrador)

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

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

Potencialmente alivia el deseo de utilizar el modelo CSS escrito en cadenas.

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

@RyanCavanaugh Para Mithril en particular, el nombre de la etiqueta se extrae a través del grupo de captura en ^([^#\.\[\]]+) (el "div" ), pero hacer coincidir ^(${htmlTagNames.join("|")}) sería suficiente para nuestros propósitos. Entonces, usando mi propuesta , esto sería suficiente para mis propósitos:

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

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

En cuanto a eventos y atributos, podríamos cambiar a esta tierra de tipos una vez negada:

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

Por cierto, esta integración perfecta y la evitación de la complejidad es la razón por la que sigo prefiriendo mi propuesta a las expresiones regulares literales.


Sin embargo, no conozco ninguna forma de hacer esto con tipos puros de expresiones regulares. Quiero señalar eso.

TODO: Ayúdenos identificando funciones de biblioteca reales que podrían beneficiarse de los tipos de RegExp y la expresión real que usaría.

bent tiene un tipo de retorno diferente basado en lo que se da como una cadena que describe el tipo de respuesta esperado, por ejemplo

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

También acepta algunos otros argumentos, como el método y la URL como cadenas, pero estos pueden aparecer en cualquier posición, por lo que si intentamos usar uniones para describir todo el tipo de retorno ( 'json' | 'buffer' | 'string' ), esto sería tonto. a solo string cuando se combinan con la URL y los tipos de método en la unión, lo que significa que no podemos inferir automáticamente el tipo de retorno en función del tipo dado en la primera llamada.

@Ovyerus ¿cómo te ayudarían los tipos de

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, no estaba claro, lo siento, creo que mi problema fue más en la línea de hacer coincidir http(s): al comienzo de una cadena para detectar la URL base.

La firma de Bent está más en la línea de

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

Sin embargo, tener BaseUrl como cadena absorbe los HttpMethods y las uniones de tipo de retorno, que terminan en solo string . Tenerlo solo como una cadena también coincide "incorrectamente" con el funcionamiento de doblado, ya que verifica la presencia de ^http: o ^https: para determinar qué debe usar como URL base.

Si tuviéramos tipos de expresiones regulares, podría definir BaseUrl como type BaseUrl = /^https?:/ , y esto idealmente verificaría adecuadamente las cadenas que no son un método HTTP o una codificación de respuesta, además de no absorberlas en el string escribe.

Exactamente, yo soy el mismo.

-
Prokop Simek

El 20 de octubre de 2019 a las 03:23:30, Michael Mitchell ([email protected])
escribió:

Oh, no estaba claro, lo siento, creo que mi problema estaba más en la línea de
http (s) coincidentes: al comienzo de una cadena para detectar la URL base.

La firma de Bent está más en la línea de

tipo HttpMethods = 'OBTENER' | 'PATCH' | ... escriba StatusCode = número; escriba BaseUrl = cadena; // Aquí es donde idealmente necesitaría ver si una cadena coincide con http (s): type Headers = {[x: string]: any; };
tipo Opciones = HttpMethods | StatusCode | BaseUrl | Encabezados;
función doblada (... args: Opciones []): RequestFunctionfunción doblada (... args: (Opciones | 'json') []): RequestFunction// etcétera

Sin embargo, tener BaseUrl como cadena absorbe los HttpMethods y devuelve
uniones de tipo, que terminan como una cuerda. Teniéndolo como una cuerda
también "incorrectamente" coincide con el funcionamiento de doblado, ya que verifica la presencia
de ^ http: o ^ https: para determinar qué se debe utilizar como base
url.

Si tuviéramos tipos de expresiones regulares, podría definir BaseUrl como tipo BaseUrl = / ^ https?: /,
y esto idealmente verificaría adecuadamente las cadenas que no son un método HTTP o
codificación de respuesta, así como no absorberlos en el tipo de cadena.

-
Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/microsoft/TypeScript/issues/6579?email_source=notifications&email_token=ABJ3U4JNK3V5MV4DJH73ZU3QPOXJFA5CNFSM4BZLAVSKYY3PNVWWK3TUL52HS4DFVDVREXWJWK3TUL52HS4DFVDVREXG43VM2
o darse de baja
https://github.com/notifications/unsubscribe-auth/ABJ3U4PHBXO4766LK7P7UXDQPOXJFANCNFSM4BZLAVSA
.

La idea que tenía de un caso de uso era detectar tipos de parámetros en una función.

Básicamente, tengo un formato de expresión regular bien definido de una cadena que representa un identificador. Podría usar decoradores, pero un tipo de cadena extendido me permitiría usar un tipo para representar el identificador pasado a la función.

Para reiterar, necesitamos ejemplos de código JavaScript que desea escribir a máquina; de lo contrario, solo podemos adivinar lo que está tratando de modelar (y si ya hay una forma de modelarlo).

@DanielRosenwasser A continuación se muestra un ejemplo de código sobre el que nos gustaría aplicar la escritura. http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAqjCSARKBeKBnYAnAlgOwHMBYAKFIGMAbAQ3XVnQiygG9SoOoxcA3a4aJn45yULBGoATAPZ5KIKAFtq + + GIywAuWAmRoA5NQCcEAAwBGQwCMArAFoAZtYBMAdlsAWcvYActw2ZM7JzNJF28TCCcANgtLPXZOAHpErl5 QWBhUXEpWXklFTw1Ji04JFQoMycAZihkqAA5AHkAFSgAQQAZTqaAdQBRRASOeu4cPgEMTOARMQkZOQVlVXVSnQq9PGlgW2pbAFd9nEk9OpSAZQAJJphO5Ga2gCF + + JU6 wah ++ BBL-oAlYZQciyTBYfbkYDSLAACkBnC4 + 0slFmxzWSAANHDOGBEcjRJYsNQ8JItKD8ARMSR4fCcUjZuocNRKFo8PtFJYmJTqdjcbNyDkBJJHiA0boGEwAHTLIrqACUrFICQAvqQVSQgA

@yannickglt , ¿parece que desea un tipo nominal, no un tipo de

// 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, el hecho de que pueda describir un UUID con una expresión regular es un artefacto del formato de la cadena en sí, mientras que lo que está tratando de expresar es que los UUID son un tipo especial de tipo cuyo formato de respaldo resulta ser una cadena .

Entonces, la combinación de Assertion Functions de 3.7 y la función nominal puede hacer esto (?)

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

  }
}

¿Esto también fallará?

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

Después de pensar un rato, veo un problema con mi solución propuesta 🤔
El asserts solo desencadena un error en tiempo de
El Regex-Validation podría desencadenar un error en tiempo de compilación -> 👍
De lo contrario, esta propuesta no tiene sentido

Editar:
Otro problema: someFunc(uuid: any): asserts uuid is UUID no devuelve un UUID, arroja o devuelve is UUID -> true .
Entonces no puedo usar esta función para asignar un UUID de esta manera a mainUser

@RyanCavanaugh Queremos que estos se escriban correctamente para Mithril:

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

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

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

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

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

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

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

Queremos rechazar estáticamente estos:

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

Idealmente, también querríamos rechazarlos estáticamente, pero no es una prioridad tan alta y podemos sobrevivir sin ellos:

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

Esto requeriría una definición de tipo mucho más complicada, una en la que necesitaríamos un mensaje de falla de verificación de tipo personalizado para ayudar a los usuarios a descubrir por qué no se pudo escribir la verificación.

Otras bibliotecas de hiperescriptos y marcos basados ​​en hiperescriptos como react-hyperscript también tienen preocupaciones similares.

¡Espero que esto ayude!

@isiahmeadows es una mejor manera de utilizar algún tipo de generador de cadenas de selección, que devolverá cadenas de marca, con los tipos correctos. Igual que:

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

@ anion155 También hay otras formas de llegar allí, pero se trata de escribir una biblioteca cuya API fue diseñada por su autor original en 2014. Si estuviera diseñando su API ahora, probablemente usaría m("div", {...attrs}, ...children) con nada del azúcar hiperescrito (más fácil de escribir, mucho más simple de procesar), pero ahora es demasiado tarde para hacer mucho al respecto.

Tengo MUCHO que decir. Sin embargo, estoy impaciente. Entonces, liberaré mis pensamientos poco a poco.

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

En cuanto a "precisionitis" (hombre, me encanta esa palabra),
No creo que debamos preocuparnos demasiado por eso.

El sistema de tipos ya se está completando.
Esto básicamente significa que podemos ser súper precisos en muchas cosas.
(¿Cómo modelar todo SQL? Enchufe desvergonzado = P)

Pero no ves (demasiadas) personas haciendo todo lo posible y usando todos los operadores de tipos de formas locas que impiden que las bibliotecas sean compatibles entre sí. Me gusta pensar que los autores de bibliotecas tienden a ser lo suficientemente sensatos ... ¿verdad?

No es frecuente que haya deseado tipos de patrones de cadenas / tipos de cadenas validados por expresiones regulares, pero definitivamente habrían ayudado a aumentar la seguridad de tipos de mi base de código.


Caso de uso

En lo alto de mi cabeza, puedo pensar en un ejemplo reciente. (Hay un montón más pero soy un ser olvidadizo)

Al integrarse con la API de Stripe (una plataforma de procesamiento de pagos), usan ch_ para charge identificadores relacionados, re_ para refund identificadores relacionados, etc.

Hubiera sido bueno codificarlos con PatternOf</^ch_.+/> y PatternOf</^re_.+/> .

De esta forma, al realizar errores tipográficos como,

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

Me saldría un error

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

Por mucho que me gusten los tipos nominales / etiquetados, son mucho menos ergonómicos y propensos a errores.
Siempre veo los tipos nominales / etiquetados como último recurso , porque significa que hay algo que el sistema de tipos TS simplemente no puede modelar.

Además, los tipos etiquetados son excelentes para los tipos fantasma.
Los tipos nominales básicamente nunca son útiles.
(De acuerdo, puedo ser parcial. Son útiles solo por unique symbol pero me gusta pensar que no estoy completamente equivocado).

El patrón "ValueObject" para la validación es aún peor y no me molestaré en hablar de ello.


Comparación

A continuación, compararé lo siguiente,

  • Tipos de patrón de cadena / tipos de cadena validados por expresiones regulares
  • Tipos nominales
  • Tipos de etiquetas estructurales

Todos podemos estar de acuerdo en que el patrón "ValueObject" es la peor solución, y no molestarnos con él en las comparaciones, ¿verdad?


Tipos de patrones de cadena

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

Mira eso.

  • Perfecto para cadenas literales.
  • No está mal para string no literales.

Tipos nominales ...

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

Mira eso.

  • TERRIBLE para cadenas literales.
  • Después de superar el obstáculo literal de la cadena, no está tan mal ... ¿verdad?

Pero el caso de uso principal de esta propuesta son los literales de cadena.
Entonces, esta es una alternativa terrible.


Tipos de etiquetas estructurales ...

Los tipos de etiquetas estructurales no son muy diferentes de los tipos nominales ...

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

Mira eso.

  • TERRIBLE para cadenas literales.
  • Después de superar el obstáculo literal de la cadena, no está tan mal ... ¿verdad?

Pero el caso de uso principal de esta propuesta son los literales de cadena.
Entonces, esta es una alternativa terrible.

Además, este ejemplo de tipo de etiqueta estructural es una copia y pegado literal (ha, juego de palabras) del ejemplo de tipo nominal .

La única diferencia está en cómo se declaran los tipos StripeChargeId y StripeRefundId .

Aunque el código es básicamente el mismo, los tipos estructurales son mejores que los tipos nominales. (Esto lo aclararé en el próximo post, lo juro).


Conclusión

¡Esto es solo una conclusión para este comentario! ¡No es una conclusión de mis pensamientos generales!

Los tipos de patrones de cadenas / tipos de cadenas validados por expresiones regulares son más ergonómicos que los tipos de etiquetas nominales / estructurales. Con suerte, mis simples ejemplos no fueron demasiado artificiales y lo han demostrado suficientemente.


Conclusión (extra)

En la medida de lo posible, las formas de tomar el subconjunto de un tipo primitivo siempre deben preferirse a los tipos nominal / estructural de etiqueta / valor-objeto.

Ejemplos de tomar el subconjunto de tipos primitivos,

  • string literales
  • number literales (excluyendo NaN, Infinity, -Infinity )
  • boolean literales
  • bigint literales
  • Incluso unique symbol solo está tomando un subconjunto de symbol

De los ejemplos anteriores, solo boolean es "suficientemente finito". Solo tiene dos valores.
Los desarrolladores están satisfechos con tener los literales true y false porque no hay mucho más que pedir.


El tipo number es finito pero tiene tantos valores que bien podríamos considerarlo infinito.
También hay agujeros en los literales que podemos especificar.

Es por eso que el tipo de número de rango y los problemas de NaN, Infinity, -Infinity son tan populares y siguen apareciendo. Ser capaz de especificar un pequeño conjunto finito de valores a partir de un conjunto infinito no es suficiente.

Especificar un rango es una de las ideas más comunes / naturales que se le ocurren a alguien cuando necesita especificar un gran subconjunto finito / infinito de un conjunto infinito.


El tipo bigint es básicamente infinito, limitado solo por la memoria.

También contribuye a la popularidad del problema del tipo de número de rango.


El tipo string es básicamente infinito, limitado solo por la memoria.

Y esta es la razón por la que este problema de tipo de cadena de tipo de patrón de cadena / tipo de cadena validado por expresiones regulares es tan popular.

Especificar una expresión regular es una de las ideas más comunes / naturales que se le ocurren a alguien cuando necesita especificar un gran subconjunto finito / infinito de un conjunto infinito.


El tipo symbol ... También es infinito. Y también ilimitado, prácticamente.

Pero los elementos del tipo symbol prácticamente no están relacionados entre sí, en casi todos los sentidos. Y, entonces, nadie ha planteado el problema de preguntar: "¿Puedo tener una manera de especificar un gran subconjunto finito / infinito de symbol ?".

Para la mayoría de la gente, esa pregunta ni siquiera tiene sentido. No hay una forma significativa de hacer esto (¿verdad?)


Sin embargo, el simple hecho de poder declarar subconjuntos de primitivas no es muy útil. También necesitamos,

  • Los literales del tipo correcto deben ser asignables sin más trabajo.

Afortunadamente, TS es lo suficientemente cuerdo como para permitir esto.

¡Imagínese no poder pasar false a (arg : false) => void !

  • Formas integradas de estrechar

    Por el momento, para estos literales, tenemos == & === como formas integradas de reducción.

    ¡Imagínese la necesidad de escribir un nuevo tipo de protección para cada literal!

El problema con los tipos de objeto de valor / etiqueta estructural / nominal es que básicamente no cumplen los dos criterios anteriores. Convierten los tipos primitivos en tipos torpes que no son del todo tipos de objetos, pero deben manejarse como tipos de objetos, de todos modos.

Ergonomía

De acuerdo, aquí hay más detalles sobre los tipos de etiquetas de patrón de cadena frente a nominal frente a estructural.

Estos argumentos también se aplican a https://github.com/microsoft/TypeScript/issues/15480 .


Compatibilidad entre bibliotecas

Los tipos nominales son los peores en cuanto a compatibilidad entre bibliotecas.
Es como usar unique symbol en dos bibliotecas e intentar que interoperen.
Simplemente no se puede hacer.
Necesita utilizar un tipo de protección estándar, o el operador de confianza ( as ).

También necesitará más texto estándar para un guardia de aserción.

Si el tipo no requiere compatibilidad entre bibliotecas, entonces usar tipos nominales está bien ...
Incluso si es muy poco ergonómico (ver el ejemplo anterior).


Para tipos estructurales, si la biblioteca A tiene,

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

Y la biblioteca B tiene,

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

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

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

Entonces necesitará un protector de tipo estándar, o el operador de confianza ( as ).

También necesitará más texto estándar para un guardia de aserción.

Si el tipo no requiere compatibilidad entre bibliotecas, entonces usar tipos estructurales está bien ...
Incluso si es muy poco ergonómico (ver el ejemplo anterior).


Para los tipos de patrones de cadena, si la biblioteca A tiene,

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

Y la biblioteca B tiene,

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

Suponga que ambas bibliotecas siempre producen cadenas por StripeChargeId que satisfarán los requisitos de ambas bibliotecas. La biblioteca A es simplemente "más perezosa" con su validación. Y la biblioteca B es "más estricta" con su validación.

Entonces, es un poco molesto. Pero no está tan mal.
Porque puede usar libraryB.stripeChargeIdRegex.test(libraryA_stripeChargeId) como typeguard. No es necesario utilizar el operador de confianza ( as ).

Sin embargo, todavía necesitará un texto estándar para los guardias de afirmación.

Si el tipo no requiere compatibilidad entre bibliotecas, entonces el uso de tipos de patrones de cadena es perfecto y también muy ergonómico.


Si necesita compatibilidad entre bibliotecas, los tipos de patrones de cadena son mejores que los tipos de etiquetas estructurales. Escúchame.

Si se comprende bien el dominio que se está modelando, es muy probable que varios autores de bibliotecas aislados terminen escribiendo la misma expresión regular. Con los tipos de etiquetas estructurales, todos podrían simplemente escribir las propiedades y los tipos que deseen en las etiquetas.

Si hay un formato de cadena estándar que especifica para lo que sea que se esté modelando, entonces está básicamente garantizado que todos los autores de la biblioteca escribirán la misma expresión regular. Si escriben una expresión regular diferente, realmente no están siguiendo el estándar. ¿Quieres usar su biblioteca? Con los tipos de etiquetas estructurales, todos podrían escribir lo que sea. (¿A menos que alguien comience un estándar de tipo de etiqueta estructural que a todos les interese? Lol)


Compatibilidad entre versiones

Como es habitual, los tipos nominales son los peores en cuanto a compatibilidad entre versiones.
Oh, ¿le subiste a tu biblioteca un parche o una versión menor?
¿El tipo de decalaración sigue siendo el mismo?
¿El código sigue siendo el mismo?
No. Son de diferentes tipos.

image


Los tipos de etiquetas estructurales aún se pueden asignar en todas las versiones (incluso las versiones principales), siempre que el tipo de etiqueta sea estructuralmente el mismo.


Los tipos de patrones de cadena aún se pueden asignar, entre versiones (incluso versiones principales), siempre que la expresión regular sea la misma.

¿O simplemente podríamos ejecutar un algoritmo completo de PSPACE para determinar si las expresiones regulares son las mismas? También podemos determinar qué subclases de expresiones regulares son las más comunes y ejecutar algoritmos de equivalencia optimizados para esas ... Pero eso parece un gran esfuerzo.

Sería genial tener verificaciones de subtipos de expresiones regulares, y definitivamente haría que el uso de tipos de patrones de cuerda sea más ergonómico. Al igual que la forma en que las comprobaciones de subtipos de rango beneficiarían a la propuesta de tipo de rango de números.

[Editar]
En este comentario,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Alguien vinculado a
https://bora.uib.no/handle/1956/3956

Titulado, "El problema de la inclusión de las expresiones regulares"
[/Editar]


Calderería

TODO (Pero podemos ver que los tipos de patrones de cadena tienen la menor cantidad de texto estándar)

Invocación literal

TODO (Pero podemos ver que los tipos de patrones de cadena son los que mejor admiten la invocación literal)

Invocación no literal

TODO (Pero podemos ver que los tipos de patrones de cadena admiten mejor la invocación no literal)

Más sobre https://github.com/microsoft/TypeScript/issues/6579#issuecomment -542405537

TypeScript no puede generar errores en las instancias de intersecciones, por lo que esto no sería parte de ningún diseño final.

No sé por qué la gente quería prohibir las intersecciones, pero tiene toda la razón en que prohibirlas no tiene sentido.


rompiendo así toda invocación no literal?

Bueno, no todas las invocaciones no literales.

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
}

Romper con una invocación no literal, donde no se puede probar que coincide con la expresión regular, no es necesariamente algo malo. Es solo una cuestión de seguridad de tipos.

Eso es como decir que los literales de cadena son malos porque ahora fallan las invocaciones no literales.
Los tipos de patrón de cadena / tipos de cadena validados por expresiones regulares simplemente le permiten definir uniones de un número infinito de literales de cadena.


cualquier uso no literal requeriría una nueva prueba o afirmación:

No veo eso como un problema en absoluto.
Es lo mismo con los tipos nominales / etiquetados en este momento.
O intentar pasar string a una función que espera cadenas literales.
O tratando de pasar un tipo más ancho a un tipo más estrecho.

En este caso particular, ha demostrado que const ZipCode = /^\d\d\d\d\d$/; y ZipCode.test(s) pueden actuar como un tipo de protección. Esto ciertamente ayudará con la ergonomía.


  • El problema que se está resolviendo no tiene mejor alternativa (incluidas alternativas plausibles que aún no están en el idioma)

Bueno, espero haber demostrado que los tipos de etiquetas nominales / estructurales no son la mejor alternativa. De hecho, son bastante malos.

  • El problema ocurre con una frecuencia significativa en bases de código reales.

Uhh ... Déjame contactarte sobre eso ...

  • La solución propuesta resuelve bien ese problema

El tipo de patrón de cuerda propuesto parece bastante bueno.


TODO: Ayúdenos identificando funciones de biblioteca reales que podrían beneficiarse de los tipos de RegExp y la expresión real que usaría.

Su opinión es que los tipos nominales / etiquetados son lo suficientemente buenos para un uso no literal.
Por lo tanto, cualquier caso de uso que se presente que muestre que el uso no literal no es lo suficientemente bueno, porque los tipos nominales / etiquetados lo cubren.

Sin embargo, hemos visto que, incluso para uso no literal,

  • Los tipos de etiquetas nominales / estructurales sufren problemas de compatibilidad entre bibliotecas / versiones
  • La cantidad de texto estándar para los tipos de etiquetas nominales / estructurales es significativamente mayor que el texto estándar para los tipos de patrones de encordado

Además, parece que los casos de uso literales planteados no han sido satisfactorios para usted, porque intentan hacer cosas tontas como la validación de correo electrónico o usan expresiones regulares que no son lo suficientemente precisas.


Pruebas de escritura: aquí es donde las entradas codificadas tienen algún sentido, aunque esto es casi un contrapunto porque su código de prueba probablemente debería proporcionar muchas entradas no válidas

Un buen caso de uso que surgió fue escribir pruebas en tiempo de ejecución . Y tiene razón, que también deberían arrojar muchas entradas no válidas para las pruebas en tiempo de ejecución.

Pero esa no es una razón para no admitir tipos de patrones de cadena. Puede darse el caso de que quieran probar entradas válidas en un determinado archivo y accidentalmente den una entrada no válida.

Pero, debido a que tienen que usar un tipo de protección o un operador de confianza ( as ) o un objeto de valor, ahora obtendrán un error en tiempo de ejecución, en lugar de saber que la prueba fallará antes de tiempo. .

El uso del operador de confianza ( as ) para pruebas en tiempo de ejecución solo debe reservarse para probar entradas no válidas. Al querer probar entradas válidas, es más claro no necesitar hacks para asignar literales a un tipo de etiqueta nominal / estructural.

Si alguna vez cambian la expresión regular en el futuro, sería bueno si sus pruebas ahora ni siquiera se ejecutan, debido a problemas de asignabilidad. Si solo nos as en todas partes en sus pruebas, no lo sabrán hasta que ejecuten las pruebas.

Y si el autor de la biblioteca solo usa as todas partes al hacer una prueba interna de su propia biblioteca ... ¿Qué pasa con los consumidores intermedios? ¿No estarán también tentados a usar as todas partes y se encontrarán con problemas de tiempo de ejecución al actualizar a una nueva versión?

Con los tipos de patrones de cadena, hay menos razones para usar as todas partes y tanto el autor de la biblioteca como los consumidores posteriores sabrán de los cambios de ruptura más fácilmente.

(Algo largo, pero espero que algunos de mis puntos se hayan superado).


Además, escribo muchas pruebas en tiempo de

Sería bueno si pudiera probar que un cierto string literal fallará / pasará una verificación de expresiones regulares en mis pruebas en tiempo de compilación. Por el momento, no puedo tener pruebas en tiempo de compilación para estas cosas y necesito usar una prueba en tiempo de ejecución.

Y si falla / pasa mis pruebas en tiempo de compilación, entonces tendré confianza en que los consumidores posteriores pueden usar esos literales de cadena (o similares) y esperar que se comporten de la manera correcta.


Parece que esto nos pone rápidamente en el camino hacia una situación de la Torre de Babel ...

En realidad, esto es aún más cierto cuando se usan tipos de etiquetas nominales / estructurales. Como han demostrado los ejemplos anteriores, funcionan muy bien para la compatibilidad entre bibliotecas / versiones ...

Sin embargo, los tipos de expresiones regulares / patrones de cadena tienen una buena posibilidad de no caer en ese problema (con suerte, gracias a la estandarización y a los autores de bibliotecas cuerdos).


EDITAR

Una cosa común en el hilo es que estas expresiones regulares ayudarían a validar el código de prueba, porque aunque en escenarios de producción el código se ejecutaría en cadenas proporcionadas en tiempo de ejecución en lugar de literales codificados, aún querría alguna validación de que sus cadenas de prueba eran " correcto". Sin embargo, esto parecería ser un argumento para cadenas nominales / etiquetadas / de marca, ya que estaría escribiendo la función de validación de cualquier manera, y el beneficio de las pruebas es que sabe que se ejecutan de manera exhaustiva (por lo tanto, cualquier error en las entradas de prueba sería ser marcado al principio del ciclo de desarrollo).

Ah ... debería haber leído todo antes de escribir esto ...

De todos modos, tengo algunos ejemplos conmigo, donde los tipos de patrones de cadena son útiles.


Biblioteca de declaración de ruta HTTP

Con esta biblioteca, puede crear objetos de declaración de ruta HTTP. Esta declaración es utilizada tanto por el cliente como por el servidor.

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

Estas son las restricciones para .append() ,

  • Solo literales de cadena (no se puede hacer cumplir esto en este momento, pero si usa no literales, el generador de declaraciones de ruta se convierte en basura)
  • Debe comenzar con una barra inclinada hacia adelante ( / )
  • No debe terminar con una barra inclinada hacia adelante ( / )
  • No debe contener el carácter de conlon ( : ); está reservado para parámetros
  • No debe contener dos o más barras diagonales consecutivas ( // )

En este momento, solo tengo comprobaciones en tiempo de ejecución para estos, que arrojan errores. Me gustaría que los consumidores posteriores tuvieran que seguir estas restricciones sin necesidad de leer algún comentario de Github README o JSDoc. Simplemente escribe la ruta y verás líneas onduladas rojas.


Otras cosas

También tengo expresiones regulares para cadenas hexadecimales, cadenas alfanuméricas.

Yo tambien tengo esto,

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

Veo esto,

Entero: rechaza incorrectamente "3e5"

También tengo esto, que no es una expresión regular entera pero usa 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,
    };
}

Sin embargo, también tengo este comentario,

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

Constructor de expresiones regulares

Curiosamente, el constructor RegExp se beneficiará de los tipos de cadenas validados por expresiones regulares.

Ahora mismo lo es

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

Sin embargo, podríamos tener,

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

TL; DR (Por favor, léalo, sin embargo, me esforcé mucho en esto: llorar :)

  • Los tipos de patrones de cuerda son más ergonómicos que los tipos de etiquetas nominales / estructurales

    • Menos repetición

  • Los tipos de patrones de cadena tienen menos probabilidades que los tipos de etiquetas nominales / estructurales de convertirse en una situación de Torre de Babel

    • Especialmente con verificaciones de subtipos de expresiones regulares

  • Los tipos de patrones de cadena son la forma más natural de definir grandes subconjuntos finitos / infinitos del tipo string

    • ¡La introducción de esta función podría incluso hacer que la gente piense en formatos de cadena válidos para sus bibliotecas más de cerca!

  • Los tipos de patrones de cadena permiten una mayor seguridad en tiempo de compilación para algunas bibliotecas (déjeme responderle sobre la prevalencia ... se escapa )

    • Constructor de expresiones regulares, cadenas hexadecimales / alfanuméricas, declaraciones de ruta de ruta, identificadores de cadena para bases de datos, etc.


¿Por qué tus expresiones regulares son tan malas?

Varios de los casos de uso planteados por otros querían introducir tipos de patrones de cadena para adaptarse a las bibliotecas

Muchas veces, siento que estas bibliotecas existentes ni siquiera usan expresiones regulares tanto para validar su entrada. O usan una expresión regular para realizar una validación simple. Luego, utilizan un analizador más complicado para realizar la validación real.

¡Pero este es un caso de uso válido real para los tipos de patrones de cadena!


Tipos de patrones de cadena para validar superconjuntos de valores de cadena válidos

Claro, una cadena que comienza con / , no termina con / , no contiene / consecutivos y no contiene : pasará el " Regex de ruta HTTP ". Pero esto solo significa que el conjunto de valores que pasan esta expresión regular es un superconjunto de rutas HTTP válidas.

Más abajo, tenemos un analizador de ruta de URL real que verifica que ? no se use, # no se use, algunos caracteres se hayan escapado, etc.

¡Pero con este tipo de patrón de cadena simple, ya hemos eliminado una gran clase de problemas comunes que puede encontrar un usuario de la biblioteca! ¡Y también lo eliminamos durante el tiempo de compilación!

No es frecuente que un usuario utilice ? en sus rutas HTTP, porque la mayoría tiene la experiencia suficiente para saber que ? es el comienzo de una cadena de consulta.


Me acabo de dar cuenta de que ya conoce este caso de uso.

Este hilo implica una amplia variedad de casos de uso; los ejemplos concretos han sido más raros. Es preocupante que muchos de estos ejemplos no parezcan estar completos: utilizan una expresión regular que rechazaría las entradas válidas.

Entonces, claro, muchas de las expresiones regulares propuestas no están "completas".
Pero siempre que no rechacen una entrada válida, debería estar bien, ¿verdad?

Está bien si permiten entradas no válidas, ¿verdad?
Dado que podríamos tener un analizador "real" durante el tiempo de ejecución, manejar la validación completa.
Y una verificación en tiempo de compilación puede eliminar muchos problemas comunes para los usuarios intermedios, aumentando la productividad.

Los ejemplos que rechazan una entrada válida deberían ser bastante fáciles de modificar, de modo que no rechacen una entrada válida, pero permitan una entrada no válida.


Tipos e intersecciones de patrones de cuerdas

De todos modos, los tipos de intersección en los tipos de patrones de cuerdas serían súper útiles.

Mi ejemplo de .append() podría escribirse como,

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;

El not PatternOf</\/\//> también podría ser,
PatternOf</^((([/])(?!\3))|[^/])+$/> pero esto es mucho más complicado

Gracias, @AnyhowStep , por las extensas demostraciones. Quería criticarte por hacerme leer tanto, ¡pero resultó ser muy útil!

A menudo me cuesta escribir mis apis internas llenas de parámetros de cadena, e inevitablemente termino con muchos condicionales que se lanzan en tiempo de ejecución. Inevitablemente, mis consumidores necesitan duplicar estas comprobaciones de patrones, ya que no quieren una excepción, quieren una forma especial de manejar la falla.

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

En el mundo de las cadenas y los patrones, un string genérico es más o menos lo mismo que unknown , eliminando una gran cantidad de seguridad de tipos en favor de las comprobaciones en tiempo de ejecución y causando inconvenientes para mis desarrolladores consumidores.

Para algunos de los casos de uso mencionados, solo se necesitaría un pequeño subconjunto de Regex, por ejemplo, la coincidencia de prefijos.

Potencialmente, esto podría hacerse con características más generales del lenguaje TS como Variadic Kinds # 5453 y la inferencia de tipos al difundir tipos de cadenas literales.

Especulación futura:

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;

Para algunos de los casos de uso mencionados, solo se necesitaría un pequeño subconjunto de Regex, por ejemplo, la coincidencia de prefijos.

Sigo manteniendo mi propuesta , que ofrece esto + algunas otras cosas, básicamente un superconjunto muy pequeño de lenguajes sin estrellas que aún puede verificar de manera razonablemente eficiente para la subconjunto y la igualdad. Y hasta ahora, no he visto ninguna otra propuesta que intente abordar el aspecto de rendimiento de las expresiones regulares arbitrarias, la mayor preocupación que tiene el equipo de TS.

El problema con los lenguajes sin estrellas es, como su nombre lo indica, que no se pueden usar estrellas, lo que dificulta la validación de cosas como las URL. Además, la mayoría de la gente probablemente querrá estrellas y solo usará un número arbitrario de secuencias repetidas para emularlas, lo que dificultaría la búsqueda de subconjuntos.

Y el rendimiento de la mayoría de las expresiones regulares representables de DFA no es tan malo, y es posible verificarlas en busca de subconjuntos o superconjuntos.

Sin embargo, todavía puede obtener * .

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

@TijmenW Lea mi propuesta un poco más de cerca: hay algunas razones ocultas allí y algunas características pequeñas que la hacen realmente práctica. No se limita directamente a especificar gramáticas sin estrellas, sino a un pequeño superconjunto extendido con lo suficiente para que sea prácticamente útil para mi caso de uso semi-avanzado. En particular, puede hacer starof ('a' | 'b' | ...) para caracteres individuales y puede usar string como equivalente a starof UnionOfAllCodePoints (de hecho, ya no es una primitiva en teoría).

Además, comprobar si un idioma regular coincide con un subconjunto de lo que coincide con otro idioma regular es NP-completo y equivalente al problema de isomorfismo de subgrafo general. Esta es la razón por la que no puede simplemente hacer lenguajes regulares estándar, y por eso traté de limitar starof tanto como sea posible, para tratar de mantener baja la complejidad computacional teórica.

TODO: Ayúdenos identificando funciones de biblioteca reales que podrían beneficiarse de los tipos de RegExp y la expresión real que usaría.

Tome esto con un grano de sal, ya que es una biblioteca nueva, pero cualquier biblioteca como https://github.com/ostrowr/ts-json-validator sería mucho más útil con algo como un tipo de expresión regular.

El objetivo de la biblioteca es generar pares de esquema de tipo TypeScript / JSON <T, s> manera que

  1. Cualquier tipo que s pueda validar se puede asignar a T
  2. La menor cantidad posible de tipos que se puedan asignar a T fallan la validación cuando se ejecutan contra s .

Un tipo de expresión regular mejoraría el rigor de (2) al permitir que el tipo validado sea más estricto sobre al menos las siguientes palabras clave:

  • format
  • patternProperties
  • propertyNames

TODO: Ayúdenos identificando funciones de biblioteca reales que podrían beneficiarse de los tipos de RegExp y la expresión real que usaría.

Todas las bibliotecas de interfaz de Excel pueden usar la validación de tipos como A1 o A5:B7 .

Claves de propiedad / Indexadores de cadenas de expresiones regulares

Algunas bibliotecas tratan los objetos de acuerdo con los nombres de las propiedades. Por ejemplo, en React queremos aplicar tipos a cualquier prop cuyo nombre comience con aria- :

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

Este es efectivamente un concepto ortogonal (podríamos agregar tipos Regex sin agregar claves de propiedad Regex, y viceversa).

Sé que esto es un poco ortogonal a todo lo que sucede aquí, pero Wesley pensó que podría usar nuestra información. Esto sigue apareciendo en Fabric por múltiples razones. Como biblioteca de componentes, queremos poder elevar una interfaz de accesorios de componentes que refleje con precisión la interfaz de componentes de React permitida por TypeScript, incluidos los atributos data- y aria- . Sin él, no podemos ofrecer interfaces precisas a nuestros consumidores para que las utilicen para estos atributos. Esto se está convirtiendo en un problema mayor con la próxima versión de Fabric, donde estamos buscando implementaciones conectables como ranuras y necesitamos definir y permitir estos atributos en interfaces arbitrarias.

Si hay algo que podamos hacer para ayudar, ¡hágamelo saber! 😄

Zona de juegos TS :

import * as React from 'react';

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

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

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

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

TODO: Ayúdenos identificando funciones de biblioteca reales que podrían beneficiarse de los tipos de RegExp y la expresión real que usaría

Trabajos cron. (muy sorprendido de que no se mencionara)

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

Solo arrojo mis dos centavos aquí: estoy trabajando en un proyecto de React en el que nos gustaría validar un accesorio que se usará como un atributo HTML id . Esto significa que debe cumplir con las siguientes reglas o se producirá un comportamiento inesperado:

  1. Tener al menos un personaje
  2. No tener espacios

En otras palabras:

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

Otro ejemplo: identificadores de tipo santuario con cadenas esperadas en formato '<namespace>/<name>[@<version>]'

Caso de uso: API DOM de tipo cadena como Navigator.registerProtocolHandler() .

Citando MDN:

Por razones de seguridad, registerProtocolHandler() restringe qué esquemas se pueden registrar.

Se puede registrar un esquema personalizado siempre que:

  • El nombre del esquema personalizado comienza con web+
  • El nombre del esquema personalizado incluye al menos 1 letra después del prefijo web+
  • El esquema personalizado solo tiene letras ASCII minúsculas en su nombre.

En otras palabras, Navigator.registerProtocolHandler() espera un string bien conocido o un string pero solo si se ajusta a un esquema específico.

Propiedades personalizadas de CSS para CSSType es otro caso de uso para proporcionar tipos cerrados para todas las propiedades, excepto para aquellas con el prefijo -- .

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

Https://github.com/frenic/csstype/issues/63 relacionados

¿Alguien puede decirme si esto es lo mismo que los tipos de refinamiento? https://github.com/microsoft/TypeScript/issues/7599

@ gautam1168 En teoría, es solo un subconjunto, donde está refinando específicamente los tipos de cadenas. (Los tipos numéricos tienen sus propias preocupaciones, por supuesto).

Para algunos de los casos de uso mencionados, solo se necesitaría un pequeño subconjunto de Regex, por ejemplo, la coincidencia de prefijos.

Sigo manteniendo mi propuesta , que ofrece esto + algunas otras cosas, básicamente un superconjunto muy pequeño de lenguajes sin estrellas que aún puede verificar de manera razonablemente eficiente para la subconjunto y la igualdad. Y hasta ahora, no he visto ninguna otra propuesta que intente abordar el aspecto de rendimiento de las expresiones regulares arbitrarias, la mayor preocupación que tiene el equipo de TS.

En este comentario,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Alguien vinculado a
https://bora.uib.no/handle/1956/3956

Titulado, "El problema de la inclusión de las expresiones regulares"


Sin embargo,

  • Si la expresión de la derecha es 1 inequívoca, el algoritmo da la respuesta correcta.
  • De lo contrario, puede dar la respuesta correcta o ninguna respuesta.

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

(Por supuesto, las expresiones regulares de JS no son regulares)

@AnyhowStep Eso podría funcionar: simplemente levantaría la restricción starof y cambiaría esa restricción en consecuencia. Me gustaría una mejor manera de caracterizar la restricción, ya que la matemática es un poco abstracta y no está claro cómo sería concretamente aplicar en la práctica (no todos los que utilizaría esos tipos son bien versado en los lenguajes formales).

Además, por separado, me gustaría mucho una mejor alternativa a starof como operador para modelar ese tipo de cosas.

Tengo curiosidad: ¿es posible decidir la inclusión / contención de expresiones regulares? Según wikipedia , es decidible. Sin embargo, ¿esto también tiene en cuenta las expresiones regulares en JS? Creo que tienen más características que los RE estándar (por ejemplo, referencias inversas). Si es decidible, ¿es factible computacionalmente?
Esto afectaría a esta característica (estrechamiento):

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

@nikeee Decidable no es suficiente para que esto sea realista. Incluso el tiempo cuadrático suele ser demasiado lento en esta escala. No TS, pero tengo algunos antecedentes sobre problemas similares.

Frente a las referencias retrospectivas, sospecho que todavía es decidible, pero probablemente exponencial, si no peor. Sin embargo, solo una suposición fundamentada.

¡Gracias por aclarar esto!

Incluso el tiempo cuadrático suele ser demasiado lento en esta escala.

Es por eso que también pregunté si es computacionalmente factible, así que supongo que no lo es.

Si lo mismo se aplica a la igualdad, ¿no significa eso que casi todas las propiedades de esta característica son inviables? Corrígeme, si me equivoco, pero parece que lo único que queda es la membresía. No creo que esto solo sea útil.

@nikeee Vale la pena tener en cuenta que este patrón se comparará literalmente con todas las propiedades de cada tipo con el que se compare . Y para los tipos con propiedades de expresiones regulares, usted tiene que calcular si una expresión regular coincide con un subconjunto de lo que otro coincidencias de expresiones regulares, una bestia bastante complicada de por sí.

No es imposible, solo difícil , y tienes que ser restrictivo si quieres que sea factible. (Por un lado, JS regexps no funcionaría, no solo no son lo suficientemente extensibles, sino también demasiado flexibles).

Editar: Yo quiero reiterar esto: yo no estoy en el equipo TS, sólo para aclarar. Solo tengo una experiencia decente en el diseño de algoritmos CS.

Mmm, entonces tal vez solo puedas admitir un subconjunto "limitado" de expresiones regulares "habituales". Dados los casos de uso, las expresiones regulares eran bastante simples hasta ahora ... (colores, números de teléfono, etc.)

¿Cómo podríamos diseñar la UX de admitir solo un subconjunto? Puede que al usuario no le quede muy claro que la característica X de la expresión regular funciona, pero Y no.

bueno ... no lo llames "regex" - para empezar. Tal vez solo "coincidencia de patrones" o algo así: see_no_ evil :. Pero sí, probablemente no sea una tarea fácil ...

¿Qué pasa con una sintaxis no regular como esta:

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'

En mi opinión, esto encajaría bastante bien en el sistema de tipos. Puede agregar una sintaxis diferente para cosas como coincidencias opcionales:

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

Agregue soporte para cuantificadores, operador codicioso y obtendrá algo bastante robusto, creo que probablemente sería suficiente para la mayoría de los casos de uso para los que los desarrolladores querrían usar esto.

Creo que este enfoque sería más fácil de usar. Sin embargo, parece ser aplicable a operaciones aritméticas sobre tipos.
Según https://github.com/microsoft/TypeScript/issues/15645#issuecomment -299917814 y https://github.com/microsoft/TypeScript/issues/15794#issuecomment -301170109, es una decisión de diseño no hacer aritmética sobre tipos.
Si no me equivoco, este enfoque puede crear fácilmente un tipo enorme. Considerar:

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

(esto supone que la implementación usaría tipos de unión. Puede funcionar con una implementación diferente / más compleja)

Dislaimer: no soy parte del equipo de TS y no estoy trabajando en TS. Solo mi 2c.

@rozzzly @nikeee Esa es más o menos la esencia de mi propuesta , solo que faltan algunas características más pequeñas. Basé el mío en un gran subconjunto de lenguajes regulares (el concepto de lenguaje formal), no en expresiones regulares en el sentido de literales regexp y demás, por lo que es mucho menos poderoso que esos, pero lo suficientemente poderoso como para hacer el trabajo.

Creo que este enfoque sería más fácil de usar. Sin embargo, parece ser aplicable a operaciones aritméticas sobre tipos.

Math dice que validar si un tipo es un subtipo de otro es computacionalmente equivalente a verificar si una cadena está contenida en un lenguaje formal dado.

La validación de dominio específicamente es en realidad algo bastante complicado de hacer si también verifica la validez de TLD / /[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+/ + con un máximo de 255 caracteres, pero incluso esto es muy complicado de escribir a menos que opte por las gramáticas regulares completas como lo demuestra la expresión regular anterior. Podría generar programáticamente el tipo de manera bastante sencilla (lo dejaré como un ejercicio para el lector) usando solo cadenas de @rozzzly o mi propuesta, pero el resultado final sigue siendo bastante complicado.

@isiahmeadows

Esa es más o menos la esencia de mi propuesta , solo que faltan algunas características más pequeñas.

La última vez que leí todo este hilo fue hace más de un año. Estaba en mi descanso y vi una notificación, leí el comentario de @rugk sobre _ "bueno ... no lo llames" regex "- para empezar" _ lo que me hizo pensar ... no me había dado cuenta de que alguien ya lo había hecho presentó una propuesta considerablemente más detallada para esencialmente la misma _ (/ una muy similar) _ idea.

... incluso esto es muy complicado de escribir a menos que opte por gramáticas regulares completas como lo demuestra la expresión regular anterior. Podría generar programáticamente el tipo de manera bastante sencilla (lo dejaré como un ejercicio para el lector) usando solo cadenas de @rozzzly o mi propuesta, pero el resultado final sigue siendo bastante complicado.

En mi opinión, alguna facilidad para la coincidencia de patrones limitada como la que sugerí permitir sería extremadamente útil para una escritura muy simplista y _necesariamente no rigurosamente estricta_. El ejemplo que di está lejos de ser preciso y no haría explotar el compilador.

Pero como @nikeee y ustedes dos señalan, esto podría llevarse a extremos peligrosos. Suponiendo una implementación más ingenua que solo apoye a los sindicatos. Alguien va a arruinar el día de todos publicando una actualización de @types/some-popular-project que contenía:

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

Poniendo eso en perspectiva, esa unión consiste en tipos distintos que es más que átomos en el universo observable .

Ahora, he visto algunos errores de asignabilidad terriblemente largos, pero imagina el error (no truncado) para eso ...

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

Así que sí ... hay algunos problemas ahí

@rozzzly ¿Qué hace que ese tipo sea diferente (en términos de viabilidad) de un TupleWithLengthBeteen1And64<Charset> ?
El cumplidor no está obligado a expandir cada tipo a una forma normalizada, explotaría rápidamente en tipos bastante normales si lo hiciera.
No digo que creo que este problema tiene sentido en el texto mecanografiado en este momento, si incluso "un número entero entre 3 y 1024" (piense en las longitudes de asignación de búfer de mensajes) se considera fuera de alcance.

@simonbuchan Al menos los tipos de prefijo y sufijo deben existir, al menos. Eso en sí mismo es necesario para muchas bibliotecas y marcos DOM.

Sé que esto ha sido golpeado hasta la muerte y ya se han hecho algunas buenas propuestas. Pero solo quería agregar cosas adicionales que algunos podrían encontrar ligeramente interesantes.

Frente a las referencias retrospectivas, sospecho que todavía es decidible, pero probablemente exponencial, si no peor. Sin embargo, solo una suposición fundamentada.

Las referencias anteriores pueden hacer que una expresión regular describa una gramática sensible al contexto, un superconjunto de gramáticas libres de contexto. Y la igualdad de idiomas para los CFG es indecidible. Por lo tanto, es aún peor para los CSG, que son equivalentes a los autómatas delimitados linealmente.


Suponiendo que solo todas las expresiones regulares que se pueden convertir a un DFA se usan en una expresión regular (concat, unión, estrella, intersección, complemento, etc.), convertir una expresión regular en un NFA es O (n), obteniendo el producto de dos NFA es O (m * n), luego, atravesar el gráfico resultante para los estados aceptados es O (m * n). Entonces, verificar la igualdad / subconjunto del idioma de dos expresiones regulares regulares también es O (m * n).

El problema es que aquí el alfabeto es muy grande. Los libros de texto se limitan a alfabetos de tamaño 1 a 5 por lo general, cuando se habla de DFA / NFA / expresiones regulares. Pero con JS regexps, tenemos todo unicode como nuestro alfabeto. Por supuesto, puede haber formas eficientes de representar funciones de transición utilizando matrices dispersas y otros trucos y optimizaciones inteligentes para las pruebas de igualdad / subconjunto ...

Estoy seguro de que es posible realizar la verificación de tipos para asignaciones regulares a regulares de manera algo eficiente.

Entonces, todas las asignaciones no regulares solo pueden requerir afirmaciones de tipo explícitas.

Recientemente trabajé en un pequeño proyecto de autómata finito, por lo que la información todavía está fresca en mi mente = x

Si no me equivoco, este enfoque puede crear fácilmente un tipo enorme. Considerar:

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

(esto supone que la implementación usaría tipos de unión. Puede funcionar con una implementación diferente / más compleja)

Curiosamente, esto es exactamente lo que es posible con los nuevos tipos de literal de cadena de plantilla. Parece que este caso se evita al tener un umbral para los tipos de unión.

Las referencias inversas de

Editar: precisión

¡ Confirmé que este comentario de @rozzzly funciona con TS 4.1.0 todas las noches!

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

Pruébelo en el patio de recreo y vea que fail tiene un error de tiempo de compilación 🤩


Actualización : después de jugar un poco con esta función, no cubrirá muchos casos de uso. Por ejemplo, no funciona para una cadena de color hexadecimal.

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

Hoy, eso falla con "La expresión produce un tipo de unión que es demasiado complejo para representarlo (2590)".

¡ Confirmé que este comentario de @rozzzly funciona con TS 4.1.0 todas las noches!

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

Pruébelo en el patio de recreo y vea que fail tiene un error de tiempo de compilación 🤩

Esto resolvería el problema de datos o aria que la mayoría de nosotros enfrentamos en las bibliotecas de UX si se puede aplicar a los índices.

Básicamente esto, pero obviamente eso no funciona porque TS solo permite cadenas | número. Dado que esto es esencialmente una cadena, ¿se puede habilitar?
https://www.typescriptlang.org/play?target=99&ts=4.1.0-dev.20201001#code/LAKALgngDgpgBAEQIZicgzgCzgXjgAwBIBvAcgBMUkBaUgXxPTACcBLAOwHM78BuUDmBjMAZkgDG8AJIAVGEzjFQcFXADalVBkwAFZgHsoALmRakWALpGmbLvxB1QocfvYL0AV3GT06I3Fl5MFxFCipqISZSI1JIsHpeIA

_Update_: después de jugar un poco con esta función, no cubrirá muchos casos de uso. Por ejemplo, no funciona para una cadena de color hexadecimal.

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

Hoy, eso falla con "La expresión produce un tipo de unión que es demasiado complejo para representarlo (2590)".

Hubo alguna referencia a esta limitación en las notas de la versión. Crea una lista de todas las posibles combinaciones válidas, en este caso crearía una unión con 16.777.216 (es decir, 16 ^ 6) miembros.

Esta es una gran idea ... Igmat hizo algunas publicaciones increíbles en 2016 que se ven bien en papel de todos modos.

Encontré esto porque quería asegurarme de que las claves de un objeto literal pasado a mi función fueran nombres de clase CSS válidos. Puedo verificar fácilmente en tiempo de ejecución ... pero me parece tan obvio que mecanografiado debería poder hacer esto en tiempo de compilación, especialmente en situaciones en las que solo estoy codificando literales de objetos y mecanografiado no debería tener que averiguar si MyUnionExtendedExotictype satisface SomeArbitraryRegexType.

Quizás algún día tenga el conocimiento suficiente para hacer una contribución más productiva: /

¡ Confirmé que este comentario de @rozzzly funciona con TS 4.1.0 todas las noches!

Guau. Honestamente, no esperaba ver que esto se implementara, al menos no pronto.

@ chadlavi-casebook

Hubo alguna referencia a esta limitación en las notas de la versión. Crea una lista de todas las posibles combinaciones válidas, en este caso crearía una unión con 16.777.216 (es decir, 16 ^ 6) miembros.

Tendría curiosidad por ver qué tan grande podría llegar a ser ese sindicato antes de que se convierta en un problema de desempeño. El ejemplo de @styfle muestra lo fácil que es llegar a ese techo. Obviamente, habrá un cierto grado de rendimiento decreciente de la utilidad de los tipos complejos frente al rendimiento.

@elfelizqueso

Quería asegurarme de que las claves de un objeto literal pasado a mi función fueran nombres de clase CSS válidos

Estoy bastante seguro de decir que no es posible con la implementación actual. Si hubiera soporte para cuantificadores y rangos, probablemente obtendría validación para nombres de clases de estilo BEM. La expresión regular js estándar para eso no es _demasiado_ terrible:
^\.[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$
También debería deshacerse de los anclajes porque, tal como está la implementación, es una coincidencia de extremo a extremo o nada, por lo que ^ y $ están implícitos. Eso es una expresión regular comparativamente simple para un subconjunto estrecho de lo que es un selector CSS válido. Por ejemplo: ಠ_ಠ es un nombre de clase válido. No estoy bromeando.

Lo siento. Tuve que hacer esto.

Implementé lenguajes regulares en TypeScript.

Más exactamente, implementé un autómata finito determinista simple usando TS 4.1

Quiero decir, ya podemos implementar máquinas de Turing en TS. Por lo tanto, los DFA y los PDA son "fáciles", en comparación con eso.

Y las cadenas de plantillas hacen que esto sea más útil.


Los tipos principales son realmente simples y se ajustan a <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>;

Especificar los autómatas es la parte difícil.

Pero estoy bastante seguro de que alguien puede crear una expresión regular para el generador DFA ™ de TypeScript ...


También me gustaría resaltar que el ejemplo de "cadena hexadecimal de longitud 6" muestra que puede hacer que los parámetros de la función solo acepten cadenas que coincidan con la expresión regular usando hacker feo,

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

Aquí hay un patio de juegos adicional ; implementa la expresión regular /^hello .*/

Y otro patio de recreo ; implementa la expresión regular / world$/

Un último ejemplo, Patio de recreo ; ¡Esta es una expresión regular de cadena de punto flotante !

@AnyhowStep Bueno, utilicé su idea de DFA para implementar una expresión regular simple [abc]{4} que significa que las letras abc en cualquier orden faltan pero exactamente la longitud de 4. (aaaa, abcc, bbcc, etc.).
Patio de recreo

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

https://github.com/CyberZHG/toolbox

Si tuviera más fuerza de voluntad, tomaría algo como lo anterior y lo usaría para convertir las expresiones regulares en TS DFAs ™ lol

De acuerdo, acabo de armar un prototipo,

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

[Editar] https://glitch.com/~efficacious-valley-repair <- Esto produce un resultado mucho mejor para expresiones regulares más complicadas

[Editar] Parece que Glitch archivará proyectos gratuitos que están inactivos durante demasiado tiempo. Entonces, aquí hay un repositorio de git con los archivos,
https://github.com/AnyhowStep/efficacious-valley-repair/tree/main/app

Paso 1, ingrese su expresión regular aquí,
image

Paso 2, haz clic en convertir,
image

Paso 3, haga clic en la URL del patio de juegos de TS generada,
image

Paso 4, desplácese hacia abajo hasta InLanguage_0 ,
image

Paso 5, juega con los valores de entrada,
image

image

Saludos a https://www.npmjs.com/package/regex2dfa , por hacer el trabajo pesado de la conversión

En caso de que alguien necesite algo un poco más poderoso, aquí tienes una máquina de Turing 😆

Patio de recreo

Este hilo se ha vuelto demasiado largo para leer y muchos de los comentarios son abordados por tipos de literal de plantilla o están fuera de tema. Creé un nuevo número # 41160 para discutir qué casos de uso restantes podrían habilitarse con esta función. Siéntase libre de continuar discutiendo los analizadores de sistemas de tipos aquí 😀

¿Fue útil esta página
0 / 5 - 0 calificaciones