Typescript: Sugestão: tipo de string validado por Regex

Criado em 22 jan. 2016  ·  146Comentários  ·  Fonte: microsoft/TypeScript

Há casos em que uma propriedade não pode ser apenas qualquer string (ou um conjunto de strings), mas precisa corresponder a um padrão.

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

É uma prática comum em JavaScript armazenar valores de cor em notação css, como na reflexão de estilo css de nós DOM ou várias bibliotecas de terceiros.

O que você acha?

Literal Types Needs Proposal Suggestion

Comentários muito úteis

Proposta de projeto

Há muitos casos em que os desenvolvedores precisam de um valor mais especificado do que apenas uma string, mas não podem enumerá-los como uma união de strings literais simples, por exemplo, cores css, e-mails, números de telefone, ZipCode, extensões swagger etc. Até mesmo a especificação de esquema json que comumente usado para descrever o esquema do objeto JSON tem padrão e propriedades padrão que, em termos de sistema de tipo de TS, podem ser chamados de regex-validated string type e regex-validated string type of index .

Metas

Fornece aos desenvolvedores um sistema de tipos que está um passo mais próximo do esquema JSON, comumente usado por eles, e também evita que se esqueçam das verificações de validação de strings quando necessário.

Visão geral sintática

A implementação deste recurso consiste em 4 partes:

Tipo validado Regex

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 variável validada por Regex

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

e o mesmo, mas mais legível

let fontColor: CssColor;

Tipo de índice de variável validada por Regex

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

e o mesmo, mas mais legível

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

Proteção de tipo para tipo de variável

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

e mesmo

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

e usando tipo definido para melhor legibilidade

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

igual a

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

Digite gurard para o 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 a

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
}

e usando tipo definido para melhor legibilidade

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

igual a

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

Visão geral semântica

atribuições

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

Infelizmente, não podemos verificar se um regex é um subtipo de outro sem impacto no desempenho devido a este artigo . Portanto, deve ser restrito. Mas existem as próximas soluções alternativas:

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

Infelizmente, a atribuição da variável string à variável regex-validated também deve ser restrita, porque não há garantia em tempo de compilação de que corresponderá ao regex.

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

Mas podemos usar elenco explícito ou protetores de tipo, conforme mostrado aqui . O segundo é recomendado.
Felizmente, não é o caso de literais de string, porque ao usá-los, podemos verificar se seu valor corresponde a regex.

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

Redução de tipo para índices

Para casos simples de regex-validated type de índice, consulte Tipo gurard para tipo de índice .
Mas pode haver casos mais 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

Literais não têm esse 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

Mas para variáveis, a melhor opção é usar protetores de tipo como nos próximos exemplos mais 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
    }
}

Mas se usarmos uma definição melhor para o tipo Gmail , haveria outro estreitamento de tipo:

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

Sindicatos e cruzamentos

Na verdade, os tipos comuns e os tipos regex-validated são realmente diferentes, então precisamos de regras sobre como lidar corretamente com suas uniões e cruzamentos.

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

Não há casos especiais para genéricos, portanto regex-validated type pode ser usado com genéricos da mesma forma que os tipos usuais.
Para genéricos com restrições como abaixo, regex-validated type se comporta como string:

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

Visão geral do Emit

Ao contrário dos tipos usuais, regex-validated tem algum impacto na emissão:

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

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

Visão geral da compatibilidade

Este recurso não tem problemas de compatibilidade, porque só há casos em que pode quebrá-lo e está relacionado a que o tipo regex-validated tem impacto de emissão ao contrário do tipo normal, então este é um código TS válido:

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

quando o código abaixo não é:

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

Mas o segundo já ERA inválido, mas por outro motivo (a declaração do tipo estava errada).
Portanto, agora temos que restringir a declaração de variável com o mesmo nome do tipo, no caso em que este tipo seja regex-validated .

PS

Sinta-se à vontade para apontar coisas que provavelmente não percebi. Se você gostar dessa proposta, eu poderia tentar criar testes que a abranjam e adicioná-los como PR.

Todos 146 comentários

Sim, eu vi isso vasculhando DefinitelyTyped,. Até poderíamos usar algo assim com ScriptElementKind na camada de serviços , onde idealmente seríamos capazes de descrevê-los como uma lista separada por vírgulas de strings específicas.

Os principais problemas são:

  • Não está claro como compô-los bem. Se eu quiser uma lista separada por vírgulas de "cat" , "dog" e "fish" , então preciso escrever algo como /dog|cat|fish(,(dog|cat|fish))*/ .

    • Se eu já tenho tipos que descrevem os tipos literais de string para "cat" , "dog "e "fish" , como faço para integrá-los a este regex?

    • É claro que há repetição aqui, o que é indesejável. Talvez corrigir o problema anterior tornasse isso mais fácil.

  • Extensões fora do padrão tornam isso meio duvidoso.

Grande +1 nisso, ZipCode, SSN, ONet, muitos outros casos de uso para isso.

Eu enfrentei o mesmo problema e vejo que ainda não foi implementado, talvez esta solução alternativa seja útil:
http://stackoverflow.com/questions/37144672/guid-uuid-type-in-typescript

Como @mhegazy sugeriu, colocarei minha sugestão (# 8665) aqui. Que tal permitir funções de validação simples em declarações de tipo? Algo parecido:

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

O valor que o tipo pode aceitar seria determinado pelo tipo de parâmetro da função e pela própria avaliação da função. Isso resolveria # 7982 também.

@rylphs +1 isso tornaria o TypeScript extremamente poderoso

Como a subtipagem funciona com _regex-validated string types_?

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?

onde RegExType_1 e RegExType_2 são _regex-validated string types_.

Edit: Parece que este problema pode ser resolvido em tempo polinomial (consulte O problema de inclusão para expressões regulares ).

Também ajudaria com TypeStyle: https://github.com/typestyle/typestyle/issues/5 : rose:

Em JSX, @RyanCavanaugh e eu vimos pessoas adicionando atributos aria- (e potencialmente data- ). Alguém realmente adicionou uma assinatura de índice de string em DefinitelyTyped como um pega-tudo. Uma nova assinatura de índice para isso teria sido útil.

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

Proposta de projeto

Há muitos casos em que os desenvolvedores precisam de um valor mais especificado do que apenas uma string, mas não podem enumerá-los como uma união de strings literais simples, por exemplo, cores css, e-mails, números de telefone, ZipCode, extensões swagger etc. Até mesmo a especificação de esquema json que comumente usado para descrever o esquema do objeto JSON tem padrão e propriedades padrão que, em termos de sistema de tipo de TS, podem ser chamados de regex-validated string type e regex-validated string type of index .

Metas

Fornece aos desenvolvedores um sistema de tipos que está um passo mais próximo do esquema JSON, comumente usado por eles, e também evita que se esqueçam das verificações de validação de strings quando necessário.

Visão geral sintática

A implementação deste recurso consiste em 4 partes:

Tipo validado Regex

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 variável validada por Regex

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

e o mesmo, mas mais legível

let fontColor: CssColor;

Tipo de índice de variável validada por Regex

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

e o mesmo, mas mais legível

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

Proteção de tipo para tipo de variável

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

e mesmo

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

e usando tipo definido para melhor legibilidade

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

igual a

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

Digite gurard para o 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 a

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
}

e usando tipo definido para melhor legibilidade

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

igual a

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

Visão geral semântica

atribuições

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

Infelizmente, não podemos verificar se um regex é um subtipo de outro sem impacto no desempenho devido a este artigo . Portanto, deve ser restrito. Mas existem as próximas soluções alternativas:

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

Infelizmente, a atribuição da variável string à variável regex-validated também deve ser restrita, porque não há garantia em tempo de compilação de que corresponderá ao regex.

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

Mas podemos usar elenco explícito ou protetores de tipo, conforme mostrado aqui . O segundo é recomendado.
Felizmente, não é o caso de literais de string, porque ao usá-los, podemos verificar se seu valor corresponde a regex.

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

Redução de tipo para índices

Para casos simples de regex-validated type de índice, consulte Tipo gurard para tipo de índice .
Mas pode haver casos mais 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

Literais não têm esse 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

Mas para variáveis, a melhor opção é usar protetores de tipo como nos próximos exemplos mais 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
    }
}

Mas se usarmos uma definição melhor para o tipo Gmail , haveria outro estreitamento de tipo:

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

Sindicatos e cruzamentos

Na verdade, os tipos comuns e os tipos regex-validated são realmente diferentes, então precisamos de regras sobre como lidar corretamente com suas uniões e cruzamentos.

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

Não há casos especiais para genéricos, portanto regex-validated type pode ser usado com genéricos da mesma forma que os tipos usuais.
Para genéricos com restrições como abaixo, regex-validated type se comporta como string:

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

Visão geral do Emit

Ao contrário dos tipos usuais, regex-validated tem algum impacto na emissão:

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

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

Visão geral da compatibilidade

Este recurso não tem problemas de compatibilidade, porque só há casos em que pode quebrá-lo e está relacionado a que o tipo regex-validated tem impacto de emissão ao contrário do tipo normal, então este é um código TS válido:

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

quando o código abaixo não é:

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

Mas o segundo já ERA inválido, mas por outro motivo (a declaração do tipo estava errada).
Portanto, agora temos que restringir a declaração de variável com o mesmo nome do tipo, no caso em que este tipo seja regex-validated .

PS

Sinta-se à vontade para apontar coisas que provavelmente não percebi. Se você gostar dessa proposta, eu poderia tentar criar testes que a abranjam e adicioná-los como PR.

Esqueci de apontar alguns casos para cruzamentos e uniões dos tipos regex-validated , mas os descrevi no último caso de teste. Devo atualizar Design proposal para refletir essa pequena alteração?

@Igmat , pergunta sobre sua proposta de design: Você poderia falar mais detalhadamente sobre a visão geral da emissão? Por que os tipos validados por regex precisam ser emitidos? Pelo que eu posso dizer, outros tipos não suportam verificações de tempo de execução ... estou faltando alguma coisa?

@alexanderbird , sim, qualquer outro tipo não tem impacto sobre o emit. A princípio, pensei que regex-validated também servisse, então comecei a criar a proposta e a brincar com a sintaxe proposta.
A primeira abordagem foi assim:

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

e isto:

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

Está tudo bem e não precisa emitir alterações, porque "#000" pode ser verificado em tempo de compilação.
Mas também temos que lidar com o estreitamento do tipo string para regex-validated type para torná-lo útil. Pensei nisso para as duas configurações anteriores:

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

Portanto, também não tem impacto sobre o emit e parece ok, exceto que o regex não é muito legível e precisa ser copiado em todos os lugares, então o usuário pode facilmente cometer um erro. Mas, neste caso particular, ainda parece ser melhor do que mudar a forma como type funciona.
Mas então eu percebi que essas coisas:

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

é um pesadelo. E está mesmo sem cruzamentos e sindicatos. Portanto, para evitar que coisas assim aconteçam, temos que alterar ligeiramente type emit como mostrado na proposta.

@DanielRosenwasser , você poderia, por favor, fornecer algum feedback para esta proposta? E também para os testes aqui referenciados, se possível?
Eu realmente quero ajudar com a implementação deste recurso, mas requer muito tempo ( tsc é um projeto realmente complicado e ainda tenho que trabalhar para entender como ele funciona internamente) e não sei se esta proposta está pronta para ser implementada ou você rejeitará este recurso implementado desta forma devido a outra visão de design de linguagem ou qualquer outro motivo.

Ei @Igmat , acho que há algumas coisas que eu deveria ter perguntado inicialmente

Para começar, ainda não entendo por que você precisa de qualquer tipo de alteração para emitir, e não acho que qualquer tipo de emissão baseada em tipos seria aceitável. Confira nossas não metas aqui .

Outra questão que deveria ter levantado é o problema das expressões regulares que usam referências anteriores. Meu entendimento (e experiência) é que as referências anteriores em uma expressão regular podem forçar um teste a ser executado no tempo exponencial para sua entrada. Este é um caso esquivo? Talvez, mas é algo que prefiro evitar em geral. Isso é especialmente importante porque, em cenários de editor, uma verificação de tipo em um local deve levar um tempo mínimo.

Outro problema é que precisaríamos confiar no mecanismo no qual o compilador TypeScript é executado ou construir um mecanismo de expressão regular personalizado para executar essas coisas. Por exemplo, TC39 está se movendo para incluir um novo sinalizador s para que . possa corresponder a novas linhas. Haveria uma discrepância entre ESXXXX e tempos de execução mais antigos que suportam isso.

@igmat - não @DanielRosenwasser disse, provavelmente não seria aprovado de qualquer maneira). Você disse

Mas também temos que lidar com o estreitamento de string para tipo validado por regex para torná-lo útil

Acho que esse é o caso apenas se quisermos restringir de uma string dinâmica a um tipo validado por regex. Isso fica muito complicado. Mesmo neste caso simples:

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

Não podemos ter certeza de que os tipos corresponderão - e se o número for negativo? E conforme as regexes ficam mais complicadas, fica cada vez mais confuso. Se realmente quiséssemos isso, talvez permitíssemos "interpolação de tipo: type Baz = /prefix:{number}/ ... mas não sei se vale a pena ir lá.

Em vez disso, poderíamos chegar parcialmente ao objetivo se apenas permitíssemos que literais de string fossem atribuídos a tipos validados por regex.

Considere o seguinte:

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'

Você acha que é uma alternativa viável?

@DanielRosenwasser , Eu li os Objetivos do Projeto com atenção e, se bem entendi, o problema é a violação das Não-metas # 5.
Mas não me parece uma violação, mas sim um aprimoramento da sintaxe. Por exemplo, anteriormente tínhamos:

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
}

Com esta proposta implementada, ficaria assim:

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 você pode ver, o código é quase o mesmo - é um uso simples e comum de regex. Mas o segundo caso é muito mais expressivo e evitará que o usuário cometa um erro acidental, como esquecer de verificar a string antes de atribuí-la à variável que deveria ser validada por regex.
A segunda coisa é que, sem esse estreitamento de tipo, não seremos capazes de usar normalmente o tipo validado por regex em índices, porque na maioria dos casos esses campos de índice funcionam com alguma variável que não pode ser verificada em tempo de execução, pois poderia ser feito com literais .

@alexanderbird , eu não sugiro tornar este código válido ou adicionar algumas verificações ocultas em tempo de execução e tempo de compilação.

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

Este código deve gerar erro devido à minha proposta. Mas isso:

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

ou isto:

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

estaria correto e até mesmo não teria impacto no código emitido.

E, como mostrei no comentário anterior, os literais definitivamente não seriam suficientes, mesmo para casos de uso comuns, porque muitas vezes temos que trabalhar com picadas de entrada do usuário ou outras fontes. Sem implementar este impacto de emissão, os usuários teriam que trabalhar com este tipo da próxima maneira:

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

ou para cruzamentos:

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
}

Não acho que forçar os usuários a duplicar o código e usar elenco explícito, quando poderia ser facilmente manipulado pelo compilador, não seja um bom caminho a percorrer. O impacto da emissão é realmente muito pequeno e previsível, tenho certeza que não surpreenderá os usuários ou levará a algum recurso mal compreendido ou difícil de localizar bugs, ao implementar esse recurso sem emitir alterações definitivamente IRÁ.

Concluindo, quero dizer que, em termos simples, regex-validated type é tanto uma variável com escopo definido quanto um tipo de compilador.

@DanielRosenwasser e @alexanderbird ok, tenho mais uma ideia para isso. Que tal uma sintaxe como esta:

const type Email = /email-regex/;

Neste caso, o usuário deve definir explicitamente que deseja tanto type quanto const , portanto, o sistema de tipo real não tem alterações de emissão a menos que seja usado com tal modificador. Mas se usado com ele ainda podemos evitar muitos erros, conversões e duplicação de código adicionando o mesmo emit que para:

const Email = /email-regex/;

Isso parece ser ainda maior do que apenas uma melhoria para esta proposta, porque isso provavelmente poderia permitir algo assim (o exemplo é de um projeto com Redux ):

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

sendo convertido para

export const type SOME_ACTION = 'SOME_ACTION';

Tentei encontrar alguma sugestão semelhante, mas não tive sucesso. Se isso puder ser uma solução alternativa e você gostar da ideia, posso preparar uma proposta de design e testes para ela.

@DanielRosenwasser , sobre seu segundo problema - eu não acho que isso aconteceria, porque em minha sugestão o compilador executa regex apenas para literais e não parece que alguém fará algo assim:

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

De qualquer forma, poderíamos testar o tamanho do literal para afetar o desempenho em tempo real e criar alguma heurística que avisará o usuário se não formos capazes de verificar enquanto ele enfrenta essas circunstâncias em alguns cenários do editor, mas verificaríamos quando ele compilar o projeto. Ou pode haver algumas outras soluções alternativas.

Sobre a terceira pergunta, não tenho certeza se entendi tudo corretamente, mas parece que o mecanismo de regex deve ser selecionado dependendo de target de tsconfig se eles tiverem implementações diferentes. Precisa de mais investigação.

@DanielRosenwasser há alguma

@Igmat Sua proposta limita a validação para ser útil apenas com tipos de string. O que você acha da proposta do @rylphs ? Isso permitiria uma validação mais genérica para todos os 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

Suspeito, entretanto, que estender esse mecanismo além dos primitivos para tipos não primitivos seria demais.
Um ponto, o problema que @DanielRosenwasser levantou - sobre a variação das implementações do mecanismo de regex - seria ampliado: dependendo do mecanismo Javascript sob o qual o compilador Typescript está sendo executado, a função de validação pode funcionar de maneira diferente.

@zspitz parece promissor, mas na minha opinião pode afetar muito o desempenho do compilador, porque a função não é limitada por nenhuma regra e vai forçar o TS a calcular algumas expressões que são muito complicadas ou mesmo dependem de alguns recursos que não estão disponíveis em tempo de compilação.

@Igmat

porque a função não é limitada por nenhuma regra

Você tem alguns exemplos específicos em mente? Talvez seja possível limitar a sintaxe de validação a um subconjunto "seguro" / conhecido em tempo de compilação de Typescript.

que tal fazer protetores de tipo definidos pelo usuário para definir um novo tipo?

// 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 parece bom, mas e quanto à extensão de tipo?

Eles absolutamente precisam ser extensíveis? Existe um princípio de design TypeScript que exige isso? Caso contrário, eu preferiria ter os tipos nominais que @disjukr sugere do que nada, mesmo que eles não sejam extensíveis.

Precisaríamos de algo muito criativo para obter a extensibilidade IMHO - não podemos determinar se um protetor de tipo arbitrário (função) é um subconjunto de outro protetor de tipo arbitrário.

Poderíamos obter "extensibilidade" rudimentar usando uma mentalidade de asserção de tipo (não estou dizendo que isso é algo "muito criativo" - estou dizendo que aqui está um paliativo até que alguém apareça com algo muito criativo):

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
}

O que pode ser útil, embora não seja bom. Mas, como disse no início, precisamos que seja extensível ou podemos implementá-lo conforme sugerido por @disjukr? (Se for o último, sugiro que o implementemos da maneira não extensível de

Tipo de offtopic, responda ao primeiro comentário de @DanielRosenwasser :
Para listas separadas por vírgulas, você deve usar âncoras ^ e $ (é relevante na maioria dos casos quando você deseja validar alguma string). E as âncoras ajudam a evitar a repetição, para seu exemplo a regexp será /^((dog|cat|fish)(,|$))+$/

Permitir que tipos de string sejam expressões regulares /#[0-9]{6}/ e permitir tipos de aninhamento em expressões 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: há uma biblioteca dedicada a escrever estilos CSS "seguros para tipos" no TypeScript typestyle . A funcionalidade proposta acima ajudaria muito, porque a biblioteca tem que expor métodos a serem usados ​​em tempo de execução, os tipos de string regex propostos seriam capazes de digitar o código de verificação em tempo de compilação e dar aos desenvolvedores uma grande inteligência.

@DanielRosenwasser @alexanderbird @Igmat : IMO, esta proposta mudaria o jogo para TypeScript e desenvolvimento web. O que atualmente o impede de ser implementado?

Eu concordo que a extensibilidade e a emissão de tipo não devem atrapalhar o resto do recurso. Se não houver um caminho claro para esses aspectos, implemente-os mais tarde, quando houver.

Eu cheguei aqui porque estou procurando um tipo UUID e não uma string, portanto, ter um regex definindo a string seria incrível neste caso + uma maneira de verificar a validade do tipo (exemplo Email.test) também seria útil.

@skbergam Estou tentando implementá-lo sozinho mais uma vez. Mas o projeto de TS é muito grande e eu também tenho um trabalho, então quase não há progresso (consegui apenas criar testes para esse novo recurso). Se alguém tiver mais experiência em estender o TS, qualquer ajuda seria muito apreciada ...

Interessante que isso efetivamente cria um tipo nominal, uma vez que seríamos incapazes de estabelecer quaisquer relações de subtipo / atribuibilidade entre quaisquer duas expressões regulares não idênticas

@RyanCavanaugh Anteriormente @maiermic comentou

Edit: Parece que este problema pode ser resolvido em tempo polinomial (consulte O problema de inclusão para expressões regulares).

Mas isso pode não ser bom o suficiente? Certamente, espera-se que não haja muitos relacionamentos regexp, mas nunca se sabe.

Com relação às verificações de tipo, se não gostamos de duplicar regexps, e typeof a const não é bom o suficiente (por exemplo, arquivos .d.ts), como o TS se sente sobre valueof e , que emite o valor literal de e iff e é um literal, caso contrário, um erro (e emite algo como undefined )?

@maxlk Também fora do tópico, mas eu peguei sua regex e a melhorei para não corresponder às vírgulas finais em uma entrada válida: /^((dog|cat|fish)(,(?=\b)|$))+$/ com o teste https://regex101.com/r/AuyP3g/1. Isso usa uma antecipação positiva para um caractere de palavra após a vírgula, forçando o anterior a revalidar de maneira SECA.

Oi!
Qual é a situação disso?
Você adicionará esse recurso em um futuro próximo? Não consigo encontrar nada sobre isso no roteiro.

@lgmat Que tal limitar a sintaxe a funções de seta de linha única, usando apenas as definições disponíveis em lib.d.ts ?

Essas melhorias incríveis estão disponíveis? Talvez em versão alfa, pelo menos?

tipos validados por regex são ótimos para escrever testes, validar entradas codificadas seria ótimo.

+1. Nosso caso de uso é muito comum, precisamos ter um formato de data de string como 'dd / mm / AAAA'.

embora, conforme proposto, seja um recurso extremamente interessante, não tem potencial:

  • a saída é sempre uma picada (embora o tempo de compilação seja verificado), não há como obter um objeto estruturado
  • a gramática é limitada ao que regexp pode fazer, e eles não podem fazer muito, o problema das expressões regulares é que elas são regulares, seu resultado é uma lista, não uma árvore, elas não podem expressar níveis aninhados longos arbitrários
  • o analisador deve ser expresso em termos de gramática datilografada, que é limitada e não extensível

a melhor maneira seria terceirizar a análise e a emissão para um plugin, como proposto em # 21861; desta forma, todas as opções acima não são um problema ao preço de uma curva de aprendizado mais íngreme, mas ei! a verificação de regexp pode ser implementada em cima disso para que a proposta original ainda se mantenha, surgindo por meio de máquinas mais avançadas

então, como eu disse, uma maneira mais geral seria provedores de sintaxe personalizados para quaisquer literais: # 21861

exemplos:

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

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

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


@lgmat Que tal limitar a sintaxe a funções de seta de linha única, usando apenas as definições disponíveis em lib.d.ts ?

@zspitz que deixaria muita gente infeliz, pois veriam, que é possível, mas proibido para elas, basicamente para sua segurança.

Essas melhorias incríveis estão disponíveis? Talvez em versão alfa, pelo menos?

Pelo que eu sei, isso ainda precisa de uma proposta. @gtamas , @AndrewEastwood

Também acho que # 11152 estaria afetando isso.

@Igmat Sua proposta limita a validação para ser útil apenas com tipos de string. O que você acha da proposta do @rylphs ? Isso permitiria uma validação mais genérica para todos os 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

Suspeito, entretanto, que estender esse mecanismo além dos primitivos para tipos não primitivos seria demais.

O principal problema que vejo com isso são questões de segurança, imagine algum código malicioso, que usaria buffers para capturar a memória do usuário durante a verificação de tipo. Teríamos que implementar muitos sandboxes em torno disso. Prefiro ver 2 soluções diferentes, uma para strings e outra para números.

RegExp é imune a isso até certo ponto, pois a única maneira de usar isso de forma maliciosa é fazer alguma expressão de retrocesso . Dito isso, alguns usuários podem fazer isso sem querer, portanto, deve haver algum tipo de proteção. Eu acho que a melhor maneira de fazer isso seria com um cronômetro.

Um ponto, o problema que @DanielRosenwasser levantou - sobre a variação das implementações do mecanismo de regex - seria ampliado: dependendo do mecanismo Javascript sob o qual o compilador Typescript está sendo executado, a função de validação pode funcionar de maneira diferente.

Isso é verdade, isso é ruim, mas podemos resolvê-lo especificando qual parte "moderna" de regExp precisamos para nossa base de código. O padrão seria a expressão regular normal (é ES3?), Que funciona em todos os nós . E a opção de ativar novos sinalizadores e declarações de lookbehind.

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

Se um usuário desabilitou o sinalizador com sinalizadores avançados.

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

O TypeScript não avaliaria o RegExp avançado, se não fosse solicitado. Mas eu sugeriria que deveria dar um aviso, explicando o que está acontecendo e como habilitar a verificação RegExp avançada.

Se o usuário habilitou o sinalizador com sinalizadores avançados e seu nó o suporta.

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

Se um usuário ativou o sinalizador com sinalizadores avançados e seu nó oferece suporte para isso.

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

Acho que essa é uma maneira razoável de fazer isso.
Equipes de programadores geralmente têm a mesma versão do NodeJS ou são facilmente capazes de atualizar, já que toda a sua base de código está funcionando para alguém com uma versão mais recente.
Os programadores solo podem se adaptar facilmente na hora,

Qual é o status atual desse problema? É realmente uma pena ver que o TypeScript tem um potencial tão grande e dezenas de propostas incríveis, mas eles não recebem muita atenção dos desenvolvedores ...

AFAIK, a proposta original era boa, exceto pela visão geral do proibida e não é realmente necessária, então não deveria bloquear a proposta.

O problema que ele está tentando resolver pode ser resolvido com a introdução de literais regex (o que não deve ser difícil, pois eles são efetivamente equivalentes a literais de string e de número) e um operador de tipo patternof (semelhante a typeof e keyof ), que pegaria um tipo literal regex e retornaria um tipo _cadeia_validada. É assim que pode ser usado:

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 Não pensei nessa solução com operador de tipo adicional, quando estava trabalhando na proposta inicial.

Eu gosto dessa abordagem de remover o impacto de emissão causado por tipos, embora isso pareça ser mais detalhado.

E isso me levou a ter uma ideia de como estender esta proposta de forma a pular a adição de novas palavras-chave (como você sugere) - IMO nós já temos uma grande quantidade delas e não temos impacto de emissão do sistema de tipo (como na minha proposta).

Isso levará 4 etapas:

  1. adicionar regexp-validated string literal tipo:
    TypeScript type Email = /some-long-email-regex/;
  2. Vamos mudar a interface RegExp da biblioteca principal para genérica:
    TypeScript interface RegExp<T extends string = string> { test(stringToTest: string): stringToTest is T; }
  3. Alterar o tipo de inferência para literais regex no código real:
    TypeScript const Email = /some-long-email-regex/; // infers to `RegExp</some-long-email-regex/>`
  4. Adicione o tipo de auxiliar usando o recurso conditional types , como InstanceType :
    TypeScript type ValidatedStringType<T extends RegExp> = T extends RegExp<infer V> ? V : string;

Exemplo 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. Sua proposta parece mais natural para o TypeScript e requer menos alterações no compilador, o que provavelmente é uma coisa boa. A única vantagem da minha proposta era que os literais regex teriam a mesma sensação que os literais de string e de número, isso pode ser confuso para alguns:

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

Mas acho que a simplicidade de sua proposta supera a desvantagem.

Edit: Eu realmente não gosto do comprimento de ValidatedStringType<R> . Se decidirmos chamar padrões de strings validados, poderíamos usar PatternOf<R> afinal. Não estou dizendo que seu tipo leva mais tempo para digitar, a maioria das pessoas simplesmente digitaria as três primeiras letras e pressionaria a tecla tab. Ele apenas tem um impacto maior de spagetification do código.

@Igmat Sua solução é excelente do ponto de desenvolvimento, mas em termos de legibilidade, seria muito melhor ter a possibilidade como @ m93a propôs. Acho que poderia ser representado internamente da mesma maneira, mas deve ser apresentado ao usuário da forma mais simples possível.

@Akxe Eu não acho que os desenvolvedores gostariam de adicionar outra palavra-chave que tenha apenas um caso de uso muito específico.

@RyanCavanaugh Você poderia nos dizer sua opinião sobre isso? (Especificamente a proposta original e os quatro últimos comentários (excluindo este).) Obrigado! : +1:

que tal ter um argumento genérico para string, que o padrão é .* ?

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

literal de string 'foo' pode ser considerado um açúcar para string<foo>

Eu realmente não gosto do comprimento de ValidatedStringType<R> . Se decidirmos chamar strings validadas de _patterns_, poderíamos usar PatternOf<R> afinal.

@ m93a , IMO, neste caso seria melhor chamá-los de PatternType<R> para serem consistentes com InstanceType e ReturnType ajudantes já existentes.

@ amir-arad, interessante. Qual será a aparência de interface RegExp neste caso?

@RyanCavanaugh Eu poderia reescrever a proposta original com uma forma recém-descoberta se isso ajudar. Eu devo?

@ amir-arad Sua sintaxe proposta está em conflito com o resto do TypeScript. Agora você só pode passar tipos como um argumento genérico, não uma expressão arbitrária. Sua sintaxe proposta seria extremamente confusa.

Pense em tipos genéricos como funções que recebem um tipo e retornam um tipo. Os dois trechos de código a seguir são muito próximos em significado e sintaxe:

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

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

Sua nova sintaxe é como propor que regex em JavaScript deve ser escrito let all = String(.*) que seria um abuso feio da sintaxe de chamada de função. Portanto, não acho que sua proposta faça muito sentido.

@ m93a minha sugestão foi para anotações de tipo, não javascript.

@Igmat do topo da minha cabeça, que tal:

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

@ amir-arad, desculpe, não posso adicionar mais detalhes valiosos à sua sugestão, mas à primeira vista parece uma mudança muito significativa em todo o compilador TS, porque string é um primitivo muito básico.

Mesmo que eu não veja nenhum problema óbvio, acho que tal proposta deveria ser muito mais detalhada e cobrir muitos cenários existentes, além de uma justificativa adequada de seu propósito.
Sua proposta adiciona um tipo e altera um tipo primitivo, enquanto a minha adiciona apenas um tipo.

Infelizmente, não estou pronto para dedicar muito tempo à criação de propostas para tal recurso (também, você deve ter notado que nem todas as propostas no TS foram implementadas sem um atraso significativo), mas se você trabalhar nisso, eu ' Terei todo o prazer em fornecer-lhe os meus comentários, se necessário.

Se esses tipos de regexp fossem expressões regulares reais (não expressões regulares do tipo Perl que não são regulares), poderíamos traduzi-los para FSM determinístico e usar a construção de produto cartesiano neles para obter todas as conjunções e disjunções. Expressões regulares são fechadas em operações booleanas.

Além disso, se os tipos de literal de string não fossem atômicos, mas representados como listas de caracteres em tempo de compilação, isso permitiria implementar todos os operadores nas bibliotecas. Isso só pioraria um pouco o desempenho.

Edit: Corrija um erro.

Observando que Mithril poderia realmente usá-los, e a segurança de tipos no caso geral é quase impossível sem ele. Esse é o caso com o hiperescrito e a sintaxe JSX. (Apoiamos ambos).

  • Nossos ganchos de ciclo de vida, oninit , oncreate , onbeforeupdate , onupdate , onbeforeremove e onremove , têm os seus próprios protótipos especiais.
  • Os manipuladores de eventos em vnodes DOM são literalmente qualquer coisa que comece com on , e oferecemos suporte a funções de ouvinte de evento e objetos de ouvinte de evento (com métodos handleEvent ), alinhados com addEventListener e removeEventListener .
  • Oferecemos suporte a chaves e referências conforme apropriado.
  • Todo o resto é tratado como um atributo ou propriedade, dependendo de sua existência no próprio nó DOM de apoio.

Portanto, com uma negação de tipo + tipo de string validada por regex, poderíamos fazer o seguinte para vnodes DOM:

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

    // Control attributes
    key: PropertyKey;
}

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

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

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

(Também seria bom ser capaz de extrair grupos de tais regexes, mas não vou prender a respiração sobre isso.)

Editar: Esclareça alguns detalhes críticos da proposta.
Edição 2: Corrija a parte técnica para ser matematicamente preciso.
Edição 3: Adicionar suporte para estrelas genéricas de uniões de um único caractere

Aqui está uma proposta concreta para tentar resolver isso de forma muito mais viável: tipos literais de modelo.

Além disso, acho que regexps completas provavelmente não são uma boa ideia, porque deve ser razoavelmente fácil mesclar com outros tipos. Talvez isso seja melhor: tipos literais de modelo.

  • `value` - Isso é literalmente equivalente a "value"
  • `value${"a" | "b"}` - Isso é literalmente equivalente a "valuea" | "valueb"
  • `value${string}` - Isso é funcionalmente equivalente à regexp /^value/ , mas "value" , "valuea" e "valueakjsfbf aflksfief fskdf d" são todos atribuíveis a ela.
  • `foo${string}bar` - Isso é funcionalmente equivalente ao regexp /^foo.*bar$/ , mas é um pouco mais fácil de normalizar.
  • É claro que pode haver várias interpolações. `foo${string}bar${string}baz` é um tipo de literal de modelo válido.
  • As interpolações devem estender string e não devem ser recursivas. (A segunda condição é por razões técnicas.)
  • Um tipo de modelo literal A ser atribuído a um tipo de modelo literal B se e somente se o conjunto de strings atribuível a A é um subconjunto do conjunto de strings atribuível a B .

Além do acima, um tipo especial starof T existiria, onde T deve consistir em apenas tipos de strings de caractere único. string existiria como um alias de tipo de starof (...) , onde ... é a união de todos os literais de string de caracteres UCS-2 de U + 0000 a U + FFFF, incluindo solitário substitutos. Isso permite definir a gramática completa para literais numéricos de base 10 do ES, por exemplo:

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

E da mesma forma, certos métodos integrados podem ser ajustados para retornar esses tipos:

  • Number.prototype.toString(base?) - Isso pode retornar o tipo Numeric ou alguma variante dele para base s estaticamente conhecidos.
  • +x , x | 0 , parseInt(x) e semelhantes - Quando x é conhecido como Numeric conforme definido acima, o tipo resultante pode ser inferido apropriadamente como um tipo de número literal.

E, finalmente, você pode extrair grupos correspondentes como: Key extends `on${infer EventName}` ? EventTypeMap[TagName][EventName] : never . A extração do modelo assume que está sempre trabalhando com nomes completos, então você deve usar explicitamente ${string} interpolações para pesquisar a inclusão arbitrária. Isso não é ganancioso, então ` "foo.bar.baz" extends $ {inferir T}. $ {Inferir U} ? [T, U] : never retorna ["foo", "bar.baz"] , não ["foo.bar", "baz"] .


Do ponto de vista técnico, isso é muito mais viável de implementar do que expressões regulares brutas. As expressões regulares JS nem mesmo são regulares - elas se tornam sensíveis ao contexto com referências anteriores e envolvem muita complexidade na forma de. Contanto que você bloqueie a recursão com elas, os tipos literais de modelo geram uma única linguagem regular cada, se alinha muito de perto com a teoria subjacente (mas suporta apenas um subconjunto dela).

  • Idioma vazio: ""
  • União: "a" | "b"
  • Concatenação: `${a}${b}`
  • Estrela de Kleene (parcial): starof T ( T só pode conter caracteres únicos e uniões.)

Isso pode tornar a verificação de subtipagem de string um subconjunto do pior cenário do problema de isomorfismo do subgráfico , mas existem alguns grandes fatores redentores aqui:

  1. De longe, o caso comum é a união de pequenas cordas finitas, algo que você pode modelar com árvores. É relativamente óbvio trabalhar com isso. (Não recomendo tentar uni-los como cordas, pois isso complicará o algoritmo de correspondência acima, mas é perfeitamente normal normalizar uniões de caractere único e semelhantes em uma única divisão + junção.)

  2. Você pode modelar todo o tipo unificado como um gráfico direcionado, onde:

    1. As uniões com estrelas de tais caracteres são subgráficos onde o nó pai tem arestas para cada personagem e cada nó filho do subgrafo, e cada personagem tem arestas para todos os outros personagens e todos os nós filhos do subgráfico.
    2. O resto do gráfico contém uma estrutura em forma de árvore direcionada que representa todas as outras possibilidades.

    De acordo com este bate-papo Math.SE em que estive brevemente (começando aproximadamente aqui ), descobri que este gráfico resultante teria tanto um gênero limitado (ou seja, com um número finito de saltos sobre outras arestas *) e, ausente qualquer starof tipos, um grau limitado. Isso significa que a igualdade de tipo reduz isso a um problema de tempo polinomial e, supondo que você normalize as uniões, também não é muito lento, pois é apenas um pouco mais rápido do que a igualdade da árvore. Eu suspeito fortemente que o caso geral para toda esta proposta (um subconjunto do problema de isomorfismo do subgrafo) também é tempo polinomial com coeficientes razoáveis. (O artigo da Wikipedia com link acima tem alguns exemplos nas seções "Algoritmos" e referências onde maiúsculas e minúsculas especiais podem ser aplicadas.)


  3. Provavelmente nenhuma dessas chaves será grande , portanto, a maior parte do custo real do tempo de execução aqui é amortizado na prática por outras coisas. Contanto que seja rápido para teclas pequenas, é bom o suficiente.


  4. Todos os subgráficos que seriam comparados compartilham pelo menos um nó: o nó raiz. (Isso representa o início da string.) Portanto, isso reduziria drasticamente o espaço do problema por si só e garantiria uma verificação de tempo polinomial.


E, claro, a interseção entre esses tipos não é trivial , mas sinto que existem fatores redentores semelhantes simplesmente devido às restrições acima. Em particular, a última restrição torna obviamente a execução em tempo polinomial.

* Matematicamente, o gênero é definido um pouco contra-intuitivamente para nós, programadores (o número mínimo de buracos que você precisa cutucar em uma superfície para desenhar o gráfico sem nenhum salto), mas um gênero limitado (número limitado de buracos) implica em um número limitado de saltos .

Usando esta proposta concreta, aqui está como meu exemplo deste comentário se traduz:

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

    // Control attributes
    key: PropertyKey;
}

interface HTMLTypeMap {
    // ...
}

interface HTMLEventMap {
    // ...
}

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

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

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

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

Edit: Isso também permitiria digitar corretamente 90% do método _.get de Lodash e métodos relacionados usando sua abreviação de propriedade, como seu método _.property(path) e sua abreviação _.map(coll, path) . Provavelmente há vários outros em que não estou pensando também, mas esse é provavelmente o maior em que consigo pensar. (Vou deixar a implementação desse tipo como um exercício para o leitor, mas posso garantir que é possível com uma combinação disso e o truque usual de tipos condicionais com um registro imediatamente indexado, algo como {0: ..., 1: ...}[Path extends "" ? 0 : 1] , para processar a string do caminho estático.)

Minha recomendação é que concentremos nossos esforços na implementação de provedores de tipo, que podem ser usados ​​para implementar tipos regex.

Por que provedores de tipo em vez de implementar diretamente tipos de regex? Porque

  1. É uma solução mais genérica que adiciona muitas novas possibilidades ao TypeScript, tornando mais fácil obter suporte de um grupo mais amplo de desenvolvedores, além daqueles que veem o valor em tipos de string regex.
  2. Os proprietários do repositório datilografado parecem estar abertos a essa ideia e aguardam a proposta certa. Veja # 3136

F # tem um provedor de tipo de regex de código aberto.

Algumas informações sobre provedores de tipo: https://link.medium.com/0wS7vgaDQV

Pode-se imaginar que, uma vez que os provedores de tipo são implementados e o provedor de tipo regex é implementado como uma biblioteca de código aberto, pode-se usá-lo assim:

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 Não estou convencido de que esse seja o caminho correto a seguir, pelo menos não para este pedido.

  • O TypeScript é digitado estruturalmente, não nominalmente, e para manipulação literal de string, quero manter esse espírito estrutural. Provedores de tipo como esse criariam um subtipo string nominal, onde RegexProvider</^foo$/> não seria tratado como equivalente a "foo" , mas um subtipo nominal dele. Além disso, RegexProvider</^foo$/> e RegexProvider</^fo{2}$/> seriam tratados como dois tipos distintos, e isso é algo de que não sou fã. Minha proposta, em vez disso, integra-se diretamente com as cordas em seu núcleo, diretamente informada pela teoria do reconhecimento formal da linguagem para garantir que se encaixe naturalmente.
  • Com o meu, você pode não apenas concatenar strings, mas extrair partes de strings via Key extends `on${infer K}` ? K : never ou mesmo Key extends `${Prefix}${infer Rest}` ? Rest : never . Os provedores de tipo não oferecem essa funcionalidade e não há uma maneira clara de como deveria se essa funcionalidade fosse adicionada.
  • O meu é consideravelmente mais simples no nível conceitual: estou apenas sugerindo que adicionemos tipos de concatenação de string e, para o RHS de tipos condicionais, a capacidade de extrair seu inverso. Também proponho que ele se integre com o próprio string para tomar o lugar de uma regexp /.*/ . Não requer alterações de API e, além das duas partes teoricamente complexas que são em sua maioria desacopladas do resto da base de código, calcular se um tipo de literal de modelo pode ser atribuído a outro e extrair uma fatia de uma string é semelhante, se não mais simples , implementar.

BTW, minha proposta ainda poderia digitar aquele PhoneNumber exemplo, também. É um pouco mais detalhado, mas estou tentando modelar dados que já estão no terreno do TS, não dados que existem em outro lugar (para que os provedores de tipo do F # são mais úteis). (É importante notar que isso tecnicamente seria expandido para a lista completa de números de telefone possíveis aqui.)

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 $ /> e RegexProvider ^ fo {2} $ /> seriam tratados como dois tipos distintos

Provedores de tipo podem exigir a implementação de algum método equals ou compare , de forma que o autor do provedor de tipo de um provedor de tipo regex possa definir que ambos os casos acima são tipos equivalentes. O autor do provedor de tipo pode implementar a tipificação estrutural ou nominal como quiser.

Talvez seja possível implementar seu tipo literal de string como um provedor de tipo também. Eu não acho que a sintaxe poderia ser a mesma, mas você poderia chegar perto de um provedor de tipo que aceita um número variável 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 Mas o tipo "123-456-7890" atribuído ao seu tipo? (Se for assim, isso complicará a implementação e tornará o verificador muito lento.)

Semi-relacionado à discussão em questão, e se o tipo não tiver um comprimento fixo (como um número de telefone)? Uma situação em que eu gostaria de usar isso recentemente é para armazenar o nome de uma sala, no formato thread_{number} .

A regex para corresponder a esse valor é thread_[1-9]\d* . Com o que está sendo proposto, não parece viável (ou mesmo possível) corresponder a tal formato. A parte numérica do valor pode ser _qualquer_ comprimento maior que zero nesta situação.

@jhpratt Eu revisei minha proposta para acomodar isso, na forma de starof ("0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9")/^\d*$/ , já que era necessária apenas uma pequena mudança. Ele otimiza da mesma forma que string otimiza como /^[\u0000-\uFFFF]*$/ , então decidi ir em frente e generalizar isso.

Não quero estender starof além disso, como aceitar uniões não recursivas arbitrárias, devido a questões de complexidade computacional: verificar se duas expressões regulares arbitrárias * são equivalentes pode ser feito no espaço polinomial ou no muito lentas na prática e AFAICT não pode ser dos dois modos. Adicione suporte para quadratura (como a{2} ), e é basicamente inviável (complexidade exponencial) . Isso é apenas para equivalência, e verificar se um regexp corresponde a um subconjunto de strings de outro regexp, necessário para verificar a capacidade de atribuição, obviamente será ainda mais complicado.

* Expressões regulares no sentido matemático: estou incluindo apenas caracteres únicos, () , (ab) , (a|b) e (a*) , onde a e b são (potencialmente diferentes) cada um dos membros desta lista.

Esta é provavelmente uma pergunta idiota, mas ... por que não é bastante fácil, se adequadamente limitado, oferecer suporte a uma função de validação (lambda ou nomeada)?

Por exemplo, suponha que usamos ":" para indicar que o próximo elemento é um validador (substitua o que quiser por ":" se você tiver uma opinião sobre isso):

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 um começo inicial, o typescript só poderia aceitar funções de validação que:

  • tem um único argumento, que é exigido / considerado do tipo "base"
  • apenas variáveis ​​de referência que são definidas na função de validador
  • retornar um valor (que será forçado a bool no processo de validação)

As condições acima parecem fáceis para o compilador de texto digitado verificar e, uma vez que essas condições sejam assumidas, grande parte da complexidade da implementação desapareceria.

Além disso, se necessário, restringir o escopo inicial a um tamanho gerenciável:

  • funções de validação só podem ser adicionadas a um subconjunto de tipos nativos (string, número)

Não acho que esta última restrição seja tão necessária, mas se houver alguma dúvida se seria, também não acho que valeria a pena perder muito tempo debatendo-a, porque uma solução com a limitação acima ainda resolveria uma grande variedade de casos de uso do mundo real. Além disso, vejo poucas desvantagens nas limitações acima, porque relaxá-las mais tarde seria uma extensão simples e natural que não exigiria nenhuma mudança na sintaxe básica e simplesmente expandiria a amplitude do suporte à linguagem pelo compilador.

@mewalig Isso significaria que algo que se parece com uma função de tempo de execução na verdade não seria executado em tempo de execução, mas em tempo de compilação (e toda vez que você quiser verificar a designação). Essas funções não podiam acessar nada do tempo de execução (variáveis, funções), o que seria muito estranho.

Além disso, você geralmente não quer que o compilador execute qualquer coisa que você jogue nele, especialmente funções mal otimizadas ou totalmente mal-intencionado while(true){} . Se você quer metaprogramação, precisa projetá-la de maneira inteligente. Permitir que o código em tempo de execução seja executado aleatoriamente em tempo de compilação seria a "maneira do PHP" de fazer isso.

Finalmente, a sintaxe que você propõe muda o padrão usual

let runtime: types = runtime;

(ou seja, tipos após dois pontos) de dentro para fora, efetivamente sendo

type types = types: runtime;

o que é horrível. Obrigado por sua proposta, mas é definitivamente uma má ideia.

Essas funções não podiam acessar nada do tempo de execução (variáveis, funções), o que seria muito estranho.

É claro que eles poderiam, se o compilador tem uma ECMAScript tempo de execução disponível para ele ( tsc acontecer, BTW!). Obviamente, você tem um problema de ambigüidade com a semântica de tempo de compilação, por exemplo, fetch() vs. semântica de tempo de execução, mas é disso que se trata a iteração.

Permitir que o código em tempo de execução seja executado aleatoriamente em tempo de compilação seria a "maneira do PHP" de fazer isso.

É muito semelhante às funções C ++ constexpr , que são boas. A solução aí é dizer que constexpr só pode usar constexpr , mas tudo pode usar constexpr . Então você poderia ter constexpr -versões equivalentes do sistema de arquivos para o sistema de arquivos em tempo de compilação, que podem ser bastante poderosas.

A sintaxe também parece boa para mim: o LHS é um tipo, claro que o RHS também é algum tipo. Meu problema é mais sobre como você comporia tipos além do tipo "base", mas isso também pode ser resolvido.

Obrigado por sua proposta, mas é definitivamente uma má ideia.

Pode acabar sendo uma má ideia, mas, por enquanto, estou apenas vendo uma ideia muito pouco especificada que provavelmente exigirá que você se distancie muito dos objetivos do texto datilografado. Isso não significa que pode não haver uma boa ideia semelhante a ela!

A discussão sobre esse recurso parece parar por agora ( PR está fechado e de acordo com a equipe de notas de design _não quero me comprometer com isso até que tenhamos tipos nominais e assinaturas de índice generalizadas, e devemos saber como eles se parecem._).

De qualquer forma, quero propor outra extensão hipotética para o PR atual que suportaria a extração de padrões regex ( @isiahmeadows apresentou sua própria proposta, mas para ser honesto, não consigo entender agora ...).

Eu realmente gosto de RP atual e basearia minha proposta nisso. Eu gostaria de propor a sintaxe baseada na inferência de argumentos de tipo genérico que temos para funções (e tipos condicionais com infer palavra-chave). Simplesmente porque as pessoas já têm alguma intuição de que em funções genéricas você pode "extrair" tipos de objetos literais passados.

Por exemplo, temos este tipo.

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

e podemos usar este tipo para testar tipos literais

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

No entanto, quando usamos o tipo Regex no parâmetro de função, podemos usar a sintaxe de colchetes angulares para significar que queremos inferir strings correspondentes. Por exemplo

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 o tipo inferido de res1 é ["foo", "bar"]

É útil?

  1. Função get Ember.js / lodash

Você poderia implementar um getter de "caminho de string" de tipo seguro para que este código funcionasse:

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

Mas provavelmente seria necessário resolver isso se quisermos evitar muitas sobrecargas para um número máximo fixo de "profundidade" possível de get .

  1. Use parâmetros extraídos em tipos mapeados.

Por exemplo, se formos capazes de fazer algo como https://github.com/Microsoft/TypeScript/issues/12754. Então poderíamos ter a possibilidade de reverter a função (retirar algum prefixo / sufixo de todas as propriedades de determinado tipo). Este provavelmente precisaria introduzir alguma forma mais generalizada de sintaxe tipada mapeada para escolher uma nova chave para a propriedade (por exemplo, sintaxe como { [ StripAsyncSuffix<P> for P in K ] : T[P] } , alguém já propôs algo assim)

Provavelmente haveria outros casos de uso também. Mas eu acho que a maioria se encaixaria nesses dois tipos (1. descobrir o tipo adequado com base no literal de string fornecido, 2. transformar nomes de propriedades do tipo de entrada em novos nomes de propriedades do novo tipo definido)

Isso é algo que poderíamos fazer.

No momento, estou construindo regras de lint personalizadas para poder validar urls - embora isso seria muito mais fácil se pudéssemos definir os parâmetros opcionais - que requerem um regex para poder validar nossos ids

Em geral, isso nos forneceria muito mais poder para afirmar a validade dos props em nossa base de código

Existe algum movimento nos provedores de tipo, literais de string de modelo ou outras sugestões? Esta seria uma ótima ferramenta.

Minha solução alternativa para isso atualmente é usar uma interface de marcador como esta .

interface TickerSymbol extends String {}

O único problema é que, quando quero usá-lo como uma chave de índice, tenho que convertê-lo em 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

No entanto, JavaScript parece funcionar bem com o tipo de índice String (com S maiúsculo).

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 Eu tenho uma proposta aqui , e uma proposta separada foi criada no final de 2016 , então os rótulos para isso poderiam ser atualizados?

Revisamos as propostas acima e temos algumas perguntas e comentários.

Aspectos problemáticos das propostas até agora

Tipos Criando Emit

Estamos comprometidos em manter o sistema de tipo totalmente apagado, de modo que as propostas que requerem aliases de tipo para produzir o código emitido estão fora do escopo. Vou destacar alguns exemplos neste tópico em que isso aconteceu de uma forma que não é óbvia:

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -220180091 - cria uma função e um tipo ao mesmo tempo

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

https://github.com/microsoft/TypeScript/issues/6579#issuecomment -261519733 - também faz isso

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

Vou reiterar: isso não é um bom começo . Os tipos no TypeScript são combináveis e emitir JS de tipos não é possível neste mundo. A proposta mais longa até agora tem extensos tipos de emissão; isso não é viável. Por exemplo, isso exigiria uma emissão extensiva direcionada ao tipo:

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

Proibições de interseções

Na verdade, os tipos comuns e os tipos validados por regex são realmente diferentes, portanto, precisamos de regras sobre como lidar corretamente com suas uniões e interseções.

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

O TypeScript não pode errar em instanciações de interseções, então isso não faria parte de nenhum projeto final.

Ergonomia

No geral, nossa conclusão mais importante é que queremos algo em que você não esteja

Dadas as preocupações acima sobre a emissão de tipo, a solução mais realista é que você escreveria a expressão no espaço de valor:

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

Você ainda poderia escrever o RegExp no espaço de tipo, é claro, mas não haveria validação de tempo de execução disponível e qualquer uso não literal exigiria um novo teste ou afirmação:

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

Coleta e Esclarecimento de Casos de Uso

Para um novo tipo de tipo, gostaríamos idealmente de ver vários exemplos em que:

  • O problema a ser resolvido não tem
  • O problema ocorre com frequência significativa em bases de código reais
  • A solução proposta resolve bem esse problema

Validação de literais em tempo de compilação

Este segmento implica em uma ampla variedade de casos de uso; exemplos concretos têm sido mais raros. É preocupante que muitos desses exemplos não pareçam completos - eles usam um RegExp que rejeitaria entradas válidas.

  • Cor da fonte - AFAIK qualquer coisa que aceite cores hexadecimais também aceita, por exemplo, "branco" ou "azul celeste". Isso também rejeita incorretamente a sintaxe rgb(255, 0, 0) .
  • SSN, CEP etc. - OK, mas por que existem SSNs ou códigos postais literais em seu código? Isso é realmente uma necessidade para tipos nominais? O que acontece se você tiver uma subclasse de strings que não pode ser descrita com precisão por um RegExp? Veja "Propostas concorrentes"

    • Inteiro - rejeita incorretamente "3e5"

    • Email - Isso geralmente é considerado uma má ideia . Mais uma vez, há literais de string de endereço de e-mail em seu código ?

    • Especificações CSS Border - eu poderia acreditar que uma biblioteca autônoma poderia fornecer um RegEx preciso para descrever o DSL que ela mesma suporta

    • Escrevendo testes - é aqui que as entradas codificadas fazem algum sentido, embora isso seja quase um contraponto, porque seu código de teste provavelmente deve fornecer muitas entradas inválidas

    • Formatos de data - como / por quê? Date tem um construtor para isso; se a entrada vem de fora do tempo de execução, então você só quer um tipo nominal

    • URI - você pode imaginar que fetch especificaria host para não estar com http(s?):

TODO: Ajude-nos identificando funções de biblioteca reais que poderiam se beneficiar dos tipos RegExp e a expressão real que você usaria.

Uma preocupação é "precisionitis" - o que acontece quando alguém útil aparece em DefinitelyTyped e adiciona tipos RegExp a todas as funções em uma biblioteca, interrompendo assim todas as invocações não literais? Pior, os autores do arquivo de definição terão que concordar exatamente com os consumidores dele qual é a "grafia correta" de um RegExp de validação.
Parece que isso nos coloca rapidamente no caminho para uma situação de Torre de Babel , onde cada biblioteca tem sua própria versão do que se qualifica como um URL, o que se qualifica como um nome de host, o que se qualifica como um e-mail, etc, e qualquer pessoa conectando duas bibliotecas tem que inserir asserções de tipo ou copiar regexes para satisfazer o compilador.

Aplicação de verificações de tempo de execução

Houve alguma discussão sobre verificações em que desejamos garantir que os argumentos de uma função foram validados por uma regex anterior, como fn na seção anterior de Ergonomia . Isso parece direto e valioso, se o RegEx que precisa ser testado for bem conhecido. No entanto, esse é um grande "se" - pelo que me lembro, não consigo me lembrar de uma única biblioteca que forneça regexes de validação. Ele pode fornecer funções de validação - mas isso implica que o recurso a ser fornecido são tipos nominais ou marcados, não tipos regex.

A contra-evidência a esta avaliação é bem-vinda.

Chaves de propriedade / indexadores de string Regex

Algumas bibliotecas tratam objetos de acordo com os nomes das propriedades. Por exemplo, no React, queremos aplicar tipos a qualquer prop cujo nome comece com aria- :

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

Este é efetivamente um conceito ortogonal (poderíamos adicionar tipos Regex sem adicionar chaves de propriedade Regex e vice-versa).

TODO (eu ou qualquer pessoa): Abra uma edição separada para isso.

Propostas concorrentes

Tipos nominais ou marcados

Digamos que temos tipos nominais / marcados de algum tipo:

type ZipCode = make_unique_type string;

Você poderia então escrever uma função

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

Nesse ponto, você realmente precisaria dos tipos RegExp? Consulte a seção de verificação de "tempo de compilação" para mais ideias.

Por outro lado, digamos que temos tipos RegExp e não tipos nominais. É muito tentador começar (ab) a usá-los para cenários de não validação:

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

Uma coisa comum no thread é que essas regexes ajudariam a validar o código de teste, porque embora em cenários de produção o código fosse executado em strings fornecidas pelo tempo de execução em vez de literais codificados permanentemente, você ainda quer alguma validação de que suas strings de teste foram " correto". Isso parece ser um argumento para strings nominais / marcadas / com marca, entretanto, uma vez que você escreveria a função de validação de qualquer maneira, e a vantagem dos testes é que você sabe que eles são executados exaustivamente (portanto, quaisquer erros nas entradas de teste seriam ser sinalizado no início do ciclo de desenvolvimento).

Sem problemas

Discutimos os seguintes aspectos e consideramos que não são bloqueadores

Capacidades de host

Os tempos de execução mais recentes suportam mais sintaxe RegExp do que os tempos de execução mais antigos. Dependendo de onde o compilador TypeScript é executado, determinado código pode ser válido ou inválido de acordo com os recursos do tempo de execução de análise de recursos RegExp mais recentes. Na prática, a maioria dos novos recursos do RegExp são bastante esotéricos ou estão relacionados à correspondência de grupo, que não parece se alinhar com a maioria dos casos de uso aqui.

atuação

RegExes pode fazer uma quantidade ilimitada de trabalho e a correspondência com uma grande string pode fazer uma quantidade arbitrariamente grande de trabalho. Os usuários já podem usar o DOS por outros meios e provavelmente não escreverão um RegExp mal-intencionado e ineficiente.

Subtipagem ( /\d*/ -> /.*/ ?), União, Cruzamento e Inabitabilidade

Em teoria, /\d+/ é um subtipo conhecível de /.+/ . Supostamente, existem algoritmos para determinar se um RegExp corresponde a um subconjunto puro de outro (sob certas restrições), mas obviamente exigiria a análise da expressão. Na prática , estamos 100% OK com RegExpes não formando relacionamentos de subtipos implícitos com base no que eles correspondem; isso é provavelmente até preferível.

As operações de união e interseção funcionariam "fora da caixa", desde que as relações de atribuibilidade fossem definidas corretamente.

No TypeScript, quando dois tipos primitivos "colidem" em uma interseção, eles se reduzem a never . Quando duas RegExpes são interceptadas, apenas manteríamos isso como /a/ & /b/ vez de tentar produzir uma nova RegExp correspondendo à interseção das duas expressões. Não haveria nenhuma redução para never , precisaríamos de um algoritmo para provar que nenhuma string poderia satisfazer ambos os lados (este é um problema paralelo ao descrito anteriormente re: subtipagem).

Próximos passos

Para resumir, as próximas etapas são:

  • Arquive um problema separado para chaves de propriedade nomeadas de Regex, também conhecidos como indexadores de strings de regex
  • Obtenha casos de uso concretos e plausíveis para validação em tempo de compilação de literais de string

    • Exemplo: identifique funções em DefinitelyTyped ou outras bibliotecas que se beneficiariam muito com isso

  • Entenda se os tipos nominais / marcados / com marca são uma solução mais flexível e amplamente aplicável para validação não literal
  • Identifique as bibliotecas que já fornecem RegExes de validação

Caso de uso: Hyperscript (https://github.com/hyperhype/hyperscript) como funções
Uma função de hiperescrito geralmente é chamada como h('div#some-id')
Um matcher de padrão regex-ish permitiria determinar o tipo de retorno de h que seria HTMLDivElement no caso de exemplo.

Se o sistema de tipo for capaz de adicionar literais de string, então basicamente qualquer propriedade CSS pode ser segura de tipo

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

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

Os seletores CSS também podem ser validados ( element.class#id - válido, div#.name - inválido)

Se a captura de grupos funcionasse (de alguma forma), o método get Lodash poderia ser seguro para tipos

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

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

Isso também pode ser uma coisa:

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

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

Caso de uso: Hyperscript (hyperhype / hyperscript) como funções

Qual seria a aparência dessa regex ou que validação ela forneceria? Isso é para sobrecarga de função baseada em regex?

FWIW A biblioteca aceita nomes de tags com namespaces e também funções em nomes de tags arbitrários

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

Ele também aceita uma combinação ilimitada de valores de classe e id

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

Os seletores CSS também podem ser validados

Os seletores CSS não podem ser validados por uma expressão regular

Qual seria a aparência dessa regex ou que validação ela forneceria? Isso é para sobrecarga de função baseada em regex?

Não o OP, mas presumo, sim, algo como as sobrecargas de HTMLDocument#createElement() , por exemplo:

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

Tenho certeza que os REs estão incompletos. Observe que este é um caso especial de validação de seletores CSS, que são usados ​​em muitos contextos de maneira regular. Por exemplo, é perfeitamente normal que HTMLDocument.querySelector() retorne HTMLElement como fallback se você estiver usando um seletor complexo.

Estou curioso para saber se existem exemplos de não sobrecarga que sejam viáveis ​​e úteis, no entanto.

TODO: Ajude-nos identificando funções de biblioteca reais que poderiam se beneficiar dos tipos RegExp e a expressão real que você usaria.

Meu caso de uso é aquele que expliquei neste comentário na biblioteca CCXT, onde tenho strings que representam TickerSymbol s. Eu realmente não me importo se eles são verificados para um padrão regex, mas eu quero que eles sejam tratados como subtipos de string então eu recebo atribuições mais rígidas, verificação de tipo de parâmetro, etc. ser muito útil quando estou fazendo programação funcional, com isso posso rastrear facilmente TickerSymbols, moedas, ativos, etc em tempo de compilação onde em tempo de execução são apenas strings normais.

@omidkrad Parece que você precisa de tipos nominais , não de tipos validados por regex.

@ m93a No meu caso,

Os seletores CSS também podem ser validados

Os seletores CSS não podem ser validados por uma expressão regular

Bem, se o regexp nos permitisse juntá-los, poderíamos copiar regexes CSS ..., certo?

O (rascunho) Modelo de Objeto Tipo CSS

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

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

Potencialmente, alivia o desejo de usar o modelo CSS tipado por string.

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

@RyanCavanaugh Para Mithril em particular, o nome da tag é extraído por meio do grupo de captura em ^([^#\.\[\]]+) (o padrão é "div" ), mas combinar ^(${htmlTagNames.join("|")}) seria suficiente para nossos propósitos. E então, usando minha proposta , isso seria suficiente para meus objetivos:

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

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

Quanto aos eventos e atributos, poderíamos mudar para este terreno de tipos antes negados:

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

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

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

BTW, essa integração perfeita e evitar a complexidade é o motivo pelo qual ainda prefiro minha proposta ao invés de regexps literais.


No entanto, não conheço nenhuma maneira de fazer isso com tipos regexp puros. Eu quero apontar isso.

TODO: Ajude-nos identificando funções de biblioteca reais que poderiam se beneficiar dos tipos RegExp e a expressão real que você usaria.

bent tem um tipo de retorno diferente com base no que é fornecido como uma string que descreve o tipo de resposta esperado, por exemplo

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

Ele também aceita alguns outros argumentos, como método e url como strings, mas eles podem aparecer em qualquer posição, então, se tentarmos usar uniões para descrever todos os tipos de retorno ( 'json' | 'buffer' | 'string' ), isso seria simplificado para apenas string quando combinado com os tipos de url e método na união, o que significa que não podemos inferir automaticamente o tipo de retorno com base no tipo fornecido na primeira chamada.

@Ovyerus, como os tipos de regex o ajudariam

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, eu não estava claro, acho que meu problema era mais próximo a http(s): no início de uma string para detectar o URL base.

A assinatura de Bent segue mais as linhas 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

No entanto, ter BaseUrl como string absorve os HttpMethods e as uniões de tipo de retorno, que acabam sendo apenas string . Tê-lo apenas como uma string também "indevidamente" corresponde ao funcionamento do bent, pois verifica a presença de ^http: ou ^https: para determinar o que deve ser usado como url base.

Se tivéssemos tipos regex, eu poderia definir BaseUrl como type BaseUrl = /^https?:/ , e isso idealmente verificaria corretamente as strings que não são um método HTTP ou codificação de resposta, bem como não as absorveria no string modelo.

Exatamente, eu sou o mesmo.

-
Prokop Simek

Em 20 de outubro de 2019 às 03:23:30, Michael Mitchell ([email protected])
escreveu:

Oh, eu não estava claro, desculpe, mas acredito que meu problema era mais na linha de
http (s) correspondente (s): no início de uma string para detectar o URL base.

A assinatura de Bent segue mais as linhas de

tipo HttpMethods = 'GET' | 'PATCH' | ... digite StatusCode = número; digite BaseUrl = string; // Aqui é onde eu idealmente precisaria ver se uma string corresponde a http (s): type Headers = {[x: string]: any; };
type Options = HttpMethods | StatusCode | BaseUrl | Cabeçalhos;
function bent (... args: Options []): RequestFunctionfunction bent (... args: (Options | 'json') []): RequestFunction// e assim por diante

No entanto, ter BaseUrl como uma string absorve os HttpMethods e retorna
tipo unions, que acaba sendo apenas string. Tê-lo apenas como uma corda
também corresponde "indevidamente" ao modo como o bent funciona, pois verifica a presença
de ^ http: ou ^ https: a fim de determinar o que deve ser usado como base
url.

Se tivéssemos tipos de regex, eu poderia definir BaseUrl como tipo BaseUrl = / ^ https?: /,
e isso idealmente verificaria corretamente as strings que não são um método HTTP ou
codificação de resposta, bem como não absorvê-los no tipo de string.

-
Você está recebendo isto porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/microsoft/TypeScript/issues/6579?email_source=notifications&email_token=ABJ3U4JNK3V5MV4DJH73ZU3QPOXJFA5CNFSM4BZLAVSKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEBYABKA#issuecomment-544211112 ,
ou cancelar
https://github.com/notifications/unsubscribe-auth/ABJ3U4PHBXO4766LK7P7UXDQPOXJFANCNFSM4BZLAVSA
.

A ideia que tive de um caso de uso foi detectar tipos de parâmetros para uma função.

Basicamente, tenho um formato regex bem definido de uma string que representa um identificador. Eu poderia usar decoradores, mas um tipo de string estendida me permitiria usar um tipo para representar o identificador passado para a função.

Para reiterar, precisamos de exemplos de código JavaScript que você deseja escrever de forma digitada - caso contrário, podemos apenas adivinhar o que você está tentando modelar (e se já existe uma maneira de modelá-lo).

@DanielRosenwasser Abaixo está um exemplo de código sobre o qual gostaríamos de forçar a digitação. http://www.typescriptlang.org/play/index.html#code/C4TwDgpgBAqjCSARKBeKBnYAnAlgOwHMBYAKFIGMAbAQ3XVnQiygG9SoOoxcA3a4aJn45yULBGoATAPZ5KIKAFtq + GIywAuWAmRoA5NQCcEAAwBGQwCMArAFoAZtYBMAdlsAWcvYActw2ZM7JzNJF28TCCcANgtLPXZOAHpErl5 + QWBhUXEpWXklFTw1Ji04JFQoMycAZihkqAA5AHkAFSgAQQAZTqaAdQBRRASOeu4cPgEMTOARMQkZOQVlVXVSnQq9PGlgW2pbAFd9nEk9OpSAZQAJJphO5Ga2gCF + ju6 + Wah ++ BBL-oAlYZQciyTBYfbkYDSLAACkBnC4 + 0slFmxzWSAANHDOGBEcjRJYsNQ8JItKD8ARMSR4fCcUjZuocNRKFo8PtFJYmJTqdjcbNyDkBJJHiA0boGEwAHTLIrqACUrFICQAvqQVSQgA

@yannickglt parece que você quer um tipo nominal, não um tipo RegExp? Você não está esperando que os chamadores apareçam com invocações validadas pelo site aleatórias como esta:

// 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 o fato de você ser capaz de descrever um UUID com uma expressão regular é um artefato do formato da própria string, enquanto o que você está tentando expressar é que UUIDs são um tipo especial de tipo cujo formato de apoio é uma string .

Portanto, a combinação de Assertion Functions do 3.7 e o recurso nominal pode fazer isso (?)

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

  }
}

Isso também falhará?

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

Depois de pensar um pouco, vejo um problema com minha solução proposta 🤔
O asserts apenas dispara um erro no tempo de execução -> 👎
O Regex-Validation pode desencadear um erro em tempo de compilação -> 👍
Caso contrário, esta proposta não faz sentido

Editar:
Outro problema: someFunc(uuid: any): asserts uuid is UUID não retorna um UUID, ele lança ou retorna is UUID -> true .
Portanto, não posso usar esta função para atribuir um UUID desta forma a mainUser

@RyanCavanaugh Queremos que sejam digitados corretamente 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 rejeitar estaticamente estes:

// 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, também gostaríamos de rejeitá-los estaticamente, mas não é de alta prioridade e podemos sobreviver sem eles:

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

Isso exigiria uma definição de tipo muito mais complicada, em que precisaríamos de uma mensagem de falha de verificação de tipo personalizada para ajudar os usuários a descobrir por que falhou na verificação de tipo.

Outras bibliotecas de hiperescritos e estruturas baseadas em hiperescritos, como react-hyperscript, também têm preocupações semelhantes.

Espero que isto ajude!

@isiahmeadows melhor maneira de você usar alguma forma de construtor de string de seletor, que retornará string de marca, com digitações corretas. Gostar:

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

@ anion155 Existem outras maneiras de chegar lá também, mas trata-se de digitar uma biblioteca cuja API foi projetada por seu autor original em 2014. Se eu estivesse projetando sua API agora, provavelmente usaria m("div", {...attrs}, ...children) com nenhum açúcar hiperscrito (mais fácil de digitar, muito mais simples de processar), mas agora é tarde demais para fazer muito a respeito.

Eu tenho MUITO a dizer. No entanto, estou impaciente. Então, vou liberar meus pensamentos um pouco de cada vez.

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

Em relação a "precisão" (cara, adoro essa palavra),
Não acho que devemos nos preocupar muito com isso.

O sistema de tipos já está completo.
Basicamente, isso significa que podemos ser superprecisos sobre muitas coisas.
(Como modelar todo o SQL? Shameless plug = P)

Mas você não vê (muitas) pessoas indo ao máximo e usando todos os operadores de tipo de maneiras malucas que impedem que as bibliotecas sejam compatíveis umas com as outras. Gosto de pensar que os autores de bibliotecas tendem a ser equilibrados o suficiente ... Certo?

Não é sempre que eu desejava tipos de padrão de string / tipos de string validados por regex, mas eles definitivamente teriam ajudado a aumentar a segurança de tipo de minha base de código.


Caso de uso

Pensando bem, posso pensar em um exemplo recente. (Tem mais um monte, mas eu sou um ser esquecido)

Ao integrar com a API do Stripe (uma plataforma de processamento de pagamento), eles usam ch_ para identificadores relacionados a charge , re_ para identificadores relacionados a refund etc.

Teria sido bom codificá-los com PatternOf</^ch_.+/> e PatternOf</^re_.+/> .

Dessa forma, ao fazer erros de digitação como,

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

Eu obteria um erro,

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

Por mais que eu ame tipos nominais / marcados, eles são muito menos ergonômicos e sujeitos a erros.
Sempre vejo tipos nominais / marcados como último recurso , porque significa que há algo que o sistema de tipo TS simplesmente não pode modelar.

Além disso, os tipos marcados são ótimos para tipos fantasmas.
Os tipos nominais basicamente nunca são úteis.
(Ok, posso ser tendencioso. Eles são úteis apenas por causa de unique symbol Mas gosto de pensar que não estou completamente errado.)

O padrão "ValueObject" para validação é ainda pior e não vou me preocupar em falar sobre isso.


Comparação

Abaixo, vou comparar o seguinte,

  • Tipos de padrão de string / tipos de string validados por regex
  • Tipos nominais
  • Tipos de tags estruturais

Todos podemos concordar que o padrão "ValueObject" é a pior solução, e não nos incomodarmos com ele nas comparações, certo?


Tipos de padrão de string

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

Olhe para isso.

  • Perfeito para literais de string.
  • Nada mal para string não literais.

Tipos nominais ...

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

Olhe para isso.

  • TERRÍVEL para literais de string.
  • Depois de superar o obstáculo literal da string, não é tão ruim ... Certo?

Mas o principal caso de uso para esta proposta são literais de string.
Portanto, esta é uma alternativa terrível.


Tipos de tags estruturais ...

Os tipos de tags estruturais não são muito diferentes dos tipos nominais ...

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

Olhe para isso.

  • TERRÍVEL para literais de string.
  • Depois de superar o obstáculo literal da string, não é tão ruim ... Certo?

Mas o principal caso de uso para esta proposta são literais de string.
Portanto, esta é uma alternativa terrível.

Além disso, este exemplo de tipo de tag estrutural é um copy-paste literal (ha, pun) do exemplo de tipo nominal .

A única diferença é como os tipos StripeChargeId e StripeRefundId são declarados.

Mesmo que o código seja basicamente o mesmo, os tipos estruturais são melhores do que os tipos nominais. (Vou esclarecer isso no próximo post, juro).


Conclusão

Esta é apenas uma conclusão para este comentário! Não é uma conclusão para meus pensamentos gerais!

Os tipos de padrão de string / tipos de string validados por regex são mais ergonômicos do que os tipos de tag nominal / estrutural. Felizmente, meus exemplos simples não foram muito inventados e demonstraram isso, de forma suficiente.


Conclusão (Extra)

Tanto quanto possível, as formas de obter o subconjunto de um tipo primitivo devem sempre ser preferidas em vez de tipos nominais / tag estrutural / objeto de valor.

Exemplos de como pegar o subconjunto de tipos primitivos,

  • string literais
  • number literais (excluindo NaN, Infinity, -Infinity )
  • boolean literais
  • bigint literais
  • Mesmo unique symbol está apenas pegando um subconjunto de symbol

Dos exemplos acima, apenas boolean é "finito o suficiente". Possui apenas dois valores.
Os desenvolvedores estão satisfeitos em ter true e false literais porque não há muito mais o que pedir.


O tipo number é finito, mas tem tantos valores que podemos considerá-lo infinito.
Também existem lacunas nos literais que podemos especificar.

É por isso que o tipo de número de intervalo e as questões de NaN, Infinity, -Infinity são tão populares e continuam aparecendo. Ser capaz de especificar um pequeno conjunto finito de valores, a partir de um conjunto infinito, não é bom o suficiente.

Especificar um intervalo é uma das ideias mais comuns / naturais que alguém chega quando precisa especificar um grande subconjunto finito / infinito de um conjunto infinito.


O tipo bigint é basicamente infinito, limitado apenas pela memória.

Também contribui para a popularidade do problema do tipo de número de intervalo.


O tipo string é basicamente infinito, limitado apenas pela memória.

E é por isso que esse tipo de padrão de string / tipo de string validado por regex é tão popular.

Especificar um regex é uma das ideias mais comuns / naturais que vêm a alguém quando precisa especificar um grande subconjunto finito / infinito de um conjunto infinito.


O tipo symbol ... Também é infinito. E também ilimitado, praticamente.

Mas os elementos do tipo symbol praticamente não estão relacionados entre si, em quase todos os sentidos. E, portanto, ninguém se perguntou: "Posso ter uma maneira de especificar um grande subconjunto finito / infinito de symbol ?".

Para a maioria das pessoas, essa pergunta nem faz sentido. Não há uma maneira significativa de fazer isso (certo?)


No entanto, apenas ser capaz de declarar subconjuntos de primitivas não é muito útil. Nos também precisamos,

  • Literais do tipo certo devem ser atribuídos sem trabalho adicional

Felizmente, TS é sensato o suficiente para permitir isso.

Imagine ser incapaz de passar false para (arg : false) => void !

  • Maneiras embutidas de estreitar

    No momento, para esses literais, temos == & === como formas integradas de estreitamento.

    Imagine precisar escrever um novo tipo de proteção para cada literal!

O problema com os tipos de tag / objeto de valor nominal / estrutural é que eles basicamente falham em cumprir os dois critérios acima. Eles transformam tipos primitivos em tipos desajeitados que não são exatamente tipos de objetos, mas devem ser tratados como tipos de objetos, de qualquer maneira.

Ergonomia

Ok, aqui está mais elaboração sobre o padrão de string vs tipos de tag nominais vs estruturais.

Esses argumentos também se aplicam a https://github.com/microsoft/TypeScript/issues/15480 .


Compatibilidade entre bibliotecas

Os tipos nominais são os piores em compatibilidade entre bibliotecas.
É como usar unique symbol em duas bibliotecas e tentar fazer com que elas interoperem.
Simplesmente não pode ser feito.
Você precisa usar um protetor do tipo clichê ou o operador trust-me ( as ).

Você também precisará de mais clichês para um guarda de asserção.

Se o tipo não requer compatibilidade entre bibliotecas, então usar tipos nominais é bom ...
Mesmo que não seja ergonômico (veja o exemplo acima).


Para tipos estruturais, se a biblioteca A tiver,

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

E a biblioteca B tem,

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

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

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

Em seguida, você precisará de um protetor do tipo clichê, ou o operador trust-me ( as ).

Você também precisará de mais clichês para um guarda de asserção.

Se o tipo não requer compatibilidade entre bibliotecas, então usar tipos estruturais é bom ...
Mesmo se muito não ergonômico (veja o exemplo acima).


Para tipos de padrão de string, se a biblioteca A tiver,

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

E a biblioteca B tem,

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

Suponha que ambas as bibliotecas sempre produzam strings para StripeChargeId que irão satisfazer os requisitos de ambas as bibliotecas. A biblioteca A é apenas "preguiçosa" com sua validação. E a biblioteca B é "mais rígida" com sua validação.

Então, é meio chato. Mas não é tão ruim.
Porque você pode apenas usar libraryB.stripeChargeIdRegex.test(libraryA_stripeChargeId) como o tipo de guia. Não há necessidade de usar o operador trust-me ( as ).

Você ainda precisará do clichê para guardas de asserção, no entanto.

Se o tipo não requer compatibilidade entre bibliotecas, usar tipos de padrão de string é perfeito e também muito ergonômico.


Se você precisa de compatibilidade entre bibliotecas, os tipos de padrão de string ainda são melhores do que os tipos de tag estrutural! Me ouça.

Se o domínio que está sendo modelado for bem compreendido, é muito provável que vários autores de biblioteca isolados acabem escrevendo a mesma regex. Com os tipos de tag estruturais, eles podem simplesmente escrever quaisquer propriedades e tipos que quiserem nas tags.

Se houver um padrão especificando formatos de string para tudo o que está sendo modelado, então é basicamente garantido que todos os autores de biblioteca escreverão o mesmo regex! Se eles escreverem uma regex diferente, eles não estão realmente seguindo o padrão. Você quer usar a biblioteca deles? Com os tipos de tag estruturais, eles ainda podiam escrever o que quer que fosse. (A menos que alguém comece um padrão de tipo de tag estrutural com o qual todos se importem? Lol)


Compatibilidade entre versões

Como de costume, os tipos nominais são os piores em compatibilidade de versão cruzada.
Oh, você alterou sua biblioteca com um patch ou uma versão secundária?
O tipo de decalaration ainda é o mesmo?
O código ainda é o mesmo?
Não. Eles são tipos diferentes.

image


Os tipos de tag estruturais ainda podem ser atribuídos, entre as versões (mesmo nas versões principais), contanto que o tipo de tag seja estruturalmente o mesmo.


Os tipos de padrão de string ainda podem ser atribuídos, entre as versões (mesmo nas versões principais), desde que a regex seja a mesma.

Ou poderíamos apenas executar um algoritmo PSPACE completo para determinar se as regexes são as mesmas? Também podemos determinar quais subclasses de regexes são as mais comuns e executar algoritmos de equivalência otimizados para elas ... Mas isso soa como um grande esforço.

Seria legal ter verificações de subtipos Regex e definitivamente tornaria o uso de tipos de padrão de string mais ergonômico. Assim como as verificações de subtipos de intervalo beneficiariam a proposta de tipo de intervalo de número.

[Editar]
Neste comentário,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Alguém ligado a,
https://bora.uib.no/handle/1956/3956

Intitulado, "O problema de inclusão para expressões regulares"
[/Editar]


Chapa de ebulição

TODO (mas podemos ver que os tipos de padrão de string têm a menor quantidade de clichê)

Invocação Literal

TODO (mas podemos ver que os tipos de padrão de string suportam melhor a invocação literal)

Invocação Não Literal

TODO (mas podemos ver que os tipos de padrão de string suportam melhor invocação não literal)

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

O TypeScript não pode errar em instanciações de interseções, então isso não faria parte de nenhum projeto final.

Não sei por que as pessoas queriam proibir os cruzamentos, mas você está absolutamente certo que proibir isso não faz sentido.


quebrando assim toda invocação não literal?

Bem, nem toda invocação não literal.

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
}

Interromper uma invocação não literal, onde não pode provar que corresponde ao regex, não é necessariamente uma coisa ruim. É apenas uma questão de segurança de tipo.

Isso é como dizer que os literais de string são ruins porque agora as invocações não literais falham.
Tipos de padrão de string / tipos de string validados por regex apenas permitem definir uniões de um número infinito de literais de string.


qualquer uso não literal exigiria um novo teste ou afirmação:

Não vejo isso como um problema.
É o mesmo com os tipos nominais / marcados agora.
Ou tentando passar um string para uma função esperando literais de string.
Ou tentando passar um tipo mais amplo para um tipo mais estreito.

Neste caso particular, você mostrou que const ZipCode = /^\d\d\d\d\d$/; e ZipCode.test(s) podem atuar como um tipo de guarda. Isso certamente ajudará na ergonomia.


  • O problema a ser resolvido não tem alternativa melhor (incluindo alternativas plausíveis que ainda não estão na língua)

Bem, espero ter mostrado que os tipos de tags nominais / estruturais não são a melhor alternativa. Eles são realmente muito ruins.

  • O problema ocorre com frequência significativa em bases de código reais

Uhh ... Deixe-me voltar para você sobre aquele ...

  • A solução proposta resolve bem esse problema

O tipo de padrão de string proposto parece ser muito bom.


TODO: Ajude-nos identificando funções de biblioteca reais que poderiam se beneficiar dos tipos RegExp e a expressão real que você usaria.

Sua opinião é que os tipos nominais / marcados são bons o suficiente para uso não literal.
Portanto, qualquer caso de uso levantado que mostre o uso não literal não é bom o suficiente, porque os tipos nominais / marcados o cobrem.

No entanto, vimos que, mesmo para uso não literal,

  • Tipos de tags nominais / estruturais sofrem de problemas de compatibilidade de biblioteca cruzada / versão
  • Quantidade de clichê para tipos de tag nominal / estrutural é significativamente maior do que clichê para tipos de padrão de string

Além disso, parece que os casos de uso literais levantados não foram satisfatórios para você, porque eles tentam e fazem coisas bobas como validação de email ou usam regexes que não são precisas o suficiente.


Escrevendo testes - é aqui que as entradas codificadas fazem algum sentido, embora isso seja quase um contraponto, porque seu código de teste provavelmente deve fornecer muitas entradas inválidas

Um bom caso de uso criado foi escrever testes de tempo de execução . E você está certo, que eles deveriam estar jogando um monte de entradas inválidas para testes de tempo de execução também.

Mas isso não é motivo para não oferecer suporte a tipos de padrão de string. Pode ser que eles queiram testar entradas válidas em um determinado arquivo e fornecer acidentalmente uma entrada inválida.

Mas, como eles têm que usar um protetor de tipo ou operador de confiança ( as ) ou objeto de valor, agora eles obterão um erro de tempo de execução, em vez de saber que o teste falhará antes do tempo .

O uso do operador trust-me ( as ) para testes de tempo de execução deve ser reservado apenas para testar entradas inválidas. Ao desejar testar entradas válidas, é mais claro não precisar de hacks para atribuir literais a um tipo de tag nominal / estrutural.

Se eles mudarem a regex no futuro, seria bom se seus testes agora não funcionassem, devido a problemas de atribuição. Se eles apenas usarem as em todos os seus testes, eles não saberão até que os executem.

E se o autor da biblioteca apenas usar as todos os lugares ao fazer dogfooding em sua própria biblioteca ... O que dizer dos consumidores posteriores? Eles também não ficarão tentados a usar as todos os lugares e ter problemas de tempo de execução ao atualizar para uma nova versão?

Com os tipos de padrão de string, há menos razão para usar as todos os lugares e tanto o autor da biblioteca quanto os consumidores posteriores saberão sobre as alterações de interrupção com mais facilidade.

(Um pouco prolixo, mas espero que alguns dos meus pontos tenham passado).


Além disso, escrevo muitos testes em tempo de

Seria bom se eu pudesse testar se um determinado string literal irá falhar / passar em uma verificação de regex em meus testes de tempo de compilação. No momento, não posso ter testes de tempo de compilação para essas coisas e preciso usar um teste de tempo de execução, em vez disso.

E se ele falhar / passar em meus testes de tempo de compilação, então terei confiança de que os consumidores downstream podem usar esses literais de string (ou similares) e esperar que eles se comportem da maneira certa.


Parece que isso nos coloca rapidamente no caminho para uma situação na Torre de Babel ...

Isso é ainda mais verdadeiro no uso de tipos de tag nominais / estruturais, na verdade. Como os exemplos acima mostraram, eles são terrivelmente úteis para compatibilidade entre bibliotecas e versões ...

No entanto, os tipos regexes / padrão de string têm uma chance decente de não cair nesse problema (felizmente, graças à padronização e aos autores de bibliotecas sãos).


EDITAR

Uma coisa comum no thread é que essas regexes ajudariam a validar o código de teste, porque embora em cenários de produção o código fosse executado em strings fornecidas pelo tempo de execução em vez de literais codificados permanentemente, você ainda quer alguma validação de que suas strings de teste foram " correto". Isso parece ser um argumento para strings nominais / marcadas / com marca, entretanto, uma vez que você escreveria a função de validação de qualquer maneira, e a vantagem dos testes é que você sabe que eles são executados exaustivamente (portanto, quaisquer erros nas entradas de teste seriam ser sinalizado no início do ciclo de desenvolvimento).

Ah ... eu deveria ter lido tudo antes de escrever isso ...

De qualquer forma, tenho alguns exemplos comigo, nos quais os tipos de padrão de string são úteis.


Biblioteca de declaração de rota HTTP

Com esta biblioteca, você pode construir objetos de declaração de rota HTTP. Esta declaração é usada tanto pelo cliente quanto pelo servidor.

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

Estas são as restrições para .append() ,

  • Apenas literais de string (não é possível impor isso no momento, mas se você usar não literais, o construtor de declaração de rota se tornará lixo)
  • Deve começar com uma barra inicial ( / )
  • Não deve terminar com uma barra ( / )
  • Não deve conter caractere conlon ( : ); é reservado para parâmetros
  • Não deve conter duas ou mais barras consecutivas ( // )

No momento, só tenho verificações de tempo de execução para esses, que geram erros. Eu gostaria que os consumidores downstream tivessem que seguir essas restrições sem precisar ler algum comentário Github README ou JSDoc. Basta escrever o caminho e ver as linhas vermelhas irregulares.


Outras coisas

Eu também tenho regexes para strings hexadecimais e alfanuméricas.

Eu também tenho isso,

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

Eu vejo isso,

Inteiro - rejeita incorretamente "3e5"

Eu também tenho isso, que não é um regex inteiro, mas usa o 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,
    };
}

Eu também tenho este comentário, no entanto,

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

Construtor RegExp

Curiosamente, o construtor RegExp se beneficiará dos tipos de string validados por regex!

Agora, é,

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

No entanto, poderíamos ter,

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

TL; DR (Por favor, leia, porém, eu me esforcei muito nisso: chore:)

  • Os tipos de padrão de corda são mais ergonômicos do que os tipos de tag nominais / estruturais

    • Menos clichê

  • Tipos de padrão de string são menos prováveis ​​do que tipos de tag nominais / estruturais para se tornarem uma situação de Torre de Babel

    • Especialmente com verificações de subtipo regex

  • Os tipos de padrão de string são a maneira mais natural de definir grandes subconjuntos finitos / infinitos do tipo string

    • A introdução desse recurso pode até fazer as pessoas pensarem mais de perto sobre formatos de string válidos para suas bibliotecas!

  • Tipos de padrão de string permitem maior segurança de tempo de compilação para algumas bibliotecas (deixe-me voltar para você sobre prevalência ... foge )

    • Construtor RegExp, strings hex / alfanuméricas, declarações de caminho de rota, identificadores de string para bancos de dados, etc.


Por que suas regexes são tão ruins?

Vários casos de uso levantados por outros queriam introduzir tipos de padrão de string para caber nas bibliotecas

Freqüentemente, sinto que essas bibliotecas existentes nem usam tanto regexes para validar sua entrada. Ou usam um regex para realizar uma validação simples. Em seguida, eles usam um analisador mais complicado para realizar a validação real.

Mas este é um caso de uso válido real para tipos de padrão de string!


Tipos de padrão de string para validar superconjuntos de valores de string válidos

Claro, uma string que começa com / , não termina com / , não contém / consecutivos e não contém : passará o " Regex do caminho HTTP ". Mas isso significa apenas que o conjunto de valores que passam esta regex é um superconjunto de caminhos HTTP válidos.

Mais abaixo, temos um analisador de caminho de URL real que verifica se ? não é usado, # não é usado, alguns caracteres têm escape, etc.

Mas com esse tipo de padrão de string simples, já eliminamos uma grande classe de problemas comuns que um usuário da biblioteca pode encontrar! E nós o eliminamos durante o tempo de compilação também!

Não é comum que um usuário use ? em seus caminhos HTTP, porque a maioria é experiente o suficiente para saber que ? é o início de uma string de consulta.


Acabei de perceber que você conhece este caso de uso.

Este segmento implica em uma ampla variedade de casos de uso; exemplos concretos têm sido mais raros. É preocupante que muitos desses exemplos não pareçam completos - eles usam um RegExp que rejeitaria entradas válidas.

Portanto, com certeza, muitas das regexes propostas não são "completas".
Mas, contanto que eles não rejeitem uma entrada válida, deve estar tudo bem, certo?

Tudo bem se eles permitirem entrada inválida, certo?
Uma vez que poderíamos ter um analisador "real" durante o tempo de execução para lidar com a validação completa.
E uma verificação em tempo de compilação pode eliminar muitos problemas comuns para usuários downstream, aumentando a produtividade.

Os exemplos que rejeitam a entrada válida devem ser fáceis de modificar, para que não rejeitem a entrada válida, mas permitam a entrada inválida.


Tipos e intersecções de padrão de string

De qualquer forma, os tipos de interseção em tipos de padrão de string seriam muito úteis!

Meu .append() exemplo poderia ser escrito 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;

O not PatternOf</\/\//> também poderia ser,
PatternOf</^((([/])(?!\3))|[^/])+$/> mas isso é muito mais complicado

Obrigado, @AnyhowStep , pelas extensas demonstrações. Queria criticar você por me fazer ler tanto, mas acabou sendo muito útil!

Freqüentemente tenho dificuldade em digitar minhas apis internas cheias de parâmetros de string e inevitavelmente acabo com um monte de condicionais que geram em tempo de execução. Inevitavelmente, meus consumidores precisam duplicar essas verificações de padrão, já que não querem uma exceção, eles querem uma maneira especial de lidar com a falha.

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

No mundo das strings e padrões, um string genérico é praticamente o mesmo que unknown , removendo muita segurança de tipo em favor das verificações de tempo de execução e causando transtornos para meus desenvolvedores consumidores.

Para alguns dos casos de uso mencionados, apenas um pequeno subconjunto de Regex seria necessário, por exemplo, correspondência de prefixo.

Potencialmente, isso poderia ser feito com recursos de linguagem TS mais gerais, como Variadic Kinds # 5453 e inferência de tipo ao espalhar tipos literais de string.

Especulação 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 alguns dos casos de uso mencionados, apenas um pequeno subconjunto de Regex seria necessário, por exemplo, correspondência de prefixo.

Eu ainda mantenho minha proposta , que oferece isso + algumas outras coisas, basicamente um superconjunto muito pequeno de linguagens sem estrelas que você ainda pode verificar de forma razoavelmente eficiente quanto a subconjuntos e igualdade. E até agora, não vi nenhuma outra tentativa de proposta de abordar o aspecto de desempenho de expressões regulares arbitrárias, a maior preocupação da equipe de TS.

O problema com as linguagens livres de estrelas é, como o nome diz, que você não pode usar estrelas, o que torna difícil validar coisas como urls. Além disso, a maioria das pessoas provavelmente vai querer estrelas e apenas usar um número arbitrário de sequências repetidas para emulá-las, o que dificultaria a verificação de subconjuntos.

E o desempenho da maioria das expressões regulares representáveis ​​do DFA não é tão ruim, e é possível verificar se há sub / superconjuntos.

No entanto, você ainda pode obter * .

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

@TijmenW Leia minha proposta um pouco mais de perto - há alguns fundamentos ocultos ali e alguns pequenos recursos que a tornam realmente prática. Não está diretamente limitado a especificar gramáticas livres de estrelas, mas um pequeno superconjunto estendido com apenas o suficiente para torná-lo praticamente útil para meu caso de uso semi-avançado. Em particular, você pode fazer starof ('a' | 'b' | ...) para caracteres individuais e pode usar string como equivalente a starof UnionOfAllCodePoints (na verdade, tornando-o não mais um primitivo em teoria).

Além disso, verificar se uma linguagem regular corresponde a um subconjunto do que outra linguagem regular corresponde é NP-completo e equivalente ao problema geral de starof tanto quanto possível, para tentar manter a complexidade computacional teórica baixa.

TODO: Ajude-nos identificando funções de biblioteca reais que poderiam se beneficiar dos tipos RegExp e a expressão real que você usaria.

Considere isso com cautela, pois é uma biblioteca totalmente nova, mas qualquer biblioteca como https://github.com/ostrowr/ts-json-validator seria muito mais útil com algo como um tipo regex.

O objetivo da biblioteca é gerar pares de tipo Typecript / esquema JSON <T, s> modo que

  1. Qualquer tipo que s pode validar é atribuível a T
  2. O mínimo possível de tipos que podem ser atribuídos a T falham na validação quando executado em s .

Um tipo de regex melhoraria a rigidez de (2), permitindo que o tipo validado fosse mais rígido com relação a pelo menos as seguintes palavras-chave:

  • format
  • patternProperties
  • propertyNames

TODO: Ajude-nos identificando funções de biblioteca reais que poderiam se beneficiar dos tipos RegExp e a expressão real que você usaria.

Todas as bibliotecas de interface do Excel podem usar a validação de tipo como A1 ou A5:B7 .

Chaves de propriedade / indexadores de string Regex

Algumas bibliotecas tratam objetos de acordo com os nomes das propriedades. Por exemplo, no React, queremos aplicar tipos a qualquer prop cujo nome comece com aria- :

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

Este é efetivamente um conceito ortogonal (poderíamos adicionar tipos Regex sem adicionar chaves de propriedade Regex e vice-versa).

Eu sei que isso é um pouco ortogonal a tudo que está acontecendo aqui, mas Wesley achou que você poderia usar nossa contribuição. Isso continua surgindo no Fabric por vários motivos. Como uma biblioteca de componentes, queremos ser capazes de elevar uma interface de componentes de adereços que reflita com precisão a interface do componente React permitida pelo TypeScript, incluindo data- e aria- atributos. Sem ele, não podemos elevar interfaces precisas para nossos consumidores usarem para esses atributos. Isso está se tornando um problema maior com a próxima versão do Fabric, onde veremos implementações conectáveis, como slots, e precisamos definir e permitir esses atributos em interfaces arbitrárias.

Se houver algo que possamos fazer para ajudar, por favor, me avise! 😄

TS Playground :

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: Ajude-nos identificando funções de biblioteca reais que poderiam se beneficiar dos tipos RegExp e a expressão real que você usaria

Cron jobs. (muito surpreso por não ter sido mencionado)

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

Estou apenas jogando meus dois centavos aqui - estou trabalhando em um projeto React onde gostaríamos de validar um prop que será usado como um atributo HTML id . Isso significa que ele deve atender às seguintes regras ou ocorrerá um comportamento inesperado:

  1. Ter pelo menos um personagem
  2. Não tem espaços

Em outras palavras:

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

Outro exemplo: identificadores do tipo santuário com strings esperadas no formato '<namespace>/<name>[@<version>]'

Caso de uso: APIs DOM digitadas em string como Navigator.registerProtocolHandler() .

Citando MDN:

Por razões de segurança, registerProtocolHandler() restringe quais esquemas podem ser registrados.

Um esquema personalizado pode ser registrado, desde que:

  • O nome do esquema personalizado começa com web+
  • O nome do esquema personalizado inclui pelo menos 1 letra após o prefixo web+
  • O esquema personalizado possui apenas letras ASCII minúsculas em seu nome.

Em outras palavras, Navigator.registerProtocolHandler() espera um string bem conhecido ou um string mas apenas se estiver de acordo com um esquema específico.

Propriedades personalizadas CSS para CSSType é outro caso de uso para fornecer tipos fechados para todas as propriedades, exceto aquelas prefixadas com -- .

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

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

Alguém pode me dizer se isso é o mesmo que tipos de refinamento? https://github.com/microsoft/TypeScript/issues/7599

@ gautam1168 É teoricamente apenas um subconjunto, onde refina especificamente os tipos de string. (Os tipos numéricos têm suas próprias preocupações, é claro.)

Para alguns dos casos de uso mencionados, apenas um pequeno subconjunto de Regex seria necessário, por exemplo, correspondência de prefixo.

Eu ainda mantenho minha proposta , que oferece isso + algumas outras coisas, basicamente um superconjunto muito pequeno de linguagens sem estrelas que você ainda pode verificar de forma razoavelmente eficiente quanto a subconjuntos e igualdade. E até agora, não vi nenhuma outra tentativa de proposta de abordar o aspecto de desempenho de expressões regulares arbitrárias, a maior preocupação da equipe de TS.

Neste comentário,
https://github.com/microsoft/TypeScript/issues/6579#issuecomment -243338433

Alguém ligado a,
https://bora.uib.no/handle/1956/3956

Intitulado, "O problema de inclusão para expressões regulares"


Contudo,

  • Se a expressão à direita for 1 inequívoca, o algoritmo fornece a resposta correta.
  • Caso contrário, pode dar a resposta correta ou nenhuma resposta.

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

(Claro, as expressões regulares JS não são regulares)

@AnyhowStep Isso pode funcionar - eu apenas levantaria a restrição starof e mudaria essa restrição de acordo. Eu gostaria de uma maneira melhor de caracterizar a restrição, já que a matemática é um pouco abstrata e não está claro como ela se aplicaria concretamente na prática (nem todos que usariam esses tipos são bem versados ​​em linguagens formais).

Além disso, separadamente, eu gostaria fortemente de uma alternativa melhor para starof como um operador para modelar esse tipo de coisa.

Estou curioso: é possível decidir inclusão / contenção de expressões regulares? De acordo com a Wikipedia , é decidível. No entanto, isso também leva em consideração as expressões regulares em JS? Acho que eles têm mais recursos do que os REs padrão (por exemplo, referências anteriores). Se for decidível, é computacionalmente viável?
Isso afetaria este recurso (estreitamento):

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

@nikeee Decidable não é suficiente para ser realista. Mesmo o tempo quadrático é geralmente muito lento nesta escala. Não o TS, mas tenho alguma experiência em questões semelhantes.

Em face das referências anteriores, suspeito que ainda seja decidível, mas provavelmente exponencial, se não pior. Apenas um palpite educado, no entanto.

Obrigado por esclarecer isso!

Mesmo o tempo quadrático é geralmente muito lento nesta escala.

É por isso que também perguntei se é computacionalmente viável, então acho que não.

Se o mesmo se aplica à igualdade, isso não significa que quase todas as propriedades desse recurso são inviáveis? Corrija-me, se eu estiver errado, mas parece que a única coisa que resta é a adesão. Não acho que isso por si só seja útil.

@nikeee Vale a pena ter em mente que esse padrão será verificado em relação a literalmente todas as propriedades de todos os tipos com os quais for comparado . E para os tipos com propriedades de expressões regulares, você tem que calcular se uma expressão regular corresponde a um subconjunto do que outros jogos de expressões regulares, uma besta bastante complicado em si mesmo.

Não é impossível, apenas difícil , e você tem que ser restritivo se quiser que seja viável. (Por um lado, as expressões regulares JS não funcionariam - elas não só não são extensíveis o suficiente, mas também são muito flexíveis.)

Edit: Eu quero reiterar isso: eu não estou na equipe TS, apenas para esclarecer. Eu só tenho um histórico decente em design de algoritmo CS.

Hmm, então talvez você só possa oferecer suporte a um subconjunto "limitado" de RegEx "normal". Dados os casos de uso, os regex'es eram bastante simples até agora ... (cores, números de telefone etc.)

Como poderíamos projetar a UX de suportar apenas um subconjunto? Pode não ser muito claro para o usuário que o Recurso X do RegEx funciona, mas Y não.

bem ... não chame de "regex" - para começar. Talvez apenas "correspondência de padrões" ou então: see_no_ evil :. Mas sim, provavelmente não é uma tarefa fácil ...

Que tal uma sintaxe não regex 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'

Na minha opinião, isso se encaixaria perfeitamente no sistema de tipos. Você pode adicionar sintaxe diferente para coisas como correspondências opcionais:

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

Adicione suporte para quantificadores, operador ganancioso e você terá algo bastante robusto, eu acho que provavelmente seria o suficiente para a maioria dos casos de uso para os quais os desenvolvedores podem querer usar isso.

Acho que essa abordagem seria mais amigável. No entanto, parece ser aquvalent para operações aritméticas em tipos.
De acordo com https://github.com/microsoft/TypeScript/issues/15645#issuecomment -299917814 e https://github.com/microsoft/TypeScript/issues/15794#issuecomment -301170109, é uma decisão de design não fazer aritmética em tipos.
Se não estou enganado, essa abordagem pode facilmente criar um 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}`

(isso pressupõe que a implementação usaria tipos de união. Pode funcionar com uma implementação diferente / mais complexa)

Dislaimer: Não faço parte da equipe do TS e não estou trabalhando no TS. Apenas meu 2c.

@rozzzly @nikeee Essa é mais ou menos a essência da minha proposta , apenas faltando alguns recursos menores. Eu baseei o meu em um grande subconjunto de linguagens regulares (o conceito de linguagem formal), não em expressões regulares no sentido de literais regexp e tal, então é muito menos poderoso do que aqueles, mas poderoso o suficiente para fazer o trabalho.

Acho que essa abordagem seria mais amigável. No entanto, parece ser aquvalent para operações aritméticas em tipos.

O Math diz que validar se um tipo é um subtipo de outro é computacionalmente equivalente a verificar se uma string está contida em uma dada linguagem formal.

A validação de domínio, especificamente, é na verdade uma coisa bem complicada de fazer se você também verificar a validade do público . Os próprios domínios genéricos de acordo com o RFC são tão simples quanto /[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)+/ + tendo no máximo 255 caracteres, mas mesmo isso é muito complicado de digitar, a menos que você vá para gramáticas regulares completas como o regexp acima demonstra. Você poderia gerar o tipo de maneira programática de maneira bastante direta (deixarei como um exercício para o leitor) usando apenas strings de @rozzzly ou minha proposta, mas o resultado final ainda é bastante complicado.

@isiahmeadows

Essa é mais ou menos a essência da minha proposta , apenas com alguns recursos menores ausentes.

A última vez que li todo este tópico foi há mais de um ano. Eu estava no meu intervalo e vi uma notificação, li o comentário de @rugk sobre _ "bem ... não chame de" regex "- para começar" _ o que me fez pensar ... Eu não tinha percebido que alguém já tinha apresentou uma proposta consideravelmente mais detalhada para essencialmente a mesma idéia _ (/ uma muito semelhante) _.

... mesmo isso é muito complicado de digitar, a menos que você vá para gramáticas regulares completas como o regexp acima demonstra. Você poderia gerar o tipo de maneira programática de maneira bastante direta (deixarei como um exercício para o leitor) usando apenas strings de @rozzzly ou minha proposta, mas o resultado final ainda é bastante complicado.

Em minha mente, alguma facilidade para correspondência de padrão limitada, como sugeri permitir, seria extremamente útil para tipagem muito simplista e _necessariamente não rigorosamente estrita_. O exemplo que dei está longe de ser preciso e não explodiria o compilador.

Mas, como @nikeee e vocês dois apontaram, isso pode ser levado a extremos perigosos. Presumindo uma implementação mais ingênua, apoiando apenas sindicatos. Alguém vai estragar o dia de todos publicando uma atualização para @types/some-popular-project que continha:

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

Colocando isso em perspectiva, essa união consiste em tipos distintos que são mais do que átomos no universo observável .

Agora, eu vi alguns erros de atribuição terrivelmente longos, mas imagine um erro (não truncado) para isso ....

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

Então, sim ... existem alguns problemas aí

@rozzzly O que torna esse tipo diferente (em termos de viabilidade) de TupleWithLengthBeteen1And64<Charset> ?
O compilador não é forçado a expandir cada tipo para uma forma normalizada, ele explodiria rapidamente em tipos razoavelmente normais se isso acontecesse.
Não estou dizendo que acho que esse problema faz sentido no texto digitado no momento, se mesmo "inteiro entre 3 e 1024" (pense em comprimentos de alocação de buffer de mensagem) for considerado fora do escopo.

@simonbuchan Pelo menos os tipos de prefixo e sufixo precisam existir, se nada mais. Isso por si só é necessário para muitas bibliotecas e estruturas DOM.

Sei que ele foi espancado até a morte e que algumas boas propostas já foram feitas. Mas eu só queria adicionar coisas extras que alguns podem achar ligeiramente interessantes.

Em face das referências anteriores, suspeito que ainda seja decidível, mas provavelmente exponencial, se não pior. Apenas um palpite educado, no entanto.

Referências anteriores podem fazer um regexp descrever uma gramática sensível ao contexto, um superconjunto de gramáticas livres de contexto. E a igualdade de linguagem para CFGs é indecidível. Portanto, é ainda pior para CSGs, que são equivalentes a autômatos lineares.


Assumindo que apenas todas as expressões regulares que podem ser convertidas em um DFA são usadas em uma regexp (concat, união, estrela, interseção, complemento, etc.), converter uma regexp em NFA é O (n), obtendo o produto de dois NFAs é O (m * n), então percorrer o gráfico resultante para aceitar os estados é O (m * n). Portanto, verificar a igualdade / subconjunto da linguagem de duas expressões regulares também é O (m * n).

O problema é que o alfabeto é muito grande aqui. Os livros didáticos se restringem a alfabetos de tamanho 1 a 5 geralmente, quando se trata de DFAs / NFAs / expressões regulares. Mas com regexps JS, temos todos Unicode como nosso alfabeto. Concedido, pode haver maneiras eficientes de representar funções de transição usando matrizes esparsas e outros hacks inteligentes e otimizações para teste de igualdade / subconjunto ...

Tenho certeza de que é possível fazer a verificação de tipo para atribuição regular a regular com certa eficiência.

Então, todas as atribuições não regulares podem exigir apenas asserções de tipo explícitas.

Recentemente, trabalhei em um pequeno projeto de autômato finito, então a informação ainda está fresca em minha mente = x

Se não estou enganado, essa abordagem pode facilmente criar um 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}`

(isso pressupõe que a implementação usaria tipos de união. Pode funcionar com uma implementação diferente / mais complexa)

Curiosamente, isso é exatamente o que é possível com os novos tipos de literais de string de modelo. Este caso é evitado por ter um limite para tipos de união, ao que parece.

@AnyhowStep JS backreferences são a única produção sensível ao contexto (e bastante simples e limitada - apenas até 9 grupos podem ser referenciados assim), e o resto da gramática regexp é regular, então é por isso que eu suspeito que é decidível. Mas, independentemente disso, acho que podemos concordar que não é prático em nenhum sentido da palavra. 🙂

Editar: precisão

Confirmei este comentário de @rozzzly funciona com TS 4.1.0 todas as noites!

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

Experimente no playground e veja que fail tem um erro de compilação 🤩


Atualização : depois de brincar um pouco com esse recurso, ele não cobrirá muitos casos de uso. Por exemplo, não funciona para uma string de cor 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';

Hoje, isso falha com "A expressão produz um tipo de união que é muito complexo para representar. (2590)"

Confirmei este comentário de @rozzzly funciona com TS 4.1.0 todas as noites!

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

Experimente no playground e veja que fail tem um erro de compilação 🤩

Isso resolveria o problema de dados ou ária que a maioria de nós enfrenta em bibliotecas de UX se puder ser aplicado a índices.

Basicamente isso, mas obviamente isso não funciona porque o TS só permite string | número. Uma vez que esta é essencialmente uma string, ela pode ser ativada?
https://www.typescriptlang.org/play?target=99&ts=4.1.0-dev.20201001#code/LAKALgngDgpgBAEQIZicgzgCzgXjgAwBIBvAcgBMUkBaUgXxPTACcBLAOwHM78BuUDmBjMAZkgDG8AJIAVGEzjFQcFXADalVBkwAFZgHsoALmRakWALpGmbLvxB1QocfvYL0AV3GT06I3Fl5MFxFCipqISZSI1JIsHpeIA

_Update_: depois de brincar um pouco com este recurso, ele não cobrirá muitos casos de uso. Por exemplo, não funciona para uma string de cor 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';

Hoje, isso falha com "A expressão produz um tipo de união que é muito complexo para representar. (2590)"

Houve alguma referência a essa limitação nas notas de versão. Ele cria uma lista de todas as combinações válidas possíveis; neste caso, criaria uma união com 16.777.216 (ou seja, 16 ^ 6) membros.

Essa é uma ótima ideia ... Igmat fez alguns posts incríveis em 2016 que parecem bons no papel de qualquer maneira.

Eu descobri isso porque queria ter certeza de que as chaves de um literal de objeto passado para minha função eram nomes de classe css válidos. Posso verificar facilmente em tempo de execução ... mas para mim parece tão óbvio que o texto digitado deve ser capaz de fazer isso em tempo de compilação, especialmente em situações onde estou apenas codificando objetos literais e o texto digitado não deveria ter que descobrir se MyUnionExtendedExotictipo satisfaz SomeArbitraryRegexType.

Talvez um dia eu tenha conhecimento suficiente para fazer uma contribuição mais produtiva: /

Confirmei este comentário de @rozzzly funciona com TS 4.1.0 todas as noites!

Uau. Sinceramente, não esperava ver isso implementado, pelo menos não tão cedo.

@ chadlavi-casebook

Houve alguma referência a essa limitação nas notas de versão. Ele cria uma lista de todas as combinações válidas possíveis; neste caso, criaria uma união com 16.777.216 (ou seja, 16 ^ 6) membros.

Eu ficaria curioso para ver o quão grande essa união poderia se tornar antes que se tornasse um problema em termos de desempenho. O exemplo de @styfle mostra como é fácil atingir esse teto. Obviamente, haverá um certo grau de diminuição dos retornos de utilidade de tipos complexos versus desempenho.

@thehappycheese

Eu queria ter certeza de que as chaves de um literal de objeto passado para minha função eram nomes de classe css válidos

Estou bastante confiante em dizer que isso não é possível com a implementação atual. Se houvesse suporte para quantificadores e intervalos, você provavelmente obteria validação para nomes de classe de estilo BEM. O js regex padrão para isso não é _muito_ terrível:
^\.[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$
Você também descartaria as âncoras porque, conforme a implementação está, ou é uma correspondência de ponta a ponta ou nada, então ^ e $ estão implícitos. Agora, isso é um regex comparativamente simples para um subconjunto estreito do que é um seletor css válido. Por exemplo: ಠ_ಠ é um nome de classe válido. Eu não estou brincando.

Eu sinto Muito. Eu tive que fazer isso.

Implementei linguagens regulares em TypeScript.

Mais precisamente, implementei um autômato finito determinístico simples usando TS 4.1

Quer dizer, já podemos implementar máquinas de Turing em TS. Portanto, DFAs e PDAs são "fáceis", comparados a isso.

E as strings de modelo tornam isso mais utilizável.


Os tipos principais são realmente simples e se encaixam em <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 os autômatos que é a parte difícil.

Mas tenho certeza de que alguém pode fazer um regex para o gerador TypeScript DFA ™ ...


Eu também gostaria de destacar que o exemplo de "string hexadecimal de comprimento 6" mostra que você pode fazer com que os parâmetros de função aceitem apenas strings que correspondam ao regex usando um hackeamento feio,

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

Aqui está um Playground bônus ; ele implementa a regex /^hello .*/

E outro Playground ; ele implementa a regex / world$/

Um exemplo final, Playground ; este é um regex de string de ponto flutuante !

@AnyhowStep Bem, usei sua ideia do DFA para implementar uma regex simples [abc]{4} que significa que as letras abc em qualquer ordem estão ausentes, mas com o comprimento exato de 4. (aaaa, abcc, bbcc, etc ...).
Parque infantil

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

https://github.com/CyberZHG/toolbox

Se eu tivesse mais força de vontade, pegaria algo como o descrito acima e usaria para transformar regexes em TS DFAs ™ lol

Ok, acabei de criar um protótipo,

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

[Editar] https://glitch.com/~efficacious-valley-repair <- Isso produz uma saída muito melhor para regexes mais complicadas

[Editar] Parece que o Glitch irá arquivar projetos gratuitos que estão inativos por muito tempo. Então, aqui está um repositório git com os arquivos,
https://github.com/AnyhowStep/efficacious-valley-repair/tree/main/app

Etapa 1, digite sua regex aqui,
image

Etapa 2, clique em converter,
image

Etapa 3, clique no URL do playground TS gerado,
image

Etapa 4, role para baixo até InLanguage_0 ,
image

Etapa 5, brincar com os valores de entrada,
image

image

Grite para https://www.npmjs.com/package/regex2dfa , por fazer o trabalho pesado da conversão

Caso alguém precise de algo um pouco mais poderoso, aqui está uma máquina de Turing 😆

Parque infantil

Este tópico ficou muito longo para ler e muitos dos comentários são endereçados por tipos literais de modelo ou estão fora do tópico. Eu criei um novo problema # 41160 para discussão sobre quais casos de uso restantes podem ser ativados por este recurso. Sinta-se à vontade para continuar discutindo os analisadores do sistema de tipos aqui 😀

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

remojansen picture remojansen  ·  3Comentários

MartynasZilinskas picture MartynasZilinskas  ·  3Comentários

manekinekko picture manekinekko  ·  3Comentários

weswigham picture weswigham  ·  3Comentários

kyasbal-1994 picture kyasbal-1994  ·  3Comentários